Вставьте код в контекст страницы, используя скрипт содержимого - PullRequest
439 голосов
/ 01 марта 2012

Я учусь создавать расширения Chrome.Я только начал разрабатывать один, чтобы ловить события YouTube.Я хочу использовать его с флеш-плеером YouTube (позже я попытаюсь сделать его совместимым с HTML5).

manifest.json:

{
    "name": "MyExtension",
    "version": "1.0",
    "description": "Gotta catch Youtube events!",
    "permissions": ["tabs", "http://*/*"],
    "content_scripts" : [{
        "matches" : [ "www.youtube.com/*"],
        "js" : ["myScript.js"]
    }]
}

myScript.js:

function state() { console.log("State Changed!"); }
var player = document.getElementById("movie_player");
player.addEventListener("onStateChange", "state");
console.log("Started!");

Проблема в том, что консоль выдает мне "Начато!" , но "Состояние изменено!" когда я играю / приостанавливаю видео на YouTube.

Когда этот код помещается в консоль, он работает.Что я делаю не так?

Ответы [ 5 ]

805 голосов
/ 01 марта 2012

Сценарии содержимого выполняются в среде «изолированного мира» .Вы должны внедрить свой метод state() в саму страницу.

Если вы хотите использовать в сценарии один из chrome.* API, вам необходимо реализовать специальный обработчик событий, как описано в этом ответе.: Расширение Chrome - получение исходного сообщения Gmail .

В противном случае, если вам не нужно использовать chrome.* API, я настоятельно рекомендую внедрить весь код JS на странице черездобавление тега <script>:

Содержание

  • Метод 1: внедрить другой файл
  • Метод 2: Вставить встроенный код
  • Метод2b: Использование функции
  • Метод 3: Использование встроенного события
  • Динамические значения в введенном коде

Метод 1: Внедрение другого файла

Это самый простой / лучший метод, когда у вас много кода. Включите ваш фактический код JS в файл вашего расширения, скажем, script.js.Затем пусть ваш контент-скрипт будет следующим (объяснено здесь: Google Chome «Ярлык приложения», пользовательский Javascript ):

var s = document.createElement('script');
// TODO: add "script.js" to web_accessible_resources in manifest.json
s.src = chrome.runtime.getURL('script.js');
s.onload = function() {
    this.remove();
};
(document.head || document.documentElement).appendChild(s);

Примечание. При использовании этого метода вводитсяФайл script.js необходимо добавить в раздел "web_accessible_resources" ( пример ).Если вы этого не сделаете, Chrome откажется загрузить ваш скрипт и отобразит в консоли следующую ошибку:

Отказ в загрузке расширения chrome: // [EXTENSIONID] / script.js.Ресурсы должны быть перечислены в ключе манифеста web_accessible_resources, чтобы их могли загружать страницы за пределами расширения.

Метод 2. Внедрение встроенного кода

Этот метод полезен, если вы хотите быстро запустить небольшой фрагмент кода.(См. Также: Как отключить горячие клавиши facebook с расширением Chrome? ).

var actualCode = `// Code here.
// If you want to use a variable, use $ and curly braces.
// For example, to use a fixed random number:
var someFixedRandomValue = ${ Math.random() };
// NOTE: Do not insert unsafe variables in this way, see below
// at "Dynamic values in the injected code"
`;

var script = document.createElement('script');
script.textContent = actualCode;
(document.head||document.documentElement).appendChild(script);
script.remove();

Примечание: литералы шаблона поддерживаются только в Chrome 41 и выше.Если вы хотите, чтобы расширение работало в Chrome 40-, используйте:

var actualCode = ['/* Code here. Example: */' + 'alert(0);',
                  '// Beware! This array have to be joined',
                  '// using a newline. Otherwise, missing semicolons',
                  '// or single-line comments (//) will mess up your',
                  '// code ----->'].join('\n');

Метод 2b: Использование функции

Для большого куска кода цитирование строки невозможно.Вместо использования массива можно использовать функцию и строку:

var actualCode = '(' + function() {
    // All code is executed in a local scope.
    // For example, the following does NOT overwrite the global `alert` method
    var alert = null;
    // To overwrite a global variable, prefix `window`:
    window.alert = null;
} + ')();';
var script = document.createElement('script');
script.textContent = actualCode;
(document.head||document.documentElement).appendChild(script);
script.remove();

Этот метод работает, поскольку оператор + для строк и функция преобразуют все объекты в строку.Если вы собираетесь использовать код более одного раза, целесообразно создать функцию, чтобы избежать повторения кода.Реализация может выглядеть следующим образом:

function injectScript(func) {
    var actualCode = '(' + func + ')();'
    ...
}
injectScript(function() {
   alert("Injected script");
});

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

var scriptToInject = function() {
    console.log(typeof scriptToInject);
};
injectScript(scriptToInject);
// Console output:  "undefined"

Метод 3: Использование встроенного события

Иногда вам нужно сразу запустить какой-то код, например, запустить какой-то код до создания элемента <head>.Это можно сделать, вставив тег <script> с помощью textContent (см. Метод 2 / 2b).

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

var actualCode = '// Some code example \n' + 
                 'console.log(document.documentElement.outerHTML);';

document.documentElement.setAttribute('onreset', actualCode);
document.documentElement.dispatchEvent(new CustomEvent('reset'));
document.documentElement.removeAttribute('onreset');

Примечание. Этот метод предполагает, что нет других глобальных прослушивателей событий, которые обрабатывают событие reset.Если есть, вы также можете выбрать одно из других глобальных событий.Просто откройте консоль JavaScript (F12), введите document.documentElement.on и выберите доступные события.

Динамические значения в введенном коде

Иногда вам нужно передать произвольную переменную ввведенная функция.Например:

var GREETING = "Hi, I'm ";
var NAME = "Rob";
var scriptToInject = function() {
    alert(GREETING + NAME);
};

Чтобы внедрить этот код, вам нужно передать переменные в качестве аргументов анонимной функции.Будьте уверены, чтобы реализовать это правильно!Следующее не работает:

var scriptToInject = function (GREETING, NAME) { ... };
var actualCode = '(' + scriptToInject + ')(' + GREETING + ',' + NAME ')';
// The previous will work for numbers and booleans, but not strings.
// To see why, have a look at the resulting string:
var actualCode = "(function(GREETING, NAME) {...})(Hi I'm,Rob)";
//                                                 ^^^^^^ ^^^ No string literals!

Решение состоит в том, чтобы использовать JSON.stringify перед передачей аргумента.Пример:

var actualCode = '(' + function(greeting, name) { ...
} + ')(' + JSON.stringify(GREETING) + ',' + JSON.stringify(NAME) + ')';

Если у вас много переменных, стоит один раз использовать JSON.stringify для улучшения читабельности, как показано ниже:

...
} + ')(' + JSON.stringify([arg1, arg2, arg3, arg4]) + ')';
50 голосов
/ 11 октября 2013

Единственное, чего не хватает , скрытого от отличного ответа Роба В., - как установить связь между введенным сценарием страницы и сценарием содержимого.

На принимающей стороне (либо сценарий содержимого, либосценарий страницы) добавьте прослушиватель событий:

document.addEventListener('yourCustomEvent', function (e) {
  var data = e.detail;
  console.log('received', data);
});

На стороне инициатора (сценарий содержимого или страницы) отправьте событие:

var data = {
  any: 'JSON-ifiable data',
  meaning: 'no DOM elements or classes/functions',
};

document.dispatchEvent(new CustomEvent('yourCustomEvent', { detail: data }));

Убедитесь, что переданные данные являются JSON-ifiable

Вы можете явно выполнить строковое преобразование / синтаксический анализ для удаления непередаваемых данных, в противном случае только null будет передано для всего detail в современном Chrome, см. crbug.com / 917703.

  • получатель:

    document.addEventListener('yourCustomEvent', function (e) {
      var data = JSON.parse(e.detail);
      console.log('received', data);
    });
    
  • инициатор:

    document.dispatchEvent(new CustomEvent('yourCustomEvent', {
      detail: JSON.stringify(data),
    }));
    
8 голосов
/ 17 мая 2015

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

function scriptFromFile(file) {
    var script = document.createElement("script");
    script.src = chrome.extension.getURL(file);
    return script;
}

function scriptFromSource(source) {
    var script = document.createElement("script");
    script.textContent = source;
    return script;
}

function inject(scripts) {
    if (scripts.length === 0)
        return;
    var otherScripts = scripts.slice(1);
    var script = scripts[0];
    var onload = function() {
        script.parentNode.removeChild(script);
        inject(otherScripts);
    };
    if (script.src != "") {
        script.onload = onload;
        document.head.appendChild(script);
    } else {
        document.head.appendChild(script);
        onload();
    }
}

Пример использования:

var formulaImageUrl = chrome.extension.getURL("formula.png");
var codeImageUrl = chrome.extension.getURL("code.png");

inject([
    scriptFromSource("var formulaImageUrl = '" + formulaImageUrl + "';"),
    scriptFromSource("var codeImageUrl = '" + codeImageUrl + "';"),
    scriptFromFile("EqEditor/eq_editor-lite-17.js"),
    scriptFromFile("EqEditor/eq_config.js"),
    scriptFromFile("highlight/highlight.pack.js"),
    scriptFromFile("injected.js")
]);

На самом деле, я немного новичок в JS, так что не стесняйтесь пинговать меня до лучших способов.

6 голосов
/ 11 января 2016

в скрипте Content, я добавляю скрипт script к заголовку, который связывает обработчик 'onmessage', внутри обработчика, который я использую, eval для выполнения кода.В скрипте содержимого стенда я также использую обработчик onmessage, поэтому я получаю двустороннюю связь. Chrome Docs

//Content Script

var pmsgUrl = chrome.extension.getURL('pmListener.js');
$("head").first().append("<script src='"+pmsgUrl+"' type='text/javascript'></script>");


//Listening to messages from DOM
window.addEventListener("message", function(event) {
  console.log('CS :: message in from DOM', event);
  if(event.data.hasOwnProperty('cmdClient')) {
    var obj = JSON.parse(event.data.cmdClient);
    DoSomthingInContentScript(obj);
 }
});

pmListener.js - слушатель URL-адреса почтового сообщения

//pmListener.js

//Listen to messages from Content Script and Execute Them
window.addEventListener("message", function (msg) {
  console.log("im in REAL DOM");
  if (msg.data.cmnd) {
    eval(msg.data.cmnd);
  }
});

console.log("injected To Real Dom");

Таким образом, я могу иметь двухстороннюю связь между CS и Real Dom.Это очень полезно, например, если вам нужно прослушать события веб-скокета или какие-либо переменные или события в памяти.

0 голосов
/ 05 апреля 2018

Если вы хотите ввести чистую функцию вместо текста, вы можете использовать этот метод:

function inject(){
    document.body.style.backgroundColor = 'blue';
}

// this includes the function as text and the barentheses make it run itself.
var actualCode = "("+inject+")()"; 

document.documentElement.setAttribute('onreset', actualCode);
document.documentElement.dispatchEvent(new CustomEvent('reset'));
document.documentElement.removeAttribute('onreset');

И вы можете передавать параметры (к сожалению, объекты и массивы не могут быть преобразованы в строку) в функции.Добавьте это в голые вещи, вот так:

function inject(color){
    document.body.style.backgroundColor = color;
}

// this includes the function as text and the barentheses make it run itself.
var color = 'yellow';
var actualCode = "("+inject+")("+color+")"; 
...