четверг, 20 февраля 2014 г.

Content Scripts

Это JavaScript выполняемый в контексте веб-страницы. Используя DOM он может получать данные из этой страницы или модифицировать ее.
Примеры того что может контентный скрипт:
  • найти ключевые слова на странице и преобразовать их в гиперссылки
  • найти разметку микроформата и извлечь информацию
  • изменить стиль страницы – увеличить шрифты, сделать их более контрастными, убрать рекламные блоки и т.д.
Однако есть ограничения. Контентный скрипт не может
  • использовать chrome.* API
  • использовать переменные или функции определенные на странице или другими контентными скриптами
  • использовать переменные или функции определенные на других страницах расширения
Но такой скрипт может обмениваться сообщениями с расширением и делать все перечисленное косвенным образом. Он также может выполнять кросс-сайтовые запросы – те что прописаны в разрешениях расширения и взаимодействовать со скриптами на странице через DOM.

Манифест

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://www.google.com/*"],
      "css": ["mystyles.css"],
      "js": ["jquery.js", "myscript.js"]
    }
  ],
  ...
}

Есть альтернативный способ вставить свой код :
{
  "name": "My extension",
  ...
  "permissions": [
    "tabs", "http://www.google.com/*"
  ],
  ...
}

Используя свойство content_scripts можно вставлять много скриптов. Каждый элемент массива может содержать следующие свойства:
ИмяТипОписание
matchesarray of stringsОбязательно. Указывает на какие страницы будет вставляться контент-скрипт.
exclude_matchesarray of stringsОпционально. Исключения – на какие страницы скрипт вставляться не будет.
cssarray of stringsОпционально. Список файлов CSS, которые будут подключаться к страницам. Подключаются в порядке объявления.
jsarray of stringsОпционально. Список файлов JavaScript, которые будут подключаться к страницам. Подключаются в порядке объявления.
run_atstring
Опционально. Задает когда js файлы будут подключаться:


  • document_start до того как будут запущены любые другие скрипты
  • document_end когда сам документ загружен и его скрипты запущены, но до того как загрузятся картинки и фреймы
  • document_idle – после события window.onload

Какой вариант лучше зависит от того как долго загружается документ (на самом деле лучше – значение по умолчанию). По умолчанию document_idle
all_framesbooleanОпционально. Будет ли скрипт загружаться во всех фреймах или только в главном.
По умолчанию false – скрипт запускается только в главном фрейме.
include_globsarray of stringОпционально. Применяется после matches чтобы скрипт подключался только для URL которые входят сюда. Эмулирует поведение @include в Greasemonkey. .
exclude_globsarray of stringОпционально. Аналогичным образом эмулирует @exclude.

Паттерны


Скрипт подключается если он входит в matches или include_globs и при этом не входит в exclude_matches или exclude_globs. matches – обязательное свойство, прочие используются только чтобы уточнить к каким страницам скрипт будет подключаться.

Пусть в matches задано [http://*.nytimes.com/*]


  • Если exclude_matches ["*://*/*business*"], тогда скрипт подключится к "http://www.nytimes.com/health" но не к "http://www.nytimes.com/business".
  • Если  include_globs  ["*nytimes.com/???s/*"], тогда он подключится к "http:/www.nytimes.com/arts/index.html" и "http://www.nytimes.com/jobs/index.html" но не к "http://www.nytimes.com/sports/index.html".
  • Если  exclude_globs is ["*science*"], тогда скрипт подключится к "http://www.nytimes.com" но не к "http://science.nytimes.com" или "http://www.nytimes.com/science".

Вопрос на засыпку: подключится ли скрипт http://www.nytimes.com/jobs/business.html ?

Свойства include_globs и exclude_globs имеют более расширенный синтаксис по сравнению с matches и exclude_matches. * означает произвольную последовательность символов, включая пустую строку, а знак вопроса – одиночный символ.

Программное внедрение скриптов


Контент-скрипт можно внедрить на страницу программно – когда логика определения страницы куда необходимо внедрить скрипт выходит за рамки паттернов. Для этого требуется разрешение кросс-скриптинга в отношении этого сайта и на работу с вкладками, или “activeTab” – для внедрения в любой сайт в текущей вкладке. Выше был приведен примеры манифеста который позволяет внедрять контентные скрипты на сайт гугла. Первый пример – статическое внедрение, скрипт подключается всегда, второй – программное.

Делается это методами


  • tabs.executeScript
  • tabs.insertCSS

Пример:
chrome.browserAction.onClicked.addListener(function(tab) {
  chrome.tabs.executeScript({
    code: 'document.body.style.backgroundColor="red"'
  }); 
});

При нажатии на иконку в панели инструментов, в текущую открытую страницу будет внедрен скрипт меняющий цвет фона на красный.

Обычно удобнее хранить скрипт в файле, метод executeScript позволяет это:
chrome.tabs.executeScript(null, {file: "content_script.js"});

Среда исполнения


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


Что будет, если контент скрипт загружает одну версию популярного js-фреймворка, а страница – другую? При этом имена многих функций и переменных совпадают, но отличаются количеством параметров и реализацией.

Но все же, лучше бы такую возможность, подключаемую отдельной опцией манифеста, сделали.

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

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

ContentScript.js
var port = chrome.runtime.connect();

window.addEventListener("message", function(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);

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

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

Безопасность



  • Будьте аккуратны с кросс-сайтовым скриптингом. Если вы берете какой-то контент на стороннем сайте и внедряете его на текущую страницу, то ответственность за безопасность лежит на вас. Особенно когда речь идет о https страницах, на которых, предположительно, может оказаться конфиденциальная информация.
  • Хотя ваш скрипт изолирован от скриптов страницы, все же следует принимать меры предосторожности, в частности не использовать eval, а передавать только данные в формате JSON.

Обращение к файлам расширения


Выполняется следующим образом:
//Code for displaying <extensionDir>/images/myimage.png:
var imgURL = chrome.extension.getURL("images/myimage.png");
document.getElementById("someImage").src = imgURL;

4 комментария:

  1. Здравствуйте, вы не подскажите, как удалить загруженный chrome.tabs.executeScript?

    ОтветитьУдалить
  2. Однако есть ограничения. Контентный скрипт не может
    использовать chrome.* API

    Однако chrome.* работает!

    ОтветитьУдалить
  3. Здравствуйте. Подскажите, пожалуйста, как получить данные из popup.html на страницу сайта?В popup в textarea клиент редактирует данные, которые сохраняются в localStorage[]. Далее клиент выделяет текст на странице, и по этому событию генерируется окно рядом с курсором, данные на котором должны сопоставлять выделенный текст и текст из textarea-popup. Никак не получатся получить textarea-popup (или localStorage-popup, или cookie-popup).

    ОтветитьУдалить