Ваше второе расширение
Если вы ознакомились со статьёй Ваше первое расширение, вы уже имеете представление о том, как писать расширения. В этой статье вы напишете несколько более сложное расширение, демонстрирующее несколько дополнительных API.
Расширение добавляет новую кнопку в панель инструментов Firefox. Когда пользователь нажимает на кнопку, открывается всплывающее окно, позволяющее выбрать животное. После выбора животного содержимое текущей страницы будет заменено изображением выбранного животного.
Для реализации этого мы будем:
- определить действие браузера — кнопку, прикреплённую к панели инструментов Firefox. Для кнопки мы предоставим:
- иконку, названную "beasts-32.png"
- всплывающее окно, которое откроется при нажатии на кнопку. Всплывающее окно будет содержать HTML, CSS и JavaScript.
- определить иконку для расширения, названную "beasts-48.png". Она будет отображаться в менеджере дополнений.
- написать скрипт содержимого "beastify.js", который будет инжектирован в веб-страницы. Это код, который фактически будет изменять страницы.
- упаковать изображения животных, для замены изображений на веб-странице. Мы сделаем изображения "веб-доступными ресурсами", чтобы веб-страница могла ссылаться на них.
Вы можете визуализировать общую структуру расширения следующим образом:
Это простое расширение, но оно демонстрирует многие базовые концепции API WebExtensions:
- добавление кнопки в панель инструментов
- определение всплывающего окна с помощью HTML, CSS и JavaScript
- инжектирование скриптов содержимого в веб-страницы
- общение между скриптами содержимого и остальной частью расширения
- упаковка ресурсов с расширением, которые могут использоваться веб-страницами
Вы можете найти полный исходный код расширения на GitHub.
Написание расширения
Создайте новую директорию и перейдите в неё:
mkdir beastify cd beastify
manifest.json
Теперь создайте новый файл с именем "manifest.json" и введите в него следующее содержимое:
{ "manifest_version": 2, "name": "Beastify", "version": "1.0", "description": "Adds a browser action icon to the toolbar. Click the button to choose a beast. The active tab's body content is then replaced with a picture of the chosen beast. See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Examples#beastify", "homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/beastify", "icons": { "48": "icons/beasts-48.png" }, "permissions": [ "activeTab" ], "browser_action": { "default_icon": "icons/beasts-32.png", "default_title": "Beastify", "default_popup": "popup/choose_beast.html" }, "web_accessible_resources": [ "beasts/frog.jpg", "beasts/turtle.jpg", "beasts/snake.jpg" ] }
- Первые три ключа:
manifest_version,nameиversionявляются обязательными и содержат основные метаданные для расширения. -
descriptionиhomepage_urlявляются необязательными, но рекомендуемыми: они предоставляют полезную информацию об расширении. -
iconsявляется необязательным, но рекомендуемым: он позволяет указать иконку для расширения, которая будет отображаться в менеджере дополнений. -
permissionsперечисляет разрешения, необходимые расширению. Здесь мы просто запрашиваемactiveTabразрешение. -
browser_actionопределяет кнопку на панели инструментов. Здесь мы предоставляем три элемента информации:-
default_iconявляется обязательным и указывает на иконку для кнопки -
default_titleявляется необязательным и будет отображаться в всплывающей подсказке -
default_popupиспользуется, если вы хотите, чтобы при нажатии пользователем на кнопку отображалось всплывающее окно. Мы этого хотим, поэтому включили этот ключ и указали ссылку на HTML-файл, включённый в расширение.
-
-
web_accessible_resourcesперечисляет файлы, которые мы хотим сделать доступными для веб-страниц. Поскольку расширение заменяет содержимое страницы изображениями, которые мы упаковали вместе с расширением, нам необходимо сделать эти изображения доступными для страницы.
Обратите внимание, что все пути указаны относительно самого manifest.json.
Иконка
Расширение должно иметь иконку. Она будет отображаться рядом с записью расширения в менеджере дополнений (его можно открыть, перейдя по адресу "about:addons"). В нашем manifest.json было обещано, что у нас будет иконка для панели инструментов по адресу "icons/beasts-48.png".
Создайте директорию "icons" и сохраните там иконку с именем "beasts-48.png". Вы можете использовать иконку из нашего примера, которая взята из набора иконок Aha-Soft's Free Retina iconset и используется в соответствии с условиями его лицензии.
Если вы выберете свою иконку, она должна быть размером 48x48 пикселей. Вы также можете предоставить иконку размером 96x96 пикселей для дисплеев высокого разрешения, и в этом случае она будет указана как свойство 96 объекта icons в manifest.json:
"icons": { "48": "icons/beasts-48.png", "96": "icons/beasts-96.png" }
Кнопка на панели инструментов
Кнопка на панели инструментов также нуждается в иконке, и в нашем manifest.json было обещано, что у нас будет иконка для панели инструментов по адресу "icons/beasts-32.png".
Сохраните иконку с именем "beasts-32.png" в директории "icons". Вы можете использовать иконку из нашего примера, которая взята из набора иконок IconBeast Lite icon set и используется в соответствии с условиями его лицензии.
Если вы не предоставите всплывающее окно, то при нажатии пользователя на кнопку будет отправлено событие клика вашему расширению. Если вы предоставите всплывающее окно, событие клика не отправляется, а вместо этого открывается всплывающее окно. Мы хотим всплывающее окно, поэтому давайте создадим его дальше.
Всплывающее окно
Функция всплывающего окна — позволить пользователю выбрать одно из трёх животных.
Создайте новую директорию с именем "popup" в корне расширения. Здесь мы будем хранить код для всплывающего окна. Всплывающее окно будет состоять из трёх файлов:
-
choose_beast.htmlопределяет содержимое панели -
choose_beast.cssстилизует содержимое -
choose_beast.jsобрабатывает выбор пользователя, выполняя скрипт содержимого в активной вкладке
mkdir popup cd popup touch choose_beast.html choose_beast.css choose_beast.js
choose_beast.html
HTML-файл выглядит следующим образом:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <link rel="stylesheet" href="choose_beast.css" /> </head> <body> <div id="popup-content"> <button>Frog</button> <button>Turtle</button> <button>Snake</button> <button type="reset">Reset</button> </div> <div id="error-content" class="hidden"> <p>Can't beastify this web page.</p> <p>Try a different page.</p> </div> <script src="choose_beast.js"></script> </body> </html>
У нас есть элемент <div> с идентификатором "popup-content", который содержит кнопки для каждого выбора животного и кнопку сброса. У нас есть другой элемент <div> с идентификатором "error-content" и классом "hidden". Мы воспользуемся им на случай, если возникнут проблемы с инициализацией всплывающего окна.
Обратите внимание, что мы включаем CSS- и JS-файлы из этого файла, точно так же, как и на веб-странице.
choose_beast.css
CSS устанавливает размер всплывающего окна, обеспечивает, чтобы три выбора заполнили всё пространство, и придает им базовую стилизацию. Также он скрывает элементы с class="hidden": это означает, что наш элемент <div id="error-content"... будет скрыт по умолчанию.
html, body { width: 100px; } .hidden { display: none; } button { border: none; width: 100%; margin: 3% auto; padding: 4px; text-align: center; font-size: 1.5em; cursor: pointer; background-color: #E5F2F2; } button:hover { background-color: #CFF2F2; } button[type="reset"] { background-color: #FBFBC9; } button[type="reset"]:hover { background-color: #EAEA9D; }
choose_beast.js
Вот JavaScript для всплывающего окна:
/** * CSS to hide everything on the page, * except for elements that have the "beastify-image" class. */ const hidePage = `body > :not(.beastify-image) { display: none; }`; /** * Listen for clicks on the buttons, and send the appropriate message to * the content script in the page. */ function listenForClicks() { document.addEventListener("click", (e) => { /** * Given the name of a beast, get the URL to the corresponding image. */ function beastNameToURL(beastName) { switch (beastName) { case "Frog": return browser.runtime.getURL("beasts/frog.jpg"); case "Snake": return browser.runtime.getURL("beasts/snake.jpg"); case "Turtle": return browser.runtime.getURL("beasts/turtle.jpg"); } } /** * Insert the page-hiding CSS into the active tab, * then get the beast URL and * send a "beastify" message to the content script in the active tab. */ function beastify(tabs) { browser.tabs.insertCSS({ code: hidePage }).then(() => { const url = beastNameToURL(e.target.textContent); browser.tabs.sendMessage(tabs[0].id, { command: "beastify", beastURL: url }); }); } /** * Remove the page-hiding CSS from the active tab, * send a "reset" message to the content script in the active tab. */ function reset(tabs) { browser.tabs.removeCSS({ code: hidePage }).then(() => { browser.tabs.sendMessage(tabs[0].id, { command: "reset", }); }); } /** * Just log the error to the console. */ function reportError(error) { console.error(`Could not beastify: ${error}`); } /** * Get the active tab, * then call "beastify()" or "reset()" as appropriate. */ if (e.target.tagName !== "BUTTON" || !e.target.closest("#popup-content")) { // Ignore when click is not on a button within <div id="popup-content">. return; } if (e.target.type === "reset") { browser.tabs.query({active: true, currentWindow: true}) .then(reset) .catch(reportError); } else { browser.tabs.query({active: true, currentWindow: true}) .then(beastify) .catch(reportError); } }); } /** * There was an error executing the script. * Display the popup's error message, and hide the normal UI. */ function reportExecuteScriptError(error) { document.querySelector("#popup-content").classList.add("hidden"); document.querySelector("#error-content").classList.remove("hidden"); console.error(`Failed to execute beastify content script: ${error.message}`); } /** * When the popup loads, inject a content script into the active tab, * and add a click handler. * If we couldn't inject the script, handle the error. */ browser.tabs .executeScript({ file: "/content_scripts/beastify.js" }) .then(listenForClicks) .catch(reportExecuteScriptError);
Начать следует с 99-й строки. Скрипт всплывающего окна выполняет скрипт содержимого в активной вкладке сразу же после загрузки всплывающего окна, используя API browser.tabs.executeScript(). Если выполнение скрипта содержимого прошло успешно, то скрипт содержимого будет оставаться загруженным на странице до закрытия вкладки или перехода пользователем на другую страницу.
Общая причина, по которой вызов browser.tabs.executeScript() может завершиться неудачей, заключается в том, что вы не можете выполнять скрипты содержимого на всех страницах. Например, вы не можете выполнять их на привилегированных страницах браузера, таких как about:debugging, и вы не можете выполнять их на страницах в домене addons.mozilla.org. Если это произойдет, reportExecuteScriptError() скроет элемент <div id="popup-content">, отобразит элемент <div id="error-content"... и запишет ошибку в консоль.
Если выполнение скрипта содержимого прошло успешно, мы вызываем listenForClicks(). Это слушает клики в всплывающем окне.
- Если клик не произошёл по кнопке в всплывающем окне, мы его игнорируем и ничего не делаем.
- Если клик произошёл по кнопке с
type="reset", тогда мы вызываемreset(). - Если клик произошёл по любой другой кнопке (т.е. кнопкам животных), тогда мы вызываем
beastify().
Функция beastify() выполняет три действия:
- сопоставляет нажатую кнопку с URL, указывающим на изображение конкретного животного
- скрывает всё содержимое страницы, инжектируя немного CSS, используя API
browser.tabs.insertCSS() - отправляет сообщение "beastify" скрипту содержимого с помощью API
browser.tabs.sendMessage(), запрашивая его, чтобы он преобразил страницу, и передавая ему URL изображения животного.
Функция reset() фактически отменяет "beastify":
- удаляет добавленный нами CSS, используя API
browser.tabs.removeCSS() - отправляет сообщение "reset" скрипту содержимого, запрашивая его, чтобы он сбросил страницу.
Скрипт содержимого
Создайте новую папку с именем «content_scripts» в корне расширения и создайте в ней новый файл «beastify.js» со следующим содержимым:
(() => { /** * Check and set a global guard variable. * If this content script is injected into the same page again, * it will do nothing next time. */ if (window.hasRun) { return; } window.hasRun = true; /** * Given a URL to a beast image, remove all existing beasts, then * create and style an IMG node pointing to * that image, then insert the node into the document. */ function insertBeast(beastURL) { removeExistingBeasts(); const beastImage = document.createElement("img"); beastImage.setAttribute("src", beastURL); beastImage.style.height = "100vh"; beastImage.className = "beastify-image"; document.body.appendChild(beastImage); } /** * Remove every beast from the page. */ function removeExistingBeasts() { const existingBeasts = document.querySelectorAll(".beastify-image"); for (const beast of existingBeasts) { beast.remove(); } } /** * Listen for messages from the background script. * Call "insertBeast()" or "removeExistingBeasts()". */ browser.runtime.onMessage.addListener((message) => { if (message.command === "beastify") { insertBeast(message.beastURL); } else if (message.command === "reset") { removeExistingBeasts(); } }); })();
Первое, что делает скрипт содержимого, это проверяет наличие глобальной переменной window.hasRun: если она установлена, скрипт завершается, в противном случае он устанавливает window.hasRun и продолжает выполнение. Это делается для того, чтобы при каждом открытии всплывающего окна всплывающее окно выполняло скрипт содержимого в активной вкладке, поэтому у нас может быть несколько экземпляров скрипта, работающих в одной вкладке. В этом случае необходимо убедиться, что только первый экземпляр будет выполнять какие-либо действия.
После этого, начинать нужно с 40-й строки, где скрипт содержимого прослушивает сообщения от всплывающего окна с помощью API browser.runtime.onMessage. Как мы видели выше, скрипт всплывающего окна может отправлять два разных типа сообщений: «beastify» и «reset».
- Если сообщение равно «beastify», ожидается, что оно будет содержать URL-адрес изображения животного. Мы удаляем любые добавленные ранее изображения животных, затем создаём и добавляем элемент
<img>, у которого атрибутsrcустановлен на URL-адрес изображения животного. - Если сообщение равно «reset», мы просто удаляем все добавленные изображения животных.
Животные
Наконец, нам нужно добавить изображения животных.
Создайте новую папку с именем «beasts» и добавьте в неё три изображения с соответствующими именами. Вы можете получить изображения из репозитория GitHub, или вот:
Проверка
Сначала убедитесь, что у вас правильные файлы в правильных местах:
beastify/
beasts/
frog.jpg
snake.jpg
turtle.jpg
content_scripts/
beastify.js
icons/
beasts-32.png
beasts-48.png
popup/
choose_beast.css
choose_beast.html
choose_beast.js
manifest.json
Теперь загрузите расширение как временное дополнение. Откройте «about:debugging» в Firefox, нажмите «Загрузить временное дополнение» и выберите файл manifest.json. После этого значок расширения должен появиться в панели инструментов Firefox:
Откройте веб-страницу, щелкните значок, выберите животное и посмотрите, как изменится веб-страница:
Разработка из командной строки
Вы можете автоматизировать этап временной установки, используя инструмент web-ext. Попробуйте это:
cd beastify
web-ext run
Что дальше?
Теперь, когда вы создали более продвинутое расширение WebExtension для Firefox:
© 2005–2023 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_second_WebExtension