Spec-Zone.ru › Web Extensions

Ваше второе расширение

Если вы ознакомились со статьёй Ваше первое расширение, вы уже имеете представление о том, как писать расширения. В этой статье вы напишете несколько более сложное расширение, демонстрирующее несколько дополнительных API.

Расширение добавляет новую кнопку в панель инструментов Firefox. Когда пользователь нажимает на кнопку, открывается всплывающее окно, позволяющее выбрать животное. После выбора животного содержимое текущей страницы будет заменено изображением выбранного животного.

Для реализации этого мы будем:

  • определить действие браузера — кнопку, прикреплённую к панели инструментов Firefox. Для кнопки мы предоставим:
    • иконку, названную "beasts-32.png"
    • всплывающее окно, которое откроется при нажатии на кнопку. Всплывающее окно будет содержать HTML, CSS и JavaScript.
  • определить иконку для расширения, названную "beasts-48.png". Она будет отображаться в менеджере дополнений.
  • написать скрипт содержимого "beastify.js", который будет инжектирован в веб-страницы. Это код, который фактически будет изменять страницы.
  • упаковать изображения животных, для замены изображений на веб-странице. Мы сделаем изображения "веб-доступными ресурсами", чтобы веб-страница могла ссылаться на них.

Вы можете визуализировать общую структуру расширения следующим образом:

The manifest.json file includes icons, browser actions, including popups, and web accessible resources. The choose beast javascript popup resource calls in the beastify script.

Это простое расширение, но оно демонстрирует многие базовые концепции 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" скрипту содержимого, запрашивая его, чтобы он сбросил страницу.
END_OF_DOCUMENT_MARKER ```

Скрипт содержимого

Создайте новую папку с именем «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, или вот:

A brown frog.

An emerald tree boa with white stripes.

A red-eared slider turtle.

Проверка

Сначала убедитесь, что у вас правильные файлы в правильных местах:

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:

The beastify icon in the Firefox toolbar

Откройте веб-страницу, щелкните значок, выберите животное и посмотрите, как изменится веб-страница:

A page replaced with the image of a turtle

Разработка из командной строки

Вы можете автоматизировать этап временной установки, используя инструмент 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API