mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-02 15:15:23 +00:00
5227147d52
They're unnecessary because the entire object is later nulled out. This in turn avoids creating 3,108 shapes when starting up B2G, which is about 73 KiB's worth.
316 lines
9.2 KiB
JavaScript
316 lines
9.2 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/**
|
|
* Helper object for APIs that deal with DOMRequests and Promises.
|
|
* It allows objects inheriting from it to create and keep track of DOMRequests
|
|
* and Promises objects in the common scenario where requests are created in
|
|
* the child, handed out to content and delivered to the parent within an async
|
|
* message (containing the identifiers of these requests). The parent may send
|
|
* messages back as answers to different requests and the child will use this
|
|
* helper to get the right request object. This helper also takes care of
|
|
* releasing the requests objects when the window goes out of scope.
|
|
*
|
|
* DOMRequestIPCHelper also deals with message listeners, allowing to add them
|
|
* to the child side of frame and process message manager and removing them
|
|
* when needed.
|
|
*/
|
|
const Cu = Components.utils;
|
|
const Cc = Components.classes;
|
|
const Ci = Components.interfaces;
|
|
const Cr = Components.results;
|
|
|
|
this.EXPORTED_SYMBOLS = ["DOMRequestIpcHelper"];
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
|
|
"@mozilla.org/childprocessmessagemanager;1",
|
|
"nsIMessageListenerManager");
|
|
|
|
this.DOMRequestIpcHelper = function DOMRequestIpcHelper() {
|
|
// _listeners keeps a list of messages for which we added a listener and the
|
|
// kind of listener that we added (strong or weak). It's an object of this
|
|
// form:
|
|
// {
|
|
// "message1": true,
|
|
// "messagen": false
|
|
// }
|
|
//
|
|
// where each property is the name of the message and its value is a boolean
|
|
// that indicates if the listener is weak or not.
|
|
this._listeners = null;
|
|
this._requests = null;
|
|
this._window = null;
|
|
}
|
|
|
|
DOMRequestIpcHelper.prototype = {
|
|
/**
|
|
* An object which "inherits" from DOMRequestIpcHelper and declares its own
|
|
* queryInterface method MUST implement Ci.nsISupportsWeakReference.
|
|
*/
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
|
|
Ci.nsIObserver]),
|
|
|
|
/**
|
|
* 'aMessages' is expected to be an array of either:
|
|
* - objects of this form:
|
|
* {
|
|
* name: "messageName",
|
|
* weakRef: false
|
|
* }
|
|
* where 'name' is the message identifier and 'weakRef' a boolean
|
|
* indicating if the listener should be a weak referred one or not.
|
|
*
|
|
* - or only strings containing the message name, in which case the listener
|
|
* will be added as a strong reference by default.
|
|
*/
|
|
addMessageListeners: function(aMessages) {
|
|
if (!aMessages) {
|
|
return;
|
|
}
|
|
|
|
if (!this._listeners) {
|
|
this._listeners = {};
|
|
}
|
|
|
|
if (!Array.isArray(aMessages)) {
|
|
aMessages = [aMessages];
|
|
}
|
|
|
|
aMessages.forEach((aMsg) => {
|
|
let name = aMsg.name || aMsg;
|
|
// If the listener is already set and it is of the same type we just
|
|
// increase the count and bail out. If it is not of the same type,
|
|
// we throw an exception.
|
|
if (this._listeners[name] != undefined) {
|
|
if (!!aMsg.weakRef == this._listeners[name].weakRef) {
|
|
this._listeners[name].count++;
|
|
return;
|
|
} else {
|
|
throw Cr.NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
aMsg.weakRef ? cpmm.addWeakMessageListener(name, this)
|
|
: cpmm.addMessageListener(name, this);
|
|
this._listeners[name] = {
|
|
weakRef: !!aMsg.weakRef,
|
|
count: 1
|
|
};
|
|
});
|
|
},
|
|
|
|
/**
|
|
* 'aMessages' is expected to be a string or an array of strings containing
|
|
* the message names of the listeners to be removed.
|
|
*/
|
|
removeMessageListeners: function(aMessages) {
|
|
if (!this._listeners || !aMessages) {
|
|
return;
|
|
}
|
|
|
|
if (!Array.isArray(aMessages)) {
|
|
aMessages = [aMessages];
|
|
}
|
|
|
|
aMessages.forEach((aName) => {
|
|
if (this._listeners[aName] == undefined) {
|
|
return;
|
|
}
|
|
|
|
// Only remove the listener really when we don't have anybody that could
|
|
// be waiting on a message.
|
|
if (!--this._listeners[aName].count) {
|
|
this._listeners[aName].weakRef ?
|
|
cpmm.removeWeakMessageListener(aName, this)
|
|
: cpmm.removeMessageListener(aName, this);
|
|
delete this._listeners[aName];
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Initialize the helper adding the corresponding listeners to the messages
|
|
* provided as the second parameter.
|
|
*
|
|
* 'aMessages' is expected to be an array of either:
|
|
*
|
|
* - objects of this form:
|
|
* {
|
|
* name: 'messageName',
|
|
* weakRef: false
|
|
* }
|
|
* where 'name' is the message identifier and 'weakRef' a boolean
|
|
* indicating if the listener should be a weak referred one or not.
|
|
*
|
|
* - or only strings containing the message name, in which case the listener
|
|
* will be added as a strong referred one by default.
|
|
*/
|
|
initDOMRequestHelper: function(aWindow, aMessages) {
|
|
// Query our required interfaces to force a fast fail if they are not
|
|
// provided. These calls will throw if the interface is not available.
|
|
this.QueryInterface(Ci.nsISupportsWeakReference);
|
|
this.QueryInterface(Ci.nsIObserver);
|
|
|
|
if (aMessages) {
|
|
this.addMessageListeners(aMessages);
|
|
}
|
|
|
|
this._id = this._getRandomId();
|
|
|
|
this._window = aWindow;
|
|
if (this._window) {
|
|
// We don't use this.innerWindowID, but other classes rely on it.
|
|
let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
|
this.innerWindowID = util.currentInnerWindowID;
|
|
}
|
|
|
|
this._destroyed = false;
|
|
|
|
Services.obs.addObserver(this, "inner-window-destroyed",
|
|
/* weak-ref */ true);
|
|
},
|
|
|
|
destroyDOMRequestHelper: function() {
|
|
if (this._destroyed) {
|
|
return;
|
|
}
|
|
|
|
this._destroyed = true;
|
|
|
|
Services.obs.removeObserver(this, "inner-window-destroyed");
|
|
|
|
if (this._listeners) {
|
|
Object.keys(this._listeners).forEach((aName) => {
|
|
this._listeners[aName].weakRef ? cpmm.removeWeakMessageListener(aName, this)
|
|
: cpmm.removeMessageListener(aName, this);
|
|
});
|
|
}
|
|
|
|
this._listeners = null;
|
|
this._requests = null;
|
|
|
|
// Objects inheriting from DOMRequestIPCHelper may have an uninit function.
|
|
if (this.uninit) {
|
|
this.uninit();
|
|
}
|
|
|
|
this._window = null;
|
|
},
|
|
|
|
observe: function(aSubject, aTopic, aData) {
|
|
if (aTopic !== "inner-window-destroyed") {
|
|
return;
|
|
}
|
|
|
|
let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
|
if (wId != this.innerWindowID) {
|
|
return;
|
|
}
|
|
|
|
this.destroyDOMRequestHelper();
|
|
},
|
|
|
|
getRequestId: function(aRequest) {
|
|
if (!this._requests) {
|
|
this._requests = {};
|
|
}
|
|
|
|
let id = "id" + this._getRandomId();
|
|
this._requests[id] = aRequest;
|
|
return id;
|
|
},
|
|
|
|
getPromiseResolverId: function(aPromiseResolver) {
|
|
// Delegates to getRequest() since the lookup table is agnostic about
|
|
// storage.
|
|
return this.getRequestId(aPromiseResolver);
|
|
},
|
|
|
|
getRequest: function(aId) {
|
|
if (this._requests && this._requests[aId]) {
|
|
return this._requests[aId];
|
|
}
|
|
},
|
|
|
|
getPromiseResolver: function(aId) {
|
|
// Delegates to getRequest() since the lookup table is agnostic about
|
|
// storage.
|
|
return this.getRequest(aId);
|
|
},
|
|
|
|
removeRequest: function(aId) {
|
|
if (this._requests && this._requests[aId]) {
|
|
delete this._requests[aId];
|
|
}
|
|
},
|
|
|
|
removePromiseResolver: function(aId) {
|
|
// Delegates to getRequest() since the lookup table is agnostic about
|
|
// storage.
|
|
this.removeRequest(aId);
|
|
},
|
|
|
|
takeRequest: function(aId) {
|
|
if (!this._requests || !this._requests[aId]) {
|
|
return null;
|
|
}
|
|
let request = this._requests[aId];
|
|
delete this._requests[aId];
|
|
return request;
|
|
},
|
|
|
|
takePromiseResolver: function(aId) {
|
|
// Delegates to getRequest() since the lookup table is agnostic about
|
|
// storage.
|
|
return this.takeRequest(aId);
|
|
},
|
|
|
|
_getRandomId: function() {
|
|
return Cc["@mozilla.org/uuid-generator;1"]
|
|
.getService(Ci.nsIUUIDGenerator).generateUUID().toString();
|
|
},
|
|
|
|
createRequest: function() {
|
|
return Services.DOMRequest.createRequest(this._window);
|
|
},
|
|
|
|
/**
|
|
* createPromise() creates a new Promise, with `aPromiseInit` as the
|
|
* PromiseInit callback. The promise constructor is obtained from the
|
|
* reference to window owned by this DOMRequestIPCHelper.
|
|
*/
|
|
createPromise: function(aPromiseInit) {
|
|
return new this._window.Promise(aPromiseInit);
|
|
},
|
|
|
|
forEachRequest: function(aCallback) {
|
|
if (!this._requests) {
|
|
return;
|
|
}
|
|
|
|
Object.keys(this._requests).forEach((aKey) => {
|
|
if (this.getRequest(aKey) instanceof this._window.DOMRequest) {
|
|
aCallback(aKey);
|
|
}
|
|
});
|
|
},
|
|
|
|
forEachPromiseResolver: function(aCallback) {
|
|
if (!this._requests) {
|
|
return;
|
|
}
|
|
|
|
Object.keys(this._requests).forEach((aKey) => {
|
|
if ("resolve" in this.getPromiseResolver(aKey) &&
|
|
"reject" in this.getPromiseResolver(aKey)) {
|
|
aCallback(aKey);
|
|
}
|
|
});
|
|
},
|
|
}
|