Модификация веб страницы

Одним из наиболее распространённых вариантов использования расширений является внесение изменение в веб-страницу. К примеру, расширение может изменить стиль, применённый к странице, скрыть существующие или вставить на страницу дополнительные DOM-узлы.

Существует два способа сделать это используя WebExtension API:

  • Декларативно: объявить шаблон, которому соответствует набор URL-адресов, и загрузить набор скриптов на страницы, которые попадают в под этот шаблон.
  • Программно: используя JavaScript API, загрузить скрипт на страницу, из определённой вкладки.

В любом случае, эти скрипты называются контентными скриптами, и отличаются от других скриптов, которые составляют расширение:

  • Они получают доступ к малому подмножеству WebExtension API.
  • Они получают прямой доступ к странице, на которой были загружены.
  • Они взаимодействуют с остальными скриптами расширения, используя API сообщений.

В этой статье мы рассмотрим оба способа загрузки скрипта.

Модификация страниц, подпадающих под URL-шаблон

Прежде всего создадим новую директорию, назовём её "modify-page". В этой директории, создадим файл "manifest.json", со следующим содержимым:

json
{
  "manifest_version": 2,
  "name": "modify-page",
  "version": "1.0",

  "content_scripts": [
    {
      "matches": ["https://developer.mozilla.org/*"],
      "js": ["page-eater.js"]
    }
  ]
}

Ключ content_scripts (en-US) - это как мы загружаем скрипты на страницы, соответствующие URL-шаблону. В нашем случае, content_scripts говорит браузеру загрузить скрипт "page-eater.js" на все страницы, начинающиеся с https://developer.mozilla.org/.

Примечание: Поскольку свойство "js" ключа content_scripts это массив, вы можете использовать его, для внедрения более одного скрипта. Если вы сделаете это, страницы получат набор, как если бы эти скрипты были загружены самой страницей, они будут загружены в той же очерёдности, в которой они расположены в массиве.

Примечание: Ключ content_scripts также имеет свойство "css", которое вы можете использовать для вставки CSS-таблиц.

Далее, создадим файл "page-eater.js", внутри директории "modify-page":

js
document.body.textContent = "";

var header = document.createElement("h1");
header.textContent = "Эта страница была съедена";
document.body.appendChild(header);

Теперь установим расширение, и перейдём на страницу https://developer.mozilla.org/:

Примечание: Обратите внимание, несмотря на то, что в указанном видео, на странице addons.mozilla.org всё работает нормально, на текущий момент, для этого сайта, контентные скрипты заблокированы.

Программная модификация страницы

Что, если вы всё ещё хотите "съедать" страницы, но лишь в тех случаях, когда пользователь попросил об этом? Давайте обновим этот пример таким образом, чтобы мы внедряли контентный скрипт, когда пользователь выбирает соответствующий пункт контентного меню.

Для начала обновим "manifest.json":

json
{
  "manifest_version": 2,
  "name": "modify-page",
  "version": "1.0",

  "permissions": ["activeTab", "contextMenus"],

  "background": {
    "scripts": ["background.js"]
  }
}

Мы удалили ключ content_scripts и добавили два новых:

  • permissions (разрешения): для внедрения скрипта, нам нужны разрешения для страниц, которые мы модифицируем. Разрешение activeTab это способ получить доступ к текущей вкладки. Нам также нужно разрешение contextMenus, чтобы добавлять в контекстное меню новые элементы.
  • background (фоновый): мы используем этот ключ, для загрузки постоянного "фонового скрипта", с именем "background.js", в котором мы настроим контекстное меню и внедрим контентный скрипт.

Давайте создадим этот файл. Создадим новый файл "background.js" в директории "modify-page" и поместим в него следующий код:

js
browser.contextMenus.create({
  id: "eat-page",
  title: "Съесть эту страницу",
});

browser.contextMenus.onClicked.addListener(function (info, tab) {
  if (info.menuItemId == "eat-page") {
    browser.tabs.executeScript({
      file: "page-eater.js",
    });
  }
});

В этом скрипте мы создаём элемент контекстного меню, передавая ему определённый идентификатор и заголовок (текст будет отображаться в элементе контекстного меню). Затем мы настраиваем обработчик событий таким образом, чтобы когда пользователь выбирает пункт контекстного меню, осуществлялась проверка, наш ли это элемент eat-page. Если это так - внедряем скрипт "page-eater.js" в текущую вкладку, используя tabs.executeScript() (en-US) API. Это API опционально принимает идентификатор вкладки, в качестве аргумента. Мы опустили его, это означает, что скрипт будет внедряться в текущую активную вкладку.

На данном этапе расширение должно иметь следующий вид:

modify-page/
    background.js
    manifest.json
    page-eater.js

Теперь перезагрузим расширение, откроем страницу (на этот раз любую) активируем контекстное меню и выберем "Съесть эту страницу":

Примечание: Обратите внимание, несмотря на то, что в указанном видео, на странице addons.mozilla.org всё работает нормально, на текущий момент, для этого сайта, контентные скрипты заблокированы.

Обмен сообщениями

Контентные и фоновые скрипты не могут на прямую взаимодействовать друг с другом. Не смотря на это они могут взаимодействовать с помощью обмена сообщениями. Для этого один конец создаёт обработчик сообщений, а другой - может посылать сообщения. В следующей таблице представлены API-интерфейсы, задействованные с каждой стороны:

В контентном скрипте В фоновом скрипте
Отправка сообщения browser.runtime.sendMessage() browser.tabs.sendMessage()
Получение сообщения browser.runtime.onMessage browser.runtime.onMessage

Давайте обновим наш пример, чтобы посмотреть, как послать сообщение из фонового скрипта.

Изменим "background.js" :

js
browser.contextMenus.create({
  id: "eat-page",
  title: "Съесть эту страницу",
});

function messageTab(tabs) {
  browser.tabs.sendMessage(tabs[0].id, {
    replacement: "Message from the extension!",
  });
}

browser.contextMenus.onClicked.addListener(function (info, tab) {
  if (info.menuItemId == "eat-page") {
    browser.tabs.executeScript({
      file: "page-eater.js",
    });

    var querying = browser.tabs.query({
      active: true,
      currentWindow: true,
    });
    querying.then(messageTab);
  }
});

Теперь, после внедрения "page-eater.js", мы используем tabs.query() (en-US), чтобы получить текущую открытую вкладку и используем tabs.sendMessage() (en-US), для отправки сообщения контентному скрипту, загруженному на этой вкладке. Сообщение несёт полезную нагрузку {replacement: "Message from the extension!"}.

Далее, обновим "page-eater.js":

js
function eatPage(request, sender, sendResponse) {
  document.body.textContent = "";

  var header = document.createElement("h1");
  header.textContent = request.replacement;
  document.body.appendChild(header);
}

browser.runtime.onMessage.addListener(eatPage);

Теперь, вместо простого "поедания страницы", контентный скрипт ждёт сообщение, используя runtime.onMessage (en-US). Когда сообщение получено, контентный скрипт выполняет в точности такой же код, как и а примере ранее, за исключением того, что заменяющий текст берётся из request.replacement.

Если мы хотим отправить сообщение наоборот, из контентного скрипта в фоновый, настройка будет обратной данному примеру, за исключением того, что мы будем использовать runtime.sendMessage() (en-US) в контентном скрипте.

Примечание: Все эти примеры внедряют JavaScript; вы можете программно внедрять стилевые таблицы CSS используя функцию tabs.insertCSS() (en-US).

Узнать больше