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 +}