Встраиваемый скрипт - Mozilla

Встраиваемый скрипт
===================

Встраиваемый скрипт - это часть расширения, которая выполняется в контексте отдельной веб-страницы (в отличии от фоновых скриптов, выполняющихся в контексте целого браузера).

Фоновые скрипты имеют доступ ко всем методам WebExtension API, но они не имеют доступа к контенту отдельных веб-страниц. Так что если вашему расширению необходимо взаимодействие с контентом веб-страницы, вам нужен встраиваемый скрипт.

Точно так же, как скрипты загружаемые веб-страницами, встраиваемый скрипт может читать и изменять контент веб-страницы, используя DOM API.

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

**Примечание:** Обратите внимание, что встраивание скриптов блокируется на следующих доменах:

* accounts-static.cdn.mozilla.net
* accounts.firefox.com
* addons.cdn.mozilla.net
* addons.mozilla.org
* api.accounts.firefox.com
* content.cdn.mozilla.net
* content.cdn.mozilla.net
* discovery.addons.mozilla.org
* input.mozilla.org
* install.mozilla.org
* oauth.accounts.firefox.com
* profile.accounts.firefox.com
* support.mozilla.org
* sync.services.mozilla.com
* testpilot.firefox.com

Если вы попытаетесь встроить скрипт на страницы, находящиеся на вышеперечисленных доменах, у вас ничего не выйдет, и веб-страница сделает запись о CSP ошибке.

По причине того, что список запрещённых доменов включает в себя addons.mozilla.org, пользователи, которые попытаются испытать ваше расширение прямо на странице загрузки, могут посчитать, что расширение не работает! В связи с этим вы могли бы предоставить им соответствующее предупреждение или onboarding page чтобы немедленно переадресовать их с addons.mozilla.org.

**Примечание:** Значения, определённые в глобальной области видимости встраиваемого скрипта с помощью `var foo` или `window.foo = "bar"` могут исчезать по причине бага 1408996.

Загрузка встраиваемого скрипта
------------------------------

Загрузка встраиваемых скриптов на веб-страницу происходит следующими тремя способами:

1. **Автоматическая загрузка скрипта на страницах с определёнными URL, объявленными при установке:** используя `content_scripts` (en-US) ключ в manifest.json, вы можете запросить браузер загружать встраиваемый скрипт каждый раз, когда браузер загружает веб-страницу, чей URL совпадает с объявленными шаблонами.
2. **Автоматическая загрузка скрипта на страницах с определёнными URL, объявленными в момент работы расширения:** используя `contentScripts` (en-US) API, вы можете запросить браузер загружать встраиваемый скрипт каждый раз, когда браузер загружает веб-страницу, чей URL совпадает с объявленными шаблонами. Это очень похоже на первый метод, но позволяет добавлять и удалять правила динамически во время работы расширения.
3. **Загрузка скрипта расширением в конкретную вкладку:** используя `tabs.executeScript()` (en-US) API, вы можете загружать встраиваемые скрипты в определённые вкладки когда захотите: например, в ответ на нажатие пользователя на browser action.

Существует только одна глобальная область видимости *для одного фрейма, для одного расширения*. Это означает, что значения из одного встраиваемого скрипта могут быть доступны для другого встраиваемого скрипта, не зависимо от того, как встраиваемый скрипт был загружен на страницу.

Используя методы (1) и (2), вы можете загружать скрипты на страницы, чьи URL могут быть представлены, используя шаблон совпадения.

Используя метод (3), вы к тому же можете загружать скрипты на страницы вашего расширения, но вы не можете загружать скрипты на привилегированные страницы браузеры (например "about:debugging" или "about:addons").

Условия работы встраиваемого скрипта
------------------------------------

### Доступ к DOM

Встраиваемые скрипты имеют доступ и могут изменять DOM на веб-странице, так же как и обычные скрипты. Так же они могут видеть любые изменения сделанные с DOM страничными скриптами.

При этом, встраиваемые скрипты имеют "чистое DOM представление". Это значит:

* Встраиваемые скрипты не могут видеть JavaScript переменных, определённых страничными скриптами.
* Если страничный скрипт переназначит встроенное свойство DOM, встраиваемый скрипт всё равно будет видеть его изначальное значение, а не переназначенное.

В Firefox это поведение называется Xray vision (en-US).

Например, рассмотрите эту веб-страницу:

```html
<!doctype html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  </head>

  <body>
    <script src="page-scripts/page-script.js"></script>
  </body>
</html>
```

Скрипт "page-script.js" делает следующее:

```js
// page-script.js

// добавляет новый элемент к DOM
var p = document.createElement("p");
p.textContent = "Этот параграф был добавлен страничным скриптом.";
p.setAttribute("id", "page-script-para");
document.body.appendChild(p);

// определяет новое свойство на объекте window
window.foo = "Эта глобальная переменная была добавлена страничным скриптом";

// переопределяет встроенную window.confirm() функцию
window.confirm = function () {
  alert("Страничный скрипт так же переопределил 'confirm'");
};
```

Теперь расширение загружает встраиваемый скрипт на страницу:

```js
// content-script.js

// получает доступ и изменяет DOM
var pageScriptPara = document.getElementById("page-script-para");
pageScriptPara.style.backgroundColor = "blue";

// не может видеть свойств определённых страничным скриптом
console.log(window.foo); // undefined

// видит изначальное значение свойства, переопределённого страничным скриптом
window.confirm("Вы уверены?"); // вызывает оригинальный window.confirm()
```

Те же самые правила применяются и наоборот: страничный скрипт не может видеть JavaScript свойств, добавленных встраиваемым скриптом.

Это означает, что встраиваемый скрипт может полагаться на то, что DOM свойства всегда будут вести себя предсказуемо, и не беспокоиться о том, что его переменные будут иметь конфликт с переменными из страничного скрипта.

Одно из последствий такого поведения состоит в том, что встраиваемый скрипт не будет иметь доступ к JavaScript библиотекам, загруженным страничным скриптом. Например, если веб-страница загружает jQuery, встраиваемый скрипт не сможет увидеть эту библиотеку.

Если встраиваемому скрипту необходимо использовать какую-либо JavaScript библиотеку, тогда эта библиотека должна быть загружена, тем же способом, как и встраиваемый скрипт вместе с ним:

```json
"content\_scripts": [
  {
    "matches": ["\*://\*.mozilla.org/\*"],
    "js": ["jquery.js", "content-script.js"]
  }
]
```

**Примечание:** Firefox предоставляет некоторое API, позволяющее встраиваемому скрипту иметь доступ к JavaScript объектам, созданным страничным скриптом, и предоставлять свои собственные JavaScript объекты страничному скрипту.

Смотрите совместное использование объектов со страничным скриптом (en-US) для получения дополнительной информации.

### Доступные WebExtension API

В дополнение стандартному DOM API, встраиваемый скрипт может использовать следующие методы WebExtension API:

Из `extension`:

* `getURL()`
* `inIncognitoContext`

Из `runtime`:

* `connect()`
* `getManifest()`
* `getURL()`
* `onConnect`
* `onMessage`
* `sendMessage()`

Из `i18n` (en-US):

* `getMessage()` (en-US)
* `getAcceptLanguages()` (en-US)
* `getUILanguage()` (en-US)
* `detectLanguage()` (en-US)

Из `menus` (en-US):

* `getTargetElement` (en-US)

Ко всему из `storage` (en-US).

### Работа с XHR и Fetch

Встраиваемые скрипты могут делать запросы используя `window.XMLHttpRequest` и `window.fetch()` API.

**Примечание:** В Firefox, запросы, совершаемые из встраиваемого скрипта (например, используя fetch()) происходят в контексте расширения, так что вам необходимо предоставлять полный URL для доступа к контенту страниц. В Chrome, эти запросы совершаются в контексте страницы, так что URL может быть относительным, например `/api` будет трансформирован в `https://[current page URL]/api`.

Встраиваемый скрипт имеет точно такие же кросс-доменные привилегии, как и всё остальное расширение: так что если расширение запросило кросс-доменный доступ на какой-либо домен используя `permissions` ключ в manifest.json, тогда его встраиваемый скрипт тоже будет иметь доступ к этому домену.

Это достигается, предоставляя встраиваемому скрипту более привилегированные XHR and fetch объекты. Что имеет побочный эффект, связанный с отсутствием `Origin` и `Referer` заголовков, которые имел бы запрос, выполняемый из страничного скрипта. Зачастую это предпочитаемо, для того чтобы предотвратить раскрытие кросс-доменной натуры запроса. Начиная с версии 58 и дальше, расширения, которым необходимо выполнять запросы, которые должны выглядеть будто они отправлены встраиваемым скриптом, могут использовать `content.XMLHttpRequest` и `content.fetch()`. Кросс-браузерные расширения должны проверять присутствие этих методов.

Взаимодействие с фоновыми скриптами
-----------------------------------

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

Существует два способа общения между фоновым и встраиваемым скриптами: вы можете посылать одиночные сообщения, ожидая необязательного ответа, или вы можете установить долгосрочное соединение на двух сторонах и использовать это соединение для обмена сообщениями.

### Одиночные сообщения

Для отправки одиночного сообщения и ожидания необязательного ответа, вы можете использовать следующее API:

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

Например, представьте встраиваемый скрипт, который обрабатывает нажатие левой кнопки мыши на веб-странице.

Если нажатие было произведено по ссылке, встраиваемый скрипт отправляет сообщение фоновому скрипту со значение URL в ссылке:

```js
// content-script.js

window.addEventListener("click", notifyExtension);

function notifyExtension(e) {
  if (e.target.tagName != "A") {
    return;
  }
  browser.runtime.sendMessage({ url: e.target.href });
}
```

Фоновый скрипт ожидает эти сообщения и, при их получении, отображает уведомления, используя `notifications` (en-US) API:

```js
// background-script.js

browser.runtime.onMessage.addListener(notify);

function notify(message) {
  browser.notifications.create({
    type: "basic",
    iconUrl: browser.extension.getURL("link.png"),
    title: "Вы нажали на ссылку!",
    message: message.url,
  });
}
```

(Этот пример кода частично взят из notify-link-clicks-i18n примера на GitHub.)

### Обмен сообщениями через постоянное соединение

Отправка одиночных сообщений может стать довольно обременительной, если вы пересылаете много сообщений между встраиваемым и фоновым скриптами. Альтернативное решение в этой ситуации будет установить постоянное соединение между двумя скриптами, и использовать его для обмена сообщениями.

Каждая из сторон имеет `runtime.Port` (en-US) объект, который они могут использовать для обмена сообщениями.

Для создания постоянного соединения:

* Одна из сторон должна ждать соединений, используя `runtime.onConnect` (en-US)
* Другая сторона должна вызвать:
    + `tabs.connect()` (en-US) (при соединении со встраиваемым скриптом); или
    + `runtime.connect()` (en-US) (при соединении с фоновым скриптом).

Результатом вызова этих методов будет возвращение `runtime.Port` (en-US) объекта.

* `runtime.onConnect` (en-US) методу передаётся аргумент, являющийся собственным портом этого скрипта, `runtime.Port` (en-US) объект.

Как только обе стороны имеют порт, они могут:

* отправлять сообщения, используя `runtime.Port.postMessage()`, и
* получать сообщения используя `runtime.Port.onMessage()`

Например, сразу после загрузки, нижерасположенный встраиваемый скрипт:

* Соединяется с фоновым скриптом
* Сохраняет `Port` в переменную `myPort`
* Начинает ожидать и записывать сообщения приходящие на `myPort`
* Использует `myPort` для отправки сообщений на фоновый скрипт, когда пользователь нажимает левую кнопку мыши на документе

```js
// content-script.js

var myPort = browser.runtime.connect({ name: "port-from-cs" });
myPort.postMessage({ greeting: "Привет из встраиваемого скрипта" });

myPort.onMessage.addListener(function (m) {
  console.log(
    "Во встраиваемом скрипте, получено сообщение из фонового скрипта: ",
  );
  console.log(m.greeting);
});

document.body.addEventListener("click", function () {
  myPort.postMessage({ greeting: "Они кликнули по странице!" });
});
```

Соответствующий фоновый скрипт:

* Ожидает попытки соединения от встраиваемого скрипта
* Когда он получает эту попытку соединения:
    + сохраняет свой порт в переменную `portFromCS`
    + отправляет сообщение встроенному скрипту, используя этот порт
    + начинает ожидать сообщений, приходящих на порт, и записывать их
* Отправляет сообщения на встраиваемый скрипт, используя `portFromCS`, когда пользователь нажимает кнопку расширения в браузере

```js
// background-script.js

var portFromCS;

function connected(p) {
  portFromCS = p;
  portFromCS.postMessage({ greeting: "Привет, встраиваемый скрипт!" });
  portFromCS.onMessage.addListener(function (m) {
    console.log(
      "В фоновом скрипте, получено сообщение от встраиваемого скрипта",
    );
    console.log(m.greeting);
  });
}

browser.runtime.onConnect.addListener(connected);

browser.browserAction.onClicked.addListener(function () {
  portFromCS.postMessage({ greeting: "Они нажали кнопку!" });
});
```

#### Несколько встраиваемых скриптов

Если вы имеете несколько встраиваемых скриптов, с которыми вы обращаетесь в одно и то же время, вы бы могли сохранять каждое соединение в массиве.

```js
// background-script.js

var ports = [];

function connected(p) {
  ports[p.sender.tab.id] = p;
  //...
}

browser.runtime.onConnect.addListener(connected);

browser.browserAction.onClicked.addListener(function () {
  ports.forEach((p) => {
    p.postMessage({ greeting: "Они нажали на кнопку!" });
  });
});
```

Взаимодействие с веб-страницей
------------------------------

Хотя по умолчанию встраиваемые скрипты не имеют доступ к объектам, которые созданы страничными скриптами, они могут взаимодействовать со страничными скриптами, используя `window.postMessage` и `window.addEventListener` API.

Например:

```js
// page-script.js

var messenger = document.getElementById("from-page-script");

messenger.addEventListener("click", messageContentScript);

function messageContentScript() {
  window.postMessage({
    direction: "from-page-script",
    message: "Сообщение со страницы"
  }, "\*");
```

```js
// content-script.js

window.addEventListener("message", function (event) {
  if (
    event.source == window &&
    event.data &&
    event.data.direction == "from-page-script"
  ) {
    alert(
      'Встраиваемый скрипт получил сообщение: "' + event.data.message + '"',
    );
  }
});
```

Для просмотра законченного и полностью рабочего примера, посетите эту страницу на GitHub и следуйте инструкциям.

**Предупреждение:** Заметьте, при взаимодействии с непроверенными страницами этим способом, вам нужно быть очень осторожными . Расширения имеют возможность запускать привилегированный код, который может обладать серьёзными возможностями, и вредоносные веб-страницы легко могут обмануть ваше расширение, ради доступа к этим возможностям.

Как простейший пример, представим встраиваемый скрипт, который получает сообщение от веб-страницы и делает что-то вроде этого:

```js
// content-script.js

window.addEventListener("message", function (event) {
  if (
    event.source == window &&
    event.data.direction &&
    event.data.direction == "from-page-script"
  ) {
    eval(event.data.message);
  }
});
```

Теперь страничный скрипт может запускать любой код со всеми привилегиями встраиваемого скрипта.

Использование `eval()` во встраиваемых скриптах
-----------------------------------------------

В Chrome, `eval()` всегда выполняет код в контексте встраиваемого скрипта, а не в контексте веб-страницы.

В Firefox:

* Если вы вызываете `eval()`, код выполняется в контексте **встраиваемого скрипта**.
* Если вы вызываете `window.eval()`, код выполняется в контексте **страничного скрипта**.

Например, рассмотрите этот встраиваемый скрипт:

```js
// content-script.js

window.eval("window.x = 1;");
eval("window.y = 2");

console.log(`Во встраиваемом скрипте, window.x: ${window.x}`);
console.log(`Во встраиваемом скрипте, window.y: ${window.y}`);

window.postMessage(
  {
    message: "check",
  },
  "\*",
);
```

Этот код создаёт переменные `x` and `y`, используя `window.eval()` и `eval()`, затем записывает их значения, и отправляет сообщение на веб-страницу.

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

```js
window.addEventListener("message", function (event) {
  if (event.source === window && event.data && event.data.message === "check") {
    console.log(`In page script, window.x: ${window.x}`);
    console.log(`In page script, window.y: ${window.y}`);
  }
});
```

В Chrome, в консоли будут записаны следующие строки:
```

In content script, window.x: 1
In content script, window.y: 2
In page script, window.x: undefined
In page script, window.y: undefined
```

В Firefox, будут записаны следующие строки:
```

In content script, window.x: undefined
In content script, window.y: 2
In page script, window.x: 1
In page script, window.y: undefined
```

Те же правила применимы к `setTimeout()`, `setInterval()`, и `Function()`.

**Предупреждение:** Будьте очень осторожны, запуская код в контексте страничного скрипта. Окружение страничного скрипта может контролироваться потенциально зловредным веб-страницей, которая может переопределить объекты, с которыми вы взаимодействуете, так что их поведение станет совершенно непредсказуемым:

```js
// page.js переопределяет console.log

var original = console.log;

console.log = function () {
  original(true);
};
```

```js
// content-script.js вызывает переопределённую версию

window.eval("console.log(false)");
```


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

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

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

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

Чтобы реализовать это, мы:

* **определим browser action (en-US) - кнопку, прикреплённую к панели инструментов Firefox.**
 Для кнопки мы предоставим:
    + иконку с именем "beasts-32.png"
    + всплывающую панель, если кнопка нажата. Панель состоит из HTML, CSS, и JavaScript.
* **определим иконку для дополнения** с именем "beasts-48.png". Иконка будет показана в Менеджере дополнений.
* **напишем сценарий содержимого(content script) "beastify.js", который будет встроен в веб-страницы**.
 Это тот код, который и будет изменять страницы.
* **упакуем несколько изображений животных для замены изображений на веб-странице**.
 Мы сделаем изображения "доступными веб-ресурсами"(web accessible resources), чтобы веб-страница могла ссылаться на них.

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

![](/ru/docs/Mozilla/Add-ons/WebExtensions/Your_second_WebExtension/untitled-1.png)

Это простое дополнение, но показывает множество основных концепций WebExtension API:

* добавление кнопки на панель инструментов
* определение всплывающей панели используя HTML, CSS, и JavaScript
* встраивание контент-скрипта в веб-страницы
* взаимодействие между сценарием содержимого и остальным дополнением
* упаковка ресурсов с вашим дополнением, которые будут использованы веб-страницами

Вы можете найти полный исходный код дополнения на GitHub.

Чтобы написать это дополнение, вам нужен Firefox 45 или новее.

Написание расширение
--------------------

Создайте новую директорию и перейдите в неё:

```bash
mkdir beastify
cd beastify
```

### manifest.json

Теперь создайте файл "manifest.json" и вставьте в него следующее содержимое:

```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` permission.
* `browser_action` (en-US) задаёт кнопку на панели инструментов. Здесь мы предоставляем три вида информации:
    + `default_icon` это обязательная иконка для кнопки
    + `default_title` необязательный заголовок, будет показан в подсказке
    + `default_popup` используется, если вы хотите, чтобы всплывающая панель была показана когда пользователь кликает по кнопке. В нашем примере мы использовали этот ключ и он указывает на HTML файл, подключённый к дополнению.
* `web_accessible_resources` (en-US) перечисляет файлы, которые мы хотим сделать доступными для веб-страниц. Поскольку дополнение заменяет содержимое страницы на изображения, которые мы упаковали вместе с дополнением, нам нужно сделать эти изображения доступными для страницы.

Обратите внимание, что все пути указаны относительно файла manifest.json.

### Иконка

Дополнение должно иметь иконку. Она будет показана рядом с дополнением в Менеджере Дополнений (Вы можете открыть менеджер перейдя по ссылке "about:addons"). Наш manifest.json обещает, что у нас будет иконка для панели инструментов по адресу "icons/beasts-48.png".

Создайте папку "icons" и сохраните там иконку с именем "beasts-48.png". Вы можете использовать иконку из нашего примера, которая взята из набора Aha-Soft's Free Retina и используется на условиях этой лицензии.

Если вы выберете свою иконку, она должна быть размером 48x48 пикселей. Вы также можете предоставить иконку размером 96x96 пикселей для дисплеев с высоким разрешением, определив свойство `"96"` объекта `icons` в файле manifest.json:

```json
"icons": {
  "48": "icons/beasts-48.png",
  "96": "icons/beasts-96.png"
}
```

### Кнопка панели инструментов

Кнопка панели инструментов также нуждается в иконке, и наш manifest.json обещает, что у нас будет иконка для панели инструментов по адресу "icons/beasts-32.png".

Сохраните иконку с именем "beasts-32.png" в папке "icons". Вы можете использовать иконку из нашего примера, которая взята из набора IconBeast Lite и используется на условиях этой лицензии.

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

### Всплывающая панель

Функция панели - позволить пользователю выбрать одного из трёх зверей.

Создайте новую папку с именем "popup" в корневой папке дополнения. Здесь мы сохраним код для панели. Панель будет состоять из трёх файлов:

* **`choose_beast.html`** определяет содержимое панели
* **`choose_beast.css`** стили для содержимого
* **`choose_beast.js`** обрабатывает выбор пользователя, выполняя content script в активной вкладке

#### choose\_beast.html

HTML выглядит так:

```html
<!doctype html>

<html>
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="choose\_beast.css" />
  </head>

  <body>
    <div class="button beast">Frog</div>
    <div class="button beast">Turtle</div>
    <div class="button beast">Snake</div>
    <div class="button clear">Reset</div>

    <script src="choose\_beast.js"></script>
  </body>
</html>
```

У нас есть элемент для каждого животного. Обратите внимание, что мы подключаем CSS и JS файлы из HTML файла, как на обыкновенной веб-странице.

#### choose\_beast.css

CSS фиксирует размер всплывающей панели, гарантирует что три варианта заполняют пространство и даёт им основной стиль:

```css
html,
body {
  width: 100px;
}

.button {
  margin: 3% auto;
  padding: 4px;
  text-align: center;
  font-size: 1.5em;
  cursor: pointer;
}

.beast:hover {
  background-color: #cff2f2;
}

.beast {
  background-color: #e5f2f2;
}

.clear {
  background-color: #fbfbc9;
}

.clear:hover {
  background-color: #eaeac9;
}
```

#### choose\_beast.js

В JavaScript для всплывающего окна мы обрабатываем события click. Если click был на одном из трёх вариантов наших животных, мы добавляем content script в активную вкладку. После того, как content script загрузится, мы отправляем ему сообщение с выбранным животным:

```js
/\*
Учитывая имя зверя, получаем URL соответствующего изображения.
\*/
function beastNameToURL(beastName) {
  switch (beastName) {
    case "Frog":
      return browser.extension.getURL("beasts/frog.jpg");
    case "Snake":
      return browser.extension.getURL("beasts/snake.jpg");
    case "Turtle":
      return browser.extension.getURL("beasts/turtle.jpg");
  }
}

/\*
Слушаем события клика во всплывающей панели.

Если кликнули одного из зверей:
 Добавляем "beastify.js" к активной вкладке.

 Затем получаем активную вкладку и отправляем сценарию "beastify.js"
 сообщение, содержащее URL к картинке с выбранным зверем.

Если кликнули кнопку, класс которой содержит "clear":
 Перезагрузить страницу.
 Закрыть всплывающую панель. Это необходимо, так как content script
 неисправен после перезагрузки страницы.
\*/

document.addEventListener("click", (e) => {
  if (e.target.classList.contains("beast")) {
    var chosenBeast = e.target.textContent;
    var chosenBeastURL = beastNameToURL(chosenBeast);

    browser.tabs.executeScript(null, {
      file: "/content\_scripts/beastify.js",
    });

    var gettingActiveTab = browser.tabs.query({
      active: true,
      currentWindow: true,
    });
    gettingActiveTab.then((tabs) => {
      browser.tabs.sendMessage(tabs[0].id, { beastURL: chosenBeastURL });
    });
  } else if (e.target.classList.contains("clear")) {
    browser.tabs.reload();
    window.close();
  }
});
```

Скрипт использует три функции WebExtension API:

* `browser.tabs.executeScript` (en-US) добавляет content script, найденный по адресу content\_scripts/beastify.js", к активной вкладке
* `browser.tabs.query` (en-US) запрашивает активную вкладку
* `browser.tabs.sendMessage` (en-US) отправляет сообщение для content script, который работает в активной вкладке. Сообщение содержит URL изображения выбранного зверя.
### Content script

Создайте новую папку с именем "content\_scripts" в корневой папке дополнения и создайте в ней новый файл с именем "beastify.js", со следующим кодом:

```js
/\*
beastify():
\* удаляет каждый узел в document.body,
\* затем вставляет выбранного зверя
\* затем удаляется как обработчик
\*/
function beastify(request, sender, sendResponse) {
  removeEverything();
  insertBeast(request.beastURL);
  browser.runtime.onMessage.removeListener(beastify);
}

/\*
Удаляет каждый узел в document.body
\*/
function removeEverything() {
  while (document.body.firstChild) {
    document.body.firstChild.remove();
  }
}

/\*
Учитывая URL изображения зверя, создаёт и стилизует узел IMG,
указывающий на это изображение, затем вставляет узел в документ.
\*/
function insertBeast(beastURL) {
  var beastImage = document.createElement("img");
  beastImage.setAttribute("src", beastURL);
  beastImage.setAttribute("style", "width: 100vw");
  beastImage.setAttribute("style", "height: 100vh");
  document.body.appendChild(beastImage);
}

/\*
Назначает beastify() обработчиком сообщений расширения.
\*/
browser.runtime.onMessage.addListener(beastify);
```

Content script добавляет обработчик к сообщениям от дополнения (в частности как в файле "choose\_beast.js" выше). В обработчике скрипт:

* удаляет каждый элемент из `document.body`
* создаёт `<img>` элемент, указывающий на переданный URL, и вставляет элемент в DOM
* удаляет обработчик сообщений.
### Звери

Наконец, нам нужно подключить изображения животных.

Создайте новую папку с именем "beasts" и добавьте туда три изображения с соответствующими именами. Вы можете получить изображения из GitHub репозитория, или прямо здесь:

![](/ru/docs/Mozilla/Add-ons/WebExtensions/Your_second_WebExtension/frog.jpg)
![](/ru/docs/Mozilla/Add-ons/WebExtensions/Your_second_WebExtension/snake.jpg)
![](/ru/docs/Mozilla/Add-ons/WebExtensions/Your_second_WebExtension/turtle.jpg)

Тестирование
------------

Во-первых, дважды проверьте, что у вас все файлы на своих местах:
```

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
```

Начиная с Firefox 45 вы можете временно установить дополнения с жёсткого диска.

Откройте "about:debugging" в Firefox, кликните "Загрузить временное дополнение", и выберете ваш файл manifest.json. После этого вы должны увидеть иконку дополнения на панели инструментов Firefox:

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

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

Вы можете автоматизировать этап временной установки используя web-ext. Попробуйте это:

```bash
cd beastify
web-ext run
```


manifest.json - Mozilla

manifest.json
=============

**Примечание:** В этой статье описан файл manifest.json для веб-расширений. Если вы ищете информацию о manifest.json для Прогрессивных веб-приложений (PWAs), смотрите статью Манифест веб-приложения.

Файл `manifest.json` это единственный файл, который обязательно должен быть в каждом расширении, использующем WebExtension API.

Используя manifest.json, вы определяете базовые метаданные о расширении, такие как имя и версия. Также можно определить некоторые аспекты функциональности (такие, как фоновые скрипты, контент скрипты и действия браузера).

Это файл в формате JSON, но в нём можно использовать комментарии, каждая строка которых должна начинаться с "`//`".

Список полей manifest.json
--------------------------

Поддерживаемые `manifest.json` поля перечислены ниже:

* action
* author
* background
* browser\_action
* browser\_specific\_settings
* chrome\_settings\_overrides
* chrome\_url\_overrides
* commands
* content\_scripts
* content\_security\_policy
* declarative\_net\_request
* default\_locale
* description
* developer
* devtools\_page
* dictionaries
* externally\_connectable
* homepage\_url
* host\_permissions
* icons
* incognito
* manifest\_version
* name
* offline\_enabled
* omnibox
* optional\_permissions
* options\_page
* options\_ui
* page\_action
* permissions
* protocol\_handlers
* short\_name
* sidebar\_action
* storage
* theme
* theme\_experiment
* user\_scripts
* version
* version\_name
* web\_accessible\_resources
### Примечания о полях manifest.json

* Поля `"manifest_version"`, `"version"` и `"name"` являются обязательными.
* Поле `"default_locale"` обязательно, если есть папка `"_locales"`, иначе его нужно опустить.
* Поле `"browser_specific_settings"` не поддерживается Google Chrome.
### Доступ к полям manifest.json во время выполнения

C помощью функции JavaScript `runtime.getManifest()` можно получить доступ к файлу манифеста расширения:

```js
browser.runtime.getManifest().version;
```

Пример
------

В следующем блоке показан основной синтаксис некоторых часто используемых полей манифеста.

**Примечание:** Этот пример не рассчитан для того, чтобы его скопировали и вставили в файл реального проекта. Выбор нужных полей зависит от особенностей создаваемого расширения.

Если нужны примеры готовых расширений, смотрите Примеры расширений.

```json
{
  "browser\_specific\_settings": {
    "gecko": {
      "id": "addon@example.com",
      "strict\_min\_version": "42.0"
    }
  },

  "background": {
    "scripts": ["jquery.js", "my-background.js"]
  },

  "browser\_action": {
    "default\_icon": {
      "19": "button/geo-19.png",
      "38": "button/geo-38.png"
    },
    "default\_title": "Whereami?",
    "default\_popup": "popup/geo.html"
  },

  "commands": {
    "toggle-feature": {
      "suggested\_key": {
        "default": "Ctrl+Shift+Y",
        "linux": "Ctrl+Shift+U"
      },
      "description": "Send a 'toggle-feature' event"
    }
  },

  "content\_security\_policy": "script-src 'self' https://example.com; object-src 'self'",

  "content\_scripts": [
    {
      "exclude\_matches": ["\*://developer.mozilla.org/\*"],
      "matches": ["\*://\*.mozilla.org/\*"],
      "js": ["borderify.js"]
    }
  ],

  "default\_locale": "en",

  "description": "...",

  "icons": {
    "48": "icon.png",
    "96": "icon@2x.png"
  },

  "manifest\_version": 2,

  "name": "...",

  "page\_action": {
    "default\_icon": {
      "19": "button/geo-19.png",
      "38": "button/geo-38.png"
    },
    "default\_title": "Whereami?",
    "default\_popup": "popup/geo.html"
  },

  "permissions": ["webNavigation"],

  "version": "0.1",

  "user\_scripts": {
    "api\_script": "apiscript.js"
  },

  "web\_accessible\_resources": ["images/my-image.png"]
}
```

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Для полного обзора всех полей манифеста и их содержимого, смотрите полную таблицу совместимости `manifest.json` (en-US) с браузерами.

Смотрите также
--------------

`permissions` (en-US) JavaScript API


Примеры расширений - Mozilla

Примеры расширений
==================

В целях иллюстрации разработки расширений, мы поддерживаем репозиторий простых демонстрационных расширений по адресу https://github.com/mdn/webextensions-examples. Данная статья описывает, как использовать эти примеры, и перечисляет их вместе с WebExtension API, которые они демонстрируют.

Эти примеры работают в Firefox Nightly: большинство из них работает и в более ранних версиях Firefox, но, чтобы удостовериться, проверьте поле strict\_min\_version в файле manifest.json.

**Предупреждение:** Некоторые примеры работают только на специальных доменах или страницах. Подробности о всех ограничениях представлены в файле readme каждого примера. По умолчанию ни один из примеров не работает в приватных окнах браузера, подробности смотрите в Extensions in Private Browsing.

Если вы хотите попробовать эти примеры, клонируйте репозиторий, а после этого совершите одно из следующих действий:

1. Загрузите расширение из папки с его исходниками, используя функцию браузера Load Temporary Add-on. Расширение останется загруженным до следующего перезапуска Firefox.
2. Откройте папку с исходниками расширения в командной строке и используйте команду `web-ext`, чтобы запустить расширение. Расширение останется загруженным до следующего перезапуска Firefox.
3. В браузере Firefox используйте **Файл** > **Открыть файл** и найдите расширение в папке `build`. Папка `build` содержит построенные и подписанные версии всех примеров. В результате пример установится надолго, как обычное расширение.

**Предупреждение:** **Важно**: Пожалуйста, не публикуйте эти примеры Веб-расширений на addons.mozilla.org (AMO) - вам не нужно подписывать примеры Веб-расширений для того, чтобы их запустить. Просто следуйте шагам, описанным выше.

Если вы хотите сделать свой вклад в репозиторий, отправьте нам pull request!

| Name | Description | JavaScript APIs |
| --- | --- | --- |
| annotate-page | Displays a sidebar that lets you take notes on web pages. | `storage.local``tabs.onActivated``tabs.onUpdated``tabs.query``tabs.Tab``windows.getCurrent``windows.Window` |
| apply-css | Adds a page action to the toolbar. Click the button to apply a red border using injected CSS. Click the button again to remove the CSS. | `pageAction.getTitle``pageAction.onClicked``pageAction.setIcon``pageAction.setTitle``pageAction.show``tabs.insertCSS``tabs.onUpdated``tabs.query``tabs.removeCSS``tabs.Tab` |
| beastify | 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. | `extension.getURL``runtime.onMessage``tabs.executeScript``tabs.insertCSS``tabs.query``tabs.removeCSS``tabs.sendMessage``tabs.Tab` |
| bookmark-it | Adds a bookmark button to the toolbar. Click the button to toggle a bookmark for the current page. | `bookmarks.create``bookmarks.onCreated``bookmarks.onRemoved``bookmarks.remove``bookmarks.search``browserAction.onClicked``browserAction.setIcon``browserAction.setTitle``tabs.onActivated``tabs.onUpdated``tabs.query``tabs.Tab``windows.onFocusChanged` |
| borderify | Adds a solid red border to all webpages matching mozilla.org. |  |
| chill-out | Show a page action after a period of inactivity. Show cat gifs when the page action is clicked. | `alarms.clearAll``alarms.create``alarms.onAlarm``pageAction.hide``pageAction.onClicked``pageAction.show``tabs.get``tabs.onActivated``tabs.onUpdated``tabs.query``tabs.update` |
| commands | Demonstrates using the commands API to set up a keyboard shortcut. The shortcut created is accessed using Ctrl+Shift+U (Command+Shift+U on a Mac). | `commands.getAll``commands.onCommand``commands.reset``commands.update``tabs.create` |
| content-script-register | Illustrates how an extension can register URL-matching content scripts at runtime. | `contentScripts.register``runtime.onMessage``runtime.sendMessage` |
| context-menu-copy-link-with-types | Add a context menu option to links to copy the link to the clipboard, as plain text and as a link in rich HTML. | `contextMenus.create``contextMenus.onClicked``tabs.executeScript` |
| contextual-identities | List, create, and remove contextual identities. | `contextualIdentities.query``tabs.create``tabs.query``tabs.remove` |
| cookie-bg-picker | Allows the user to customize the background color and tiled pattern on sites they visit, and also saves their preferences via a cookie, reapplying them whenever they revisit a site they previously customized. | `cookies.get``cookies.onChanged``cookies.remove``cookies.set``extension.getURL``runtime.onMessage``tabs.onActivated``tabs.onUpdated``tabs.query``tabs.Tab``tabs.sendMessage` |
| devtools-panels | Demonstrates some of the devtools APIs. | `devtools.inspectedWindow``devtools.panels``runtime.getURL``runtime.onMessage``runtime.sendMessage``tabs.executeScript` |
| discogs-search | Demonstrates adding a custom search engine with the chrome\_settings\_overrides key. |  |
| dnr-block-only | Demonstrates how to block network requests without host permissions using the declarativeNetRequest API with the `declarative\_net\_request` manifest key. | `declarativeNetRequest.Rule``declarativeNetRequest.RuleAction``declarativeNetRequest.RuleCondition` |
| dnr-dynamic-with-options | Demonstrates a generic way to request host permissions and register declarativeNetRequest rules to modify network requests, without any install-time permission warnings. The options\_ui page offers a way to request permissions and register DNR rules. | `declarativeNetRequest.Rule``declarativeNetRequest.getDynamicRules``declarativeNetRequest.getSessionRules``declarativeNetRequest.updateDynamicRules``declarativeNetRequest.updateSessionRules``permissions.getAll``permissions.remove``permissions.request` |
| dnr-redirect-url | Demonstrates multiple ways to redirect requests using the declarativeNetRequest API through the `declarative\_net\_request` manifest key. Demonstrates aspects of Manifest Version 3 (MV3): action, host\_permissions, and web\_accessible\_resources, and includes a comparison with Manifest Version 2 (MV2). | `declarativeNetRequest.Redirect``declarativeNetRequest.Rule``declarativeNetRequest.RuleAction``declarativeNetRequest.RuleCondition``declarativeNetRequest.URLTransform``permissions.contains``permissions.remove``permissions.request` |
| dynamic-theme | Dynamic theme example | `alarms.create``alarms.onAlarm``theme.update` |
| emoji-substitution | Replaces words with emojis. |  |
| eslint-example | Demonstrates how to configure an extension with eslint. |  |
| export-helpers | Demonstrates how to use export helpers like cloneInto to share objects with page scripts. | `notifications.create``runtime.onMessage``runtime.sendMessage` |
| favourite-colour | An example options page, letting you store your favourite colour. | `browserAction.onClicked``runtime.openOptionsPage``storage.managed``storage.sync` |
| find-across-tabs | Demonstration of the find API. | `browserAction.onClicked``extension.getBackgroundPage``find.find``find.highlightResults``runtime.getURL``runtime.onMessage``runtime.sendMessage``tabs.create``tabs.query``tabs.Tab` |
| firefox-code-search | Demonstrates how to use the omnibox API. | `omnibox.onInputChanged``omnibox.onInputEntered``omnibox.setDefaultSuggestion``tabs.create``tabs.update` |
| forget-it | Demonstrates how to use the browsingData API. | `browserAction.onClicked``browsingData.remove``notifications.create``storage.local` |
| google-userinfo | Demonstrates how to use the identity API. | `browserAction.onClicked``identity.getRedirectURL``identity.launchWebAuthFlow``notifications.create` |
| history-deleter | History API demo: deletes history items for a given domain | `history.deleteUrl``history.search``pageAction.show``tabs.onUpdated``tabs.query` |
| http-response | Demonstrates how to rewrite HTTP responses using the webRequest.filterResponseData() API. | `webRequest.filterResponseData``webRequest.onBeforeRequest` |
| imagify | Using a sidebar, illustrates the use of file picker and drag and drop. A content script replaces the current page content with the chosen image. | `extension.getURL``runtime.onMessage``tabs.executeScript``tabs.query``tabs.sendMessage` |
| latest-download | Shows the last downloaded item, and lets you open or delete it. | `downloads.erase``downloads.getFileIcon``downloads.open``downloads.removeFile``downloads.search` |
| list-cookies | This extensions list the cookies in the active tab. | `cookies.getAll``tabs.query` |
| menu-accesskey-visible | Shows how to set up a single letter access key for a menu item. | `i18n.getMessage``menus.update``menus.create``menus.onClicked` |
| menu-demo | Demonstrates adding and manipulating menu items using the menus API. | `i18n.getMessage``menus.create``menus.onClicked``menus.remove``menus.update``runtime.lastError``tabs.executeScript` |
| menu-labelled-open | Shows how an extension can listen for the display of a menu and then add, remove, or update its menu items. | `menus.create``menus.onClicked``menus.onShown``menus.refresh``menus.update``tabs.update` |
| menu-remove-element | Shows how to detect the page element at the cursor position and remove that element, or a parent element, from the page. This example includes use of the polyfill, illustrating cross-browser extension development. | `menus.create``menus.onClicked``menus.getTargetElement``pageAction.openPopup``pageAction.show``tabs.executeScript` |
| menu-search | Illustrates how to retrieve a list of search engines and issue a search request, using search engine details added to the context menu for selected text. | `search.search``search.get``menus.create``menus.onClicked` |
| mocha-client-tests | This example shows two methods of testing an extension: running tests from within the extension, and running tests from the command line using Karma. | `runtime.onMessage``runtime.sendMessage` |
| native-messaging | Example of native messaging, including a Python application and an extension which exchanges messages with it. | `browserAction.onClicked``runtime.connectNative` |
| navigation-stats | Demonstration of the webNavigation API, showing basic stats about which pages you've visited. | `storage.local``webNavigation.onCommitted``webNavigation.onCompleted` |
| notify-link-clicks-i18n | Shows a localized notification when the user clicks on links. | `extension.getURL``i18n.getMessage``notifications.create``runtime.onMessage``runtime.sendMessage` |
| open-my-page-button | Adds a browser action icon to the toolbar. When the browser action is clicked, the add-on opens a page that was packaged with it. | `browserAction.onClicked``tabs.create` |
| page-to-extension-messaging | Demonstrates how a web page and a content script can exchange messages. Visit https://mdn.github.io/webextensions-examples/content-script-page-script-messaging.html for the demo. |  |
| permissions | Demonstrates optional permissions using the permissions API. | `browserAction.onClicked``permissions.getAll``permissions.remove``permissions.request``runtime.getURL``tabs.create` |
| private-browsing-theme | Example dynamic theme: sets a dark theme for private windows. | `theme.reset``theme.update``windows.getAll``windows.onCreated` |
| proxy-blocker | Uses the proxy API to block requests to hosts specified on a list. | `extension.getURL``proxy.onRequest``proxy.onError``storage.local``storage.onChanged` |
| quicknote | Allows the user to make quick notes by clicking a button and entering text into the resulting popup. The notes are saved in storage. | `storage.local` |
| root-cert-stats | Shows how to get details about a request's TLS connection. | `webRequest.getSecurityInfo` |
| runtime-examples | Demo of various runtime APIs. | `browserAction.onClicked``notifications.create``runtime.getManifest``runtime.onInstalled``runtime.reload` |
| selection-to-clipboard | Demonstrates how to write to the clipboard from a content script |  |
| session-state | Demonstrates how to retrieve extension-defined state from restored tabs. | `menus.create``menus.onClicked``sessions.getTabValue``sessions.setTabValue``tabs.insertCSS``tabs.onCreated``tabs.onUpdated``tabs.query` |
| store-collected-images | Demonstrates how to use the idb-file-storage library to store and manipulate files in an extension. | `browserAction.onClicked``contextMenus.create``contextMenus.onClicked``runtime.onMessage``runtime.sendMessage``tabs.create``windows.create` |
| stored-credentials | Performs basic authentication by supplying stored credentials. | `storage.local``webRequest.onAuthRequired``webRequest.onCompleted``webRequest.onErrorOccurred` |
| tabs-tabs-tabs | Demonstrates tab manipulation: opening, closing, moving, zooming tabs. | `browserAction.setBadgeBackgroundColor``browserAction.setBadgeText``tabs.create``tabs.duplicate``tabs.getZoom``tabs.move``tabs.onCreated``tabs.onMoved``tabs.onRemoved``tabs.query``tabs.reload``tabs.remove``tabs.setZoom``tabs.Tab``tabs.update` |
| theme-integrated-sidebar | A sidebar that integrates with the current theme. | `theme.getCurrent``theme.onUpdated``windows.getCurrent` |
| theme-switcher | An example of how to use the management API for themes. | `management.getAll``management.setEnabled` |
| themes | A collection of themes illustrating:* weta\_fade: a basic theme employing a single image specified in `theme_frame:`.
* weta\_fade\_chrome: the weta\_fade theme implemented with Chrome compatible manifest keys.
* weta\_tiled: a theme using a tiled image.
* weta\_mirror: a theme using multiple images and aligning those images in the header.
* animated: use of an animated PNG.
 |  |
| top-sites | Demonstration of the topSites API. | `topSites.get` |
| user-agent-rewriter | Demonstrates using the webRequest API to rewrite the User-Agent HTTP header. | `extension.getBackgroundPage``webRequest.onBeforeSendHeaders` |
| user-script-register | Illustrates how an extension can register URL-matching user scripts at runtime. | `userScripts.register``runtime.onMessage``runtime.sendMessage` |
| webpack-modules | Demonstrates how to use webpack to package npm modules in an extension. | `runtime.onMessage``runtime.sendMessage` |
| window-manipulator | Demonstrates how to manipulate windows: opening, closing, resizing windows. | `windows.create``windows.getAll``windows.getCurrent``windows.remove``windows.update``windows.Window` |
| open-irc-links | Demonstrates the use of protocol handlers. |  |


Что такое расширения? - Mozilla

Что такое расширения?
=====================

Дополнения расширяют и изменяют функциональность браузера. Они разрабатываются с использованием стандартных Веб-технологий - JavaScript, HTML и CSS, а также некоторых специальных JavaScript API, которые позволяют вам делать намного больше, чем то, на что вы способны на любом из сайтов. Вот некоторые из примеров того, что вы можете делать:

**Расширять или дополнять сайты**: Используйте дополнения, чтобы предоставить информацию или дополнительные функции для браузера. Позвольте пользователям собирать информацию с посещённых ими страниц для улучшения предлагаемых вами услуг.

![Пример использования Amazon Assistant for Firefox](https://mdn.dev/archives/media/attachments/2018/02/13/15808/f81a8cc5b196af29cd9d558ee3c5dbdc/Amazon_add_on.png)

Примеры: Amazon Assistant for Firefox, OneNote Web Clipper и Grammarly for Firefox.

**Дайте пользователям продемонстрировать себя**: Дополнения могут управлять содержимым сайтов, например, позволять пользователям добавлять их любимые изображения как фоновые картинки для каждого сайта, которые они посещают. Ещё дополнения могут изменять и то, как выглядит сам интерфейс Firefox, делая это тем же способом, что и обычные темы (en-US).

![Как расширение может изменить фоновую картинку сайта](https://mdn.dev/archives/media/attachments/2018/02/13/15809/ce69d140dc91da804ce6eb8f20d03c07/MyWeb_New_Tab_add_on.png)

Примеры: MyWeb New Tab, Tabliss и VivaldiFox.

**Добавьте или скройте содержимое веб-страниц**: Возможно, вы захотите помочь пользователям заблокировать назойливую рекламу, дать доступ к туристическим путеводителям, когда на странице упоминается страна или город, или отформатировать содержимое страницы так, чтобы дать незабываемый опыт прочтения. С доступом к HTML и CSS дополнения могут помогать пользователям смотреть на Интернет так, как они хотят.

![Как работает uBlock Origin, популярный блокировщик рекламы](https://mdn.dev/archives/media/attachments/2018/02/13/15807/4e85eb0560fc8d5945e64cf75a1a8e50/ublock_origin_add_on.png)

Примеры: uBlock Origin, Reader и Toolbox for Google Play Store™.

**Дайте новые инструменты и функции**: Добавляйте новые пункты в список дел или генерируйте QR-коды для текста страницы или различных ссылок. При помощи гибких опций интерфейса и мощью WebExtension API вы можете с лёгкостью добавлять новые функции в браузер. Причём вы можете расширить таким образом функциональность любого сайта, он не обязательно должен быть вашим.

![Как выглядит генератор QR-кодов QRUTILS.com](https://mdn.dev/archives/media/attachments/2018/02/13/15806/b9070a5f71c40c18d0a4ae722bca2e4a/QR_Code_Image_Generator_add_on.png)

Примеры: Swimlanes for Trello и Tomato Clock.

**Игры**: Давайте геймерам тот же опыт, что и в традиционных компьютерных играх - или же исследуйте новые игровые возможности, например, внедряя геймплей в ежедневный просмотр веб-страниц.

![Пример игры Asteroids in Popup](https://mdn.dev/archives/media/attachments/2018/02/13/15805/259d5d3c0620469521d43a897a7b653b/Asteroids_in_Popup_add_on%20.png)

Примеры: Asteroids in Popup, Solitaire Card Game New Tab и 2048 Prime.

**Добавляйте инструменты для разработки**: вы можете предлагать инструменты разработки как часть вашего бизнеса, или же как то, что вы нового открыли для веб-разработки и чем хотите поделиться. Например, вы можете добавлять свои инструменты во встроенный набор инструментов разработчика Firefox.

![То, как интегрируется aXe в инструменты разработчика Firefox](https://mdn.dev/archives/media/attachments/2018/02/13/15804/a2f3ed2cd857626d42352dd0de550486/aXe_Developer_Tools_add_on.png)

Примеры: Web Developer, Web React Developer Tools и aXe Developer Tools.

Расширения для Firefox создаются с помощью WebExtension API — кросс-браузерной системы разработки дополнений браузера. В значительной степени её API совместим с extension API, который поддерживается браузерами Google Chrome и Opera. Расширения, разработанные для этих браузеров, в большинстве случаев будут работать в Firefox или Microsoft Edge с минимальными изменениями. Также API полностью совместим с мультипроцессным Firefox.

Мы также намерены расширять API для поддержки нужд разработчиков дополнений, и если у вас есть идеи, мы будем рады услышать их. Вы можете связаться с нами через рассылку dev-addons mailing list или на IRC канале #webextensions.

Что дальше?
-----------

* Попробуйте создать Ваше первое расширение.
* Изучите структуру и Внутреннее устройство расширений.
* Посмотрите другие Примеры расширений.


Intercept HTTP requests - Mozilla

Intercept HTTP requests
=======================

Для перехвата HTTP запросов используйте `webRequest` API. Этот API позволит вам добавлять обработчики, на различных этапах создания HTTP запросов. В обработчиках вы можете:

* получить доступ к заголовкам и телам запроса, к заголовкам ответа
* отменять и перенаправлять запросы
* изменять запрос и заголовки ответа

В этой статье мы рассмотрим три разных способа использования `webRequest` модуля:

* Логирование URL сделанных запросов.
* Перенаправление запросов.
* Модификация заголовков запроса.
Логирование URL запросов
------------------------

Создайте новый каталог "requests". В нём создайте файл "manifest.json" со следующим содержимым:

```json
{
  "description": "Demonstrating webRequests",
  "manifest\_version": 2,
  "name": "webRequest-demo",
  "version": "1.0",

  "permissions": ["webRequest", "<all\_urls>"],

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

Далее, создайте файл "background.js" со следующим содержимым:

```js
function logURL(requestDetails) {
  console.log("Загрузка: " + requestDetails.url);
}

browser.webRequest.onBeforeRequest.addListener(logURL, {
  urls: ["<all\_urls>"],
});
```

Здесь мы используем `onBeforeRequest` (en-US) для вызова функции `logURL()` перед началом запроса. Функция `logURL()` берёт URL запроса из объекта event и выводит в консоль браузера. Шаблон `{urls: ["<all_urls>"]}` означает, что мы будем перехватывать HTTP запросы ко всем URL.

Для проверки установите WebExtension, откройте консоль браузера и откройте какую-нибудь веб-страницу. В консоли вы должны увидеть URL для каждого ресурса, который запрашивает браузер:

Перенаправление запросов
------------------------

Теперь давайте использовать `webRequest` для перенаправления HTTP-запросов. Во-первых, замените manifest.json на это:

```json
{
  "description": "Demonstrating webRequests",
  "manifest\_version": 2,
  "name": "webRequest-demo",
  "version": "1.0",

  "permissions": [
    "webRequest",
    "webRequestBlocking",
    "https://developer.mozilla.org/"
  ],

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

Единственное изменение здесь заключается в добавлении `"webRequestBlocking"` в `permission`. Мы должны запрашивать это дополнительное разрешение каждый раз, когда мы изменяем запрос.

Затем замените «background.js» следующим образом:

```js
let pattern = "https://developer.mozilla.org/\*";

function redirect(requestDetails) {
  console.log("Redirecting: " + requestDetails.url);
  return {
    redirectUrl:
      "https://38.media.tumblr.com/tumblr\_ldbj01lZiP1qe0eclo1\_500.gif",
  };
}

browser.webRequest.onBeforeRequest.addListener(
  redirect,
  { urls: [pattern], types: ["image"] },
  ["blocking"],
);
```

Опять же, мы используем `onBeforeRequest` (en-US) обработчик событий для запуска функции непосредственно перед каждым запросом. Эта функция заменит целевой URL на `redirectUrl` указанный в функции.

На этот раз мы не перехватываем каждый запрос: опция `{urls: [pattern], types: ["image"]}` указывает, что мы должны перехватывать запросы (1) для URL-адресов, находящихся в разделе "https://developer.mozilla.org/" (2) для ресурсов изображения. Подробнее см. `webRequest.RequestFilter` (en-US).

Также обратите внимание, что мы передаём опцию `"blocking"`: нам нужно передать это, когда мы хотим изменить запрос. Это заставляет функцию обработчика блокировать сетевой запрос, поэтому браузер ждёт, пока обработчик вернётся, прежде чем продолжить. Дополнительную информацию о `"blocking"` смотрите в документации `webRequest.onBeforeRequest` (en-US).

Чтобы проверить это, откройте страницу в MDN, которая содержит много изображений (например, https://developer.mozilla.org/ru/docs/Tools/Network\_Monitor), перезагрузите расширение и перезагрузите страницу MDN :

Modifying request headers
-------------------------

 Finally, use `webRequest` to modify request headers.
 In this example, you change the "User-Agent" header so the browser identifies itself as Opera 12.16, but only when visiting pages under "http://useragentstring.com/".

The "manifest.json" can stay the same as in the previous example.

Replace "background.js" with code like this:

```js
var targetPage = "http://useragentstring.com/\*";

var ua =
  "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16";

function rewriteUserAgentHeader(e) {
  for (var header of e.requestHeaders) {
    if (header.name.toLowerCase() == "user-agent") {
      header.value = ua;
    }
  }
  return { requestHeaders: e.requestHeaders };
}

browser.webRequest.onBeforeSendHeaders.addListener(
  rewriteUserAgentHeader,
  { urls: [targetPage] },
  ["blocking", "requestHeaders"],
);
```

Here we use the `onBeforeSendHeaders` (en-US) event listener to run a function just before the request headers are sent.

The listener function will be called only for requests to URLs matching the `targetPage` pattern. Also note that we've again passed `"blocking"` as an option. We've also passed `"requestHeaders"`, which means that the listener will be passed an array containing the request headers that we expect to send. See `webRequest.onBeforeSendHeaders` (en-US) for more information on these options.

The listener function looks for the "User-Agent" header in the array of request headers, replaces its value with the value of the `ua` variable, and returns the modified array. This modified array will now be sent to the server.

To test it out, open useragentstring.com and check that it identifies the browser as Firefox. Then reload the add-on, reload useragentstring.com, and check that Firefox is now identified as Opera:

Learn more
----------

To learn about all the things you can do with the `webRequest` API, see its reference documentation.


Chrome incompatibilities - Mozilla

Chrome incompatibilities
========================

Веб расширения разработаны с поддержкой совместимости с расширениями Chrome и Оперы на сколько это возможно. Расширения, написанные для этих браузеров, должны работать в Firefox с минимальными изменениями.

Всё же, Firefox на данный момент имеет поддержку только для ограниченного набора функций и API, поддержуемых в Chrome и Опере. Мы работаем над добавлением большей поддержки, но много функций пока ещё не поддерживаются и некоторые из них никогда не будут поддерживаться.

Эта статья перечисляет все функции и API, которые полностью поддерживаются в Firefox Developer Edition (на данный момент Firefox 47). Там где функция поддерживается частично, мы указали на проблемные места.

**Примечание:** You should assume that features and APIs not listed here at all are not yet supported.

manifest.json функция
---------------------

### Полностью поддерживаемые ключи

* `applications`
* `browser_action`
* `default_locale`
* `description`
* `icons`
* `manifest_version`
* `name`
* `page_action`
* `version`
* `web_accessible_resources`
### Частично поддерживаемые ключи

#### background

Firefox не поддерживает `"устойчивое"` свойство. Фоновые скрипты остаются загруженными всё время.

#### commands

Firefox не поддерживает:

* Media keys как сокращение
* `global`
* специальную команду `_execute_browser_action`

#### content\_scripts

Firefox не поддерживает:

* `match_about_blank`

#### content\_security\_policy

Firefox не поддерживает:

* `http://127.0.0.1` or `http://localhost` as script sources: they must be served over HTTPS.

#### options\_ui

Firefox не поддерживает:

* `chrome_style`

Using `options_ui` requires a valid value for the applications.gecko.id (en-US) property.

#### permissions

Firefox не поддерживает следующие разрешения:

* `background`
* `clipboardRead`
* `clipboardWrite`
* `geolocation`
* `unlimitedStorage`

Obviously, it doesn't support permissions for APIs that are themselves not supported.

#### incognito

Firefox does not support the following incognito (private browsing) modes:

* `split`
JavaScript APIs
---------------

### Fully supported APIs

* alarms (en-US)
* browserAction (en-US)
    + Relative URLs passed to `setPopup()` are resolved relative to the caller document, rather than to the extension root
* commands (en-US)
* contextMenus (en-US)
* cookies
* i18n (en-US)
* pageAction (en-US)
    + Relative URLs passed to `setPopup()` are resolved relative to the caller document, rather than to the extension root
### Partially supported APIs

#### bookmarks

* Firefox does not support:
    + `import()`
    + `export()`
    + `onCreated`
    + `onRemoved`
    + `onChanged`
    + `onMoved`
    + `onChildrenReordered`
    + `onImportBegan`
    + `onImportEnded`
    + `BookmarkTreeNodeUnmodifiable`
* Firefox has special bookmarks like "Recently Bookmarked" or "Recently Visited"

#### contextMenus

* Firefox does not support:
    + the "browser\_action" or "page\_action" context types

#### downloads

* Firefox does not support:
    + `drag()`
    + `acceptDanger()`
    + `setShelfEnabled()`
    + `onDeterminingFilename`
* The `saveAs` option and values other than `"GET"` for the `method` option are not supported by `download()`

#### extension

* Firefox does not support:
    + `setUpdateUrlData()`
* Additionally, the following deprecated properties will not be supported:
    + `onRequest`
    + `onRequestExternal`
    + `getExtensionTabs()`
    + `sendRequest()`

#### idle

* Firefox does not support:
    + `onStateChanged`
    + `setDetectionInterval()`

Additoinally, `queryState()` always returns `"active"` in Firefox, regardless of the current system idle state.

#### notifications

* Firefox does not support:
    + `update()`
* Firefox doesn't provide `byUser` data.

#### runtime

* Firefox does not support:
    + `getPackageDirectoryEntry()`
    + `reload()`
    + `requestUpdateCheck()`
    + `restart()`
    + `sendNativeMessage()`
    + `onBrowserUpdateAvailable`
    + `onConnectExternal`
    + `onInstalled`
    + `onMessageExternal`
    + `onRestartRequired`
    + `onStartup`
    + `onSuspend`
    + `onSuspendCanceled`
    + `onUpdateAvailable`

#### storage

* Firefox does not support:
    + `managed` storage area
    + `sync` storage area
    + `getBytesInUse()`.

#### tabs

* Firefox treats `highlighted` and `active` as the same, since Firefox has no concept of selecting multiple tabs.
* In Firefox, you need the `tabs` permission if you want to include `url` in the `queryInfo` parameter to `tabs.query()` (en-US).
* In Firefox, relative URLs passed into `tabs.executeScript()` or `tabs.insertCSS()` are resolved relative to the current page URL. In Chrome, these URLs are resolved relative to the add-on's base URL. To work cross-browser, you can specify the path as an absolute URL, starting at the add-on's root, like this:

```html
/path/to/script.js
```

* In Firefox, you can't open (using `tabs.create` (en-US)), or navigate to (using `tabs.update` (en-US)) privileged URLs:
    + chrome: URLs
    + javascript: URLs
    + data: URLs
    + privileged about: URLs (for example, about:config, about:addons, about:debugging)
* Additionally, the following deprecated properties will not be supported:
    + `sendRequest()`
    + `getSelected()`
    + `onActiveChanged`
    + `onHighlightChanged`
    + `onSelectionChanged`

#### webNavigation

* Firefox does not support:
    + `onCreatedNavigationTarget`
    + `onTabReplaced`

#### webRequest

* Firefox does not support:
    + `onAuthRequired`
    + filtering by `windowId` and `tabId`
    + the `"requestBody"` instruction in `opt_extraInfoSpec`
* In Firefox requests can be redirected only if their original URL uses the http or https scheme

#### windows

* Firefox does not support:
    + the `focused` option in `create()`
* In Firefox `onFocusChanged` will trigger multiple times for a given focus change.
### Planned APIs

We don't support the following APIs, but plan to, soon:

* Devtools (mostly panels)
* debugger
* omnibox
* permissions

This doesn't mean that these are the only additional APIs we will support, but that they are our current priorities.

### Miscellaneous incompatibilities

#### Optional arguments

#### URLs in CSS

Firefox resolves URLs in injected CSS files relative to the CSS file itself, rather than to the page it's injected into.

#### Additional incompatibilities

Firefox does not support using alert() from background pages. Using `alert(message)` from a background page will cause the Browser Console to be opened and both a line stating "alert() is not supported in background windows; please use console.log instead." and the `message` will be output to the console.


Политика защиты содержимого - Mozilla

Политика защиты содержимого
===========================

Политика защиты содержимого (англ. Content Security Policy) автоматически применяется ко всем расширениям, разработанным с использованием WebExtension API. Она ограничивает источники, из которых расширение может загружать <script> и <object> (en-US) ресурсы, и препятствует потенциально опасным практикам, например использованию `eval()`.

Статья в краткой форме объясняет значимость этой политики, каковы её изначальные правила, как они влияют на расширение, и как расширение может изменять эти правила.

Политика защиты содержимого - это механизм, помогающий веб-сайтам предотвращать выполнение умышленно вредного кода. Веб-сайт устанавливает политику защиты, используя HTTP заголовок, посылаемый с сервера. Главным образом эта политика участвует в устанавливании допустимых источников для загрузки различного вида контента, к примеру, скриптов или встроенных плагинов. Например, веб-сайт может использовать политику защиты для инструктирования браузера, чтобы тот выполнял скрипты, загруженные только с самого сайта, а не из каких-либо других источников. Политика защиты содержимого так же может запретить браузеру использовать потенциально опасные практики, например использование eval().

Так же как веб-сайты, расширения могут загружать контент из различных источников. Например, всплывающее окно расширения определяется HTML документом, и может подключать JavaScript и CSS файлы из различных источников, точно так же, как и нормальная веб-страница:

```html
<!doctype html>

<html>
  <head>
    <meta charset="utf-8" />
  </head>

  <body>
    <!-- Некоторый HTML контент -->

    <!--
 Подключение сторонней библиотеки.
 Смотрите также https://developer.mozilla.org/ru/docs/Web/Security/Subresource\_Integrity.
 -->
    <script>
 src="https://code.jquery.com/jquery-2.2.4.js"
 integrity="sha256-iT6Q9iMJYuQiMWNd9lDyBUStIq/8PuOW33aOqmvFpqI="
 crossorigin="anonymous">
 </script>

    <!-- Include my popup's own script-->
    <script src="popup.js"></script>
  </body>
</html>
```

В сравнении с веб-сайтами, расширения имеют доступ к дополнительному привилегированному API, так что вероятность получения к нему доступа сторонним кодом - это очень большой риск. По этой причине:

* довольно строгая политика по защите содержимого применяются изначально. Смотрите изначальные правила по защите содержимого.
* авторы расширений могут изменять изначальную политику, используя `content_security_policy` ключ в manifest.json, но даже в этом случае существуют ограничения на возможные разрешения. Для дополнительной информации смотрите `content_security_policy`.
Изначальные правила по защите содержимого
-----------------------------------------

Следующие правила по защите содержимого являются изначальными:
```

"script-src 'self'; object-src 'self';"
```

Они применяются к любому расширению, которое самостоятельно не указывает свою политику защиты, используя `content_security_policy` ключ в manifest.json. Это имеет следующие последствия:

* Вы можете загружать только локальные к расширению <script> и <object> ресурсы.
* Расширению не разрешено выполнять код, представленный в виде JavaScript строк.
* Встраиваемый (inline) JavaScript-код не будет выполняться.
### Расположение script и object ресурсов

Используя изначальную политику защиты содержимого, вы можете загружать только локальные к расширению <script> и <object> (en-US) ресурсы. Например, рассмотрите эту строку из документа расширения:

```html
<script src="https://code.jquery.com/jquery-2.2.4.js"></script>
```

Она не будет загружать запрашиваемый ресурс, и вы не сможете найти ни один ожидаемый от ресурса объект. К этой ситуации существует два решения:

* Скачать этот ресурс, упаковать его в ваше расширение и ссылаться к нему локально.
* Использовать ключ `content_security_policy` в manifest.json, чтобы позволить загрузку контента из вышеупомянутого источника..
### eval() и товарищи

Изначальная политика защиты содержимого не позволяет выполнять код из JavaScript строк. Это означает, что следующие примеры кода изначально запрещены:

```js
eval("console.log('some output');");
```

```js
window.setTimeout("alert('Hello World!');", 500);
```

```js
var f = new Function("console.log('foo');");
```

### Встраиваемый (inline) JavaScript

Изначальная политика защиты содержимого не позволяет выполнять JavaScript-код, встраиваемый в HTML теги. Это запрещает как выполнение JavaScript-кода вложенного прямо в `<script>` тег, так и выполнение вписанных в атрибут обработчиков событий, означая, что следующий код так же не будет работать:

```html
<script>
 console.log("foo");
</script>
```

```html
<div onclick="console.log('click')">Click me!</div>
```

Вместо того, чтобы использовать код `<body onload="main()">` для запуска вашего скрипта после загрузки страницы, поставьте обработчики событий на DOMContentLoaded (en-US) или load.


Пользовательский интерфейс - Mozilla

Пользовательский интерфейс
==========================

У расширений есть несколько органов взаимодействия с пользователем, с их помощью человек пользуется *расширенной* функциональностью. Все они перечислены ниже, с инструкцией как использовать в своём расширении каждый из них.

**Примечание:** Советы как с помощью этих элементов пользовательского интерфейса (UI) обеспечить отличное взаимодействие человека с программой вы найдёте в статье User experience best practices.

| Элемент UI | Назначение | Как выглядит? |
| --- | --- | --- |
| Кнопка на панели инструментов | Кнопка на панели инструментов доступна изо всех вкладок. Реакцию на нажатие кнопки обеспечивает расширение. | Example of a WebExtension toolbar button |
| ~~Browser toolbar button with a popup (en-US)~~ | ~~Кнопка со всплывающим окном на панели инструментов браузера, которая открывается при нажатии. Всплывающее окно определяется в документе HTML, который обрабатывает взаимодействие пользователя.~~ | ~~Example of a WebExtension toolbar button with a popup~~ |
| Кнопка в адресной строке (en-US) | В отличие от панели инструментов, в адресной строке кнопка должна появляться только рядом с адресом предназначенной для неё страницы. | Example showing an address bar button (page action) |
| Всплывающие окна (en-US) | При нажатии на кнопку всплывает (popup) web-страница с органами управления. | Example of a popup on the address bar button |
| Контекстное меню | Органы управления могут быть упорядочены в ветвящейся структуре контекстных меню браузера. |  |
| Боковая панель | В боковой панели рядом с интернет-сайтом открывается web-страница с интерфейсом пользователя. Человек может её закрыть и открыть когда в ней появится потребность. | Example of a WebExtension's sidebar |
| Страница настройки дополнения (en-US) | Страница пользовательских настроек доступна из меню управления дополнениями единообразно для всех расширений. | Example showing the options page content added in the favorite colors example. |
| Дополнительные веб-страницы (en-US) | Некоторые варианты содержимого (например, справку или форму обратной связи) удобнее расположить на дополнительной web-странице, которая открывается в собственной вкладке или окне. | Example of a simple bundled page displayed as a detached panel. |
| Уведомления | Пользователю можно сообщить что-либо средствами уведомления, предусмотренными в ОС. Расширение отреагирует когда уведомление будет либо прочитано пользователем, либо скрыто (явным решением человека или логикой программы) так и оставшись непрочитанным. | Example notification from a WebExtension |
| Угадывание адреса по мере ввода | Предлагать настраиваемые варианты адресной строки при вводе пользователем ключевого слова. | Example showing the result of the firefox_code_search WebExtension's customization of the address bar suggestions. |
| Панели инструмента разработчика (en-US) | Вкладка со связанным HTML-документом, который отображается в инструментах разработчика браузера. | New panel tab in the Developer Tools tab bar |

В следующих руководствах приведены пошаговые инструкции по созданию некоторых из этих параметров пользовательского интерфейса:

* Add a button to the toolbar (en-US)
* Implement a settings page (en-US)
* Extending the developer tools (en-US)


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

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

Одним из наиболее распространённых вариантов использования расширений является внесение изменение в веб-страницу. К примеру, расширение может изменить стиль, применённый к странице, скрыть существующие или вставить на страницу дополнительные 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).

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

* Руководство по Content scripts
* Ключ манифеста`content_scripts` (en-US)
* Ключ манифеста`permissions`
* `tabs.executeScript()` (en-US)
* `tabs.insertCSS()` (en-US)
* `tabs.sendMessage()` (en-US)
* `runtime.sendMessage()` (en-US)
* `runtime.onMessage` (en-US)
* Примеры использования `content_scripts`:
    + borderify
    + notify-link-clicks-i18n
    + page-to-extension-messaging
* Примеры использования `tabs.executeScript()`:
    + beastify
    + context-menu-demo


Шаблоны совпадения в расширении - Mozilla

Шаблоны совпадения в расширении
===============================

Шаблоны совпадения - являются способом обозначения необходимых групп URL. Они используются в нескольких местах в WebExtension API, главным образом для объявления какие веб-страницы должны загружать встраиваемые скрипты и по каким URL необходимо ожидать событий из `webRequest` API.

API, которое использует шаблоны совпадения, обычно принимает их списками, и выполняет соответствующие действия, если URL имеет совпадение с одним из шаблонов. Например, взгляните на `content_scripts` (en-US) ключ в manifest.json.

Структура шаблона совпадения
----------------------------

**Примечание:** Некоторые браузеры не поддерживают определённые схемы.
 Смотрите Browser compatibility table для дополнительной информации.

Все шаблоны совпадения объявляются в виде строк. За исключением специального `<all_urls>` шаблона, шаблоны совпадения состоят из трёх компонентов: *схема*, *хост* и *путь*. Схема и хост разделяются с помощью `://`.
```

<схема>://<хост><путь>
```

### Схема

Компонент *схема* может принимать один из двух видов:

| Вид | Совпадение |
| --- | --- |
| `*` | Только "http" and "https", так же "ws" и "wss" в некоторых браузерах. |
| Одно из `http`, `https`, `ws`, `wss`, `ftp`, `ftps`, `data` или `file`. | Только данная схема. |

### Хост

Компонент *хост* может принимать один из трёх видов:

| Form | Matches |
| --- | --- |
| `*` | Любой хост. |
| `*.` за которыми следует частичное имя хоста. | Данный хост и любые его субдомены. |
| Полное название хоста без `*`. | Только данный хост. |

*Хост* не должен включать в себя значение порта.

*Хост* не обязателен, если *схема* задана, как "file".

Заметьте, что `*` звёздочка (символ универсального значения) может быть написана только в начале строки.

### Путь

Компонент *путь* должен начинаться с `/` символа.

Вслед за ним он может иметь любую комбинацию символов и `*` звёздочек, которые позволено использовать для обозначения URL и строки параметров (начинается после `?`). В отличии от *хоста*, *путь* может содержать `*` звёздочку в середине или в конце строки, и `*` звёздочка может появляться в строке более одного раза.

Значение *пути* сравнивается со строкой, которая представляет из себя URL и строку параметров. Если строка параметров присутствует в URL, тогда она отделяется от основного URL знаком `?`. Если вы хотите иметь соответствие с URL на любом домене, где URL путь кончается на `foo.bar`, не зависимо от присутствия строки параметров, тогда вам нужно иметь массив шаблонов совпадений, например `['*://*/*foo.bar', '*://*/*foo.bar?*']`. В этой ситуации необходимо использовать `?*`, а не `bar*`, для того чтобы обозначить, что `*` будет применяться к строке параметров, а не к основной части URL пути.

Ни идентификатор якоря, ни предшествующая ему `#`, не считаются частями *пути*.

### <all\_urls>

Специальное значение `<all_urls>` совпадает со всеми URL, если они используются со следующими схемами: "http", "https", "ws", "wss", "ftp", "data", and "file".

Примеры
-------

| Шаблон | Пример совпадения | Пример несовпадения |
| --- | --- | --- |
| `<all_urls>` Совпадение со всеми URL. | `http://example.org/` `https://a.org/some/path/` `ws://sockets.somewhere.org/` `wss://ws.example.com/stuff/``ftp://files.somewhere.org/` `ftps://files.somewhere.org/` | `resource://a/b/c/` (неподдерживаемая схема) |
| `*://*/*` Совпадение со всеми HTTP, HTTPS и WebSocket URL. | `http://example.org/` `https://a.org/some/path/` `ws://sockets.somewhere.org/` `wss://ws.example.com/stuff/` | `ftp://ftp.example.org/` (несовпадающая схема) `ftps://ftp.example.org/` (несовпадающая схема) `file:///a/` (несовпадающая схема) |
| `*://*.mozilla.org/*`Совпадение со всеми HTTP, HTTPS и WebSocket URL, которые находятся на "mozilla.org" или одном из её субдоменов. | `http://mozilla.org/` `https://mozilla.org/` `http://a.mozilla.org/` `http://a.b.mozilla.org/` `https://b.mozilla.org/path/` `ws://ws.mozilla.org/` `wss://secure.mozilla.org/something` | `ftp://mozilla.org/` (несовпадающая схема) `http://mozilla.com/` (несовпадающий хост) `http://firefox.org/` (несовпадающий хост) |
| `*://mozilla.org/`Совпадение со всеми HTTP, HTTPS и WebSocket URL, которые находятся исключительно на "mozilla.org/". | `http://mozilla.org/` `https://mozilla.org/` `ws://mozilla.org/` `wss://mozilla.org/` | `ftp://mozilla.org/` (несовпадающая схема) `http://a.mozilla.org/` (несовпадающий хост) `http://mozilla.org/a` (несовпадающий путь) |
| `ftp://mozilla.org/`Совпадение только с "ftp://mozilla.org/". | `ftp://mozilla.org` | `http://mozilla.org/` (несовпадающая схема) `ftp://sub.mozilla.org/` (несовпадающий хост) `ftp://mozilla.org/path` (несовпадающий путь) |
| `https://*/path` Совпадение со всеми HTTPS URL на любом хосте, чей путь точно соответствует "path". | `https://mozilla.org/path` `https://a.mozilla.org/path` `https://something.com/path` | `http://mozilla.org/path` (несовпадающая схема) `https://mozilla.org/path/` (несовпадающий путь) `https://mozilla.org/a` (несовпадающий путь) `https://mozilla.org/` (несовпадающий путь) `https://mozilla.org/path?foo=1` (несовпадающий путь из-за строки параметров) |
| `https://*/path/`Совпадение со всеми HTTPS URL на любом хосте, чей путь точно соответствует "path/" и не имеет строки параметров. | `https://mozilla.org/path/` `https://a.mozilla.org/path/` `https://something.com/path`/ | `http://mozilla.org/path/` (несовпадающая схема) `https://mozilla.org/path` (несовпадающий путь) `https://mozilla.org/a` (несовпадающий путь) `https://mozilla.org/` (несовпадающий путь) `https://mozilla.org/path/` `?foo=1` (несовпадающий путь из-за строки параметров) |
| `https://mozilla.org/*` Совпадение со всеми HTTPS URL только на домене "mozilla.org", с любым URL путём и строкой параметров. | `https://mozilla.org/` `https://mozilla.org/path` `https://mozilla.org/another` `https://mozilla.org/path/to/doc` `https://mozilla.org/path/to/doc?foo=1` | `http://mozilla.org/path` (несовпадающая схема) `https://mozilla.com/path` (несовпадающий хост) |
| `https://mozilla.org/a/b/c/`Совпадение только с данным URL, или данным URL, имеющим идентификатор якоря. | `https://mozilla.org/a/b/c/` `https://mozilla.org/a/b/c/#section1` | Всё остальное. |
| `https://mozilla.org/*/b/*/`Совпадение только с HTTPS URL, которые находятся на "mozilla.org", чей путь имеет сегмент "b" где-то в середине. Совпадёт с URL со строкой параметров, если эта строка параметров заканчивается на `/`. | `https://mozilla.org/a/b/c/` `https://mozilla.org/d/b/f/` `https://mozilla.org/a/b/c/d/` `https://mozilla.org/a/b/c/d/#section1` `https://mozilla.org/a/b/c/d/?foo=/` `https://mozilla.org/a?foo=21314&bar=/b/&extra=c/` | `https://mozilla.org/b/*/` (несовпадающий путь) `https://mozilla.org/a/b/` (несовпадающий путь) `https://mozilla.org/a/b/c/d/?foo=bar` (несовпадающий путь из-за строки параметров) |
| `file:///blah/*`Совпадает с любым FILE URL, чей путь начинается с "blah". | `file:///blah/` `file:///blah/bleh` | `file:///bleh/` (несовпадающий путь) |

### Недопустимые шаблоны совпадения

| Недопустимый шаблон | Причина |
| --- | --- |
| `resource://path/` | Неподдерживаемая схема. |
| `https://mozilla.org` | Отсутствие пути. |
| `https://mozilla.*.org/` | "\*" в хосте должна присутствовать только в начале. |
| `https://*zilla.org/` | "\*" в хосте должен быть единственным символом или сопровождаться ".". |
| `http*://mozilla.org/` | "\*" в схеме должен быть единственным символом. |
| `https://mozilla.org:80/` | Хост не должен включать в себя номер порта. |
| `*://*` | Пустой путь: должно быть переписано, как "`*://*/*`". |
| `file://*` | Пустой путь: должно быть переписано, как "`file:///*`". |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


JavaScript APIs - Mozilla

JavaScript APIs
===============

JavaScript WebExtension API может быть использован в фоновых скриптах расширения, а так же в любых других документах, поставляемых вместе с расширением, таких как документы во всплывающих окнах после нажатия кнопок активации расширения browser actionна панели инструментов или page action (en-US) в строке адреса, боковой панели, страницах настроек (en-US) или новых открытых вкладках (en-US). Некоторые из этих API могут быть доступны на встраиваемых в страницу скриптах (см. список в руководстве по встраиваемым скриптам).

Для использования отдельных более продвинутых API, необходимо запросить разрешения в manifest.json вашего дополнения.

Вы можете получить доступ к API, используя пространство имён `browser`:

```js
function logTabs(tabs) {
  console.log(tabs);
}

browser.tabs.query({ currentWindow: true }, logTabs);
```

Многие API выполняются асинхронно, возвращая `Promise`:

```js
function logCookie(c) {
  console.log(c);
}

function logError(e) {
  console.error(e);
}

var setCookie = browser.cookies.set({ url: "/" });
setCookie.then(logCookie, logError);
```

Обратите внимание, что это отличается от расширений Google Chrome, которые используют пространство имён `chrome` вместо `browser` и колбэки для асинхронных функций вместо промисов.

Для поддержки портирования, реализация Firefox WebExtension API так же поддерживает пространство имён `chrome` и использование колбэков. Это позволяет в большинстве случаев не изменять код, изначально написанный для Chrome.

Mozilla так же предоставляет полифил, который позволяет коду, использующему `browser` и промисы, работать без изменений в Chrome: https://github.com/mozilla/webextension-polyfill.

Microsoft Edge использует пространство имён `browser`, но ещё не поддерживает, основанный на промисах асинхронный API. В Edge на данный момент асинхронные вызовы API должны использовать колбэки.

Не все браузеры поддерживают все API: детали см. Browser support for JavaScript APIs (en-US).

cookiesПозволяет расширениям получать и устанавливать куки, а также сообщать об их изменении.

downloadsПозволяет расширениям взаимодействовать с менеджером загрузки браузера. Этот модуль API можно использовать для загрузки, отмены, приостановки, возобновления загрузки файлов и отображения загруженных файлов в файловом менеджере.

windowsВзаимодействие с окнами браузера. Вы можете использовать этот API, чтобы получить информацию об открытых окнах, а также открывать, изменять и закрывать окна. Вы также можете обрабатывать события открытия, закрытия окна, и активировать события.

Веб ЗапросДобавляет обработчики событий на различных стадиях HTTP запроса. Обработчик получить детальную информацию о запросе и способен изменить или отменить запрос.