dom/messages/SystemMessageManager.js

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 const Cc = Components.classes;
     8 const Ci = Components.interfaces;
     9 const Cu = Components.utils;
    10 const Cr = Components.results;
    12 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    13 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
    14 Cu.import("resource://gre/modules/Services.jsm");
    16 const kSystemMessageInternalReady = "system-message-internal-ready";
    18 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
    19                                    "@mozilla.org/childprocessmessagemanager;1",
    20                                    "nsISyncMessageSender");
    22 function debug(aMsg) {
    23    // dump("-- SystemMessageManager " + Date.now() + " : " + aMsg + "\n");
    24 }
    26 // Implementation of the DOM API for system messages
    28 function SystemMessageManager() {
    29   // If we have a system message handler registered for messages of type
    30   // |type|, this._dispatchers[type] equals {handler, messages, isHandling},
    31   // where
    32   //  - |handler| is the message handler that the page registered,
    33   //  - |messages| is a list of messages which we've received while
    34   //    dispatching messages to the handler, but haven't yet sent, and
    35   //  - |isHandling| indicates whether we're currently dispatching messages
    36   //    to this handler.
    37   this._dispatchers = {};
    39   // Pending messages for this page, keyed by message type.
    40   this._pendings = {};
    42   // Flag to specify if this process has already registered the manifest URL.
    43   this._registerManifestURLReady = false;
    45   // Flag to determine this process is a parent or child process.
    46   let appInfo = Cc["@mozilla.org/xre/app-info;1"];
    47   this._isParentProcess =
    48     !appInfo || appInfo.getService(Ci.nsIXULRuntime)
    49                   .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
    51   // An oberver to listen to whether the |SystemMessageInternal| is ready.
    52   if (this._isParentProcess) {
    53     Services.obs.addObserver(this, kSystemMessageInternalReady, false);
    54   }
    55 }
    57 SystemMessageManager.prototype = {
    58   __proto__: DOMRequestIpcHelper.prototype,
    60   _dispatchMessage: function(aType, aDispatcher, aMessage) {
    61     if (aDispatcher.isHandling) {
    62       // Queue up the incomming message if we're currently dispatching a
    63       // message; we'll send the message once we finish with the current one.
    64       //
    65       // _dispatchMethod is reentrant because a page can spin up a nested
    66       // event loop from within a system message handler (e.g. via alert()),
    67       // and we can then try to send the page another message while it's
    68       // inside this nested event loop.
    69       aDispatcher.messages.push(aMessage);
    70       return;
    71     }
    73     aDispatcher.isHandling = true;
    75     // We get a json blob, but in some cases we want another kind of object
    76     // to be dispatched. To do so, we check if we have a valid contract ID of
    77     // "@mozilla.org/dom/system-messages/wrapper/TYPE;1" component implementing
    78     // nsISystemMessageWrapper.
    79     debug("Dispatching " + JSON.stringify(aMessage) + "\n");
    80     let contractID = "@mozilla.org/dom/system-messages/wrapper/" + aType + ";1";
    81     let wrapped = false;
    83     if (contractID in Cc) {
    84       debug(contractID + " is registered, creating an instance");
    85       let wrapper = Cc[contractID].createInstance(Ci.nsISystemMessagesWrapper);
    86       if (wrapper) {
    87         aMessage = wrapper.wrapMessage(aMessage, this._window);
    88         wrapped = true;
    89         debug("wrapped = " + aMessage);
    90       }
    91     }
    93     aDispatcher.handler
    94       .handleMessage(wrapped ? aMessage
    95                              : Cu.cloneInto(aMessage, this._window));
    97     // We need to notify the parent one of the system messages has been handled,
    98     // so the parent can release the CPU wake lock it took on our behalf.
    99     cpmm.sendAsyncMessage("SystemMessageManager:HandleMessagesDone",
   100                           { type: aType,
   101                             manifestURL: this._manifestURL,
   102                             pageURL: this._pageURL,
   103                             handledCount: 1 });
   105     aDispatcher.isHandling = false;
   107     if (aDispatcher.messages.length > 0) {
   108       this._dispatchMessage(aType, aDispatcher, aDispatcher.messages.shift());
   109     } else {
   110       // No more messages that need to be handled, we can notify the
   111       // ContentChild to release the CPU wake lock grabbed by the ContentParent
   112       // (i.e. NewWakeLockOnBehalfOfProcess()) and reset the process's priority.
   113       //
   114       // TODO: Bug 874353 - Remove SystemMessageHandledListener in ContentParent
   115       Services.obs.notifyObservers(/* aSubject */ null,
   116                                    "handle-system-messages-done",
   117                                    /* aData */ null);
   118     }
   119   },
   121   mozSetMessageHandler: function(aType, aHandler) {
   122     debug("set message handler for [" + aType + "] " + aHandler);
   124     if (this._isInBrowserElement) {
   125       debug("the app loaded in the browser cannot set message handler");
   126       // Don't throw there, but ignore the registration.
   127       return;
   128     }
   130     if (!aType) {
   131       // Just bail out if we have no type.
   132       return;
   133     }
   135     let dispatchers = this._dispatchers;
   136     if (!aHandler) {
   137       // Setting the dispatcher to null means we don't want to handle messages
   138       // for this type anymore.
   139       delete dispatchers[aType];
   140       return;
   141     }
   143     // Last registered handler wins.
   144     dispatchers[aType] = { handler: aHandler, messages: [], isHandling: false };
   146     // Ask for the list of currently pending messages.
   147     cpmm.sendAsyncMessage("SystemMessageManager:GetPendingMessages",
   148                           { type: aType,
   149                             pageURL: this._pageURL,
   150                             manifestURL: this._manifestURL });
   151   },
   153   mozHasPendingMessage: function(aType) {
   154     debug("asking pending message for [" + aType + "]");
   156     if (this._isInBrowserElement) {
   157       debug("the app loaded in the browser cannot ask pending message");
   158       // Don't throw there, but pretend to have no messages available.
   159       return false;
   160     }
   162     // If we have a handler for this type, we can't have any pending message.
   163     if (aType in this._dispatchers) {
   164       return false;
   165     }
   167     return cpmm.sendSyncMessage("SystemMessageManager:HasPendingMessages",
   168                                 { type: aType,
   169                                   pageURL: this._pageURL,
   170                                   manifestURL: this._manifestURL })[0];
   171   },
   173   uninit: function()  {
   174     this._dispatchers = null;
   175     this._pendings = null;
   177     if (this._isParentProcess) {
   178       Services.obs.removeObserver(this, kSystemMessageInternalReady);
   179     }
   181     if (this._isInBrowserElement) {
   182       debug("the app loaded in the browser doesn't need to unregister " +
   183             "the manifest URL for listening to the system messages");
   184       return;
   185     }
   187     cpmm.sendAsyncMessage("SystemMessageManager:Unregister",
   188                           { manifestURL: this._manifestURL,
   189                             pageURL: this._pageURL,
   190                             innerWindowID: this.innerWindowID });
   191   },
   193   // Possible messages:
   194   //
   195   //   - SystemMessageManager:Message
   196   //     This one will only be received when the child process is alive when
   197   //     the message is initially sent.
   198   //
   199   //   - SystemMessageManager:GetPendingMessages:Return
   200   //     This one will be received when the starting child process wants to
   201   //     retrieve the pending system messages from the parent (i.e. after
   202   //     sending SystemMessageManager:GetPendingMessages).
   203   receiveMessage: function(aMessage) {
   204     let msg = aMessage.data;
   205     debug("receiveMessage " + aMessage.name + " for [" + msg.type + "] " +
   206           "with manifest URL = " + msg.manifestURL +
   207           " and page URL = " + msg.pageURL);
   209     // Multiple windows can share the same target (process), the content
   210     // window needs to check if the manifest/page URL is matched. Only
   211     // *one* window should handle the system message.
   212     if (msg.manifestURL !== this._manifestURL ||
   213         msg.pageURL !== this._pageURL) {
   214       debug("This page shouldn't handle the messages because its " +
   215             "manifest URL = " + this._manifestURL +
   216             " and page URL = " + this._pageURL);
   217       return;
   218     }
   220     let messages = (aMessage.name == "SystemMessageManager:Message")
   221                    ? [msg.msg]
   222                    : msg.msgQueue;
   224     // We only dispatch messages when a handler is registered.
   225     let dispatcher = this._dispatchers[msg.type];
   226     if (dispatcher) {
   227       if (aMessage.name == "SystemMessageManager:Message") {
   228         // Send an acknowledgement to parent to clean up the pending message
   229         // before we dispatch the message to apps, so a re-launched app won't
   230         // handle it again, which is redundant.
   231         cpmm.sendAsyncMessage("SystemMessageManager:Message:Return:OK",
   232                               { type: msg.type,
   233                                 manifestURL: this._manifestURL,
   234                                 pageURL: this._pageURL,
   235                                 msgID: msg.msgID });
   236       }
   238       messages.forEach(function(aMsg) {
   239         this._dispatchMessage(msg.type, dispatcher, aMsg);
   240       }, this);
   241     } else {
   242       // Since no handlers are registered, we need to notify the parent as if
   243       // all the queued system messages have been handled (notice |handledCount:
   244       // messages.length|), so the parent can release the CPU wake lock it took
   245       // on our behalf.
   246       cpmm.sendAsyncMessage("SystemMessageManager:HandleMessagesDone",
   247                             { type: msg.type,
   248                               manifestURL: this._manifestURL,
   249                               pageURL: this._pageURL,
   250                               handledCount: messages.length });
   252       // We also need to notify the ContentChild to release the CPU wake lock
   253       // grabbed by the ContentParent (i.e. NewWakeLockOnBehalfOfProcess()) and
   254       // reset the process's priority.
   255       //
   256       // TODO: Bug 874353 - Remove SystemMessageHandledListener in ContentParent
   257       Services.obs.notifyObservers(/* aSubject */ null,
   258                                    "handle-system-messages-done",
   259                                    /* aData */ null);
   260     }
   261   },
   263   // nsIDOMGlobalPropertyInitializer implementation.
   264   init: function(aWindow) {
   265     debug("init");
   266     this.initDOMRequestHelper(aWindow,
   267                               ["SystemMessageManager:Message",
   268                                "SystemMessageManager:GetPendingMessages:Return"]);
   270     let principal = aWindow.document.nodePrincipal;
   271     this._isInBrowserElement = principal.isInBrowserElement;
   272     this._pageURL = principal.URI.spec;
   274     let appsService = Cc["@mozilla.org/AppsService;1"]
   275                         .getService(Ci.nsIAppsService);
   276     this._manifestURL = appsService.getManifestURLByLocalId(principal.appId);
   278     // Two cases are valid to register the manifest URL for the current process:
   279     // 1. This is asked by a child process (parent process must be ready).
   280     // 2. Parent process has already constructed the |SystemMessageInternal|.
   281     // Otherwise, delay to do it when the |SystemMessageInternal| is ready.
   282     let readyToRegister = true;
   283     if (this._isParentProcess) {
   284       let ready = cpmm.sendSyncMessage(
   285         "SystemMessageManager:AskReadyToRegister", null);
   286       if (ready.length == 0 || !ready[0]) {
   287         readyToRegister = false;
   288       }
   289     }
   290     if (readyToRegister) {
   291       this._registerManifestURL();
   292     }
   294     debug("done");
   295   },
   297   observe: function(aSubject, aTopic, aData) {
   298     if (aTopic === kSystemMessageInternalReady) {
   299       this._registerManifestURL();
   300     }
   302     // Call the DOMRequestIpcHelper.observe method.
   303     this.__proto__.__proto__.observe.call(this, aSubject, aTopic, aData);
   304   },
   306   _registerManifestURL: function() {
   307     if (this._isInBrowserElement) {
   308       debug("the app loaded in the browser doesn't need to register " +
   309             "the manifest URL for listening to the system messages");
   310       return;
   311     }
   313     if (!this._registerManifestURLReady) {
   314       cpmm.sendAsyncMessage("SystemMessageManager:Register",
   315                             { manifestURL: this._manifestURL,
   316                               pageURL: this._pageURL,
   317                               innerWindowID: this.innerWindowID });
   319       this._registerManifestURLReady = true;
   320     }
   321   },
   323   classID: Components.ID("{bc076ea0-609b-4d8f-83d7-5af7cbdc3bb2}"),
   325   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMNavigatorSystemMessages,
   326                                          Ci.nsIDOMGlobalPropertyInitializer,
   327                                          Ci.nsIObserver,
   328                                          Ci.nsISupportsWeakReference])
   329 }
   331 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SystemMessageManager]);

mercurial