Bug 891986 - Keep the source ArrayBuffer to a decodeAudioData call alive until the...
[gecko.git] / dom / base / DOMRequestHelper.jsm
blob69ad623c5df8009137660dd3f3b9ba836ea6cb2f
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 need to release them
7  * when the window goes out of scope.
8  */
9 const Cu = Components.utils;
10 const Cc = Components.classes;
11 const Ci = Components.interfaces;
13 this.EXPORTED_SYMBOLS = ["DOMRequestIpcHelper"];
15 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
16 Cu.import("resource://gre/modules/Services.jsm");
18 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
19                                    "@mozilla.org/childprocessmessagemanager;1",
20                                    "nsIMessageListenerManager");
22 /**
23  * We use DOMRequestIpcHelperMessageListener to avoid leaking objects which
24  * "inherit" from DOMRequestIpcHelper.
25  *
26  * The issue is that the message manager will hold a strong ref to the message
27  * listener we register with it.  But we don't want to hold a strong ref to the
28  * DOMRequestIpcHelper object, because that object may be arbitrarily large.
29  *
30  * So instead the message manager holds a strong ref to the
31  * DOMRequestIpcHelperMessageListener, and that holds a /weak/ ref to its
32  * DOMRequestIpcHelper.
33  *
34  * Additionally, we want to unhook all of these message listeners when the
35  * appropriate window is destroyed.  We use DOMRequestIpcHelperMessageListener
36  * for this, too.
37  */
38 this.DOMRequestIpcHelperMessageListener = function(aHelper, aWindow, aMessages) {
39   this._weakHelper = Cu.getWeakReference(aHelper);
41   this._messages = aMessages;
42   this._messages.forEach(function(msgName) {
43     cpmm.addMessageListener(msgName, this);
44   }, this);
46   Services.obs.addObserver(this, "inner-window-destroyed", /* weakRef */ true);
48   // aWindow may be null; in that case, the DOMRequestIpcHelperMessageListener
49   // is not tied to a particular window and lives forever.
50   if (aWindow) {
51     let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
52                       .getInterface(Ci.nsIDOMWindowUtils);
53     this._innerWindowID = util.currentInnerWindowID;
54   }
57 DOMRequestIpcHelperMessageListener.prototype = {
58   QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
59                                          Ci.nsIObserver,
60                                          Ci.nsISupportsWeakReference]),
62   observe: function(aSubject, aTopic, aData) {
63     if (aTopic !== "inner-window-destroyed") {
64       return;
65     }
67     let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
68     if (wId != this._innerWindowID) {
69       return;
70     }
72     this.destroy();
73   },
75   receiveMessage: function(aMsg) {
76     let helper = this._weakHelper.get();
77     if (helper) {
78       helper.receiveMessage(aMsg);
79     } else {
80       this.destroy();
81     }
82   },
84   destroy: function() {
85     // DOMRequestIpcHelper.destroy() calls back into this function.
86     if (this._destroyed) {
87       return;
88     }
89     this._destroyed = true;
91     Services.obs.removeObserver(this, "inner-window-destroyed");
93     this._messages.forEach(function(msgName) {
94       cpmm.removeMessageListener(msgName, this);
95     }, this);
96     this._messages = null;
98     let helper = this._weakHelper.get();
99     if (helper) {
100       helper.destroyDOMRequestHelper();
101     }
102   }
105 this.DOMRequestIpcHelper = function DOMRequestIpcHelper() {
108 DOMRequestIpcHelper.prototype = {
109   /**
110    * An object which "inherits" from DOMRequestIpcHelper and declares its own
111    * queryInterface method MUST implement Ci.nsISupportsWeakReference.
112    */
113   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]),
115   initDOMRequestHelper: function(aWindow, aMessages) {
116     this._DOMRequestIpcHelperMessageListener =
117       new DOMRequestIpcHelperMessageListener(this, aWindow, aMessages);
119     this._window = aWindow;
120     this._requests = [];
121     this._id = this._getRandomId();
123     if (this._window) {
124       // We don't use this.innerWindowID, but other classes rely on it.
125       let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
126                              .getInterface(Ci.nsIDOMWindowUtils);
127       this.innerWindowID = util.currentInnerWindowID;
128     }
129   },
131   getRequestId: function(aRequest) {
132     let id = "id" + this._getRandomId();
133     this._requests[id] = aRequest;
134     return id;
135   },
137   getRequest: function(aId) {
138     if (this._requests[aId])
139       return this._requests[aId];
140   },
142   removeRequest: function(aId) {
143     if (this._requests[aId])
144       delete this._requests[aId];
145   },
147   takeRequest: function(aId) {
148     if (!this._requests[aId])
149       return null;
150     let request = this._requests[aId];
151     delete this._requests[aId];
152     return request;
153   },
155   _getRandomId: function() {
156     return Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
157   },
159   destroyDOMRequestHelper: function() {
160     // This function is re-entrant --
161     // DOMRequestIpcHelperMessageListener.destroy() calls back into this
162     // function, and this.uninit() may also call it.
163     if (this._destroyed) {
164       return;
165     }
166     this._destroyed = true;
168     this._DOMRequestIpcHelperMessageListener.destroy();
169     this._requests = [];
170     this._window = null;
172     if(this.uninit) {
173       this.uninit();
174     }
175   },
177   createRequest: function() {
178     return Services.DOMRequest.createRequest(this._window);
179   }