Скрипты контента

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

Понимание возможностей скрипта контента

Скрипты контента могут напрямую обращаться к следующим API расширений:

Сценарии контента не могут напрямую обращаться к другим API. Но они могут получить к ним доступ косвенно, обмениваясь сообщениями с другими частями вашего расширения.

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

Работа в изолированных мирах

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

Расширение может работать на веб-странице с кодом, подобным следующему примеру.

веб-страница.html

<html>   <button id="mybutton">click me</button>   <script>     var greeting = "hello, ";     var button = document.getElementById("mybutton");     button.person_name = "Bob";     button.addEventListener(         "click", () => alert(greeting + button.person_name + "."), false);   </script> </html> 

Это расширение может внедрить следующий сценарий содержимого, используя один из методов, описанных в разделе «Внедрение сценариев» .

контент-script.js

var greeting = "hola, "; var button = document.getElementById("mybutton"); button.person_name = "Roberto"; button.addEventListener(     "click", () => alert(greeting + button.person_name + "."), false); 

Благодаря этому изменению оба оповещения появляются последовательно при нажатии кнопки.

Внедрить скрипты

Сценарии содержимого могут быть объявлены статически , объявлены динамически или внедрены программно .

Внедрить статические объявления

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

Статически объявленные скрипты регистрируются в манифесте под ключом "content_scripts" . Они могут включать файлы JavaScript, файлы CSS или и то, и другое. Все сценарии автозапуска содержимого должны указывать шаблоны соответствия .

манифест.json

{  "name": "My extension",  ...  "content_scripts": [    {      "matches": ["https://*.nytimes.com/*"],      "css": ["my-styles.css"],      "js": ["content-script.js"]    }  ],  ... }  
Имя Тип Описание
matches массив строк Необходимый. Указывает, на какие страницы будет внедрен этот скрипт содержимого. Подробную информацию о синтаксисе этих строк см. в разделе «Шаблоны сопоставления» , а также «Шаблоны сопоставления и шаблоны» для получения информации о том, как исключить URL-адреса.
css массив строк Необязательный. Список файлов CSS, которые будут вставлены в соответствующие страницы. Они вводятся в том порядке, в котором они появляются в этом массиве, до того, как будет создан или отображен какой-либо DOM для страницы.
js массив строк Необязательный. Список файлов JavaScript, которые будут внедрены на соответствующие страницы. Файлы внедряются в том порядке, в котором они появляются в этом массиве. Каждая строка в этом списке должна содержать относительный путь к ресурсу в корневом каталоге расширения. Ведущие косые черты (`/`) автоматически обрезаются.
run_at RunAt Необязательный. Указывает, когда сценарий следует внедрить на страницу. По умолчанию — document_idle .
match_about_blank логическое значение Необязательный. Должен ли сценарий внедриться в кадр about:blank , где родительский или открывающий кадр соответствует одному из шаблонов, объявленных в matches . По умолчанию ложь.
match_origin_as_fallback логическое значение Необязательный. Должен ли сценарий внедряться в кадры, созданные соответствующим источником, но URL-адрес или источник которых могут не соответствовать шаблону напрямую. К ним относятся кадры с различными схемами, например about: , data: , blob: и filesystem: . См. также Вставка в связанные кадры .
world ИсполнениеМир Необязательный. Мир JavaScript, в котором выполняется скрипт. По умолчанию ISOLATED . См. также Работа в изолированных мирах .

Внедрить динамические объявления

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

Динамические объявления, представленные в Chrome 96, аналогичны статическим объявлениям , но объект сценария содержимого регистрируется в Chrome с использованием методов в пространстве имен chrome.scripting а не в манифесте.json . API сценариев также позволяет разработчикам расширений:

Как и статические объявления, динамические объявления могут включать файлы JavaScript, файлы CSS или и то, и другое.

сервис-worker.js

chrome.scripting   .registerContentScripts([{     id: "session-script",     js: ["content.js"],     persistAcrossSessions: false,     matches: ["*://example.com/*"],     runAt: "document_start",   }])   .then(() => console.log("registration complete"))   .catch((err) => console.warn("unexpected error", err)) 

сервис-worker.js

chrome.scripting   .updateContentScripts([{     id: "session-script",     excludeMatches: ["*://admin.example.com/*"],   }])   .then(() => console.log("registration updated")); 

сервис-worker.js

chrome.scripting   .getRegisteredContentScripts()   .then(scripts => console.log("registered content scripts", scripts)); 

сервис-worker.js

chrome.scripting   .unregisterContentScripts({ ids: ["session-script"] })   .then(() => console.log("un-registration complete")); 

Внедрить программно

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

Чтобы программно внедрить сценарий содержимого, вашему расширению необходимы разрешения хоста для страницы, в которую оно пытается внедрить сценарии. Разрешения хоста можно предоставить, запросив их как часть манифеста вашего расширения, или временно используя "activeTab" .

Ниже приведены различные версии расширения на основе activeTab.

манифест.json:

{   "name": "My extension",   ...   "permissions": [     "activeTab",     "scripting"   ],   "background": {     "service_worker": "background.js"   },   "action": {     "default_title": "Action Button"   } } 

Сценарии содержимого можно внедрить в виде файлов.

контент-script.js

 document.body.style.backgroundColor = "orange"; 

сервис-worker.js:

chrome.action.onClicked.addListener((tab) => {   chrome.scripting.executeScript({     target: { tabId: tab.id },     files: ["content-script.js"]   }); }); 

Или тело функции можно внедрить и выполнить как сценарий содержимого.

сервис-worker.js:

function injectedFunction() {   document.body.style.backgroundColor = "orange"; }  chrome.action.onClicked.addListener((tab) => {   chrome.scripting.executeScript({     target : {tabId : tab.id},     func : injectedFunction,   }); }); 

Имейте в виду, что внедренная функция является копией функции, указанной в вызове chrome.scripting.executeScript() , а не самой исходной функции. В результате тело функции должно быть автономным; ссылки на переменные вне функции приведут к тому, что сценарий содержимого выдаст ошибку ReferenceError .

При внедрении в качестве функции вы также можете передавать ей аргументы.

сервис-worker.js

function injectedFunction(color) {   document.body.style.backgroundColor = color; }  chrome.action.onClicked.addListener((tab) => {   chrome.scripting.executeScript({     target : {tabId : tab.id},     func : injectedFunction,     args : [ "orange" ],   }); }); 

Исключить совпадения и глобусы

Чтобы настроить указанное соответствие страниц, включите следующие поля в декларативную регистрацию.

Имя Тип Описание
exclude_matches массив строк Необязательный. Исключает страницы, в которые в противном случае был бы внедрен этот сценарий содержимого. Подробную информацию о синтаксисе этих строк см. в разделе «Шаблоны совпадений» .
include_globs массив строк Необязательный. Применяется после matches , чтобы включать только те URL-адреса, которые также соответствуют этому шаблону. Это предназначено для эмуляции ключевого слова @include Greasemonkey.
exclude_globs массив строк Необязательный. Применяется после matches , чтобы исключить URL-адреса, соответствующие этому шаблону. Предназначен для эмуляции ключевого слова @exclude Greasemonkey.

Сценарий содержимого будет внедрен на страницу, если выполняются оба следующих условия:

  • Его URL-адрес соответствует любому шаблону matches и любому шаблону include_globs .
  • URL-адрес также не соответствует шаблону exclude_matches или exclude_globs . Поскольку свойство matches является обязательным, exclude_matches , include_globs и exclude_globs можно использовать только для ограничения того, какие страницы будут затронуты.

Следующее расширение вводит сценарий содержимого в https://www.nytimes.com/health но не в https://www.nytimes.com/business .

манифест.json

{   "name": "My extension",   ...   "content_scripts": [     {       "matches": ["https://*.nytimes.com/*"],       "exclude_matches": ["*://*/*business*"],       "js": ["contentScript.js"]     }   ],   ... } 

сервис-worker.js

chrome.scripting.registerContentScripts([{   id : "test",   matches : [ "https://*.nytimes.com/*" ],   excludeMatches : [ "*://*/*business*" ],   js : [ "contentScript.js" ], }]); 

Свойства Glob имеют другой, более гибкий синтаксис, чем шаблоны сопоставления . Допустимыми glob-строками являются URL-адреса, которые могут содержать подстановочные знаки и звездочки. Звездочка ( * ) соответствует любой строке любой длины, включая пустую строку, а знак вопроса ( ? ) соответствует любому одиночному символу.

Например, glob https://???.example.com/foo/\* соответствует любому из следующих значений:

  • https://www.example.com/foo/bar
  • https://the.example.com/foo/

Однако он не соответствует следующему:

  • https://my.example.com/foo/bar
  • https://example.com/foo/
  • https://www.example.com/foo

Это расширение внедряет скрипт контента в https://www.nytimes.com/arts/index.html и https://www.nytimes.com/jobs/index.htm* , но не в https://www.nytimes.com/sports/index.html :

манифест.json

{   "name": "My extension",   ...   "content_scripts": [     {       "matches": ["https://*.nytimes.com/*"],       "include_globs": ["*nytimes.com/???s/*"],       "js": ["contentScript.js"]     }   ],   ... } 

Это расширение встраивает сценарий содержимого в https://history.nytimes.com и https://.nytimes.com/history , но не в https://science.nytimes.com или https://www.nytimes.com/science :

манифест.json

{   "name": "My extension",   ...   "content_scripts": [     {       "matches": ["https://*.nytimes.com/*"],       "exclude_globs": ["*science*"],       "js": ["contentScript.js"]     }   ],   ... } 

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

манифест.json

{   "name": "My extension",   ...   "content_scripts": [     {       "matches": ["https://*.nytimes.com/*"],       "exclude_matches": ["*://*/*business*"],       "include_globs": ["*nytimes.com/???s/*"],       "exclude_globs": ["*science*"],       "js": ["contentScript.js"]     }   ],   ... } 

Время выполнения

Поле run_at определяет, когда файлы JavaScript внедряются на веб-страницу. Предпочтительным значением по умолчанию является "document_idle" . Другие возможные значения см. в типе RunAt .

манифест.json

{   "name": "My extension",   ...   "content_scripts": [     {       "matches": ["https://*.nytimes.com/*"],       "run_at": "document_idle",       "js": ["contentScript.js"]     }   ],   ... } 

сервис-worker.js

chrome.scripting.registerContentScripts([{   id : "test",   matches : [ "https://*.nytimes.com/*" ],   runAt : "document_idle",   js : [ "contentScript.js" ], }]); 
Имя Тип Описание
document_idle нить Предпочтительно. Используйте "document_idle" когда это возможно.

Браузер выбирает время для внедрения скриптов между "document_end" и сразу после срабатывания события window.onload . Точный момент внедрения зависит от того, насколько сложен документ и сколько времени занимает его загрузка, и оптимизирован для скорости загрузки страницы.

Сценариям содержимого, работающим в "document_idle" не нужно прослушивать событие window.onload , они гарантированно запустятся после завершения DOM. Если сценарий обязательно должен быть запущен после window.onload , расширение может проверить, запущена ли уже onload используя свойство document.readyState .
document_start нить Скрипты вводятся после любых файлов из css , но до создания любого другого DOM или запуска любого другого скрипта.
document_end нить Скрипты внедряются сразу после завершения DOM, но до загрузки подресурсов, таких как изображения и фреймы.

Укажите рамки

Для сценариев декларативного содержимого, указанных в манифесте, поле "all_frames" позволяет расширению указать, следует ли вставлять файлы JavaScript и CSS во все кадры, соответствующие указанным требованиям URL-адреса, или только в самый верхний кадр на вкладке:

манифест.json

{   "name": "My extension",   ...   "content_scripts": [     {       "matches": ["https://*.nytimes.com/*"],       "all_frames": true,       "js": ["contentScript.js"]     }   ],   ... } 

При программной регистрации сценариев содержимого с помощью chrome.scripting.registerContentScripts(...) параметр allFrames можно использовать, чтобы указать, следует ли вставлять сценарий содержимого во все кадры, соответствующие указанным требованиям URL-адреса, или только в самый верхний кадр на вкладке. Это можно использовать только с tabId и нельзя использовать, если указаны FrameIds или DocumentIds:

сервис-worker.js

chrome.scripting.registerContentScripts([{   id: "test",   matches : [ "https://*.nytimes.com/*" ],   allFrames : true,   js : [ "contentScript.js" ], }]); 

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

Это тот случай, когда расширение хочет внедрить в кадры URL-адреса, имеющие схемы about: , data: , blob: и filesystem: :. В этих случаях URL-адрес не будет соответствовать шаблону сценария содержимого (а в случае about: и data: вообще не включайте родительский URL-адрес или источник в URL-адрес, как в about:blank или data:text/html,<html>Hello, World!</html> ). Однако эти кадры по-прежнему можно связать с создающим кадром.

Для внедрения в эти кадры расширения могут указать свойство "match_origin_as_fallback" в спецификации сценария содержимого в манифесте.

манифест.json

{   "name": "My extension",   ...   "content_scripts": [     {       "matches": ["https://*.google.com/*"],       "match_origin_as_fallback": true,       "js": ["contentScript.js"]     }   ],   ... } 

Если указано значение true , Chrome будет проверять происхождение инициатора кадра, чтобы определить, соответствует ли кадр, а не URL-адрес самого кадра. Обратите внимание, что это также может отличаться от источника целевого кадра (например, data: URL-адреса имеют нулевое происхождение).

Инициатором кадра является кадр, который создал или перемещался по целевому кадру. Хотя обычно это прямой родительский элемент или средство открытия, это может и не быть (как в случае с фреймом, перемещающимся по iframe внутри iframe).

Поскольку при этом сравнивается источник кадра-инициатора, кадр-инициатор может находиться на любом пути от этого источника. Чтобы прояснить этот вывод, Chrome требует, чтобы все сценарии содержимого, для которых для параметра "match_origin_as_fallback" установлено значение true , также указывали путь * .

Если указаны оба "match_origin_as_fallback" и "match_about_blank" , приоритет имеет "match_origin_as_fallback" .

Связь со страницей встраивания

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

Пример можно реализовать с помощью window.postMessage() :

контент-script.js

var port = chrome.runtime.connect();  window.addEventListener("message", (event) => {   // We only accept messages from ourselves   if (event.source !== window) {     return;   }    if (event.data.type && (event.data.type === "FROM_PAGE")) {     console.log("Content script received: " + event.data.text);     port.postMessage(event.data.text);   } }, false); 

пример.js

document.getElementById("theButton").addEventListener("click", () => {   window.postMessage(       {type : "FROM_PAGE", text : "Hello from the webpage!"}, "*"); }, false); 

Страница без расширения, example.html, отправляет сообщения самому себе. Это сообщение перехватывается и проверяется сценарием содержимого, а затем отправляется в процесс расширения. Таким образом, страница устанавливает канал связи с процессом расширения. Обратное возможно с помощью аналогичных средств.

Доступ к файлам расширений

Чтобы получить доступ к файлу расширения из сценария содержимого, вы можете вызвать chrome.runtime.getURL() , чтобы получить абсолютный URL-адрес вашего ресурса расширения, как показано в следующем примере ( content.js ):

контент-script.js

let image = chrome.runtime.getURL("images/my_image.png") 

Чтобы использовать шрифты или изображения в файле CSS, вы можете использовать @@extension_id для создания URL-адреса, как показано в следующем примере ( content.css ):

контент.css

body {  background-image:url('chrome-extension://__MSG_@@extension_id__/background.png'); }  @font-face {  font-family: 'Stint Ultra Expanded';  font-style: normal;  font-weight: 400;  src: url('chrome-extension://__MSG_@@extension_id__/fonts/Stint Ultra Expanded.woff') format('woff'); } 

Все ресурсы должны быть объявлены как доступные через Интернет ресурсы в файле manifest.json :

манифест.json

{  ...  "web_accessible_resources": [    {      "resources": [ "images/*.png" ],      "matches": [ "https://example.com/*" ]    },    {      "resources": [ "fonts/*.woff" ],      "matches": [ "https://example.com/*" ]    }  ],  ... } 

Оставайтесь в безопасности

Хотя изолированные миры обеспечивают уровень защиты, использование сценариев содержимого может создать уязвимости в расширении и веб-странице. Если сценарий контента получает контент с отдельного веб-сайта, например, путем вызова fetch() , будьте осторожны, чтобы отфильтровать контент от атак с использованием межсайтовых сценариев перед его внедрением. Общайтесь только через HTTPS, чтобы избежать атак «человек посередине» .

Обязательно отфильтруйте вредоносные веб-страницы. Например, следующие шаблоны опасны и запрещены в Манифесте V3:

Не

контент-script.js

const data = document.getElementById("json-data"); // WARNING! Might be evaluating an evil script! const parsed = eval("(" + data + ")");
Не

контент-script.js

const elmt_id = ... // WARNING! elmt_id might be '); ... evil script ... //'! window.setTimeout("animate(" + elmt_id + ")", 200);

Вместо этого отдайте предпочтение более безопасным API, которые не запускают сценарии:

Делать

контент-script.js

const data = document.getElementById("json-data") // JSON.parse does not evaluate the attacker's scripts. const parsed = JSON.parse(data);
Делать

контент-script.js

const elmt_id = ... // The closure form of setTimeout does not evaluate scripts. window.setTimeout(() => animate(elmt_id), 200);