Вставка кода в контекст страницы с помощью сценария содержимого

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

манифест.в 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 ответов


сценарии выполняются в "изолированный мир" окружающая среда. Вы должны ввести свой state() метод в саму страницу.

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

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

содержание

  • метод 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.extension.getURL('script.js');
s.onload = function() {
    this.remove();
};
(document.head || document.documentElement).appendChild(s);

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

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

Метод 2: Inject embedded code

этот метод полезен, когда вы хотите быстро запустить небольшой кусок кода. (См. также: как отключить горячие клавиши 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: использование функции

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

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();

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

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]) + ')';

единственное отсутствует скрытый от строки W отличный ответ - как вызвать из введенного скрипта сценарий содержимого и наоборот (особенно если у вас есть объекты, которые не могут быть строковыми).

в любом введенном или вашем скрипте содержимого добавьте прослушиватель событий:

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

С другой стороны (содержимое или введенный скрипт) вызовите событие:

var data="anything";

// updated: this works with Chrome 30:
var evt=document.createEvent("CustomEvent");
evt.initCustomEvent("yourCustomEvent", true, true, data);
document.dispatchEvent(evt);

// the following stopped working in Chrome 30 (Windows), detail was 
// not received in the listener:
// document.dispatchEvent(new CustomEvent('yourCustomEvent', { detail: data }));

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

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, поэтому не стесняйтесь пинговать меня лучшими способами.


в 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");

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


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

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');

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

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+")";