toolkit/components/social/FrameWorker.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 5 * You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 6 */
michael@0 7
michael@0 8 /*
michael@0 9 * This is an implementation of a "Shared Worker" using a remote browser
michael@0 10 * in the hidden DOM window. This is the implementation that lives in the
michael@0 11 * "chrome process". See FrameWorkerContent for code that lives in the
michael@0 12 * "content" process and which sets up a sandbox for the worker.
michael@0 13 */
michael@0 14
michael@0 15 "use strict";
michael@0 16
michael@0 17 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
michael@0 18
michael@0 19 Cu.import("resource://gre/modules/Services.jsm");
michael@0 20 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 21 Cu.import("resource://gre/modules/MessagePortBase.jsm");
michael@0 22 Cu.import("resource://gre/modules/Promise.jsm");
michael@0 23
michael@0 24 XPCOMUtils.defineLazyModuleGetter(this, "SocialService",
michael@0 25 "resource://gre/modules/SocialService.jsm");
michael@0 26
michael@0 27 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
michael@0 28 const HTML_NS = "http://www.w3.org/1999/xhtml";
michael@0 29
michael@0 30 this.EXPORTED_SYMBOLS = ["getFrameWorkerHandle"];
michael@0 31
michael@0 32 var workerCache = {}; // keyed by URL.
michael@0 33 var _nextPortId = 1;
michael@0 34
michael@0 35 // Retrieves a reference to a WorkerHandle associated with a FrameWorker and a
michael@0 36 // new ClientPort.
michael@0 37 this.getFrameWorkerHandle =
michael@0 38 function getFrameWorkerHandle(url, clientWindow, name, origin, exposeLocalStorage = false) {
michael@0 39 // prevent data/about urls - see bug 891516
michael@0 40 if (['http', 'https'].indexOf(Services.io.newURI(url, null, null).scheme) < 0)
michael@0 41 throw new Error("getFrameWorkerHandle requires http/https urls");
michael@0 42
michael@0 43 // See if we already have a worker with this URL.
michael@0 44 let existingWorker = workerCache[url];
michael@0 45 if (!existingWorker) {
michael@0 46 // create a remote browser and _Worker object - this will message the
michael@0 47 // remote browser to do the content side of things.
michael@0 48 let browserPromise = makeRemoteBrowser();
michael@0 49 let options = { url: url, name: name, origin: origin,
michael@0 50 exposeLocalStorage: exposeLocalStorage };
michael@0 51
michael@0 52 existingWorker = workerCache[url] = new _Worker(browserPromise, options);
michael@0 53 }
michael@0 54
michael@0 55 // message the content so it can establish a new connection with the worker.
michael@0 56 let portid = _nextPortId++;
michael@0 57 existingWorker.browserPromise.then(browser => {
michael@0 58 browser.messageManager.sendAsyncMessage("frameworker:connect",
michael@0 59 { portId: portid });
michael@0 60 });
michael@0 61 // return the pseudo worker object.
michael@0 62 let port = new ParentPort(portid, existingWorker.browserPromise, clientWindow);
michael@0 63 existingWorker.ports.set(portid, port);
michael@0 64 return new WorkerHandle(port, existingWorker);
michael@0 65 };
michael@0 66
michael@0 67 // A "_Worker" is an internal representation of a worker. It's never returned
michael@0 68 // directly to consumers.
michael@0 69 function _Worker(browserPromise, options) {
michael@0 70 this.browserPromise = browserPromise;
michael@0 71 this.options = options;
michael@0 72 this.ports = new Map();
michael@0 73 browserPromise.then(browser => {
michael@0 74 browser.addEventListener("oop-browser-crashed", () => {
michael@0 75 Cu.reportError("FrameWorker remote process crashed");
michael@0 76 notifyWorkerError(options.origin);
michael@0 77 });
michael@0 78
michael@0 79 let mm = browser.messageManager;
michael@0 80 // execute the content script and send the message to bootstrap the content
michael@0 81 // side of the world.
michael@0 82 mm.loadFrameScript("resource://gre/modules/FrameWorkerContent.js", true);
michael@0 83 mm.sendAsyncMessage("frameworker:init", this.options);
michael@0 84 mm.addMessageListener("frameworker:port-message", this);
michael@0 85 mm.addMessageListener("frameworker:notify-worker-error", this);
michael@0 86 });
michael@0 87 }
michael@0 88
michael@0 89 _Worker.prototype = {
michael@0 90 // Message handler.
michael@0 91 receiveMessage: function(msg) {
michael@0 92 switch (msg.name) {
michael@0 93 case "frameworker:port-message":
michael@0 94 let port = this.ports.get(msg.data.portId);
michael@0 95 port._onmessage(msg.data.data);
michael@0 96 break;
michael@0 97 case "frameworker:notify-worker-error":
michael@0 98 notifyWorkerError(msg.data.origin);
michael@0 99 break;
michael@0 100 }
michael@0 101 }
michael@0 102 }
michael@0 103
michael@0 104 // This WorkerHandle is exposed to consumers - it has the new port instance
michael@0 105 // the consumer uses to communicate with the worker.
michael@0 106 // public methods/properties on WorkerHandle should conform to the SharedWorker
michael@0 107 // api - currently that's just .port and .terminate()
michael@0 108 function WorkerHandle(port, worker) {
michael@0 109 this.port = port;
michael@0 110 this._worker = worker;
michael@0 111 }
michael@0 112
michael@0 113 WorkerHandle.prototype = {
michael@0 114 // A method to terminate the worker. The worker spec doesn't define a
michael@0 115 // callback to be made in the worker when this happens, so we just kill the
michael@0 116 // browser element.
michael@0 117 terminate: function terminate() {
michael@0 118 let url = this._worker.options.url;
michael@0 119 if (!(url in workerCache)) {
michael@0 120 // terminating an already terminated worker - ignore it
michael@0 121 return;
michael@0 122 }
michael@0 123 delete workerCache[url];
michael@0 124 // close all the ports we have handed out.
michael@0 125 for (let [portid, port] of this._worker.ports) {
michael@0 126 port.close();
michael@0 127 }
michael@0 128 this._worker.ports.clear();
michael@0 129 this._worker.ports = null;
michael@0 130 this._worker.browserPromise.then(browser => {
michael@0 131 let iframe = browser.ownerDocument.defaultView.frameElement;
michael@0 132 iframe.parentNode.removeChild(iframe);
michael@0 133 });
michael@0 134 // wipe things out just incase other reference have snuck out somehow...
michael@0 135 this._worker.browserPromise = null;
michael@0 136 this._worker = null;
michael@0 137 }
michael@0 138 };
michael@0 139
michael@0 140 // The port that lives in the parent chrome process. The other end of this
michael@0 141 // port is the "client" port in the content process, which itself is just a
michael@0 142 // shim which shuttles messages to/from the worker itself.
michael@0 143 function ParentPort(portid, browserPromise, clientWindow) {
michael@0 144 this._clientWindow = clientWindow;
michael@0 145 this._browserPromise = browserPromise;
michael@0 146 AbstractPort.call(this, portid);
michael@0 147 }
michael@0 148
michael@0 149 ParentPort.prototype = {
michael@0 150 __exposedProps__: {
michael@0 151 onmessage: "rw",
michael@0 152 postMessage: "r",
michael@0 153 close: "r",
michael@0 154 toString: "r"
michael@0 155 },
michael@0 156 __proto__: AbstractPort.prototype,
michael@0 157 _portType: "parent",
michael@0 158
michael@0 159 _dopost: function(data) {
michael@0 160 this._browserPromise.then(browser => {
michael@0 161 browser.messageManager.sendAsyncMessage("frameworker:port-message", data);
michael@0 162 });
michael@0 163 },
michael@0 164
michael@0 165 _onerror: function(err) {
michael@0 166 Cu.reportError("FrameWorker: Port " + this + " handler failed: " + err + "\n" + err.stack);
michael@0 167 },
michael@0 168
michael@0 169 _JSONParse: function(data) {
michael@0 170 if (this._clientWindow) {
michael@0 171 return XPCNativeWrapper.unwrap(this._clientWindow).JSON.parse(data);
michael@0 172 }
michael@0 173 return JSON.parse(data);
michael@0 174 },
michael@0 175
michael@0 176 close: function() {
michael@0 177 if (this._closed) {
michael@0 178 return; // already closed.
michael@0 179 }
michael@0 180 // a leaky abstraction due to the worker spec not specifying how the
michael@0 181 // other end of a port knows it is closing.
michael@0 182 this.postMessage({topic: "social.port-closing"});
michael@0 183 AbstractPort.prototype.close.call(this);
michael@0 184 this._clientWindow = null;
michael@0 185 // this._pendingMessagesOutgoing should still be drained, as a closed
michael@0 186 // port will still get "entangled" quickly enough to deliver the messages.
michael@0 187 }
michael@0 188 }
michael@0 189
michael@0 190 // Make the <browser remote="true"> element that hosts the worker.
michael@0 191 function makeRemoteBrowser() {
michael@0 192 let deferred = Promise.defer();
michael@0 193 let hiddenDoc = Services.appShell.hiddenDOMWindow.document;
michael@0 194 // Create a HTML iframe with a chrome URL, then this can host the browser.
michael@0 195 let iframe = hiddenDoc.createElementNS(HTML_NS, "iframe");
michael@0 196 iframe.setAttribute("src", "chrome://global/content/mozilla.xhtml");
michael@0 197 iframe.addEventListener("load", function onLoad() {
michael@0 198 iframe.removeEventListener("load", onLoad, true);
michael@0 199 let browser = iframe.contentDocument.createElementNS(XUL_NS, "browser");
michael@0 200 browser.setAttribute("type", "content");
michael@0 201 browser.setAttribute("disableglobalhistory", "true");
michael@0 202 browser.setAttribute("remote", "true");
michael@0 203
michael@0 204 iframe.contentDocument.documentElement.appendChild(browser);
michael@0 205 deferred.resolve(browser);
michael@0 206 }, true);
michael@0 207 hiddenDoc.documentElement.appendChild(iframe);
michael@0 208 return deferred.promise;
michael@0 209 }
michael@0 210
michael@0 211 function notifyWorkerError(origin) {
michael@0 212 // Try to retrieve the worker's associated provider, if it has one, to set its
michael@0 213 // error state.
michael@0 214 SocialService.getProvider(origin, function (provider) {
michael@0 215 if (provider)
michael@0 216 provider.errorState = "frameworker-error";
michael@0 217 Services.obs.notifyObservers(null, "social:frameworker-error", origin);
michael@0 218 });
michael@0 219 }

mercurial