dom/base/DOMRequestHelper.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/base/DOMRequestHelper.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,306 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +/**
     1.9 + * Helper object for APIs that deal with DOMRequests and Promises.
    1.10 + * It allows objects inheriting from it to create and keep track of DOMRequests
    1.11 + * and Promises objects in the common scenario where requests are created in
    1.12 + * the child, handed out to content and delivered to the parent within an async
    1.13 + * message (containing the identifiers of these requests). The parent may send
    1.14 + * messages back as answers to different requests and the child will use this
    1.15 + * helper to get the right request object. This helper also takes care of
    1.16 + * releasing the requests objects when the window goes out of scope.
    1.17 + *
    1.18 + * DOMRequestIPCHelper also deals with message listeners, allowing to add them
    1.19 + * to the child side of frame and process message manager and removing them
    1.20 + * when needed.
    1.21 + */
    1.22 +const Cu = Components.utils;
    1.23 +const Cc = Components.classes;
    1.24 +const Ci = Components.interfaces;
    1.25 +const Cr = Components.results;
    1.26 +
    1.27 +this.EXPORTED_SYMBOLS = ["DOMRequestIpcHelper"];
    1.28 +
    1.29 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.30 +Cu.import("resource://gre/modules/Services.jsm");
    1.31 +
    1.32 +XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
    1.33 +                                   "@mozilla.org/childprocessmessagemanager;1",
    1.34 +                                   "nsIMessageListenerManager");
    1.35 +
    1.36 +this.DOMRequestIpcHelper = function DOMRequestIpcHelper() {
    1.37 +  // _listeners keeps a list of messages for which we added a listener and the
    1.38 +  // kind of listener that we added (strong or weak). It's an object of this
    1.39 +  // form:
    1.40 +  //  {
    1.41 +  //    "message1": true,
    1.42 +  //    "messagen": false
    1.43 +  //  }
    1.44 +  //
    1.45 +  // where each property is the name of the message and its value is a boolean
    1.46 +  // that indicates if the listener is weak or not.
    1.47 +  this._listeners = null;
    1.48 +  this._requests = null;
    1.49 +  this._window = null;
    1.50 +}
    1.51 +
    1.52 +DOMRequestIpcHelper.prototype = {
    1.53 +  /**
    1.54 +   * An object which "inherits" from DOMRequestIpcHelper and declares its own
    1.55 +   * queryInterface method MUST implement Ci.nsISupportsWeakReference.
    1.56 +   */
    1.57 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
    1.58 +                                         Ci.nsIObserver]),
    1.59 +
    1.60 +   /**
    1.61 +   *  'aMessages' is expected to be an array of either:
    1.62 +   *  - objects of this form:
    1.63 +   *    {
    1.64 +   *      name: "messageName",
    1.65 +   *      weakRef: false
    1.66 +   *    }
    1.67 +   *    where 'name' is the message identifier and 'weakRef' a boolean
    1.68 +   *    indicating if the listener should be a weak referred one or not.
    1.69 +   *
    1.70 +   *  - or only strings containing the message name, in which case the listener
    1.71 +   *    will be added as a strong reference by default.
    1.72 +   */
    1.73 +  addMessageListeners: function(aMessages) {
    1.74 +    if (!aMessages) {
    1.75 +      return;
    1.76 +    }
    1.77 +
    1.78 +    if (!this._listeners) {
    1.79 +      this._listeners = {};
    1.80 +    }
    1.81 +
    1.82 +    if (!Array.isArray(aMessages)) {
    1.83 +      aMessages = [aMessages];
    1.84 +    }
    1.85 +
    1.86 +    aMessages.forEach((aMsg) => {
    1.87 +      let name = aMsg.name || aMsg;
    1.88 +      // If the listener is already set and it is of the same type we just
    1.89 +      // bail out. If it is not of the same type, we throw an exception.
    1.90 +      if (this._listeners[name] != undefined) {
    1.91 +        if (!!aMsg.weakRef == this._listeners[name]) {
    1.92 +          return;
    1.93 +        } else {
    1.94 +          throw Cr.NS_ERROR_FAILURE;
    1.95 +        }
    1.96 +      }
    1.97 +
    1.98 +      aMsg.weakRef ? cpmm.addWeakMessageListener(name, this)
    1.99 +                   : cpmm.addMessageListener(name, this);
   1.100 +      this._listeners[name] = !!aMsg.weakRef;
   1.101 +    });
   1.102 +  },
   1.103 +
   1.104 +  /**
   1.105 +   * 'aMessages' is expected to be a string or an array of strings containing
   1.106 +   * the message names of the listeners to be removed.
   1.107 +   */
   1.108 +  removeMessageListeners: function(aMessages) {
   1.109 +    if (!this._listeners || !aMessages) {
   1.110 +      return;
   1.111 +    }
   1.112 +
   1.113 +    if (!Array.isArray(aMessages)) {
   1.114 +      aMessages = [aMessages];
   1.115 +    }
   1.116 +
   1.117 +    aMessages.forEach((aName) => {
   1.118 +      if (this._listeners[aName] == undefined) {
   1.119 +        return;
   1.120 +      }
   1.121 +
   1.122 +      this._listeners[aName] ? cpmm.removeWeakMessageListener(aName, this)
   1.123 +                             : cpmm.removeMessageListener(aName, this);
   1.124 +      delete this._listeners[aName];
   1.125 +    });
   1.126 +  },
   1.127 +
   1.128 +  /**
   1.129 +   * Initialize the helper adding the corresponding listeners to the messages
   1.130 +   * provided as the second parameter.
   1.131 +   *
   1.132 +   * 'aMessages' is expected to be an array of either:
   1.133 +   *
   1.134 +   *  - objects of this form:
   1.135 +   *    {
   1.136 +   *      name: 'messageName',
   1.137 +   *      weakRef: false
   1.138 +   *    }
   1.139 +   *    where 'name' is the message identifier and 'weakRef' a boolean
   1.140 +   *    indicating if the listener should be a weak referred one or not.
   1.141 +   *
   1.142 +   *  - or only strings containing the message name, in which case the listener
   1.143 +   *    will be added as a strong referred one by default.
   1.144 +   */
   1.145 +  initDOMRequestHelper: function(aWindow, aMessages) {
   1.146 +    // Query our required interfaces to force a fast fail if they are not
   1.147 +    // provided. These calls will throw if the interface is not available.
   1.148 +    this.QueryInterface(Ci.nsISupportsWeakReference);
   1.149 +    this.QueryInterface(Ci.nsIObserver);
   1.150 +
   1.151 +    if (aMessages) {
   1.152 +      this.addMessageListeners(aMessages);
   1.153 +    }
   1.154 +
   1.155 +    this._id = this._getRandomId();
   1.156 +
   1.157 +    this._window = aWindow;
   1.158 +    if (this._window) {
   1.159 +      // We don't use this.innerWindowID, but other classes rely on it.
   1.160 +      let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
   1.161 +                             .getInterface(Ci.nsIDOMWindowUtils);
   1.162 +      this.innerWindowID = util.currentInnerWindowID;
   1.163 +    }
   1.164 +
   1.165 +    this._destroyed = false;
   1.166 +
   1.167 +    Services.obs.addObserver(this, "inner-window-destroyed",
   1.168 +                             /* weak-ref */ true);
   1.169 +  },
   1.170 +
   1.171 +  destroyDOMRequestHelper: function() {
   1.172 +    if (this._destroyed) {
   1.173 +      return;
   1.174 +    }
   1.175 +
   1.176 +    this._destroyed = true;
   1.177 +
   1.178 +    Services.obs.removeObserver(this, "inner-window-destroyed");
   1.179 +
   1.180 +    if (this._listeners) {
   1.181 +      Object.keys(this._listeners).forEach((aName) => {
   1.182 +        this._listeners[aName] ? cpmm.removeWeakMessageListener(aName, this)
   1.183 +                               : cpmm.removeMessageListener(aName, this);
   1.184 +        delete this._listeners[aName];
   1.185 +      });
   1.186 +    }
   1.187 +
   1.188 +    this._listeners = null;
   1.189 +    this._requests = null;
   1.190 +
   1.191 +    // Objects inheriting from DOMRequestIPCHelper may have an uninit function.
   1.192 +    if (this.uninit) {
   1.193 +      this.uninit();
   1.194 +    }
   1.195 +
   1.196 +    this._window = null;
   1.197 +  },
   1.198 +
   1.199 +  observe: function(aSubject, aTopic, aData) {
   1.200 +    if (aTopic !== "inner-window-destroyed") {
   1.201 +      return;
   1.202 +    }
   1.203 +
   1.204 +    let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
   1.205 +    if (wId != this.innerWindowID) {
   1.206 +      return;
   1.207 +    }
   1.208 +
   1.209 +    this.destroyDOMRequestHelper();
   1.210 +  },
   1.211 +
   1.212 +  getRequestId: function(aRequest) {
   1.213 +    if (!this._requests) {
   1.214 +      this._requests = {};
   1.215 +    }
   1.216 +
   1.217 +    let id = "id" + this._getRandomId();
   1.218 +    this._requests[id] = aRequest;
   1.219 +    return id;
   1.220 +  },
   1.221 +
   1.222 +  getPromiseResolverId: function(aPromiseResolver) {
   1.223 +    // Delegates to getRequest() since the lookup table is agnostic about
   1.224 +    // storage.
   1.225 +    return this.getRequestId(aPromiseResolver);
   1.226 +  },
   1.227 +
   1.228 +  getRequest: function(aId) {
   1.229 +    if (this._requests && this._requests[aId]) {
   1.230 +      return this._requests[aId];
   1.231 +    }
   1.232 +  },
   1.233 +
   1.234 +  getPromiseResolver: function(aId) {
   1.235 +    // Delegates to getRequest() since the lookup table is agnostic about
   1.236 +    // storage.
   1.237 +    return this.getRequest(aId);
   1.238 +  },
   1.239 +
   1.240 +  removeRequest: function(aId) {
   1.241 +    if (this._requests && this._requests[aId]) {
   1.242 +      delete this._requests[aId];
   1.243 +    }
   1.244 +  },
   1.245 +
   1.246 +  removePromiseResolver: function(aId) {
   1.247 +    // Delegates to getRequest() since the lookup table is agnostic about
   1.248 +    // storage.
   1.249 +    this.removeRequest(aId);
   1.250 +  },
   1.251 +
   1.252 +  takeRequest: function(aId) {
   1.253 +    if (!this._requests || !this._requests[aId]) {
   1.254 +      return null;
   1.255 +    }
   1.256 +    let request = this._requests[aId];
   1.257 +    delete this._requests[aId];
   1.258 +    return request;
   1.259 +  },
   1.260 +
   1.261 +  takePromiseResolver: function(aId) {
   1.262 +    // Delegates to getRequest() since the lookup table is agnostic about
   1.263 +    // storage.
   1.264 +    return this.takeRequest(aId);
   1.265 +  },
   1.266 +
   1.267 +  _getRandomId: function() {
   1.268 +    return Cc["@mozilla.org/uuid-generator;1"]
   1.269 +             .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
   1.270 +  },
   1.271 +
   1.272 +  createRequest: function() {
   1.273 +    return Services.DOMRequest.createRequest(this._window);
   1.274 +  },
   1.275 +
   1.276 +  /**
   1.277 +   * createPromise() creates a new Promise, with `aPromiseInit` as the
   1.278 +   * PromiseInit callback. The promise constructor is obtained from the
   1.279 +   * reference to window owned by this DOMRequestIPCHelper.
   1.280 +   */
   1.281 +  createPromise: function(aPromiseInit) {
   1.282 +    return new this._window.Promise(aPromiseInit);
   1.283 +  },
   1.284 +
   1.285 +  forEachRequest: function(aCallback) {
   1.286 +    if (!this._requests) {
   1.287 +      return;
   1.288 +    }
   1.289 +
   1.290 +    Object.keys(this._requests).forEach((aKey) => {
   1.291 +      if (this.getRequest(aKey) instanceof this._window.DOMRequest) {
   1.292 +        aCallback(aKey);
   1.293 +      }
   1.294 +    });
   1.295 +  },
   1.296 +
   1.297 +  forEachPromiseResolver: function(aCallback) {
   1.298 +    if (!this._requests) {
   1.299 +      return;
   1.300 +    }
   1.301 +
   1.302 +    Object.keys(this._requests).forEach((aKey) => {
   1.303 +      if ("resolve" in this.getPromiseResolver(aKey) &&
   1.304 +          "reject" in this.getPromiseResolver(aKey)) {
   1.305 +        aCallback(aKey);
   1.306 +      }
   1.307 +    });
   1.308 +  },
   1.309 +}

mercurial