使用内容脚本将代码插入到页面上下文中
我正在学习如何创buildChrome扩展程序。 我刚刚开始开发一个YouTube的事件。 我想用它与YouTube的Flash播放器(后来我会尽量使其与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!");
问题是控制台给了我“开始!” ,但是没有“国家改变!” 当我玩/暂停YouTubevideo。
当这个代码被放在控制台,它的工作。 我究竟做错了什么?
内容脚本在“孤立的世界”环境中执行。 你必须注入你的state()
方法到页面本身。
如果您想在脚本中使用某个chrome.*
API,则必须实现一个特殊的事件处理程序,如本答复中所述: Chrome扩展程序 – 检索Gmail的原始消息 。
否则,如果你不需要使用chrome.*
API,我强烈build议通过添加一个<script>
标签将所有的JS代码注入页面。
目录
- 方法1:注入另一个文件
- 方法2:注入embedded的代码
- 方法2b:使用一个函数
- 方法3:使用内联事件
- 注入代码中的dynamic值
方法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扩展的加载:// [EXTENSIONID] /script.js。 必须在web_accessible_resources清单键中列出资源,才能通过扩展名之外的页面加载资源。
方法2:注入embedded的代码
当您想要快速运行一小段代码时,此方法非常有用。 (另请参阅: 如何使用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();
注意:仅在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:使用一个函数
对于一大块代码,引用string是不可行的。 而不是使用数组,可以使用一个函数并进行string化:
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();
这个方法是有效的,因为string和函数的+
操作符将所有的对象转换为一个string。 如果你打算多次使用这个代码,创build一个避免代码重复的函数是明智的。 一个实现可能如下所示:
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>
元素创build之前运行一些代码。 这可以通过插入带有textContent
的<script>
标签来完成(参见方法2 / 2b)。
另一种方法, 但不推荐使用内联事件。 不build议这样做,因为如果页面定义禁止内联脚本的内容安全策略,则内联事件侦听器将被阻止。 另一方面,由扩展注入的内联脚本仍然运行。 如果您仍然想使用内联事件,那么这是:
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
事件的其他全局事件侦听器。 如果有,您还可以select其他全球事件之一。 只需打开JavaScript控制台(F12),键入document.documentElement.on
,然后select可用的事件。
注入代码中的dynamic值
有时候,你需要传递一个任意的variables给注入的函数。 例如:
var GREETING = "Hi, I'm "; var NAME = "Rob"; var scriptToInject = function() { alert(GREETING + NAME); };
要注入此代码,您需要将variables作为parameter passing给匿名函数。 一定要正确实施! 以下将不起作用:
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) + ')';
如果你有很多variables,那么使用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的答案 。
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还是比较陌生的,所以随时可以用更好的方法来平衡我。
在内容脚本中,我添加脚本标记到绑定'onmessage'处理程序的头部,在我使用的处理程序中,eval执行代码。 在展位内容脚本中,我也使用onmessage处理程序,所以我得到双向沟通。 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是一个post消息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之间的双向沟通。 例如,如果你需要监听webscoket事件,或者任何内存variables或事件,它非常有用。