我正在学习如何创建Chrome扩展程序。我刚刚开始开发一个来捕捉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!");
问题是控制台为我提供了“已启动!”,但在播放/暂停YouTube视频时没有“State Changed!”。
将此代码放入控制台时,它可以正常工作。我做错了什么?
答案 0 :(得分:787)
内容脚本在"isolated world" environment中执行。您必须将state()
方法注入页面本身。
如果要在脚本中使用其中一个chrome.*
API,则必须实现一个特殊的事件处理程序,如本答案中所述:Chrome extension - retrieving Gmail's original message。
否则,如果您不必使用chrome.*
API,我强烈建议您通过添加<script>
标记在页面中注入所有JS代码:
当您拥有大量代码时,这是最简单/最好的方法。将您的实际JS代码包含在扩展程序的文件中,例如script.js
。然后让您的内容脚本如下(在此解释: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);
注意:如果您使用此方法,则必须将注入的script.js
文件添加到"web_accessible_resources"
部分(example)。如果不这样做,Chrome将拒绝加载您的脚本并在控制台中显示以下错误:
拒绝加载chrome-extension:// [EXTENSIONID] /script.js。资源必须列在web_accessible_resources清单键中,以便由扩展名外的页面加载。
当您想快速运行一小段代码时,此方法很有用。 (另见:How to disable facebook hotkeys with Chrome extension?)。
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及更高版本支持template literals。如果您希望扩展程序在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');
对于大量代码,引用字符串是不可行的。可以使用函数,而不是使用数组,并使用字符串化:
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"
有时,您希望立即运行一些代码,例如在创建<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]) + ')';
答案 1 :(得分:49)
唯一 在注入或您的内容脚本中添加一个事件监听器: 另一方面(内容或注入脚本)调用事件: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 }));
答案 2 :(得分:8)
我还遇到了加载脚本的排序问题,这是通过顺序加载脚本来解决的。加载基于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的新手,所以请随时向我发出更好的方法。
答案 3 :(得分:6)
在内容脚本中,我将脚本标记添加到头部,该标签绑定了一个&#39; onmessage&#39;处理程序,我使用的处理程序内部,eval执行代码。 在booth内容脚本中我也使用onmessage handler,所以我得到双向通信。 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 listener
//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事件, 或任何内存变量或事件。
答案 4 :(得分:3)
您可以使用我创建的实用程序功能,以便在页面上下文中运行代码并获取返回的值。
这是通过将函数序列化为字符串并将其注入到网页来完成的。
实用程序为available here on GitHub。
用法示例-
// Some code that exists only in the page context -
window.someProperty = 'property';
function someFunction(name = 'test') {
return new Promise(res => setTimeout(()=>res('resolved ' + name), 1200));
}
/////////////////
// Content script examples -
await runInPageContext(() => someProperty); // returns 'property'
await runInPageContext(() => someFunction()); // returns 'resolved test'
await runInPageContext(async (name) => someFunction(name), 'with name' ); // 'resolved with name'
await runInPageContext(async (...args) => someFunction(...args), 'with spread operator and rest parameters' ); // returns 'resolved with spread operator and rest parameters'
await runInPageContext({
func: (name) => someFunction(name),
args: ['with params object'],
doc: document,
timeout: 10000
} ); // returns 'resolved with params object'
答案 5 :(得分:0)
如果您希望注入纯函数而不是文本,则可以使用此方法:
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+")";