dom/base/DOMRequestHelper.jsm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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 /**
     6  * Helper object for APIs that deal with DOMRequests and Promises.
     7  * It allows objects inheriting from it to create and keep track of DOMRequests
     8  * and Promises objects in the common scenario where requests are created in
     9  * the child, handed out to content and delivered to the parent within an async
    10  * message (containing the identifiers of these requests). The parent may send
    11  * messages back as answers to different requests and the child will use this
    12  * helper to get the right request object. This helper also takes care of
    13  * releasing the requests objects when the window goes out of scope.
    14  *
    15  * DOMRequestIPCHelper also deals with message listeners, allowing to add them
    16  * to the child side of frame and process message manager and removing them
    17  * when needed.
    18  */
    19 const Cu = Components.utils;
    20 const Cc = Components.classes;
    21 const Ci = Components.interfaces;
    22 const Cr = Components.results;
    24 this.EXPORTED_SYMBOLS = ["DOMRequestIpcHelper"];
    26 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    27 Cu.import("resource://gre/modules/Services.jsm");
    29 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
    30                                    "@mozilla.org/childprocessmessagemanager;1",
    31                                    "nsIMessageListenerManager");
    33 this.DOMRequestIpcHelper = function DOMRequestIpcHelper() {
    34   // _listeners keeps a list of messages for which we added a listener and the
    35   // kind of listener that we added (strong or weak). It's an object of this
    36   // form:
    37   //  {
    38   //    "message1": true,
    39   //    "messagen": false
    40   //  }
    41   //
    42   // where each property is the name of the message and its value is a boolean
    43   // that indicates if the listener is weak or not.
    44   this._listeners = null;
    45   this._requests = null;
    46   this._window = null;
    47 }
    49 DOMRequestIpcHelper.prototype = {
    50   /**
    51    * An object which "inherits" from DOMRequestIpcHelper and declares its own
    52    * queryInterface method MUST implement Ci.nsISupportsWeakReference.
    53    */
    54   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
    55                                          Ci.nsIObserver]),
    57    /**
    58    *  'aMessages' is expected to be an array of either:
    59    *  - objects of this form:
    60    *    {
    61    *      name: "messageName",
    62    *      weakRef: false
    63    *    }
    64    *    where 'name' is the message identifier and 'weakRef' a boolean
    65    *    indicating if the listener should be a weak referred one or not.
    66    *
    67    *  - or only strings containing the message name, in which case the listener
    68    *    will be added as a strong reference by default.
    69    */
    70   addMessageListeners: function(aMessages) {
    71     if (!aMessages) {
    72       return;
    73     }
    75     if (!this._listeners) {
    76       this._listeners = {};
    77     }
    79     if (!Array.isArray(aMessages)) {
    80       aMessages = [aMessages];
    81     }
    83     aMessages.forEach((aMsg) => {
    84       let name = aMsg.name || aMsg;
    85       // If the listener is already set and it is of the same type we just
    86       // bail out. If it is not of the same type, we throw an exception.
    87       if (this._listeners[name] != undefined) {
    88         if (!!aMsg.weakRef == this._listeners[name]) {
    89           return;
    90         } else {
    91           throw Cr.NS_ERROR_FAILURE;
    92         }
    93       }
    95       aMsg.weakRef ? cpmm.addWeakMessageListener(name, this)
    96                    : cpmm.addMessageListener(name, this);
    97       this._listeners[name] = !!aMsg.weakRef;
    98     });
    99   },
   101   /**
   102    * 'aMessages' is expected to be a string or an array of strings containing
   103    * the message names of the listeners to be removed.
   104    */
   105   removeMessageListeners: function(aMessages) {
   106     if (!this._listeners || !aMessages) {
   107       return;
   108     }
   110     if (!Array.isArray(aMessages)) {
   111       aMessages = [aMessages];
   112     }
   114     aMessages.forEach((aName) => {
   115       if (this._listeners[aName] == undefined) {
   116         return;
   117       }
   119       this._listeners[aName] ? cpmm.removeWeakMessageListener(aName, this)
   120                              : cpmm.removeMessageListener(aName, this);
   121       delete this._listeners[aName];
   122     });
   123   },
   125   /**
   126    * Initialize the helper adding the corresponding listeners to the messages
   127    * provided as the second parameter.
   128    *
   129    * 'aMessages' is expected to be an array of either:
   130    *
   131    *  - objects of this form:
   132    *    {
   133    *      name: 'messageName',
   134    *      weakRef: false
   135    *    }
   136    *    where 'name' is the message identifier and 'weakRef' a boolean
   137    *    indicating if the listener should be a weak referred one or not.
   138    *
   139    *  - or only strings containing the message name, in which case the listener
   140    *    will be added as a strong referred one by default.
   141    */
   142   initDOMRequestHelper: function(aWindow, aMessages) {
   143     // Query our required interfaces to force a fast fail if they are not
   144     // provided. These calls will throw if the interface is not available.
   145     this.QueryInterface(Ci.nsISupportsWeakReference);
   146     this.QueryInterface(Ci.nsIObserver);
   148     if (aMessages) {
   149       this.addMessageListeners(aMessages);
   150     }
   152     this._id = this._getRandomId();
   154     this._window = aWindow;
   155     if (this._window) {
   156       // We don't use this.innerWindowID, but other classes rely on it.
   157       let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
   158                              .getInterface(Ci.nsIDOMWindowUtils);
   159       this.innerWindowID = util.currentInnerWindowID;
   160     }
   162     this._destroyed = false;
   164     Services.obs.addObserver(this, "inner-window-destroyed",
   165                              /* weak-ref */ true);
   166   },
   168   destroyDOMRequestHelper: function() {
   169     if (this._destroyed) {
   170       return;
   171     }
   173     this._destroyed = true;
   175     Services.obs.removeObserver(this, "inner-window-destroyed");
   177     if (this._listeners) {
   178       Object.keys(this._listeners).forEach((aName) => {
   179         this._listeners[aName] ? cpmm.removeWeakMessageListener(aName, this)
   180                                : cpmm.removeMessageListener(aName, this);
   181         delete this._listeners[aName];
   182       });
   183     }
   185     this._listeners = null;
   186     this._requests = null;
   188     // Objects inheriting from DOMRequestIPCHelper may have an uninit function.
   189     if (this.uninit) {
   190       this.uninit();
   191     }
   193     this._window = null;
   194   },
   196   observe: function(aSubject, aTopic, aData) {
   197     if (aTopic !== "inner-window-destroyed") {
   198       return;
   199     }
   201     let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
   202     if (wId != this.innerWindowID) {
   203       return;
   204     }
   206     this.destroyDOMRequestHelper();
   207   },
   209   getRequestId: function(aRequest) {
   210     if (!this._requests) {
   211       this._requests = {};
   212     }
   214     let id = "id" + this._getRandomId();
   215     this._requests[id] = aRequest;
   216     return id;
   217   },
   219   getPromiseResolverId: function(aPromiseResolver) {
   220     // Delegates to getRequest() since the lookup table is agnostic about
   221     // storage.
   222     return this.getRequestId(aPromiseResolver);
   223   },
   225   getRequest: function(aId) {
   226     if (this._requests && this._requests[aId]) {
   227       return this._requests[aId];
   228     }
   229   },
   231   getPromiseResolver: function(aId) {
   232     // Delegates to getRequest() since the lookup table is agnostic about
   233     // storage.
   234     return this.getRequest(aId);
   235   },
   237   removeRequest: function(aId) {
   238     if (this._requests && this._requests[aId]) {
   239       delete this._requests[aId];
   240     }
   241   },
   243   removePromiseResolver: function(aId) {
   244     // Delegates to getRequest() since the lookup table is agnostic about
   245     // storage.
   246     this.removeRequest(aId);
   247   },
   249   takeRequest: function(aId) {
   250     if (!this._requests || !this._requests[aId]) {
   251       return null;
   252     }
   253     let request = this._requests[aId];
   254     delete this._requests[aId];
   255     return request;
   256   },
   258   takePromiseResolver: function(aId) {
   259     // Delegates to getRequest() since the lookup table is agnostic about
   260     // storage.
   261     return this.takeRequest(aId);
   262   },
   264   _getRandomId: function() {
   265     return Cc["@mozilla.org/uuid-generator;1"]
   266              .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
   267   },
   269   createRequest: function() {
   270     return Services.DOMRequest.createRequest(this._window);
   271   },
   273   /**
   274    * createPromise() creates a new Promise, with `aPromiseInit` as the
   275    * PromiseInit callback. The promise constructor is obtained from the
   276    * reference to window owned by this DOMRequestIPCHelper.
   277    */
   278   createPromise: function(aPromiseInit) {
   279     return new this._window.Promise(aPromiseInit);
   280   },
   282   forEachRequest: function(aCallback) {
   283     if (!this._requests) {
   284       return;
   285     }
   287     Object.keys(this._requests).forEach((aKey) => {
   288       if (this.getRequest(aKey) instanceof this._window.DOMRequest) {
   289         aCallback(aKey);
   290       }
   291     });
   292   },
   294   forEachPromiseResolver: function(aCallback) {
   295     if (!this._requests) {
   296       return;
   297     }
   299     Object.keys(this._requests).forEach((aKey) => {
   300       if ("resolve" in this.getPromiseResolver(aKey) &&
   301           "reject" in this.getPromiseResolver(aKey)) {
   302         aCallback(aKey);
   303       }
   304     });
   305   },
   306 }

mercurial