使用内容脚本访问页面上下文变量和函数 [英] Use a content script to access the page context variables and functions

查看:102
本文介绍了使用内容脚本访问页面上下文变量和函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在学习如何创建Chrome扩展程序.我刚刚开始开发一个捕捉YouTube事件的工具.我想将其与YouTube Flash Player配合使用(稍后,我将尝试使其与HTML5兼容).

I'm learning how to create Chrome extensions. I just started developing one to catch YouTube events. I want to use it with YouTube flash player (later I will try to make it compatible with 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视频时没有状态已改变!" .

The problem is that the console gives me the "Started!", but there is no "State Changed!" when I play/pause YouTube videos.

将此代码放入控制台后,它可以工作.我在做什么错了?

When this code is put in the console, it worked. What am I doing wrong?

推荐答案

根本原因:
内容脚本在孤立的世界"中执行.环境.

Underlying cause:
Content scripts are executed in an "isolated world" environment.

解决方案::
为了访问页面上下文(主要世界")的功能/变量,您必须将想要访问它们的代码注入页面本身.如果您想将函数/变量公开到页面上下文(在您的情况下,它是state()方法),则同样.

Solution::
To access functions/variables of the page context ("main world") you have to inject the code that wants to access them into the page itself. Same thing if you want to expose your functions/variables to the page context (in your case it's the state() method).

请注意在需要chrome API的情况下:
由于chrome.* API不能直接在公开页面脚本中使用,因此您必须在内容脚本中使用它们,并通过特殊的DOM CustomEvent处理程序与两个.如果不必使用chrome.* API,只需添加如下所示的<script>标签即可将所有JS代码注入页面中.

Note in case chrome API is needed:
Since chrome.* APIs can't be used in the exposed page script directly, you have to use them in the content script and communicate to the exposed page script via a special DOM CustomEvent handler, example one and two. If you don't have to use chrome.* APIs, simply inject all of your JS code in the page by adding a <script> tag as shown below.

安全警告:
页面可能会重新定义或扩充/挂钩内置的原型,因此,如果页面以不兼容的方式进行操作,则暴露的代码可能会失败.如果您想确保公开的代码在安全的环境中运行,则您应该a)使用,并使用方法2-3(而非1),或b)通过空的iframe(

Safety warning:
A page may redefine or augment/hook a built-in prototype so your exposed code may fail if the page did it in an incompatible fashion. If you want to make sure your exposed code runs in a safe environment then you should either a) declare your content script with "run_at": "document_start" and use Methods 2-3 not 1, or b) extract the original native built-ins via an empty iframe, example. Note that with document_start you may need to use DOMContentLoaded event inside the exposed code to wait for DOM.

  • 方法1:注入另一个文件
  • 方法2:注入嵌入式代码
  • 方法2b:使用函数
  • 方法3:使用内联事件
  • 注入代码中的动态值

当您有很多代码时,这是最简单/最好的方法.在扩展名中的文件中包含实际的JS代码,例如script.js.然后,让您的内容脚本如下所示(在此处说明: Google Chome应用程序快捷方式"自定义Javascript ):

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. Then let your content script be as follows (explained here: 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);

注意:出于安全原因,Chrome禁止加载js文件.您的文件必须添加为 "web_accessible_resources" (示例):

// manifest.json must include: 
"web_accessible_resources": ["script.js"],

否则,控制台中将出现以下错误:

If not, the following error will appear in the console:

拒绝chrome-extension://[EXTENSIONID]/script.js.必须在web_accessible_resources清单密钥中列出资源,以便扩展名以外的页面加载资源.

Denying load of 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.

方法2:注入嵌入式代码

当您想快速运行一小段代码时,此方法很有用. (另请参见:如何通过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-中运行,请使用:

Note: template literals are only supported in Chrome 41 and above. If you want the extension to work in Chrome 40-, use:

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:使用函数

对于一大段代码,引用字符串是不可行的.除了使用数组,还可以使用函数并对其进行字符串化:

Method 2b: Using a function

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"

方法3:使用内联事件

有时候,您想立即运行一些代码,例如在创建<head>元素之前运行一些代码.可以通过在textContent中插入<script>标记来完成此操作(请参见方法2/2b).

Method 3: Using an inline event

Sometimes, you want to run some code immediately, e.g. to run some code before the <head> element is created. This can be done by inserting a <script> tag with textContent (see method 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');

注意:此方法假定没有其他全局事件侦听器处理reset事件.如果存在,您还可以选择其他全局事件之一.只需打开JavaScript控制台(F12),键入document.documentElement.on,然后选择可用的事件即可.

Note: This method assumes that there are no other global event listeners that handle the reset event. 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.

有时,您需要将任意变量传递给注入的函数.例如:

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!

解决方案是使用 JSON.stringify 在传递参数之前.示例:

The solution is to use JSON.stringify before passing the argument. Example:

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

如果您有很多变量,则值得一次使用JSON.stringify来提高可读性,如下所示:

If you have many variables, it's worthwhile to use JSON.stringify once, to improve readability, as follows:

...
} + ')(' + JSON.stringify([arg1, arg2, arg3, arg4]) + ')';

这篇关于使用内容脚本访问页面上下文变量和函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆