设置@grant 值时如何访问`window`(目标页面)对象?

IT技术 javascript firefox greasemonkey
2021-01-18 16:00:11

假设我正在使用以下网页:

<html>
<body>
<span id="click">click me</span>
<script>
var hello = function() {
    alert('hello');
}

document.getElementById('click').addEventListener('click', function(e) {
    hello();
});
</script>
</body>
</html>

我的 Greasemonkey 脚本是:

// ==UserScript==
// @name        My Script
// @include     http://example.com/hello.html
// @version     1
// @grant       none
// ==/UserScript==

window.hello = function() {
    alert('goodbye');
}

禁用 Greasemonkey 脚本后,单击#click页面上的元素会显示“hello”警报。启用脚本后,单击该元素会显示“再见”警报。

足够简单。hello从网页的功能正在由Greasemonkey的脚本功能所取代。

现在假设我想使用 Greasemonkey API。当我将该@grant设置为除“none”(例如// @grant GM_setClipboard以外的有效值时[这会导致 Greasemonkey 将脚本作为“内容脚本”运行,而不是像“none”那样在页面范围内运行],Greasemonkey 脚本无法工作。

window.hello 不再针对页面上的正确对象。

替换window.hellounsafeWindow.hello看起来可以,但是在 JS 控制台中抛出以下错误:

错误:访问对象的权限被拒绝

如何在@grant GM_setClipboard设置为目标并替换hello页面上的原始函数的同时重写 Greasemonkey 脚本

系统信息:

  • 视窗 7 64 位
  • 火狐 32.0
  • 油猴 2.2
2个回答

当您设置@grant除 none 以外值时,Greasemonkey 会激活其沙箱,并且Greasemonkey 2.0 彻底改变了 unsafeWindow 处理

现在,为了在目标页面范围内创建或覆盖变量,您必须从技术菜单中正确选择。例如:

读书:

  • 一个简单的变量:

    Target page sets:       var foo = "bar";
    GM script can read:     unsafeWindow.foo    //-- "bar"
    
  • 一个简单的对象:

    Target page sets:       var obj = {A: 1};
    GM script can read:     unsafeWindow.obj    //-- Object { A: 1 }
    
  • 一个复杂的对象: 这并不总是可能的。

致电:

  • 一个简单的函数:

    Target page sets:       function func () {console.log ('Hi');}
    GM script can call:     unsafeWindow.func() //-- "Hi"
    
  • 一个复杂的功能: 这并不总是可能的。

写入/设置:

  • 一个简单的变量:

    unsafeWindow.foo = "Apple";
    
  • 一个简单的对象:

    var gmObject        = {X: "123"};
    unsafeWindow.obj    = cloneInto (gmObject, unsafeWindow);
    
  • 一个简单的函数:

    function gmFunc () {
        console.log ("Lorem ipsum");
        //-- Can use GM_ functions in here! :)
    }
    unsafeWindow.func   = exportFunction (gmFunc, unsafeWindow);
    

考虑这个 HTML:

<button id="helloBtn">Say "Hello".</button>

这个javascript:

var simpleGlobalVar = "A simple, global var in the page scope.";
var globalObject    = {Letter: "A", Number: 2};

function simpleFunction () {
    console.log ("The target page's simpleFunction was called.");
}

var sayHello = function() {
    console.log ('Hello.');
}

document.getElementById ('helloBtn').addEventListener ('click', function () {
    sayHello ();
} );

您可以在此 jsFiddle 页面上实时查看

如果您针对该页面安装并运行此 Greasemonkey 脚本:

// ==UserScript==
// @name     _Demonstrate accessing target-page variables with @grant values set
// @include  http://fiddle.jshell.net/sepwL7n6/*/show/
// @require  http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// @grant    GM_addStyle
// ==/UserScript==

console.log ("*** Greasemonkey script start.");

$("body").append ('<div id="gmArea">Added by Greasemonkey:<p></p></div>');
$("#gmArea > p:first").append ('<button id="gmShow">Access select target-page variables and functions</button>');
$("#gmArea > p:first").append ('<button id="gmChange">Change javascript things in the target-page scope.</button>');

$("#gmShow").click ( function () {
    //-- Access things from the target-page scope:
    console.log ("----------------");
    console.log ("==> simpleGlobalVar is: ", unsafeWindow.simpleGlobalVar);
    console.log ("==> globalObject    is: ", unsafeWindow.globalObject);
    console.log ("==> Calling target's simpleFunction():");
    unsafeWindow.simpleFunction ();

    //-- WARNING! This next technique is not robust, but works in some cases.
    console.log ("==> Calling target's button's click().");
    unsafeWindow.document.getElementById ('helloBtn').click ();
} );

$("#gmChange").click ( function () {
    this.disabled = true;   //-- Can only click once.
    unsafeWindow.simpleGlobalVar    = "Simple var... Intercepted by GM!";
    unsafeWindow.globalObject       = cloneInto (gmObject, unsafeWindow);
    unsafeWindow.sayHello           = exportFunction (sayHello, unsafeWindow);
    console.log ("==> Target page objects were changed.");
} );

var gmMessageStr    = "Function... Intercepted by GM, but also can use GM_ functions!";
function sayHello () {
    sayHello.K      = (sayHello.K  ||  0) + 1;
    console.log (gmMessageStr);
    GM_addStyle ('body {background: ' + (sayHello.K % 2  ?  "lime"  :  "white") + ';}');
}
var gmObject        = {message: "Object overridden by GM."};


打开控制台并按下按钮,您将看到 GM 脚本能够读取和更改页面的变量和函数。


笔记:

  1. 这都是Firefox 特有的
  2. 对于跨平台代码,以及一些复杂的情况,你可以使用脚本注入来代替。但是注入的代码不能直接访问GM_函数。
  3. 请注意,这些技术仅适用于全局的 javascript 变量和函数。
unsafeWindow行为实际上是不是来自 Firefox 30,就像这篇博客文章中所说的那样据推测,GM 2.0 中的相关更改与确保新 API 可用于 GM 脚本有关,也许是 GM 本身正确使用它们而不是依赖旧行为。
2021-03-17 16:00:11
@SamB,不完全是。请注意,该博客条目是关于 SDK,而不是 Firefox。GM 不使用 SDK,也不必进行当时所做的更改。目前还不清楚未来对 FF 的计划更改是否最终会强制更改,但新方法更好,因为它允许更容易地将 GM 功能集成到目标页面代码中(一种危险的做法)。
2021-03-29 16:00:11

将您的代码包装在一个 lambda 函数中,例如:

(function(window){      // and more arguments if you need it

  console.log(window);  // here, should be the real 'window'

})(window.unsafeWindow)