Using importsScripts within Blob in a karma environment - javascript

I am working on a small project of mine using karma, and jasmine. My targeted browser is chrome 32.
I am trying to import scripts within a web worker whom I have instanciated through a blob as follows :
describeAsyncAppliPersephone("When the application project to DOM", function()
{
it("it should call the function of DomProjection in the project associated with its event", function()
{
var eventSentBack = {
eventType: 'testReceived',
headers: { id: 14, version: 4 },
payLoad: { textChanged: 'newText' }
};
var isRendered = false;
var fnProjection = function(event, payload)
{
isRendered = true;
}
var bootstrap = [
{ eventType: 'testReceived', projection: fnProjection },
{ eventType: 'test2Received', projection: function() { } }
];
runs(function()
{
var factory = new WorkerFactory();
var worker = factory.CreateByScripts('importScripts("/base/SiteWeb/project/js/app/application.js"); var app = new application(self); app.projectOnDOM(' + JSON.stringify(eventSentBack) + '); ');
console.log(worker.WorkerLocation);
var applicationQueue = new queueAsync(worker);
var projectQueue = new queueSync(worker);
var p = new project(applicationQueue, persephoneQueue, bootstrap);
applicationQueue.publish(eventSentBack);
});
waitsFor(function() { return isRendered }, "Projection called", 500);
runs(function()
{
expect(isRendered).toBe(true);
});
});
});
workerFactory is as follows :
this.CreateByScripts = function(scripts, fDefListener, fOnError)
{
var arrayScripts = scripts;
if (!arrayScripts)
throw "unable to load worker for undefined scripts";
if (Object.prototype.toString.call(arrayScripts) !== '[object Array]')
arrayScripts = [arrayScripts];
var blob = new Blob(arrayScripts, { type: "text/javascript" });
var w = createWorker(window.URL.createObjectURL(blob));
return new QueryableWorker(w, fDefListener, fOnError);
}
where createWorker is :
createWorker = function(sUrl)
{
return new Worker(sUrl);
}
But the importScripts throws me the following error :
Uncaught SyntaxError: Failed to execute 'importScripts': the URL
'/base/SiteWeb/project/js/app/application.js' is invalid.
I have tried with the path within the browser :
http://mylocalhost:9876/base/SiteWeb/project/js/app/application.js
and it does work well.
What is the path I should use to make importScripts working successfully ?
Thanks,

You can't use relative path in worker created with Blob.
Had this problem today. Solution is explained in "The Basics of Web Workers", but a little hidden in length of the article:
The reason being: the worker (now created from a blob URL) will be resolved with a blob: prefix, while your app will be running from a different (presumably http://) scheme. Hence, the failure will be due to cross origin restrictions.
If you are determined to avoid hardcoding domain name in your workers the article has also a solution for this. Import your scripts on receiving a message with URL as one of its parameters:
self.onmessage = function(e) {
importScripts(e.data.url + 'yourscript.js');
};
and start your worker with sending that url
worker.postMessage({url: document.location.protocol + '//' + document.location.host});
The code above is simplified for clarity.

Related

Browser displays "Upgrade Required" when running this program

So I searched for a week now, tried every solution in other Posts or Forums, still nothing so yeah I'm in need of help .. please.
Node is up to date, if thats important. FYI. v9.4.0
If there's something else you guys need to know let me know.
'use strict'
var EventEmitter = require('events').EventEmitter
var util = require('util')
var WebSocketServer = require('ws').Server
var CONNECTION_ERROR_LOG_RATE = 1000 * 60 * 60
var Browser = function () {
if (!(this instanceof Browser)) return new Browser()
EventEmitter.call(this)
this.wss = null
this.ws = null
this.lastConnectionErrorLog = null
}
util.inherits(Browser, EventEmitter)
Browser.prototype.listen = function listen (port) {
console.log('Listening on websocket port %d', port)
this.wss = new WebSocketServer({port, host: '127.0.0.1'})
var self = this
this.wss.on('connection', function (ws) {
self.ws = ws
ws.on('message', function (data) {
var res = JSON.parse(data)
self.emit('message', res)
})
self.lastConnectionErrorLog = null
self.emit('connected')
})
this.wss.on('close', function () {
self.emit('closed')
})
this.wss.on('error', function (err) {
self.emit('error', err)
})
}
Browser.prototype.isConnected = function isConnected () {
return !!this.ws
}
Browser.prototype.send = function send (req) {
if (!this.ws) {
var elapsed = this.lastConnectionErrorLog === null ||
Date.now() - this.lastConnectionErrorLog > CONNECTION_ERROR_LOG_RATE
if (elapsed) {
console.log('browser not connected')
this.lastConnectionErrorLog = Date.now()
}
return
}
var self = this
var message = JSON.stringify(req)
this.ws.send(message, function (err) {
if (err) {
var elapsed = self.lastConnectionErrorLog === null ||
Date.now() - self.lastConnectionErrorLog > CONNECTION_ERROR_LOG_RATE
if (elapsed) {
self.lastConnectionErrorLog = Date.now()
self.emit('messageError', err)
}
} else {
self.emit('messageSent')
}
})
}
module.exports = Browser
I am kind of new to Websockets/Node/Javascript so it may be that the answer is very simple..
I apologize in advance
Thank you kindly
The error you're seeing is because you are sending a non-WebSocket request (i.e, a normal HTTP request from a web browser) to a WebSockets server.
To connect to a WebSockets server in a browser, you'll need to use the WebSocket interface in Javascript.
The code you posted doesn't include the string "Upgrade required" so the problem must be coming from elsewhere. Since you're working with Node/NPM, it's usually pretty easy to figure out where this problem is coming from. Just use your IDE to search across all files in your project director (including the node_modules folder) to see where the "Upgrade required" string is found. This will at least point you to which component is triggering that error and point you towards where to look for further troubleshooting and/or upgrading.

Worker blocking UI thread in Chrome

I'm building a web app that uses EvaporateJS to upload large files to Amazon S3 using Multipart Uploads. I noticed an issue where every time a new chunk was started the browser would freeze for ~2 seconds. I want the user to be able to continue to use my app while the upload is in progress, and this freezing makes that a bad experience.
I used Chrome's Timeline to look into what was causing this and found that it was SparkMD5's hashing. So I've moved the entire upload process into a Worker, which I thought would fix the issue.
Well the issue is now fixed in Edge and Firefox, but Chrome still has the exact same problem.
Here's a screenshot of my Timeline:
As you can see, during the freezes my main thread is doing basically nothing, with <8ms of JavaScript running during that time. All the work is occurring in my Worker thread, and even that is only running for ~600ms or so, not the 1386ms that my frame takes.
I'm really not sure what's causing the issue, are there any gotchas with Workers that I should be aware of?
Here's the code for my Worker:
var window = self; // For Worker-unaware scripts
// Shim to make Evaporate work in a Worker
var document = {
createElement: function() {
var href = undefined;
var elm = {
set href(url) {
var obj = new URL(url);
elm.protocol = obj.protocol;
elm.hostname = obj.hostname;
elm.pathname = obj.pathname;
elm.port = obj.port;
elm.search = obj.search;
elm.hash = obj.hash;
elm.host = obj.host;
href = url;
},
get href() {
return href;
},
protocol: undefined,
hostname: undefined,
pathname: undefined,
port: undefined,
search: undefined,
hash: undefined,
host: undefined
};
return elm;
}
};
importScripts("/lib/sha256/sha256.min.js");
importScripts("/lib/spark-md5/spark-md5.min.js");
importScripts("/lib/url-parse/url-parse.js");
importScripts("/lib/xmldom/xmldom.js");
importScripts("/lib/evaporate/evaporate.js");
DOMParser = self.xmldom.DOMParser;
var defaultConfig = {
computeContentMd5: true,
cryptoMd5Method: function (data) { return btoa(SparkMD5.ArrayBuffer.hash(data, true)); },
cryptoHexEncodedHash256: sha256,
awsSignatureVersion: "4",
awsRegion: undefined,
aws_url: "https://s3-ap-southeast-2.amazonaws.com",
aws_key: undefined,
customAuthMethod: function(signParams, signHeaders, stringToSign, timestamp, awsRequest) {
return new Promise(function(resolve, reject) {
var signingRequestId = currentSigningRequestId++;
postMessage(["signingRequest", signingRequestId, signParams.videoId, timestamp, awsRequest.signer.canonicalRequest()]);
queuedSigningRequests[signingRequestId] = function(signature) {
queuedSigningRequests[signingRequestId] = undefined;
if(signature) {
resolve(signature);
} else {
reject();
}
}
});
},
//logging: false,
bucket: undefined,
allowS3ExistenceOptimization: false,
maxConcurrentParts: 5
}
var currentSigningRequestId = 0;
var queuedSigningRequests = [];
var e = undefined;
var filekey = undefined;
onmessage = function(e) {
var messageType = e.data[0];
switch(messageType) {
case "init":
var globalConfig = {};
for(var k in defaultConfig) {
globalConfig[k] = defaultConfig[k];
}
for(var k in e.data[1]) {
globalConfig[k] = e.data[1][k];
}
var uploadConfig = e.data[2];
Evaporate.create(globalConfig).then(function(evaporate) {
var e = evaporate;
filekey = globalConfig.bucket + "/" + uploadConfig.name;
uploadConfig.progress = function(p, stats) {
postMessage(["progress", p, stats]);
};
uploadConfig.complete = function(xhr, awsObjectKey, stats) {
postMessage(["complete", xhr, awsObjectKey, stats]);
}
uploadConfig.info = function(msg) {
postMessage(["info", msg]);
}
uploadConfig.warn = function(msg) {
postMessage(["warn", msg]);
}
uploadConfig.error = function(msg) {
postMessage(["error", msg]);
}
e.add(uploadConfig);
});
break;
case "pause":
e.pause(filekey);
break;
case "resume":
e.resume(filekey);
break;
case "cancel":
e.cancel(filekey);
break;
case "signature":
var signingRequestId = e.data[1];
var signature = e.data[2];
queuedSigningRequests[signingRequestId](signature);
break;
}
}
Note that it relies on the calling thread to provide it with the AWS Public Key, AWS Bucket Name and AWS Region, AWS Object Key and the input File object, which are all provided in the 'init' message. When it needs something signed, it sends a 'signingRequest' message to the parent thread, which is expected to provided the signature in a 'signature' message once it's been fetched from my API's signing endpoint.
I can't give a very good example or analyze what you are doing with only the Worker code, but I strongly suspect that the issue either has to do with either the reading of the chunk on the main thread or some unexpected processing that you are doing on the chunk on the main thread. Maybe post the main thread code that calls postMessage to the Worker?
If I were debugging it right now, I'd try moving your FileReader operations into the Worker. If you don't mind the Worker blocking while it loads a chunk, you could also use FileReaderSync.
Post-comments update
Does generating the presigned URL require hashing the file content + metadata + a key? Hashing file content is going to take O(n) in the size of the chunk and it's possible, if the hash is the first operation that reads from the Blob, that the loading of the file content could be deferred until the hashing starts. Unless you are compelled to keep the signing in the main thread (you don't trust the worker with key material?) that would be another good thing to bring into the worker.
If moving the signing into the Worker is too much, you could have the worker do something to force the Blob to be read and/or pass the ArrayBuffer(or Uint8Array or what have you) of file content back to the main thread for signing; this would ensure that reading the chunk does not occur on the main thread.

Communicate with the serial port from client web browser.

In my web application(sencha extjs 5) I have a user requirement to read/write data to the client PC serial port.
I am aware of the client browser can not access local machine hardware without installing some binaries on the local machine(Native app, Windows Service, etc..).
I have seen the same question is discussed few years back in stackoverflow forums. But I need to know what is the best way of doing this today with the available technologies?
Using Web Serial API. I am using this to ONLY read the data from my Weight Scale with RS232 Serial Interface
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Serial</title>
</head>
<body>
<div class="serial-scale-div">
<button class="btn" id="connect-to-serial">Connect with Serial Device</button>
</div>
<button id="get-serial-messages">Get serial messages</button>
<div id="serial-messages-container">
<div class="message"></div>
</div>
<script>
"use strict";
class SerialScaleController {
constructor() {
this.encoder = new TextEncoder();
this.decoder = new TextDecoder();
}
async init() {
if ('serial' in navigator) {
try {
const port = await navigator.serial.requestPort();
await port.open({ baudRate: 9600 });
this.reader = port.readable.getReader();
let signals = await port.getSignals();
console.log(signals);
}
catch (err) {
console.error('There was an error opening the serial port:', err);
}
}
else {
console.error('Web serial doesn\'t seem to be enabled in your browser. Try enabling it by visiting:');
console.error('chrome://flags/#enable-experimental-web-platform-features');
console.error('opera://flags/#enable-experimental-web-platform-features');
console.error('edge://flags/#enable-experimental-web-platform-features');
}
}
async read() {
try {
const readerData = await this.reader.read();
console.log(readerData)
return this.decoder.decode(readerData.value);
}
catch (err) {
const errorMessage = `error reading data: ${err}`;
console.error(errorMessage);
return errorMessage;
}
}
}
const serialScaleController = new SerialScaleController();
const connect = document.getElementById('connect-to-serial');
const getSerialMessages = document.getElementById('get-serial-messages');
connect.addEventListener('pointerdown', () => {
serialScaleController.init();
});
getSerialMessages.addEventListener('pointerdown', async () => {
getSerialMessage();
});
async function getSerialMessage() {
document.querySelector("#serial-messages-container .message").innerText += await serialScaleController.read()
}
</script>
</body>
</html>
Checkout this demo and this code for a more descriptive example.
You might need to turn on the Serial API feature on your browser.
Following is the quote from References
As you can imagine, this is API is only supported by modern Chromium
based desktop browsers right now (April 2020) but hopefully support
will improve in the near future. At this moment you need to enable
your browser's Experimental Web Platform Features, just copy and paste
the right URL:
chrome://flags/#enable-experimental-web-platform-features
opera://flags/#enable-experimental-web-platform-features
edge://flags/#enable-experimental-web-platform-features
References:
https://dev.to/unjavascripter/the-amazing-powers-of-the-web-web-serial-api-3ilc
https://github.com/UnJavaScripter/web-serial-example
Well, One way to do this is develop a chrome app. You can use chrome.serial API.
https://developer.chrome.com/apps/serial
Sample Code,
In your manifest.json,
{
"name": "Serial Sample",
"description": "Read/Write from/to serial port.",
"version": "1.0",
"manifest_version": 2,
"permissions": ["serial"],
"app": {
"background": {
"scripts": ["background.js"]
}
}
}
In your background.js,
const DEVICE_PATH = 'COM1';
const serial = chrome.serial;
var dataRecieved="";
/* Interprets an ArrayBuffer as UTF-8 encoded string data. */
var ab2str = function(buf) {
var bufView = new Uint8Array(buf);
var encodedString = String.fromCharCode.apply(null, bufView);
return decodeURIComponent(escape(encodedString));
};
/* Converts a string to UTF-8 encoding in a Uint8Array; returns the array buffer. */
var str2ab = function(str) {
var encodedString = unescape(encodeURIComponent(str));
var bytes = new Uint8Array(encodedString.length);
for (var i = 0; i < encodedString.length; ++i) {
bytes[i] = encodedString.charCodeAt(i);
}
return bytes.buffer;
};
var SerialConnection = function() {
this.connectionId = -1;
this.lineBuffer = "";
this.boundOnReceive = this.onReceive.bind(this);
this.boundOnReceiveError = this.onReceiveError.bind(this);
this.onConnect = new chrome.Event();
this.onReadLine = new chrome.Event();
this.onError = new chrome.Event();
};
SerialConnection.prototype.onConnectComplete = function(connectionInfo) {
if (!connectionInfo) {
log("Connection failed.");
return;
}
this.connectionId = connectionInfo.connectionId;
chrome.serial.onReceive.addListener(this.boundOnReceive);
chrome.serial.onReceiveError.addListener(this.boundOnReceiveError);
this.onConnect.dispatch();
};
SerialConnection.prototype.onReceive = function(receiveInfo) {
if (receiveInfo.connectionId !== this.connectionId) {
return;
}
this.lineBuffer += ab2str(receiveInfo.data);
var index;
while ((index = this.lineBuffer.indexOf('\n')) >= 0) {
var line = this.lineBuffer.substr(0, index + 1);
this.onReadLine.dispatch(line);
this.lineBuffer = this.lineBuffer.substr(index + 1);
}
};
SerialConnection.prototype.onReceiveError = function(errorInfo) {
if (errorInfo.connectionId === this.connectionId) {
this.onError.dispatch(errorInfo.error);
}
};
SerialConnection.prototype.connect = function(path) {
serial.connect(path, this.onConnectComplete.bind(this))
};
SerialConnection.prototype.send = function(msg) {
if (this.connectionId < 0) {
throw 'Invalid connection';
}
serial.send(this.connectionId, str2ab(msg), function() {});
};
SerialConnection.prototype.disconnect = function() {
if (this.connectionId < 0) {
throw 'Invalid connection';
}
serial.disconnect(this.connectionId, function() {});
};
var connection = new SerialConnection();
connection.onConnect.addListener(function() {
//console.log('connected to: ' + DEVICE_PATH);
});
connection.onReadLine.addListener(function (line) {
//Serial port data recieve event.
dataRecieved = dataRecieved +line;
});
connection.connect(DEVICE_PATH);
Once you create the chrome app to communicate with the serial port the next thing is to allow your external web page to communicate with the chrome app using JavaScript.
For this on your manifest.json file add,
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
This will allow external webpage on your example.com domain communicate with your chrome app.
In your webpage,
// The ID of the extension we want to talk to.
var editorExtensionId = "nboladondmajlaalmcdupihoilpcketyl";
// Make a simple request:
chrome.runtime.sendMessage(editorExtensionId,
{ data: "data to pass to the chrome app" },
function (response)
{
alert(response);
});
In your chrome app,
chrome.runtime.onMessageExternal.addListener(
function (request, sender, sendResponse) {
sendResponse("Send serial port data to the web page");
});
https://developer.chrome.com/apps/messaging
I set up a website and a simple example for running a serial terminal in your browser. You should host it on a https server.
The serial terminal features are now available in chrome 88.
Live demo https://www.SerialTerminal.com
Full source.
https://github.com/mmiscool/serialTerminal.com/blob/main/index.html

httpChannel.redirectTo() infinite load

I'm working on a firefox extension for the first time, and thanks to the documentation, it's going on pretty fast.
I've a problem however : I wan't to redirect the users if they go on some domains.
const {Cc, Ci, Cr, Cu} = require("chrome");
const buttons = require('sdk/ui/button/action');
const tabs = require("sdk/tabs");
var httpRequestObserver =
{
observe: function(subject, topic, data)
{
if (topic == "http-on-modify-request") {
var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
var eTLDService = Cc["#mozilla.org/network/effective-tld-service;1"].getService(Ci.nsIEffectiveTLDService);
var suffix = eTLDService.getPublicSuffixFromHost(httpChannel.originalURI.host);
var regexp = new RegExp('google\.'+suffix,'i');
if (regexp.test(httpChannel.originalURI.host)) {
Cu.import("resource://gre/modules/Services.jsm");
httpChannel.redirectTo(Services.io.newURI("http://test.tld", null, null));
}
}
get observerService() {
return Cc["#mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
},
register: function()
{
this.observerService.addObserver(this, "http-on-modify-request", false);
},
unregister: function()
{
this.observerService.removeObserver(this, "http-on-modify-request");
}
};
httpRequestObserver.register();
I'm trying to do a little POC, but it seems to load indefinitely.
Do you know what I am doing wrong?
Don't test the originalURI! It will stay the same even after a redirect
/**
* The original URI used to construct the channel. This is used in
* the case of a redirect or URI "resolution" (e.g. resolving a
* resource: URI to a file: URI) so that the original pre-redirect
* URI can still be obtained. ...
*/
So you redirect, that creates a new channel with the same originalURI but different URI, so your test triggers again and again and again... causing the infinite redirection loop (and redirecting by this API also is not subject to the usual redirection limit).
Instead test the .URI of a channel, which gives the current URI.

Expose file writing to a webpage with a Firefox extension

I have a web application that my client uses for the cash registry.
What I need to do is to create a local file as the cash register's software needs to read from that file in order to print.
Until now i was using this code:
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var file = Components.classes["#mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
file.initWithPath(filePath);
Unfortunately with the latest version of firefox this isn't working anymore so I was told that i need and add-on to create the file.I've tried to develop an add-on(don't know if succesfully) and i have main.js looking like this :
var FileManager =
{
Write:
function (File, Text)
{
if (!File) return;
const unicodeConverter = Components.classes["#mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
unicodeConverter.charset = "UTF-8";
Text = unicodeConverter.ConvertFromUnicode(Text);
const os = Components.classes["#mozilla.org/network/file-output-stream;1"]
.createInstance(Components.interfaces.nsIFileOutputStream);
os.init(File, 0x02 | 0x08 | 0x20, 0700, 0);
os.write(Text, Text.length);
os.close();
},
Read:
function (File)
{
if (!File) return;
var res;
const is = Components.classes["#mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
const sis = Components.classes["#mozilla.org/scriptableinputstream;1"]
.createInstance(Components.interfaces.nsIScriptableInputStream);
is.init(File, 0x01, 0400, null);
sis.init(is);
res = sis.read(sis.available());
is.close();
return res;
},
};
Any ideas how should I use main.js?Where I find it after the add-on is installed?
I need to use something like this : FileManager.Write(path,text).
Sorry about the super-late reply.
If I understand your question correctly, you have a P.O.S application that runs in Firefox talking to some sort of local webserver via HTTP. The client-side JavaScript of your application needs to be able to read & write files from the local filesystem of the browser's PC.
If that's correct, then you can do so as follows. You'll need to create a Firefox addon, the simpliest kind of which is called a "bootstrapped" (or "restartless") addon.
A restartless addon consists of two files:
bootstrap.js (The JavaScript file containing your 'privileged' code)
install.rdf (an XML file describing your addon to Firefrox)
To build the addon, simply place both files inside the top-level (no folders!) of a ZIP file with the file extension .xpi. To install the addon, navigate to about:addons then from the tools menu, click Install from file, find your XPI, open it, then after a short delay choose Install.
In install.rdf put something like this:
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>youraddonname#yourdomain</em:id>
<em:type>2</em:type>
<em:name>Name of your addon</em:name>
<em:version>1.0</em:version>
<em:bootstrap>true</em:bootstrap>
<em:description>Describe your addon.</em:description>
<em:creator>Your name</em:creator>
<!-- Firefox Desktop -->
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>4.0.*</em:minVersion>
<em:maxVersion>29.0.*</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>
You need to implement two mandatory JavaScript functions in the bootstrap.js:
startup() - called when you install the addon, and when your browser starts up.
shutdown() - called when you uninstall the addon, and when your browser shuts down.
You should call the rest of your 'privileged' code in startup(). For hygiene, you can (and probably should) also implement install() and uninstall() functions.
Start by implementing the following code in bootstrap.js:
const Cc = Components.classes;
const Ci = Components.interfaces;
let consoleService = Cc["#mozilla.org/consoleservice;1"]
.getService(Ci.nsIConsoleService);
let wm = Cc["#mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
function LOG(msg) {
consoleService.logStringMessage("EXTENSION: "+msg);
}
function startup() {
try {
LOG("starting up...");
let windows = wm.getEnumerator("navigator:browser");
while (windows.hasMoreElements()) {
let chromeWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
WindowListener.setupBrowserUI(chromeWindow);
}
wm.addListener(WindowListener);
LOG("done startup.");
} catch (e) {
LOG("error starting up: "+e);
}
}
function shutdown() {
try {
LOG("shutting down...");
let windows = wm.getEnumerator("navigator:browser");
while (windows.hasMoreElements()) {
let chromeWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
WindowListener.tearDownBrowserUI(chromeWindow);
}
wm.addListener(WindowListener);
LOG("done shutdown.");
} catch (e) {
LOG("error shutting down: "+e);
}
}
Basically, that calls WindowListener.setupBrowserUI() for each current & future window of your web-browser. WindowListener is defined as follows:
var WindowListener = {
setupBrowserUI: function(chromeWindow) {
chromeWindow.gBrowser.addEventListener('load', my_load_handler, true);
},
tearDownBrowserUI: function(chromeWindow) {
chromeWindow.gBrowser.removeEventListener('load', my_load_handler, true);
},
onOpenWindow: function(xulWindow) {
let chromeWindow = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
chromeWindow.addEventListener("load", function listener() {
chromeWindow.removeEventListener("load", listener, false);
var domDocument = chromeWindow.document.documentElement;
var windowType = domDocument.getAttribute("windowtype");
if (windowType == "navigator:browser")
WindowListener.setupBrowserUI(chromeWindow);
}, false);
},
onCloseWindow: function(chromeWindow) { },
onWindowTitleChange: function(chromeWindow, newTitle) { }
};
That sets up an event listener for the OpenWindow event, and in turn installs an event listener for load events in the TabBrowser of each ChromeWindow. The load event handler is defined as:
var my_load_handler = function (evt) {
try {
var browserEnumerator = wm.getEnumerator("navigator:browser");
while (browserEnumerator.hasMoreElements()) {
var browserWin = browserEnumerator.getNext();
var tabbrowser = browserWin.gBrowser;
var numTabs = tabbrowser.browsers.length;
for (var index = 0; index < numTabs; index++) {
var currentBrowser = tabbrowser.getBrowserAtIndex(index);
var domWindow = currentBrowser.contentWindow.wrappedJSObject;
// identify your target page(s)...
if (domWindow.location.href == 'http://yourserver/yourpage') {
// install the privileged methods (if not already there)
if (!domWindow.hasOwnProperty('__my_priv_members__') {
install_my_privileged_methods(browserWin, domWindow);
}
}
}
}
} catch (e) {
LOG(e);
}
}
That targets the correct pages (by checking the window.location.href and calls install_my_privileged_methods on their window object, which is defined as:
function install_my_privileged_methods(chromeWindow, domWindow) {
install_privileged_method(chromeWindow, domWindow, 'WriteFile',
function(priv) {
return function(File, Text, cb) {
priv.call([File, Text], function(rstatus, rdata, rerror){
if (cb) cb(rstatus, rerror);
});
};
},
function (chromeWindow, args, cb) {
var [File, Text] = args;
if (!File) return cb(0, null, "need a filename");
try {
const unicodeConverter =
Cc["#mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
unicodeConverter.charset = "UTF-8";
Text = unicodeConverter.ConvertFromUnicode(Text);
const os = Cc["#mozilla.org/network/file-output-stream;1"]
.createInstance(Ci.nsIFileOutputStream);
os.init(File, 0x02 | 0x08 | 0x20, 0700, 0);
os.write(Text, Text.length);
os.close();
cb(1, null, null);
} catch (e) {
cb(0, null, "error writing file: "+e);
}
}
);
install_privileged_method(chromeWindow, domWindow, 'ReadFile',
function(priv) {
return function(File, cb) {
priv.call([File], function(rstatus, rdata, rerror){
if (cb) cb(rstatus, rdata, rerror);
});
};
},
function (chromeWindow, args, cb) {
var [File] = args;
if (!File) return cb(0, null, "need a filename");
try {
const is = Cc["#mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
const sis = Cc["#mozilla.org/scriptableinputstream;1"]
.createInstance(Ci.nsIScriptableInputStream);
is.init(File, 0x01, 0400, null);
sis.init(is);
var Text = sis.read(sis.available());
is.close();
cb(1, Text, null);
} catch (e) {
cb(0, null, "error reading file: "+e);
}
}
);
}
I didn't test this code. It's a straigh-forward translation of what you wrote above... I'll assume that works!
That add two special methods, WriteFile & ReadFile, to the chosen window objects. In your web application's (unprivileged) JavaScript code use them like this:
var buffer = '...'; // the text to be written
window.WriteFile('C:\\path\\to\\file.txt', buffer, function(ok, errmsg) {
if (!ok) alert(errmsg);
});
window.ReadFile('C:\\path\\to\\file.txt', function(ok, buffer, errmsg) {
if (!ok) return alert(errmsg);
// use buffer here!
});
Finally, install_privileged_method is defined as:
var install_privileged_method = (function(){
var gensym = (function (){
var __sym = 0;
return function () { return '__sym_'+(__sym++); }
})();
return function (chromeWindow, target, slot, handler, methodFactory) {
try {
target.__pmcache__ = target.hasOwnProperty('__pmcache__')
? target.__pmcache__
: { ticket_no: 0, callbacks: {}, namespace: gensym() };
target[slot] = methodFactory({ call: function(fargs, fcb) {
try {
var ticket_no = target.__pmcache__.ticket_no++;
target.__pmcache__.callbacks[ticket_no] = fcb;
var cevent = target.document.createEvent("CustomEvent");
cevent.initCustomEvent(
target.__pmcache__.namespace+'.'+slot,
true, true, { fargs: fargs, ticket_no: ticket_no }
);
target.dispatchEvent(cevent);
} catch (ue) {
fcb(0, null, 'untrusted dispatcher error: '+ue);
}
}});
LOG("installed untrusted dispatcher for method '"+slot+"'.");
target.addEventListener(
target.__pmcache__.namespace+'.'+slot,
function(cevent){
var ticket_no = cevent.detail.ticket_no;
var fargs = cevent.detail.fargs;
var fcb = target.__pmcache__.callbacks[ticket_no];
try {
handler(chromeWindow, fargs, fcb);
} catch (pe) {
fcb(0, null, 'privileged handler error: '+pe);
}
},
false,
true
);
LOG("installed privileged handler for method '"+slot+"'.");
} catch (ie) {
LOG("ERROR installing handler/factory for privileged "+
"method '"+slot+"': "+ie);
}
};
})();

Categories