web workers inline instead of fetching another page - javascript

I would like to create web workers in line instead of referencing an external script (so that I can deploy a single HTML page instead of an HTML file and a JS file). I found a cool method online using Blobs here, but for some reason I cannot get it to work. I noticed mixed results in the comments section of that article too.
I am getting an error: Failed to load resource: the server responded with a status of 404 (Not Found) which errors on line: localhost:63342/[object%20Worker]:1
I'm guessing that the web worker isn't really the issue, it's in creating the temporary url resource? If so what am I missing still?
Here's my code, in the script tag in the HTML file:
function createWorker(fn) {
var blob = new Blob(['self.onmessage = ', fn.toString()], { type: 'text/javascript' });
var url = window.URL.createObjectURL(blob);
return new Worker(url);
}
var generic = function(e) {
self.postMessage('in line web worker code');
};
var worker = createWorker(generic);
if (window.Worker) {
var getEquipmentW = new Worker(worker);
getEquipmentW.postMessage({
msg: 'hi'
});
getEquipmentW.onmessage = function (e) {
console.log(e.data);
};
}

I'll answer my own question, you can include a Web Worker in a separate script tag. I believe the script type isn't an official one, so the code in this tag is not evaluated until later. It'll look something like this:
<script id="myWorkerCode" type="javascript/worker">
self.onmessage = function(e) {
const data = e.data
self.postMessage('received some data in worker thread');
};
</script>
then, in the script where you need the worker, create a Blob and assign the content to be the "javascript" type. make that Blob be a URL that you can feed into the Worker constructor:
if(window.Worker)
{
//# select the ID of the SCRIPT that contains your worker code
const blob = new Blob([
document.getElementById('myWorkerCode').textContent
], {type: "text/javascript"});
//# Note: window.webkitURL.createObjectURL() in Chrome 10+.
const worker = new Worker(window.URL.createObjectURL(blob));
worker.onMessage = (e) =>
{
console.log('received data in main thread : ')
console.log( e.data );
};
}
It's more of a "in-a-line-above" than "inline", but it allows web workers to be composed and used in the same file. The trick is using script tags and converting unevaluated javascript by assigning the script tags to the given types in the code above. Still not the most elegant solution, but very handy when pushing processes to another thread.

You function createWorker already returns a worker, so you can replace:
var worker = createWorker(generic);
var getEquipmentW = new Worker(worker);
With this:
var getEquipmentW = createWorker(generic);

Related

Could worker.postmessage and worker.onmessage be called from inside different java script files, to use one worker.js?

I am new in Java script, and have recently learned about Web Workers, which are basically the solution to multi-threading in Java Script. In all the examples I found, they used both posting a message and receiving the response message from a web worker file in the same js file. My question is, could I start the execution of a web worker inside one java script file and then receive the result of it on another location in a separate java script file like the example below:
//start.js
function startWebWorker()
{
var message = "execute";
var myWorker = new Worker("worker.js");
myWorker.postMessage(message);
}
//worker.js
this.onmessage = function(e)
{
if (e.data == "execute")
var result ;
result = doSomething();
this.postMessage(result);
}
//receive.js
function processResult(){
var myWorker = new Worker("worker.js");
myWorker.onmessage = function(e)
document.setElementById("myresult") = e.result;
}
You could assign the worker to a global variable and access it from any script
window.myWorker = new Worker('worker.js')
Which you would access in some other script with
window.myWorker.postMessage('hi there')
Or, even better, you could define the worker in a module
export const worker = new Worker('worker.js')
and in modules, import it
<script type="module">
import { worker } from './worker-module.js'
worker.postMessage('hi there')
</script>

Unable to access ActiveXObjects inside an HTA worker

I have an IE 11 HTA that extracts data from various Excel spreadsheets which the user chooses at run time. There are several categories of spreadsheets, and several examples of each category (daily, weekly, monthly reports, etc) so the user has to choose one spreadsheet from each category at run-time. This is working well, but it's slower than I would like as it's using a blocking, single-threaded logic, so when the HTA is opening a spreadsheet from a particular category and extracting the data the HTA is on hold.
I've been trying to switch to a multi-threaded model using web workers to do the job of opening the spreadsheets so that the HTA is not blocked, but I've been unable to do so as the worker files seem to be unable to access ActiveXObjects.
As an example, here is a trimmed-down main file:
function main () {
var output = document.getElementById ('output');
var myWorker = new Worker ('worker.js');
sendOut ('Calling worker.');
myWorker.postMessage ('');
myWorker.onmessage = function (m) {
sendOut (m.data);
}
}
function sendOut (m) {
output.innerHTML += '<br /> ' + (m);
}
window.callWorker = main;
While the worker.js file looks like this:
var fso = new ActiveXObject ('Scripting.FileSystemObject')
onmessage = function (m) {
postMessage ('Worker responding.');
}
If I comment out the first line of the worker.js file, the HTA runs as expected and the messages 'Calling worker.' and 'Worker responding.' appears in the 'output' element. However, if it's left uncommented, I get the error message 'Automation server can't create object'.
From this, it looks like worker files can't access ActiveXObjects, but I cannot find anything to confirm or explain this.
I've also tried creating ActiveXObjects in the main file and passing them as arguments like this:
function main () {
var fso = new ActiveXObject ('Scripting.FileSystemObject')
var output = document.getElementById ('output');
var myWorker = new Worker ('worker1.js');
sendOut ('Calling worker.');
myWorker.postMessage (fso);
myWorker.onmessage = function (m) {
sendOut (m.data);
}
}
function sendOut (m) {
output.innerHTML += '<br /> ' + (m);
}
window.callWorker = main;
But now I just get the error "DataCloneError". I've not been able to find any reference materials on this, but it looks like what I'm trying to do simple isn't possible. Is anyone able to confirm this, or do you know of a way around it?

Firefox Add-on: get document from a tab

I was just trying to get the document of a tab and read information from it, but if I try to read the information on the Add-on-side I get an error "doc.getElementById is not a function". In the content-script it works fine. So is there a problem with passing whole objects through self.port?
var tabs = require('sdk/tabs');
var myTab;
var myScript = "self.port.on('getDocument', function() {" +
" var doc = window.document;" +
" console.log(doc.getElementById('lga').style.height);" +
" self.port.emit('answer', doc);" +
"})";
for each (var tab in tabs) {
if (tab.url == "https://www.google.com/") {
myTab = tab;
}
}
worker = myTab.attach({
contentScript: myScript
});
worker.port.emit("getDocument");
worker.port.on("answer", function(doc) {
console.log(doc.getElementById('lga').style.height);
});
You can only pass values via a message that could be serialized to JSON. doc, being a document, cannot be passed.
In your message, you could pass the actual value of the style instead:
self.port.emit('answer', doc.getElementById('lga').style.height);
Rather than try to import the document into main.js, create a new Javascript file in the data folder, ContentScript.js. Inject it with contentScriptFile into the page like so:
worker = myTab.attach({
contentScriptFile: require('sdk/self').data.url('ContentScript.js')
});
Meanwhile, in ContentScript.js
var doc = window.document;
//Now have your way with the document
Then if you ever need any variables in main.js, do what #nmaier said.
I realize that this may be obvious, but this is the intended behaviour, and it means you don't have to write a script as a string and provides more detailed logging.

Change html page content

I am creating Firefox addon using the Add-on SDK. I want to get data from remote url and inject it in current html. As of now i m able to fetch data using request module of Firefox addon sdk but m not able to inject it in current page.
for example : i am fetching response from website "abc.com".after fetching response i will augment current page with response
// main.js
var widgets = require("sdk/widget");
var tabs = require("sdk/tabs");
var Request = require("sdk/request").Request;
//create addon widget
var widget = widgets.Widget({
id: "div-show",
label: "Show divs",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
//initializing request module to fetch content
quijote.get();
}
});
//fetch content of requested page
var quijote = Request({
url: "http://localhost/abc/",
overrideMimeType: "text/plain; charset=latin1",
onComplete: function (response) {
//check if content is fetched successfully
addContent(response);
}
});
//try and modify current page
function addContent(response){
//initialize page modification module
var pageMod = require("sdk/page-mod");
tabs.activeTab.attach({
contentScript: 'document.body.innerHTML = ' + ' "<h1>'+response.text+'</h1>";'
});
}
Is their any way in which i can augment my current page???
Your code will bitterly fail e.g. when response.text includes a double quote.
Then your code would be (assume it is world):
document.body.innerHTML = "<h1>world</h1>";
This is obviously invalid code.
Your code basically constructs a dynamic script from unsanitized data, which is a bad idea because (other than the escaping problem above)
you'll be running an unsanitized content script if that code is even valid and
if that would succeed, the page might run unsanitized code as well.
This is the web equivalent to SQL injection attacks....
First, lets tackle 1.) with messaging (more):
var worker = tabs.activeTab.attach({
contentScript: 'self.port.on("setdom", function(data) { ' +
+ 'document.body.innerHTML = data; /* still a security issue! */'
+ '});'
});
worker.port.emit("setdom", response.text);
This guarantees that the content script will be valid (can even run) and does not run arbitrary code.
However 2.) is still a problem. Read DOM Building and HTML insertion.

'Uncaught Error: DATA_CLONE_ERR: DOM Exception 25' thrown by web worker

So I'm creating a web worker:
var arrayit = function(obj) {
return Array.prototype.slice.call(obj);
};
work = arrayit(images);
console.log(work);
//work = images.push.apply( images, array );
// Method : "load+scroll"
var worker = new Worker('jail_worker.js');
worker.postMessage(work)
worker.onmessage = function(event) {
console.log("Worker said:" + event.data);
};
Here's what images is:
$.jail.initialStack = this;
// Store the selector into 'triggerEl' data for the images selected
this.data('triggerEl', (options.selector) ? $(options.selector) : $window);
var images = this;
I think my problem has something to do with this:
http://dev.w3.org/html5/spec/Overview.html#safe-passing-of-structured-data
How can I get around this? as you can see, I tried slicing the host object into a real array, but that didn't work.
Here's a link to the file I'm hacking on:
https://github.com/jtmkrueger/JAIL
UPDATE--------------------------------------------------
This is what I had to do based on the accepted answer from #davin:
var arrayit = function(obj) {
return Array.prototype.slice.call(obj);
};
imgArray = arrayit(images);
work = _.map(images, function(i){ return i.attributes[0].ownerElement.outerHTML; });
var worker = new Worker('jail_worker.js');
worker.postMessage(work)
worker.onmessage = function(event) {
console.log("Worker said:" + event.data);
};
NOTE: I used underscore.js to assure compatibility.
The original exception was most likely thrown because you tried passing a host object to the web worker (most likely a dom element). Your subsequent attempts don't throw the same error. Remember two key points: there isn't shared memory between the different threads, and the web workers can't manipulate the DOM.
postMessage supports passing structured data to threads, and will internally serialise (or in some other way copy the value of the data recursively) the data. Serialising DOM elements often results in circular reference errors, so your best bet is to map the object you want serialised and extract relevant data to be rebuilt in the web worker.
Uncaught DataCloneError: An object could not be cloned was reproduced when tried save to indexeddb function as object's key. Need double recheck that saved object is serializable

Categories

Resources