toolkit/components/social/FrameWorker.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial