需要 jQuery 到 Tampermonkey 脚本和控制台中的安全变量

require jQuery to a safe variable in Tampermonkey script and console

通常我使用下面的代码片段,这个通常有效:

(function () {
    function main() {
        //...script body...
    }

    var OGloboCSS = { // var could be named anything, appropriate to the page
        addJQuery: function (callback) {
            var script = document.createElement("script");
            script.setAttribute("src", "https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js");
            script.addEventListener('load', function () {
                var script = document.createElement("script");
                script.textContent = "window.jQ=jQuery.noConflict(true);(" + callback.toString() + ")();";
                document.body.appendChild(script);
            }, false);
            document.body.appendChild(script);
        }
    };

    OGloboCSS.addJQuery(main);

})();

但偶尔我会在 https://steamcommunity.com:

上遇到这样的错误

Refused to load the script 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js' because it violates the following Content Security Policy directive: "script-src 'unsafe-eval' 'self' 'unsafe-inline' 'unsafe-eval' https://steamcommunity-a.akamaihd.net/ https://api.steampowered.com/ http://www.google-analytics.com https://ssl.google-analytics.com https://www.google.com https://www.gstatic.com https://apis.google.com".

解决方案基本上是,您必须改用 TamperMonkey 的 @require header 指令,如下所示:

// @require      https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js
// ==/UserScript==

window.jQ = $;

(function () {
/*this used to be: function main() */
    //...same script body, without container function...

})();

这工作得很好,但它不能安全地将 jQuery 加载到 jQ 变量,它会破坏 $。真的有问题吗?是的,您可以在 steamcommunity.com 的讨论线程中看到分页是如何中断的。

有什么方法可以 @require jQ= 然后 @grant jQ - 仍然在 Tampermonkey 脚本和控制台中给我 jQ 没有破坏 in-page变量?

TamperMonkey 似乎没有这样的功能,但另一种方法是立即调用 $.noConflict with the removeAll 参数设置为 true。这将导致 jQuery 将原始 $jQuery 重置为原始值。

示例用户脚本:

// ==UserScript==
// @name         jQuery noConflict test
// @namespace    http://example.com/
// @version      0.1
// @description  test jQuery noConflict
// @author       You
// @match        https://steamcommunity.com/
// @require      https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js
// @grant        none
// ==/UserScript==

window.jQ = $.noConflict(true);

从下面的示例控制台输入可以看出,jQ@require加载的jQuery2.1.3,jQueryjQuery的版本jQuery 页面加载,并且 $ 仍然是原始原型库对象,没有 jQuery 的 fn 属性.[=23= 就证明了这一点]

示例控制台 Input/Output:

> jQ.fn.jquery
< "2.1.3"
> jQuery.fn.jquery
< "1.11.1"
> $.fn
< undefined

除了避免版本冲突和破坏内容页面外,我还在我的默认用户脚本模板中做了一些其他事情,其中​​包括 jQuery。

下面的模板努力实现以下目标,以成为将 jQuery 包含在新脚本中的通用模板,而不管内容页面当前或未来的状态是否包含或缺少其中,任何版本的jQuery。

  • 使用@grant 避免安全沙箱none
  • 一般来说,脚本在哪里执行是未知的inserted/when
  • 为我们的 UserScript
  • 包含特定版本的 jQuery
  • 保留内容页面的 $ 和 jQuery 引用,无论它们是什么,jQuery 或不是。
  • 允许我们的脚本为其 jQuery 版本使用 $ 别名

@grant none 的使用是这里的关键

//@grant          none

@grant none 适用于我 99% 的脚本。它通过维护 UserScript(this) 的全局范围并通过在两个范围内搜索变量来提供对内容页面的全局范围 (window) 的无限制访问,从而允许轻松访问内容页面的变量、函数和对象,函数标识符在此,window顺序。因此,如果您避免重复内容页面中存在的标识符,则可以访问内容页面变量、函数和函数 return 值而无需限定("this." 或 "window.")。您可以在脚本的任何位置执行此操作。

这突出了 OP 的原始问题。 @grant none 提供的便利也是@require 指令在加载时覆盖window 的$ 和jQuery 引用的原因。 jQuery 库包含 noConflict() 函数已有很长一段时间来处理这个问题。

我们可以立即对上述答案进行一些快速改进,以增加我们的 safety/compatibility/reuse。

window.jQ = $.noConflict(true);
  • 这个调用没有明确地将 $ 限定在一个范围内
  • 如果内容页面还包含一些别名为 $ 的其他库,则效果不会特别好。特别是如果内容页面在我们到达这里之前已经调用了 jQuery noConflict 本身。这是一个不必要的兔子洞。
  • 此代码将对我们库的引用放在 window 的范围内,这有点令人担忧,稍后内容页面很可能会以某种方式对其进行更改。 出于显而易见的原因,将任何内容暴露给 window 范围通常是不好的做法。

改为此代码

this.$ = window.jQuery.noConflict(true);
  • 将我们的库保留在它所属的范围内
  • 在我们的范围内为我们提供 $ 别名以继续在我们的脚本中使用
  • 将 window 的 $ 和 jQuery 别名恢复为 jQuery 加载时的别名,无论它们实际上是 jQuery 还是不是
  • 为我们提供了使用脚本加载的 jQuery 对象调用 noConflict
  • 的最佳机会

最后一点可能有点矫枉过正,但为什么不明确并使用 jQuery 标识符来进行 noConflict 调用。虽然在这个调用运行时如果 $ 不是 jQuery 这将是我们的错(@require 用于另一个使用 $ 的库,在这段代码运行之前自己使用它,等等),但它可以免费防止以后的调试努力。

虽然我们可以在那里完成并感到高兴,但我们还可以更进一步,以确保在最广泛的页面之间的兼容性,并通过最大范围的内容页面更改。

(function ($, undefined) {
  $(function () {
    //Your code here;
  });
})(window.jQuery.noConflict(true));

虽然仍然不能保证,但此模式通过将 $ 别名置于全局范围之外并利用 jQuery 的 DOM 确保页面完成的就绪事件。顺便说一句,它还确保您的代码具有一致的未定义值。

// ==UserScript==
// @name          jQuery safe inclusion template
// @description   include jQuery and make sure window.$ is the content page's jQuery version, and this.$ is our jQuery version.
// @version       0.0.1
// @author        Sonic Beard
// @match         http://*.site.com/*
// @require       http://cdn.jsdelivr.net/jquery/2.1.3/jquery.min.js
// @grant         none
// ==/UserScript==

(function ($, undefined) {
  $(function () {
    //Your code here;
  });
})(window.jQuery.noConflict(true));