是的你可以!(就是这样):
我不知道是不是因为在提出这个问题后的四年里发生了一些变化,但完全有可能完全按照问题的要求去做。它甚至不是特别困难。诀窍是从直接包含其代码的数据 url初始化共享工作器,而不是从createObjectURL(blob)
.
这可能最容易通过示例来演示,因此这里是stackoverflow.com的一个小用户脚本,它使用共享工作器为每个 stackoverflow 窗口分配一个唯一的 ID 号,显示在选项卡标题中。请注意,共享工作者代码直接作为模板字符串包含在内(即在反引号之间):
// ==UserScript==
// @name stackoverflow userscript shared worker example
// @namespace stackoverflow test code
// @version 1.0
// @description Demonstrate the use of shared workers created in userscript
// @icon https://stackoverflow.com/favicon.ico
// @include http*://stackoverflow.com/*
// @run-at document-start
// ==/UserScript==
(function() {
"use strict";
var port = (new SharedWorker('data:text/javascript;base64,' + btoa(
// =======================================================================================================================
// ================================================= shared worker code: =================================================
// =======================================================================================================================
// This very simple shared worker merely provides each window with a unique ID number, to be displayed in the title
`
var lastID = 0;
onconnect = function(e)
{
var port = e.source;
port.onmessage = handleMessage;
port.postMessage(["setID",++lastID]);
}
function handleMessage(e) { console.log("Message Recieved by shared worker: ",e.data); }
`
// =======================================================================================================================
// =======================================================================================================================
))).port;
port.onmessage = function(e)
{
var data = e.data, msg = data[0];
switch (msg)
{
case "setID": document.title = "#"+data[1]+": "+document.title; break;
}
}
})();
我可以确认这适用于 FireFox v79 + Tampermonkey v4.11.6117。
有一些小的警告:
首先,可能是您的用户脚本所针对的页面带有一个Content-Security-Policy
标头,该标头明确限制了脚本或工作脚本(script-src 或 worker-src 策略)的来源。在这种情况下,带有脚本内容的数据 url 可能会被阻止,并且 OTOH 我想不出解决方法,除非添加一些未来的 GM_ 函数以允许用户脚本覆盖页面的 CSP 或更改其 HTTP标头,或者除非用户使用扩展程序或浏览器设置运行他们的浏览器以禁用 CSP(参见例如禁用 Chrome 中的同源策略)。
其次,用户脚本可以定义为在多个域上运行,例如您可以在https://amazon.com和https://amazon.co.uk上运行相同的用户脚本。但即使由这个单一用户脚本创建时,共享工作程序也遵循同源策略,因此应该有一个为所有.com窗口和所有.co.uk窗口创建的共享工作程序的不同实例。请注意这一点!
最后,一些浏览器可能会对 data-url 的长度施加大小限制,从而限制共享 worker 的最大代码长度。即使不受限制,将冗长、复杂的共享 worker 的所有代码转换为 base64 并在每个窗口加载时都非常低效。通过极长的 URL 对共享工作器进行索引也是如此(因为您根据匹配的确切 URL 连接到现有的共享工作器)。所以你可以做的是 (a) 从一个最初非常小的共享工作者开始,然后使用它eval()
来添加真正的(可能更长的)代码,以响应传递给第一个打开的窗口的“InitWorkerRequired”消息之类的东西工人,以及 (b) 为了提高效率,预先计算包含初始最小共享工人引导代码的 base-64 字符串。
下面是在(也经过测试,确认工作)将这两皱纹上述例子的修改版本,即上运行两个 stackoverflow.com和en.wikipedia.org(只是让你可以验证不同的域确实使用独立的共享工作者实例):
// ==UserScript==
// @name stackoverflow & wikipedia userscript shared worker example
// @namespace stackoverflow test code
// @version 2.0
// @description Demonstrate the use of shared workers created in userscript, with code injection after creation
// @icon https://stackoverflow.com/favicon.ico
// @include http*://stackoverflow.com/*
// @include http*://en.wikipedia.org/*
// @run-at document-end
// ==/UserScript==
(function() {
"use strict";
// Minimal bootstrap code used to first create a shared worker (commented out because we actually use a pre-encoded base64 string created from a minified version of this code):
/*
// ==================================================================================================================================
{
let x = [];
onconnect = function(e)
{
var p = e.source;
x.push(e);
p.postMessage(["InitWorkerRequired"]);
p.onmessage = function(e) // Expects only 1 kind of message: the init code. So we don't actually check for any other sort of message, and page script therefore mustn't send any other sort of message until init has been confirmed.
{
(0,eval)(e.data[1]); // (0,eval) is an indirect call to eval(), which therefore executes in global scope (rather than the scope of this function). See http://perfectionkills.com/global-eval-what-are-the-options/ or https://stackoverflow.com/questions/19357978/indirect-eval-call-in-strict-mode
while(e = x.shift()) onconnect(e); // This calls the NEW onconnect function, that the eval() above just (re-)defined. Note that unless windows are opened in very quick succession, x should only have one entry.
}
}
}
// ==================================================================================================================================
*/
// Actual code that we want the shared worker to execute. Can be as long as we like!
// Note that it must replace the onconnect handler defined by the minimal bootstrap worker code.
var workerCode =
// ==================================================================================================================================
`
"use strict"; // NOTE: because this code is evaluated by eval(), the presence of "use strict"; here will cause it to be evaluated in it's own scope just below the global scope, instead of in the global scope directly. Practically this shouldn't matter, though: it's rather like enclosing the whole code in (function(){...})();
var lastID = 0;
onconnect = function(e) // MUST set onconnect here; bootstrap method relies on this!
{
var port = e.source;
port.onmessage = handleMessage;
port.postMessage(["WorkerConnected",++lastID]); // As well as providing a page with it's ID, the "WorkerConnected" message indicates to a page that the worker has been initialized, so it may be posted messages other than "InitializeWorkerCode"
}
function handleMessage(e)
{
var data = e.data;
if (data[0]==="InitializeWorkerCode") return; // If two (or more) windows are opened very quickly, "InitWorkerRequired" may get posted to BOTH, and the second response will then arrive at an already-initialized worker, so must check for and ignore it here.
// ...
console.log("Message Received by shared worker: ",e.data); // For this simple example worker, there's actually nothing to do here
}
`;
// ==================================================================================================================================
// Use a base64 string encoding minified version of the minimal bootstrap code in the comments above, i.e.
// btoa('{let x=[];onconnect=function(e){var p=e.source;x.push(e);p.postMessage(["InitWorkerRequired"]);p.onmessage=function(e){(0,eval)(e.data[1]);while(e=x.shift()) onconnect(e);}}}');
// NOTE: If there's any chance the page might be using more than one shared worker based on this "bootstrap" method, insert a comment with some identification or name for the worker into the minified, base64 code, so that different shared workers get unique data-URLs (and hence don't incorrectly share worker instances).
var port = (new SharedWorker('data:text/javascript;base64,e2xldCB4PVtdO29uY29ubmVjdD1mdW5jdGlvbihlKXt2YXIgcD1lLnNvdXJjZTt4LnB1c2goZSk7cC5wb3N0TWVzc2FnZShbIkluaXRXb3JrZXJSZXF1aXJlZCJdKTtwLm9ubWVzc2FnZT1mdW5jdGlvbihlKXsoMCxldmFsKShlLmRhdGFbMV0pO3doaWxlKGU9eC5zaGlmdCgpKSBvbmNvbm5lY3QoZSk7fX19')).port;
port.onmessage = function(e)
{
var data = e.data, msg = data[0];
switch (msg)
{
case "WorkerConnected": document.title = "#"+data[1]+": "+document.title; break;
case "InitWorkerRequired": port.postMessage(["InitializeWorkerCode",workerCode]); break;
}
}
})();