Sat, 03 Jan 2015 20:18:00 +0100
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 | } |