Создание расширения браузера Google Chrome для извлечения всех изображений web-страницы. Часть 1
Расширения браузера — это web-приложения, которые устанавливаются в web-браузер, чтобы расширить его возможности. Обычно, для того чтобы воспользоваться расширением, пользователю нужно найти его в Chrome Web Store и установить.
В этой статье я покажу как создать расширение для браузера Google Chrome с нуля. Это расширение будет использовать API браузера, для того чтобы получить доступ к содержимому web-страницы любой открытой вкладки. С помощью этих API можно не только читать информацию с открытых web-сайтов, но и взаимодействовать с этими страницами, например, переходить по ссылкам или нажимать на кнопки. Таким образом расширения браузера могут использоваться для широкого круга задач автоматизации на стороне клиента, таких как web-scrape или даже автоматизированное тестирование frontend.
Мы создадим расширение, которое называется Image Grabber. Оно будет содержать интерфейс для подключения к web-странице и для извлечения из нее информации о всех изображениях. Далее, при нажатии на кнопку «GRAB NOW» список абсолютных URL этих изображений будет скопирован в буфер обмена. В этом процессе вы познакомитесь с фундаментальными строительными блоками, которые в дальнейшем можно будет использовать для создания других расширений.
Расширения, создаваемые таким образом для браузера Chrome совместимы с другими браузерами, основанными на движке Chromium и могут быть установлены, например, в Yandex-браузер или Opera.
В результате при правильном выполнении всех шагов, вы получите расширение, которое будет выглядеть и работать так, как показано на следующем видео:
Это только первая часть истории. Во второй части я покажу, как расширить интерфейс, чтобы выгрузить все или выбранные изображения в виде ZIP-архива, а также, как опубликовать свое расширение в Chrome Web Store.
Базовая структура расширения
Расширение Google Chrome — это web-приложение, содержащее любое количество HTML-страниц, файлов CSS и изображений, а также, файл manifest.json, который определяет как расширение выглядит и работает. Все эти файлы должны быть в одной папке.
Минимальное расширение состоит только из одного файла manifest.json. Вот пример этого файла, который вы можете использовать как шаблон при начале создания любого расширения:
< "name": "Image Grabber", "description": "Extract all images from current web page", "version": "1.0", "manifest_version": 3, "icons": <>, "action": <>, "permissions": [], "background":<> >
Здесь только manifest_version должен быть равен 3. Остальные поля заполняются произвольно в зависимости от назначения расширения: name — название расширения, description — краткое описание, version — версия. Остальные параметры мы будем заполнять по ходу разработки интерфейса. Полный список параметров manifest.json можно найти в официальной документации.
Папка с одним файлом manifest.json — это минимальное расширение, которое может быть установлено в Google Chrome и запущено. Оно ничего не будет делать, однако именно с установки нужно начинать. Поэтому создайте папку image_grabber, затем добавьте в нее текстовый файл manifest.json с содержимым приведенным выше.
Установка расширения
В процессе разработки расширения, оно представляет собой папку с файлами. В терминологии Google Chrome это называется «unpacked extension». После завершения разработки, его нужно упаковать в ZIP-архив и загрузить в Chrome Web Store, откуда оно потом может быть установлено в браузер.
Однако на этапе разработки, «unpacked extension» тоже можно установить, просто как папку с файлами. Для этого нужно ввести chrome://extensions в браузере, чтобы открыть Chrome Extensions Manager:

Это покажет список уже установленных расширений. Чтобы устанавливать в него «unpacked extensions», включите флажок «Developer mode» в правом верхнем углу. После этого должна отобразиться панель управления расширениями.

Затем нажмите первую кнопку Load unpacked и укажите папку, в которой находится расширение с файлом manifest.json. В нашем случае это папка image_grabber. Расширение должно отобразиться в списке:

Эта панель должна показывать данные расширения такие как имя, описание и версию, ранее указанные в manifest.json, а также уникальный идентификатор, присвоенный этому расширению. Каждый раз после изменения manifest.json и файлов, непосредственно указанных в нем, нужно обновлять расширение в браузере, нажимая на кнопку «Reload«:

Чтобы запустить и использовать установленное расширение в браузере, нажмите кнопку Extensions на панели инструментов Google Chrome рядом с URL и найдите «Image Grabber» в списке установленных расширений:

Также можно нажать иконку Pin в списке рядом с расширением, чтобы добавить кнопку данного расширения на панель Chrome, рядом с другими расширениями:


Вот как выглядит минимальное расширение — кнопка с серым прямоугольником и буквой внутри. При нажатии на эту кнопку ничего не происходит. Давайте заполним остальные поля файла manifest.json, чтобы изменить картинку расширения и заставить его выполнять нужные действия.
Добавление иконок
Параметр icons файла manifest.json принимает объект JavaScript, ключами которого являются размеры иконок, а значениями пути к файлам с этими иконками. Иконка это картинка с расширением PNG (для прозрачности). Расширение должно иметь иконки разных размеров: 16×16, 32×32, 48×48 и 128×128. Я создал иконки для всех этих размеров и поместил их в папку «icons» внутри папки с расширением. Сделайте то же самое. Имена файлов могут быть любыми:
< "name": "Image Grabber", "description": "Extract all images from current web page", "version": "1.0", "manifest_version": 3, "icons": < "16":"icons/16.png", "32":"icons/32.png", "48":"icons/48.png", "128":"icons/128.png" >, "action": <>, "permissions": [], "background":<> >
Как видно, здесь указаны относительные пути к файлам в папке icons.
После изменения manifest.json нажмите кнопку «Reload» на панели расширения Image Grabber в Chrome Extensions Manager, чтобы обновить установленное расширение. Если все сделано как описано выше, то картинка на кнопке расширения должна измениться:

Так значительно лучше, однако когда мы нажимаем на эту кнопку все еще ничего не происходит. Пришло время добавить «действия» (actions) к расширению.
Создание интерфейса расширения
Расширение должно что-то делать, то есть запускать определенные действия. Расширение может выполнять действия двумя способами:
- В фоне, при запуске расширения и при дальнейшей его работе
- Из интерфейса, когда пользователь нажимает кнопку расширения и затем взаимодействует с различными интерфейсными элементами.
Расширение может использовать обе эти возможности одновременно.
Чтобы запускать действия в фоне, нужно создать Javascript-файл и указать путь к нему в параметре «background» файла manifest.json. Этот скрипт может содержать функции обработчики различных событий браузера и жизненного цикла самого расширения. Background-скрипт слушает эти события и запускает написанные для них функции обработчики.
Однако в этом расширении мы этим пользоваться не будем и параметр «background» останется пустым. Он присутствует только для того, чтобы показать что это возможно и что данный файл manifest.json может использоваться для создания расширений любого типа. Однако расширение Image Grabber выполняет действия только когда пользователь нажимает кнопку «GRAB NOW» из интерфейса.
Соответственно мы создадим интерфейс для расширения. Интерфейс расширения это фактически Web-сайт, состоящий из HTML-страниц с элементами управления, CSS-таблиц стилей и скриптов, которые реагируют на события элементов управления из этих HTML-страниц и выполняют определенные действия, используя в частности API расширений Google Chrome.
Интерфейс должен содержать главную страницу, которая появляется при нажатии на кнопку расширения. Эта страница может появляться либо в новой вкладке браузера, либо внутри всплывающего окна, как было показано на видео. Именно так и будет реализован интерфейс Image Grabber. Чтобы создать интерфейс всплывающего окна, внесите следующие изменения в manifest.json:
< "name": "Image Grabber", "description": "Extract all images from current web page", "version": "1.0", "manifest_version": 3, "icons": < "16":"icons/16.png", "32":"icons/32.png", "48":"icons/48.png", "128":"icons/128.png" >, "action": < "default_popup":"popup.html" >, "permissions": [], "background":<> >
Здесь определено, что основное действие (action) расширения это всплывающее окно (popup), которое содержит страницу popup.html.
Теперь создайте файл popup.html с заголовком Image Grabber и кнопкой «GRAB NOW» и поместите его в папку с расширением:
Image Grabber Image Grabber
Кнопка имеет идентификатор grabBtn который в дальнейшем будет использоваться из скрипта, чтобы реагировать на нажатия.
Чтобы увидеть, что изменилось, снова обновите расширение в Chrome Extensions Manager. Если все было сделано как написано выше, то при нажатии на иконку расширения будет появляться содержимое файла popup.html во всплывающем окне:

Работает!! Но выглядит не очень. Теперь нужно стилизовать этот интерфейс с помощью CSS.
Создайте файл popup.css со следующим содержимым и поместите его в папку с расширением:
body < text-align:center; width:200px; >button
Этот файл определяет стили для кнопки и для body всей страницы: все содержимое выровнено по центру, а также, ширина содержимого будет 200 пикселей.
Добавьте ссылку на файл с этими стилями в popup.html:
Image Grabber Image Grabber
Теперь при нажатии на кнопку расширения появится стилизованный интерфейс:

Обратите внимание, что в данном случае не требовалось нажимать кнопку Reload, для того чтобы обновить расширение. Изменения в файлах web-интерфейса расширения начинают работать автоматически.
Чтобы придать интерфейсу завершенность, добавим JavaScript-код, который будет реагировать на нажатия кнопки «GRAB NOW«. Создайте файл popup.js в папке с расширением со следующим содержимым:
const grabBtn = document.getElementById("grabBtn"); grabBtn.addEventListener("click",() => < alert("CLICKED"); >)
и добавьте ссылку на этот файл в popup.html:
Image Grabber Image Grabber
Таким образом мы добавили событие onClick для кнопки с идентификатором «grabBtn«. Теперь, при нажатии на кнопку «GRAB NOW» в интерфейсе, должно появляться предупреждение «CLICKED».
В результате мы имеем такую файловую систему расширения:

Это все файлы, других больше добавлять не будем. Такую структуру можно использовать как базу для создания расширений Google Chrome с интерфейсом основанном на всплывающих окнах.
Теперь реализуем бизнес логику расширения: при нажатии на кнопку «GRAB NOW» расширение должно извлечь список путей ко всем картинкам текущей страницы в браузере и скопировать этот список в буфер обмена.
Функция «GRAB NOW»
Используя Javascript в расширении можно делать все то же самое, что и при использовании JavaScript на web-сайте, например открывать различные web-страницы из текущей или делать AJAX HTTP-запросы к различным серверам. Но в дополнении к этому, Javascript-код в расширении браузера может использовать различные API Chrome для взаимодействия с компонентами самого браузера. Большинство этих API доступно через пространство имен chrome . В частности, расширение Image Grabber будет использовать следующие API:
- chrome.tabs — Chrome Tabs API. Будет использоваться для доступа к активной вкладке браузера.
- chrome.scripting — Chrome Scripting API. Будет использоваться для внедрения кода JavaScript на web-страницу активной вкладки и для исполнения этого кода в контексте этой страницы.
Получение необходимых разрешений
По умолчанию, из соображений безопасности, расширение Chrome не имеет доступа ко всем API браузера. Необходимо запросить этот доступ через механизм разрешений. Для этого нужно указать список требуемых разрешений в параметре permissions в файле manifest.json. Существует множество различных разрешений, которые описаны здесь: https://developer.chrome.com/docs/extensions/mv3/declare_permissions/. Для Image Grabber нужно указать следующие из них:
- activeTab — разрешение для получения доступа к текущей вкладке браузера.
- scripting — разрешение для получения доступа к Chrome Scripting API для исполнения скриптов в контексте Web-страницы, открытой в браузере.
Добавьте эти разрешения в параметр permissions в файле manifest.json:
< "name": "Image Grabber", "description": "Extract all images from current web page", "version": "1.0", "manifest_version": 3, "icons": < "16":"icons/16.png", "32":"icons/32.png", "48":"icons/48.png", "128":"icons/128.png" >, "action": < "default_popup":"popup.html", >, "permissions": ["scripting", "activeTab"], "background":<> >
и обновите расширение в Chrome Extensions Manager.
Между тем, листинг выше — это окончательный вариант файла manifest.json для расширения. Теперь здесь есть все: название, описание, версия, иконки, ссылка на главную страницу интерфейса и разрешения на доступ к браузеру, которые этому интерфейсу необходимы.
Получение информации об активной вкладке браузера
Для получения информации о вкладках браузера используется функция chrome.tabs.query , которая определена следующим образом:
chrome.tabs.query(queryObject,callback)
- queryObject — объект запроса в котором указываются параметры поиска нужных вкладок.
- callback — функция обратного вызова, которая выполняется после выполнения этого запроса. В нее передается массив tabs , содержащий все найденные вкладки, соответствующие критерию запроса. Каждый элемент этого массива — это объект типа Tab , содержащий информацию о вкладке, включая ее уникальный идентификатор. Этот идентификатор будет использоваться для исполнения Javascript-кода на этой вкладке.
Здесь я не буду полностью описывать синтаксис queryObject , а также все поля возвращаемых объектов Tabs . Эту информацию можно найти в официальной документации по chrome.tabs : https://developer.chrome.com/docs/extensions/reference/tabs/.
Для расширения Image Grabber нужно получить только активную вкладку. Для этой цели queryObject должен быть определен как: .
Теперь давайте изменим код обработчика нажатия кнопки grabBtn в popup.js чтобы он получал активную вкладку и ее идентификатор, когда пользователь нажимает на кнопку «GRAB NOW» в интерфейсе расширения:
const grabBtn = document.getElementById("grabBtn"); grabBtn.addEventListener("click",() => < chrome.tabs.query(, (tabs) => < const tab = tabs[0]; if (tab) < alert(tab.id) >else < alert("There are no active tabs") >>) >)
Этот код выполняет запрос ко всем вкладкам, которые активны. Может быть только одна такая вкладка, поэтому к ней можно обратиться как tabs[0] . Если такая вкладка существует, то функция отображает ее id, а если не существует, то показывает сообщение об ошибке.
Если все сделано как описано выше, то при нажатии кнопки GRAB NOW должен появляться идентификатор текущей вкладки браузера. Далее мы изменим этот код таким образом, чтобы этот идентификатор использовался для доступа к web-странице, открытой на этой вкладке.
Извлечение изображений из текущей страницы
Расширение может взаимодействовать с открытыми страницами с помощью интерфейса Chrome Scripting API, доступного через chrome.scripting . В частности, мы будем использовать функцию для внедрения своего кода в текущую web-страницу и для исполнения этого кода в ее контексте. При запуске этот код будет иметь доступ к DOM-дереву этой страницы и сможет делать нужные нам действия с любыми HTML-тэгами.
Для внедрения скрипта в страницу Web-браузера используется функция executeScript , определенная следующим образом:
chrome.scripting.executeScript(injectSpec,callback)
Рассмотрим ее параметры.
injectSpec
Это объект типа ScriptInjection. В нем определяется какой Javascript-код, в какую web-страницу и каким образом внедрить. В частности, в параметре target указывается идентификатор вкладки браузера со страницей, которая нам нужна. Мы получили его ранее. Остальные параметры указывают каким образом передать скрипт на эту страницу. Возможно использовать следующие параметры для передачи скрипта:
- files — указывается список путей к JS-файлам, которые нужно внедрить, относительно корневой папки расширения
- func — указывается Javascript-функция, которую нужно внедрить. Функция должна быть предварительно написана.
Скрипт, который планируется внедрить, будет извлекать список изображений с web-страницы и возвращать список URL этих изображений. Это небольшой скрипт, поэтому достаточно создать функцию для него. Она будет называться grabImages . Эта функция должна извлекать список URL изображений web-страницы и возвращать его. Возвращаемое значение этой функции будет передано расширению.
В итоге injectSpec будет определен следующим образом:
< target:< tabId: tab.id, allFrames: true >, func: grabImages, >,
Здесь функция, указанная в параметре func будет внедрена на страницу, определенную параметром target . В параметре target указывается полученный на предыдущем этапе идентификатор вкладки, а также, устанавливается параметр allFrames . Этот дополнительный параметр говорит что скрипт должен выполняться во всех фреймах web-страницы, в случае если они на этой странице есть.
Саму функцию grabImages мы напишем чуть позже.
callback
Это функция обратного вызова, которая будет вызвана после того, как внедренный скрипт исполнится на странице и во всех ее фреймах. В качестве параметра в ней будет массив результатов, которые вернула функция grabImages для каждого фрейма (возможно это будет всего один элемент, если фреймов нет). Каждый элемент этого массива — это объект типа InjectionResult. Он в частности содержит свойство result . Именно оно содержит то, что возвращает функция grabImages, т.е. список URL-ов.
Теперь соберем все вместе в следующем коде:
const grabBtn = document.getElementById("grabBtn"); grabBtn.addEventListener("click",() => < chrome.tabs.query(, function(tabs) < var tab = tabs[0]; if (tab) < chrome.scripting.executeScript( < target:, func:grabImages >, onResult ) > else < alert("There are no active tabs") >>) >) function grabImages() < // TODO - Запросить список изображений // и вернуть список их URL-ов >function onResult(frames) < // TODO - Объединить списки URL-ов, полученных из каждого фрейма в один, // затем объединить их в строку, разделенную символом перевода строки // и скопировать в буфер обмена >
Функция grabImages может быть определена следующим образом:
/** * Функция исполняется на удаленной странице браузера, * получает список изображений и возвращает массив * путей к ним * * @return Array массив строк */ function grabImages() < const images = document.querySelectorAll("img"); return Array.from(images).map(image=>image.src); >
Здесь все просто: получаем список DOM-элементов и извлекаем свойство src каждого из них в итоговый массив. Считаю нужным напомнить, что объект document , указанный в этой функции указывает на содержимое не той HTML-страницы, где находится функция grabImages , а удаленной web-страницы, в которую эта функция будет внедрена.
После того как данная функция выполнится в каждом фрейме, результаты будут объединены в единый массив и переданы в функцию onResult . Эта функция может быть определена следующим образом:
/** * Выполняется после того как вызовы grabImages * выполнены во всех фреймах удаленной web-страницы. * Функция объединяет результаты в строку и копирует * список путей к изображениям в буфер обмена * * @param <[]InjectionResult>frames Массив результатов * функции grabImages */ function onResult(frames) < // Если результатов нет if (!frames || !frames.length) < alert("Could not retrieve images from specified page"); return; >// Объединить списки URL из каждого фрейма в один массив const imageUrls = frames.map(frame=>frame.result) .reduce((r1,r2)=>r1.concat(r2)); // Скопировать в буфер обмена полученный массив // объединив его в строку, используя символ перевода строки // как разделитель window.navigator.clipboard .writeText(imageUrls.join("\n")) .then(()=>< // закрыть окно расширения после // завершения window.close(); >); >
Следует упомянуть что не все вкладки браузера — это вкладки с web-страницами. Например, может быть вкладка свойств браузера. Страницы на таких вкладках не имеют свойства document . В этом случае функция grabImages не выполнится и не вернет результатов. Этот случай обрабатывается в самом начале функции. Затем массив массивов результатов объединяется в единый плоский массив используя концепцию map/reduce, затем функция window.navigator.clipboard используется, чтобы скопировать его в буфер обмена. Предварительно массив результатов преобразовывается в строку, разделенную символом перевода строки.
Завершающие штрихи
Здесь я описал только незначительную часть Chrome Scripting API и описал только в контексте данного расширения. Полная документация по Chrome Scripting API доступна здесь: https://developer.chrome.com/docs/extensions/reference/scripting/.
Теперь немного почистим код. Здесь я считаю нужным часть функции обработчика grabBtn, которая выполняет chrome.scripting.executeScript вынести в отдельную функцию execScript . В результате файл popup.js выглядит так:
const grabBtn = document.getElementById("grabBtn"); grabBtn.addEventListener("click",() => < // Получить активную вкладку браузера chrome.tabs.query(, function(tabs) < var tab = tabs[0]; // и если она есть, то выполнить на ней скрипт if (tab) < execScript(tab); >else < alert("There are no active tabs") >>) >) /** * Выполняет функцию grabImages() на веб-странице указанной * вкладки и во всех ее фреймах, * @param tab Объект вкладки браузера */ function execScript(tab) < // Выполнить функцию на странице указанной вкладки // и передать результат ее выполнения в функцию onResult chrome.scripting.executeScript( < target:, func:grabImages >, onResult ) > /** * Получает список абсолютных путей всех картинок * на удаленной странице * * @return Array Массив URL */ function grabImages() < const images = document.querySelectorAll("img"); return Array.from(images).map(image=>image.src); > /** * Выполняется после того как вызовы grabImages * выполнены во всех фреймах удаленной web-страницы. * Функция объединяет результаты в строку и копирует * список путей к изображениям в буфер обмена * * @param <[]InjectionResult>frames Массив результатов * функции grabImages */ function onResult(frames) < // Если результатов нет if (!frames || !frames.length) < alert("Could not retrieve images from specified page"); return; >// Объединить списки URL из каждого фрейма в один массив const imageUrls = frames.map(frame=>frame.result) .reduce((r1,r2)=>r1.concat(r2)); // Скопировать в буфер обмена полученный массив // объединив его в строку, используя возврат каретки // как разделитель window.navigator.clipboard .writeText(imageUrls.join("\n")) .then(()=>< // закрыть окно расширения после // завершения window.close(); >); >
Заключение
Теперь, если нажать по иконке расширения Image Grabber и затем кликнуть кнопку GRAB NOW, то в буфере обмена будет список URL-ов всех изображений текущей web-страницы. Можно вставить его в любой текстовый редактор.
Для начала, возможно, неплохо, но практическая ценность такого расширения не велика. Поэтому в следующей части я покажу как выгрузить все эти изображения в виде zip-архива, а также, создать дополнительный интерфейс, для того чтобы выбрать какие картинки добавлять в этот ZIP-архив, а какие нет. Также опишу процесс публикации готового расширения в Chrome Store.
Поэтому подписывайтесь, думаю к концу следующей недели вторая часть будет доступна вместе с полным исходным кодом.
Вторая часть опубликована и доступна здесь.
Ваше первое расширение
Примечание: Если вы уже знакомы с основными понятиями о браузерных расширениях, то можете пропустить этот раздел и узнать как собираются файлы расширений. Также вы можете использовать справочную документацию чтобы начать создавать свои расширения. Посетите Мастер-класс по расширениям Firefox чтобы узнать больше о тестировании и публикации расширений для Firefox
В этой статье мы пройдём весь путь создания расширения для Firefox от начала и до конца. Это расширение будет просто добавлять красную рамку ко всем страницам, загруженным с mozilla.org или любого из его поддоменов.
Написание расширения
Создайте новую папку с именем borderify и перейдите в неё. Вы можете сделать это с помощью Проводника вашего компьютера или терминала командной строки (en-US) . Умение работать с терминалом командной строки очень полезно, поскольку оно помогает вести более продвинутую разработку расширений. Используя терминал, создать папку можно следующим образом:
mkdir borderify cd borderify
manifest.json
Используя удобный текстовый редактор, создайте в папке borderify новый файл с именем manifest.json и таким содержимым:
"manifest_version": 2, "name": "Borderify", "version": "1.0", "description": "Adds a red border to all webpages matching mozilla.org.", "icons": "48": "icons/border-48.png" >, "content_scripts": [ "matches": ["*://*.mozilla.org/*"], "js": ["borderify.js"] > ] >
- Первые три ключа: manifest_version , name и version , являются обязательными и содержат основные метаданные о расширении.
- description не обязателен, но рекомендуется добавлять его: это описание отображается в Управлении дополнениями.
- icons не обязателен, но рекомендуется добавлять его: он позволяет указать значок дополнения, который будет виден в Управлении дополнениями.
Самый интересный ключ здесь — это content_scripts (en-US) , он даёт указание Firefox загружать указанный скрипт на веб-страницах, у которых URL совпадает с заданным шаблоном. В нашем случае, мы просим Firefox загрузить скрипт с названием borderify.js на всех HTTP или HTTPS страницах, полученных с mozilla.org или любого из его поддоменов.
Предупреждение: В некоторых случаях вам нужно указать ID для вашего дополнения. Если это необходимо, то добавьте ключ browser_specific_settings (en-US) в файл manifest.json и установите свойство gecko.id :
"browser_specific_settings": "gecko": "id": "borderify@example.com" > >
icons/border-48.png
Если у расширения есть значок, то он будет отображаться в списке расширений Управления дополнениями. Наш файл manifest.json сообщает, что значок находится в файле icons/border-48.png .
Создайте папку icons внутри borderify и поместите в неё файл значка с именем border-48.png . Вы можете использовать значок из нашего примера, который взят из набора Google Material Design и используется по лицензии Creative Commons Attribution-ShareAlike.
Вы можете использовать собственный значок. Его размер должен быть 48×48 пикселей или 96×96 пикселей для отображения на мониторах с высоким разрешением. В этом случае необходимо указать его в качестве свойства 96 объекта icons в файле manifest.json :
"icons": "48": "icons/border-48.png", "96": "icons/border-96.png" >
Также можно использовать файл в формате SVG, и он будет правильно масштабироваться. Но нужно иметь в виду, что если вы используете SVG и ваш значок содержит текст, то возможно стоит воспользоваться инструментом «преобразовать в контур» в редакторе SVG, чтобы текст масштабировался корректно.
borderify.js
Наконец, создайте в папке borderify файл с именем borderify.js с таким содержимым:
.body.style.border = "5px solid red";
Этот скрипт будет встраиваться в страницы, адрес которых совпадает с шаблоном, указанном в ключе content_scripts файла manifest.json . Этот скрипт имеет прямой доступ ко всему документу, как если бы он был загружен самой страницей.
Пробуем
Сначала внимательно проверьте, что вы правильно разместили файлы и дали им правильные имена:
borderify/ icons/ border-48.png borderify.js manifest.json
Установка
В Firefox: откройте страницу about:debugging, кликните Этот Firefox , далее нажмите на кнопку Загрузить временное дополнение. и выберите любой файл из папки с нашим примером.
Расширение будет установлено и остается установленным до перезапуска Firefox.
Также можно запустить расширение с помощью командной строки используя утилиту web-ext.
Тестирование
Примечание: По умолчанию расширения не работают в режиме приватного просмотра. Если вы хотите протестировать расширение в режиме приватного просмотра, то откройте about:addons , выберите расширение и установите переключатель Запуск в приватных окнах в положение Разрешить .
Теперь перейдите на любую страницу » https://www.mozilla.org/ru/ » и вы должны будете увидеть красную границу вокруг страницы.

Примечание: Не проверяйте на addons.mozilla.org ! Встраиваемые скрипты на данный момент запрещены на этом поддомене.
Попробуйте немного поэкспериментировать. Измените цвет границы или сделайте что-нибудь ещё с содержимым страницы. Для этого внесите изменения в скрипт и перезагрузите файлы расширения, нажав кнопку Обновить в about:debugging . Сразу после этого изменения станут видны.
Упаковка и публикация
Чтобы другие люди могли использовать ваше дополнение, необходимо запаковать его и отправить в Mozilla для добавления подписи. Узнать больше об этом можно в разделе «Publish» Мастер-класса по расширениям Firefox.
Что дальше?
Теперь, когда вы имеете представление о разработке расширений для Firefox, вы можете:
- создать более сложное расширение,
- узнать больше о внутреннем устройстве расширений,
- посмотреть примеры расширений,
- узнать, что необходимо для разработки, тестирования и публикации расширений (en-US).
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 11 дек. 2023 г. by MDN contributors.
Your blueprint for a better internet.
Разрабатываем расширение для браузера
В этом небольшом туториале мы с вами разработаем простое, но полезное расширение для браузера с помощью Plasmo.
Наше расширение будет представлять собой вызываемый сочетанием клавиш попап с инпутом для поиска информации на MDN с выводом 5 лучших результатов в виде списка. Кроме основного функционала, мы добавим страницу настроек для кастомизации цветов и отображения хлебных крошек. Мы будем разрабатывать расширения для Chrome, которое также будет работать в Firefox.
Вот как это будет выглядеть:

Для тех, кого интересует только код, вот ссылка на соответствующий репозиторий.
Основной функционал — попап с поиском
Для работы с зависимостями будет использоваться Yarn.
Создаем шаблон приложения:
# mdn-finder - название приложения/расширения yarn create plasmo mdn-finder
Переходим в созданную директорию и устанавливаем зависимости:
cd mdn-finder yarn
Устанавливаем дополнительные зависимости, необходимые для работы поиска:
yarn add @plasmohq/storage downshift flexsearch fzf swr
- @plasmohq/storage — абстракция над Storage API, который может использоваться расширениями браузера для локального хранения данных;
- downshift — библиотека, предоставляющая примитивы для разработки простых, гибких, отвечающих всем критериям WAI-ARIA React-компонентов autocomplete/combobox или select/dropdown;
- flexsearch — библиотека для реализации полнотекстового поиска;
- fzf — библиотека для реализации неточного (fuzzy) поиска;
- swr — хуки React для получения, кэширования и мутации данных.
Структура проекта будет следующей:
- assets - icon.png - search-index.json - search.png - src - components - Search.tsx - search - fuzzy-search.ts - search-utils.ts - search.tsx - background.ts - options.tsx - popup.tsx - storage.ts - style.css - .
После переноса файлов в директорию src , необходимо немного отредактировать файл tsconfig.json :
// . "compilerOptions": "baseUrl": ".", "paths": "~*": [ "./src/*" ] > > >
О самом поиске я рассказывал в этой статье, поэтому в данном туториале мы сосредоточимся на Plasmo. Скопируйте файлы из директорий components , search и assets , а также файл style.css из репозитория проекта. Поисковый индекс ( search-index.json ), также можно копировать с MDN. Запросы к MDN из другого источника блокируются CORS, поэтому поисковый индекс хранится локально.
Для того, чтобы иметь возможность работать с поисковым индексом, необходимо немного отредактировать файл package.json :
// . "manifest": "web_accessible_resources": [ "resources": [ "assets/search-index.json" ], "matches": [ "https://*/*" ] > ], "host_permissions": [ "https://*/*" ] > >
Точкой входа приложения Plasmo является файл popup.tsx . Как следует из названия, этот компонент отвечает за рендеринг попапа, в котором будет находиться инпут для поиска. Редактируем этот файл следующим образом:
import Search from './components/Search' import './style.css' function IndexPopup() return Search preload=true> /> > export default IndexPopup
Запускаем сервер для разработки с помощью команды yarn dev . Выполнение этой команды приводит к генерации директории build/chrome-mv3-dev с файлами расширения.
Переходим по адресу chrome://extensions/ и загружаем расширение в браузер (кнопка «Загрузить распакованное расширение»/»Load unpacked extension»):

В режиме разработки расширение, загруженное в браузер, автоматически обновляется при изменении соответствующих файлов.
Сочетание клавиш для запуска расширения можно установить на странице chrome://extensions/shortcuts :
Для создания производственной сборки необходимо выполнить команду yarn build . По умолчанию создается сборка для Chrome. В настоящее время Plasmo также поддерживает создание сборок для Firefox. Команда для создания такой сборки: yarn build —target=firefox-mv2 . Подробнее почитать об этом можно здесь.
Для тестирования расширения в Firefox необходимо сделать 2 вещи:
- создать в директории src файл background.ts следующего содержания:
export >
Этот файл предназначен для запуска скриптов, отвечающих за выполнение фоновых задач. К таким скриптам относится, например, логика сервис-воркера. Подробнее почитать об этом можно здесь. Почему-то без этого файла расширение в Firefox не запускается.
- создать производственную сборку в виде архива с помощью команды yarn build —target=firefox-mv2 —zip .
Дополнительный функционал — страница настроек
Для инициализации страницы настроек достаточно создать файл options.tsx в директории src .
Простейшим способом обмена данными между попапом и страницей настроек (а также другими скриптами) является использование предоставляемого Plasmo хранилища.
Создаем в директории src файл storage.ts следующего содержания:
import Storage > from '@plasmohq/storage' // ключ объекта настроек export const OPTIONS_KEY = 'mdn_finder_options' // дефолтные настройки export const defaultOptions = // цвет фона backgroundColor: '#282c34', // цвет текста textColor: '#f7f7f7', // фон выделения selectionBackground: '#5cb85c', // цвет выделения selectionColor: '#282c34', // индикатор отображения хлебных крошек в списке результатов поиска showUrl: true > // создаем экземпляр хранилища const storage = new Storage() // и экспортируем его export default storage
Редактируем файл options.tsx следующим образом:
import useRef > from 'react' import storage, defaultOptions, OPTIONS_KEY > from '~storage' import './style.css' export default function IndexOptions() // ссылка на кнопку отправки формы const btnRef = useRefHTMLButtonElement | null>(null) // обработчик отправки формы const onSubmit: React.FormEventHandler = async (e) => e.preventDefault() // получаем данные формы в виде объекта const formData = Object.fromEntries( new FormData(e.target as HTMLFormElement).entries(), ) try // записываем настройки в хранилище await storage.set(OPTIONS_KEY, formData) // меняем текст кнопки if (btnRef.current) btnRef.current.textContent = 'Saved' const id = setTimeout(() => btnRef.current.textContent = 'Save' clearTimeout(id) >, 1000) > > catch (e) console.log(e) > > return ( form className='options' onSubmit=onSubmit>> label> Background color:' '> input type='color' name='backgroundColor' defaultValue=defaultOptions.backgroundColor> /> /label> label> Result item color:' '> input type='color' name='textColor' defaultValue=defaultOptions.textColor> /> /label> label> Selection background:' '> input type='color' name='selectionBackground' defaultValue=defaultOptions.selectionBackground> /> /label> label> Selection color:' '> input type='color' name='selectionColor' defaultValue=defaultOptions.selectionColor> /> /label> label> Show URL:' '> input type='checkbox' name='showUrl' defaultChecked=defaultOptions.showUrl> /> /label> button ref=btnRef>>Save/button> /form> ) >
Для того, чтобы попасть на страницу настроек, необходимо кликнуть по иконке расширения и выбрать пункт «Параметры»/»Options»:


Возвращаемся к попапу. Редактируем файл search/search.tsx . Импортируем хранилище и извлекаем из него настройки:
import storage, defaultOptions, OPTIONS_KEY > from '~storage' // . function InnerSearchNavigateWidget(props: InnerSearchNavigateWidgetProps) // . const [options, setOptions] = useState(defaultOptions) // . useEffect(() => storage.gettypeof options>(OPTIONS_KEY).then((opts) => if (opts) setOptions(opts) > >) >, []) // далее работаем с этим компонентом >
Индикатор отображения хлебных крошек ( options.showUrl ) используется при формировании списка результатов поиска:
resultItems.map((item, i) => ( div . getItemProps( key: item.url, className: 'result-item ' + (i === highlightedIndex ? 'highlight' : ''), item, index: i, >)> > HighlightMatch title=item.title> q=inputValue> /> /* ! */> Boolean(options.showUrl) ? ( > br /> BreadcrumbURI uri=item.url> positions=item.positions> /> /> ) : null> /div> ))
Цвет фона ( options.backgroundColor ) передается элементу формы:
form // . style= '--background-color': options.backgroundColor, > as React.CSSProperties > > /* . */> /form>
В файле style.css у нас имеются такие строки:
.search-form --background-color: var(--dark); /* . */ background-color: var(--background-color); >
Остальные цвета передаются контейнеру с результатами поиска:
div className='search-results' style= '--text-color': options.textColor, '--selection-background': options.selectionBackground, '--selection-color': options.selectionColor, > as React.CSSProperties > > searchResults> /div>
В style.css у нас имеются такие строки:
.search-results --text-color: var(--light); --selection-background: var(--success); --selection-color: var(--dark); > .result-item span, .result-item small color: var(--text-color); > .result-item mark background-color: var(--selection-background); color: var(--selection-color); >
Спасибо переменным CSS за их динамичность 🙂


Видим, что настройки благополучно применяются к попапу.
Следует отметить, что проект, созданный с помощью Plasmo CLI, включает в себя GitHub Action Browser Platform Publisher для автоматической публикации расширения во всех поддерживаемых сторах. Подробнее почитать об этом можно здесь. Соответствующий файл можно найти в директории .github/workflows .
К слову, поисковый индекс со статьями на русском языке можно найти здесь.
Надеюсь, вы узнали что-то новое и не зря потратили время.
Как создать расширение для Chrome
У вас в браузере наверняка есть хотя бы одно расширение. Если вам интересно узнать, как они делаются, читайте наш материал по написанию самого простого плагина для Google Chrome.
Расширения и плагины — полезные дополнения к уже существующим функциям на сайте и в браузере. С их помощью можно записывать аудио и видео с экрана, включать поиск ошибок, а также многое другое.
В этой статье мы рассмотрим создание самого простого расширения — запускатора избранных сайтов. Хотя приложение и будет примитивным, оно всё-таки раскроет процесс создания и загрузки расширения для google Chrome.
Желательно знать HTML, CSS и JS (если придётся расширить набор функций) на самом базовом уровне, чтобы понимать материал лучше, но в любом случае мы будем объяснять код.
В каждом расширении для Chrome должен быть файл manifest.json . Он служит только для описания функций приложения, общего описания, номера версии и разрешений. Более подробно вы сможете ознакомиться с этим файлом в блоге команды разработчиков Chrome.
Давайте же внесём свой вклад в развитие web
Здесь всё очень просто:
< "manifest_version": 2, "name": "Tproger Launcher", "description": "Запускатор представительств Tproger", "version": "1.0.0", "icons": , "browser_action": < "default_icon": "icon.png", "default_popup": "popup.html" >, "permissions": ["activeTab"] >
После того как мы описали наше расширение в файле manifest.json , можно благополучно переходить к следующему этапу, а именно к разметке.
Для начала давайте напишем базовый HTML-код:
Выше мы написали каркас для плагина, сейчас он полностью пуст и нужно указать название, ссылки на иконки и шрифт. Это можно сделать с помощью тега link , обратите внимание, он не закрывается:
Tproger Media Quick Launcher
Не забывайте указывать кодировку, иначе не отобразятся кириллические буквы.
Перейдём ко второму блоку кода, а именно к тегу body и его содержимому.
Так как наше расширение — модальное окно, давайте соответствующим образом назовём контейнеры. Сначала добавим контейнер шапки расширения, в которой укажем ссылку к иконке, напишем название и добавим номер версии.
Переходим к следующему контейнеру. Он содержит описание функций расширений.
Быстрый доступ к контентным площадкам Типичного Программиста
Далее следует контейнер modal-icons , внутри которого ещё 5 контейнеров.
Для каждой иконки мы выделили отдельный контейнер с классом flex , чтобы знать, к каким элементам будем применять Flexbox.
Кроме того, мы указали названия иконок для каждого ресурса. Более детально со всеми доступными элементами можно ознакомиться на сайте Bootstrap.
Стили
Чтобы расширение выглядело красивее и было удобнее, чем сейчас, нужно добавить стили на CSS.
/* Модальная структура документа */ /*общие настройки для всего документа*/ html, body < font-family: 'Open Sans', sans-serif; font-size: 14px; margin: 0; min-height: 180px; padding: 0; width: 380px; >/*задаём настройки для заголовков первого уровня*/ h1 < font-family: 'Menlo', monospace; font-size: 22px; font-weight: 400; margin: 0; color: #2f5876; >a:link, a:visited < color: #000000; outline: 0; text-decoration: none; >/*задаём ширину картинки*/ img < width: 30px; /*ширина изображений*/ >.modal-header < align-items: center; /*выравнивание элементов по центру*/ border-bottom: 0.5px solid #dadada; /*свойства нижней разделительной линии*/ height: 50px; >.modal-content < padding: 0 22px; /*отступы сверху и снизу, сверху и слева*/ >.modal-icons < border-top: 0.5px solid #dadada; /*свойства верхней разделительной линии*/ height: 50px; width: 100%; >.logo < padding: 16px; /*отступы со всех сторон*/ >.logo-icon < vertical-align: text-bottom; /*выравнивание по нижней части текста*/ margin-right: 12px; /*задётся отступ элементов от изображения*/ >.version
Основные настройки документа заданы, давайте перейдём к следующему фрагменту кода, в котором как раз и будет применён Flexbox, о котором шла речь в начале статьи.
.flex-container < display: flex; /*отображает контейнер в виде блочного элемента*/ justify-content: space-between; /*равномерное выравнивание элементов*/ padding: 10px 22px; >/*задаём настройки для контейнеров с иконками*/ .flex < opacity: 1; /*параметр непрозрачности иконок*/ width: 120px; >.flex:hover < opacity: 0.4; /*уровень непрозрачности при наведении курсора на элемент*/ >.flex .fa
Мы постарались как можно подробнее объяснить в комментариях относительно сложные моменты. А сейчас нам нужно лишь загрузить наше расширение в браузер Chrome и оно будет работать, а если пройдёт модерацию, то появится в магазине расширений (плагинов).
Теперь давайте добавим файл с расширением .js, если вдруг потребуется расширить функции дополнения для браузера.
Tproger Media Quick Launcher
Проверка кода и публикация
Прежде чем опубликовать, проверьте ещё раз весь код. Если вы делали всё так, как мы, то у должно было получиться следующее:
Запускатор Tproger /* Модальная структура документа */ /*общие настройки для всего документа*/ html, body < font-family: 'Open Sans', sans-serif; font-size: 14px; margin: 0; min-height: 180px; padding: 0; width: 380px; >/*задаём настройки для заголовков первого уровня*/ h1 < font-family: 'Menlo', monospace; font-size: 22px; font-weight: 400; margin: 0; color: #2f5876; >a:link, a:visited < color: #000000; outline: 0; text-decoration: none; >/*задаём ширину картинки*/ img < width: 30px; /*ширина изображений*/ >.modal-header < align-items: center; /*выравнивание элементов по центру*/ border-bottom: 0.5px solid #dadada; /*свойства нижней разделительной линии*/ height: 50px; >.modal-content < padding: 0 22px; /*отступы сверху и снизу, сверху и слева*/ >.modal-icons < border-top: 0.5px solid #dadada; /*свойства верхней разделительной линии*/ height: 50px; width: 100%; >.logo < padding: 16px; /*отступы со всех сторон*/ >.logo-icon < vertical-align: text-bottom; /*выравнивание по нижней части текста*/ margin-right: 12px; /*задётся отступ элементов от изображения*/ >.version < color: #444; font-size: 18px; >.flex-container < display: flex; /*отображает контейнер в виде блочного элемента*/ justify-content: space-between; /*равномерное выравнивание элементов*/ padding: 10px 22px; >/*задаём настройки для контейнеров с иконками*/ .flex < opacity: 1; /*параметр непрозрачности иконок*/ width: 120px; >.flex:hover < opacity: 0.4; /*уровень непрозрачности при наведении курсора на элемент*/ >.flex .fa --> Быстрый доступ к контентным площадкам Типичного Программиста