How can I load a shared web worker with a user-script?

。_饼干妹妹 提交于 2019-11-26 06:40:58

问题


I want to load a shared worker with a user-script. The problem is the user-script is free, and has no business model for hosting a file - nor would I want to use a server, even a free one, to host one tiny file. Regardless, I tried it and I (of course) get a same origin policy error:

Uncaught SecurityError: Failed to construct \'SharedWorker\': Script at
\'https://cdn.rawgit.com/viziionary/Nacho-Bot/master/webworker.js\'
cannot be accessed from origin \'http://stackoverflow.com\'.

There\'s another way to load a web worker by converting the worker function to a string and then into a Blob and loading that as the worker but I tried that too:

var sharedWorkers = {};
var startSharedWorker = function(workerFunc){
    var funcString = workerFunc.toString();
    var index = funcString.indexOf(\'{\');
    var funcStringClean = funcString.substring(index + 1, funcString.length - 1);
    var blob = new Blob([funcStringClean], { type: \"text/javascript\" });
    sharedWorkers.google = new SharedWorker(window.URL.createObjectURL(blob));
    sharedWorkers.google.port.start();
};

And that doesn\'t work either. Why? Because shared workers are shared based on the location their worker file is loaded from. Since createObjectURL generates a unique file name for each use, the workers will never have the same URL and will therefore never be shared.

How can I solve this problem?


Note: I tried asking about specific solutions, but at this point I think the best I can do is ask in a more broad manner for any solution to the problem, since all of my attempted solutions seem fundamentally impossible due to same origin policies or the way URL.createObjectURL works (from the specs, it seems impossible to alter the resulting file URL).

That being said, if my question can somehow be improved or clarified, please leave a comment.


回答1:


You can use fetch(), response.blob() to create an Blob URL of type application/javascript from returned Blob; set SharedWorker() parameter to Blob URL created by URL.createObjectURL(); utilize window.open(), load event of newly opened window to define same SharedWorker previously defined at original window, attach message event to original SharedWorker at newly opened windows.

javascript was tried at console at How to clear the contents of an iFrame from another iFrame, where current Question URL should be loaded at new tab with message from opening window through worker.port.postMessage() event handler logged at console.

Opening window should also log message event when posted from newly opened window using worker.postMessage(/* message */), similarly at opening window

window.worker = void 0, window.so = void 0;

fetch("https://cdn.rawgit.com/viziionary/Nacho-Bot/master/webworker.js")
  .then(response => response.blob())
  .then(script => {
    console.log(script);
    var url = URL.createObjectURL(script);
    window.worker = new SharedWorker(url);
    console.log(worker);
    worker.port.addEventListener("message", (e) => console.log(e.data));
    worker.port.start();

    window.so = window.open("https://stackoverflow.com/questions/" 
                            + "38810002/" 
                            + "how-can-i-load-a-shared-web-worker-" 
                            + "with-a-user-script", "_blank");

    so.addEventListener("load", () => {
      so.worker = worker;
      so.console.log(so.worker);
      so.worker.port.addEventListener("message", (e) => so.console.log(e.data));
      so.worker.port.start();
      so.worker.port.postMessage("hi from " + so.location.href);
    });

    so.addEventListener("load", () => {
      worker.port.postMessage("hello from " + location.href)
    })

  });

At console at either tab you can then use, e.g.; at How to clear the contents of an iFrame from another iFrame worker.postMessage("hello, again") at new window of current URL How can I load a shared web worker with a user-script?, worker.port.postMessage("hi, again"); where message events attached at each window, communication between the two windows can be achieved using original SharedWorker created at initial URL.




回答2:


Precondition

  • As you've researched and as it has been mentioned in comments, SharedWorker's URL is subject to the Same Origin Policy.
  • According to this question there's no CORS support for Worker's URL.
  • According to this issue GM_worker support is now a WONT_FIX, and seems close enough to impossible to implement due to changes in Firefox. There's also a note that sandboxed Worker (as opposed to unsafeWindow.Worker) doesn't work either.

Design

What I suppose you want to achieve is a @include * userscript that will collect some statistics or create some global UI what will appear everywhere. And thus you want to have a worker to maintain some state or statistic aggregates in runtime (which will be easy to access from every instance of user-script), and/or you want to do some computation-heavy routine (because otherwise it will slow target sites down).

In the way of any solution

The solution I want to propose is to replace SharedWorker design with an alternative.

  • If you want just to maintain a state in the shared worker, just use Greasemonkey storage (GM_setValue and friends). It's shared among all userscript instances (SQLite behide the scenes).
  • If you want to do something computation-heavy task, to it in unsafeWindow.Worker and put result back in Greasemonkey storage.
  • If you want to do some background computation and it must be run only by single instance, there are number of "inter-window" synchronisation libraries (mostly they use localStorage but Greasemomkey's has the same API, so it shouldn't be hard to write an adapter to it). Thus you can acquire a lock in one userscript instance and run your routines in it. Like, IWC or ByTheWay (likely used here on Stack Exchange; post about it).

Other way

I'm not sure but there may be some ingenious response spoofing, made from ServiceWorker to make SharedWorker work as you would like to. Starting point is in this answer's edit.




回答3:


I am pretty sure you want a different answer, but sadly this is what it boils down to.

Browsers implement same-origin-policies to protect internet users, and although your intentions are clean, no legit browser allows you to change the origin of a sharedWorker.

All browsing contexts in a sharedWorker must share the exact same origin

  • host
  • protocol
  • port

You cannot hack around this issue, I've trying using iframes in addition to your methods, but non will work.

Maybe you can put it your javascript file on github and use their raw. service to get the file, this way you can have it running without much efforts.

Update

I was reading chrome updates and I remembered you asking about this. Cross-origin service workers arrived on chrome!

To do this, add the following to the install event for the SW:

self.addEventListener('install', event => {
  event.registerForeignFetch({
    scopes: [self.registration.scope], // or some sub-scope
    origins: ['*'] // or ['https://example.com']
  });
});

Some other considerations are needed aswell, check it out:

Full link: https://developers.google.com/web/updates/2016/09/foreign-fetch?hl=en?utm_campaign=devshow_series_crossoriginserviceworkers_092316&utm_source=gdev&utm_medium=yt-desc



来源:https://stackoverflow.com/questions/38810002/how-can-i-load-a-shared-web-worker-with-a-user-script

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!