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

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

mercurial