本文翻译自:Insert code into the page context using a content script
I'm learning how to create Chrome extensions. 我正在学习如何创建Chrome扩展程序。 I just started developing one to catch YouTube events. 我刚刚开始开发一个捕捉YouTube事件的工具。 I want to use it with YouTube flash player (later I will try to make it compatible with HTML5). 我想将其与YouTube Flash Player结合使用(稍后,我将尝试使其与HTML5兼容)。
manifest.json: 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: myScript.js:
function state() { console.log("State Changed!"); }
var player = document.getElementById("movie_player");
player.addEventListener("onStateChange", "state");
console.log("Started!");
The problem is that the console gives me the "Started!" 问题在于控制台为我提供了“开始!” , but there is no "State Changed!" ,但没有“状态已更改!” when I play/pause YouTube videos. 当我播放/暂停YouTube视频时。
When this code is put in the console, it worked. 将此代码放入控制台后,它就可以工作。 What am I doing wrong? 我究竟做错了什么?
#1楼
参考:https://stackoom.com/question/dvT6/使用内容脚本将代码插入页面上下文
#2楼
The only thing 唯一的事情 missing 失踪 hidden from Rob W's excellent answer is how to communicate between the injected page script and the content script. Rob W出色的答案隐藏的是如何在注入的页面脚本和内容脚本之间进行通信。
On the receiving side (either your content script or the page script) add an event listener: 在接收端(您的内容脚本或页面脚本)添加事件侦听器:
document.addEventListener('yourCustomEvent', function (e) {
var data = e.detail;
console.log('received', data);
});
On the initiator side (content or page script) send the event: 在启动器端(内容或页面脚本)发送事件:
var data = {
any: 'JSON-ifiable data',
meaning: 'no DOM elements or classes/functions',
};
document.dispatchEvent(new CustomEvent('yourCustomEvent', { detail: data }));
Make sure the transferred data is JSON-ifiable 确保传输的数据是JSON-ifiable
You can perform stringifying/parsing explicitly to strip the non-transferrable data, otherwise only null
will be transferred for the entire detail
in modern Chrome, see crbug.com/917703 . 您可以显式执行字符串化/解析以剥离不可传输的数据,否则在现代Chrome浏览器中,整个detail
将仅传输null
,请参阅crbug.com/917703 。
receiver: 接收者:
document.addEventListener('yourCustomEvent', function (e) { var data = JSON.parse(e.detail); console.log('received', data); });
initiator: 发起人:
document.dispatchEvent(new CustomEvent('yourCustomEvent', { detail: JSON.stringify(data), }));
#3楼
I've also faced the problem of ordering of loaded scripts, which was solved through sequential loading of scripts. 我还遇到了加载脚本排序的问题,该问题通过顺序加载脚本解决。 The loading is based on Rob W's answer . 加载基于Rob W的答案 。
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();
}
}
The example of usage would be: 用法示例为:
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")
]);
Actually, I'm kinda new to JS, so feel free to ping me to the better ways. 其实,我是JS的新手,所以随时向我推荐更好的方法。
#4楼
in Content script , i add script tag to the head which binds a 'onmessage' handler, inside the handler i use , eval to execute code. 在Content脚本中,我将脚本标签添加到绑定“ onmessage”处理程序的头部,在我使用的处理程序内部,eval执行代码。 In booth content script i use onmessage handler as well , so i get two way communication. 在展位内容脚本中,我也使用onmessage处理程序,因此我得到了两种通信方式。 Chrome Docs Chrome文件
//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 is a post message url listener 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");
This way , I can have 2 way communication between CS to Real Dom. 这样,我就可以在CS与Real Dom之间进行2路通信。 Its very usefull for example if you need to listen webscoket events , or to any in memory variables or events. 例如,如果您需要侦听webscoket事件或任何内存变量或事件,它就非常有用。
#5楼
If you wish to inject pure function, instead of text, you can use this method: 如果希望注入纯函数而不是文本,则可以使用以下方法:
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');
And you can pass parameters (unfortunatelly no objects and arrays can be stringifyed) to the functions. 您可以将参数(不幸的是,不能对对象和数组进行字符串化)传递给函数。 Add it into the baretheses, like so: 将其添加到裸露的容器中,如下所示:
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+")";
#6楼
Content scripts are executed in an "isolated world" environment . 内容脚本在“隔离的世界”环境中执行。 You have to inject your state()
method into the page itself. 您必须将state()
方法注入页面本身。
When you want to use one of the chrome.*
APIs in the script, you have to implement a special event handler, as described in this answer: Chrome extension - retrieving Gmail's original message . 如果要在脚本中使用chrome.*
API之一,则必须实现特殊的事件处理程序,如以下答案中所述: Chrome扩展程序-检索Gmail的原始消息 。
Otherwise, if you don't have to use chrome.*
APIs, I strongly recommend to inject all of your JS code in the page via adding a <script>
tag: 否则,如果您不必使用chrome.*
API,我强烈建议通过添加<script>
标签将所有JS代码注入页面中:
Table of contents 目录
- Method 1: Inject another file 方法1:注入另一个文件
- Method 2: Inject embedded code 方法2:注入嵌入式代码
- Method 2b: Using a function 方法2b:使用一个函数
- Method 3: Using an inline event 方法3:使用一个内联事件
- Dynamic values in the injected code 注入代码中的动态值
Method 1: Inject another file 方法1:注入另一个文件
This is the easiest/best method when you have lots of code. 当您有很多代码时,这是最简单/最佳的方法。 Include your actual JS code in a file within your extension, say script.js
. 将实际的JS代码包括在扩展名内的文件中,例如script.js
。 Then let your content script be as follows (explained here: Google Chome “Application Shortcut” Custom Javascript ): 然后,让您的内容脚本如下所示(在此处进行说明: Google Chome“ Application Shortcut” Custom 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);
Note: If you use this method, the injected script.js
file has to be added to the "web_accessible_resources"
section ( example ). 注意:如果使用此方法,则必须将注入的script.js
文件添加到"web_accessible_resources"
部分 ( 示例 )。 If you do not, Chrome will refuse to load your script and display the following error in the console: 如果不这样做,Chrome会拒绝加载脚本并在控制台中显示以下错误:
Denying load of chrome-extension://[EXTENSIONID]/script.js. 拒绝加载chrome-extension:// [EXTENSIONID] /script.js。 Resources must be listed in the web_accessible_resources manifest key in order to be loaded by pages outside the extension. 必须在web_accessible_resources清单键中列出资源,以便由扩展之外的页面加载。
Method 2: Inject embedded code 方法2:注入嵌入式代码
This method is useful when you want to quickly run a small piece of code. 当您想快速运行一小段代码时,此方法很有用。 (See also: How to disable facebook hotkeys with Chrome extension? ). (另请参见: 如何使用Chrome扩展程序禁用Facebook热键? )。
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();
Note: template literals are only supported in Chrome 41 and above. 注意: 模板文字仅在Chrome 41及更高版本中受支持。 If you want the extension to work in Chrome 40-, use: 如果您希望扩展程序在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');
Method 2b: Using a function 方法2b:使用一个函数
For a big chunk of code, quoting the string is not feasible. 对于一大段代码,引用字符串是不可行的。 Instead of using an array, a function can be used, and 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();
This method works, because the +
operator on strings and a function converts all objects to a string. 此方法有效,因为字符串和函数上的+
运算符会将所有对象转换为字符串。 If you intend on using the code more than once, it's wise to create a function to avoid code repetition. 如果您打算多次使用该代码,则明智的做法是创建一个避免代码重复的函数。 An implementation might look like: 一个实现可能看起来像:
function injectScript(func) {
var actualCode = '(' + func + ')();'
...
}
injectScript(function() {
alert("Injected script");
});
Note: Since the function is serialized, the original scope, and all bound properties are lost! 注意:由于该函数已序列化,因此原始作用域和所有绑定的属性都将丢失!
var scriptToInject = function() {
console.log(typeof scriptToInject);
};
injectScript(scriptToInject);
// Console output: "undefined"
Method 3: Using an inline event 方法3:使用一个内联事件
Sometimes, you want to run some code immediately, eg to run some code before the <head>
element is created. 有时,您想立即运行一些代码,例如在创建<head>
元素之前运行一些代码。 This can be done by inserting a <script>
tag with textContent
(see method 2/2b). 这可以通过插入来完成<script>
与标签textContent
(参见方法2 / 2B)。
An alternative, but not recommended is to use inline events. 一种替代方法, 但不建议使用内联事件。 It is not recommended because if the page defines a Content Security policy that forbids inline scripts, then inline event listeners are blocked. 不建议这样做,因为如果页面定义了禁止内联脚本的内容安全策略,则内联事件侦听器将被阻止。 Inline scripts injected by the extension, on the other hand, still run. 另一方面,由扩展名注入的内联脚本仍在运行。 If you still want to use inline events, this is how: 如果您仍想使用内联事件,则可以这样:
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');
Note: This method assumes that there are no other global event listeners that handle the reset
event. 注意:此方法假定没有其他全局事件侦听器来处理reset
事件。 If there is, you can also pick one of the other global events. 如果存在,您还可以选择其他全局事件之一。 Just open the JavaScript console (F12), type document.documentElement.on
, and pick on of the available events. 只需打开JavaScript控制台(F12),键入document.documentElement.on
,然后选择可用事件。
Dynamic values in the injected code 注入代码中的动态值
Occasionally, you need to pass an arbitrary variable to the injected function. 有时,您需要将任意变量传递给注入的函数。 For example: 例如:
var GREETING = "Hi, I'm ";
var NAME = "Rob";
var scriptToInject = function() {
alert(GREETING + NAME);
};
To inject this code, you need to pass the variables as arguments to the anonymous function. 要注入此代码,您需要将变量作为参数传递给匿名函数。 Be sure to implement it correctly! 确保正确实施! The following will not work: 以下将无法正常工作:
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!
The solution is to use JSON.stringify
before passing the argument. 解决方案是在传递参数之前使用JSON.stringify
。 Example: 例:
var actualCode = '(' + function(greeting, name) { ...
} + ')(' + JSON.stringify(GREETING) + ',' + JSON.stringify(NAME) + ')';
If you have many variables, it's worthwhile to use JSON.stringify
once, to improve readability, as follows: 如果您有很多变量,那么值得一次使用JSON.stringify
来提高可读性,如下所示:
...
} + ')(' + JSON.stringify([arg1, arg2, arg3, arg4]) + ')';