lib/goog/disposable/disposable.js

1// Copyright 2005 The Closure Library Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS-IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/**
16 * @fileoverview Implements the disposable interface. The dispose method is used
17 * to clean up references and resources.
18 * @author arv@google.com (Erik Arvidsson)
19 */
20
21
22goog.provide('goog.Disposable');
23/** @suppress {extraProvide} */
24goog.provide('goog.dispose');
25/** @suppress {extraProvide} */
26goog.provide('goog.disposeAll');
27
28goog.require('goog.disposable.IDisposable');
29
30
31
32/**
33 * Class that provides the basic implementation for disposable objects. If your
34 * class holds one or more references to COM objects, DOM nodes, or other
35 * disposable objects, it should extend this class or implement the disposable
36 * interface (defined in goog.disposable.IDisposable).
37 * @constructor
38 * @implements {goog.disposable.IDisposable}
39 */
40goog.Disposable = function() {
41 if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
42 if (goog.Disposable.INCLUDE_STACK_ON_CREATION) {
43 this.creationStack = new Error().stack;
44 }
45 goog.Disposable.instances_[goog.getUid(this)] = this;
46 }
47 // Support sealing
48 this.disposed_ = this.disposed_;
49 this.onDisposeCallbacks_ = this.onDisposeCallbacks_;
50};
51
52
53/**
54 * @enum {number} Different monitoring modes for Disposable.
55 */
56goog.Disposable.MonitoringMode = {
57 /**
58 * No monitoring.
59 */
60 OFF: 0,
61 /**
62 * Creating and disposing the goog.Disposable instances is monitored. All
63 * disposable objects need to call the {@code goog.Disposable} base
64 * constructor. The PERMANENT mode must be switched on before creating any
65 * goog.Disposable instances.
66 */
67 PERMANENT: 1,
68 /**
69 * INTERACTIVE mode can be switched on and off on the fly without producing
70 * errors. It also doesn't warn if the disposable objects don't call the
71 * {@code goog.Disposable} base constructor.
72 */
73 INTERACTIVE: 2
74};
75
76
77/**
78 * @define {number} The monitoring mode of the goog.Disposable
79 * instances. Default is OFF. Switching on the monitoring is only
80 * recommended for debugging because it has a significant impact on
81 * performance and memory usage. If switched off, the monitoring code
82 * compiles down to 0 bytes.
83 */
84goog.define('goog.Disposable.MONITORING_MODE', 0);
85
86
87/**
88 * @define {boolean} Whether to attach creation stack to each created disposable
89 * instance; This is only relevant for when MonitoringMode != OFF.
90 */
91goog.define('goog.Disposable.INCLUDE_STACK_ON_CREATION', true);
92
93
94/**
95 * Maps the unique ID of every undisposed {@code goog.Disposable} object to
96 * the object itself.
97 * @type {!Object.<number, !goog.Disposable>}
98 * @private
99 */
100goog.Disposable.instances_ = {};
101
102
103/**
104 * @return {!Array.<!goog.Disposable>} All {@code goog.Disposable} objects that
105 * haven't been disposed of.
106 */
107goog.Disposable.getUndisposedObjects = function() {
108 var ret = [];
109 for (var id in goog.Disposable.instances_) {
110 if (goog.Disposable.instances_.hasOwnProperty(id)) {
111 ret.push(goog.Disposable.instances_[Number(id)]);
112 }
113 }
114 return ret;
115};
116
117
118/**
119 * Clears the registry of undisposed objects but doesn't dispose of them.
120 */
121goog.Disposable.clearUndisposedObjects = function() {
122 goog.Disposable.instances_ = {};
123};
124
125
126/**
127 * Whether the object has been disposed of.
128 * @type {boolean}
129 * @private
130 */
131goog.Disposable.prototype.disposed_ = false;
132
133
134/**
135 * Callbacks to invoke when this object is disposed.
136 * @type {Array.<!Function>}
137 * @private
138 */
139goog.Disposable.prototype.onDisposeCallbacks_;
140
141
142/**
143 * If monitoring the goog.Disposable instances is enabled, stores the creation
144 * stack trace of the Disposable instance.
145 * @const {string}
146 */
147goog.Disposable.prototype.creationStack;
148
149
150/**
151 * @return {boolean} Whether the object has been disposed of.
152 * @override
153 */
154goog.Disposable.prototype.isDisposed = function() {
155 return this.disposed_;
156};
157
158
159/**
160 * @return {boolean} Whether the object has been disposed of.
161 * @deprecated Use {@link #isDisposed} instead.
162 */
163goog.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed;
164
165
166/**
167 * Disposes of the object. If the object hasn't already been disposed of, calls
168 * {@link #disposeInternal}. Classes that extend {@code goog.Disposable} should
169 * override {@link #disposeInternal} in order to delete references to COM
170 * objects, DOM nodes, and other disposable objects. Reentrant.
171 *
172 * @return {void} Nothing.
173 * @override
174 */
175goog.Disposable.prototype.dispose = function() {
176 if (!this.disposed_) {
177 // Set disposed_ to true first, in case during the chain of disposal this
178 // gets disposed recursively.
179 this.disposed_ = true;
180 this.disposeInternal();
181 if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
182 var uid = goog.getUid(this);
183 if (goog.Disposable.MONITORING_MODE ==
184 goog.Disposable.MonitoringMode.PERMANENT &&
185 !goog.Disposable.instances_.hasOwnProperty(uid)) {
186 throw Error(this + ' did not call the goog.Disposable base ' +
187 'constructor or was disposed of after a clearUndisposedObjects ' +
188 'call');
189 }
190 delete goog.Disposable.instances_[uid];
191 }
192 }
193};
194
195
196/**
197 * Associates a disposable object with this object so that they will be disposed
198 * together.
199 * @param {goog.disposable.IDisposable} disposable that will be disposed when
200 * this object is disposed.
201 */
202goog.Disposable.prototype.registerDisposable = function(disposable) {
203 this.addOnDisposeCallback(goog.partial(goog.dispose, disposable));
204};
205
206
207/**
208 * Invokes a callback function when this object is disposed. Callbacks are
209 * invoked in the order in which they were added.
210 * @param {function(this:T):?} callback The callback function.
211 * @param {T=} opt_scope An optional scope to call the callback in.
212 * @template T
213 */
214goog.Disposable.prototype.addOnDisposeCallback = function(callback, opt_scope) {
215 if (!this.onDisposeCallbacks_) {
216 this.onDisposeCallbacks_ = [];
217 }
218
219 this.onDisposeCallbacks_.push(
220 goog.isDef(opt_scope) ? goog.bind(callback, opt_scope) : callback);
221};
222
223
224/**
225 * Deletes or nulls out any references to COM objects, DOM nodes, or other
226 * disposable objects. Classes that extend {@code goog.Disposable} should
227 * override this method.
228 * Not reentrant. To avoid calling it twice, it must only be called from the
229 * subclass' {@code disposeInternal} method. Everywhere else the public
230 * {@code dispose} method must be used.
231 * For example:
232 * <pre>
233 * mypackage.MyClass = function() {
234 * mypackage.MyClass.base(this, 'constructor');
235 * // Constructor logic specific to MyClass.
236 * ...
237 * };
238 * goog.inherits(mypackage.MyClass, goog.Disposable);
239 *
240 * mypackage.MyClass.prototype.disposeInternal = function() {
241 * // Dispose logic specific to MyClass.
242 * ...
243 * // Call superclass's disposeInternal at the end of the subclass's, like
244 * // in C++, to avoid hard-to-catch issues.
245 * mypackage.MyClass.base(this, 'disposeInternal');
246 * };
247 * </pre>
248 * @protected
249 */
250goog.Disposable.prototype.disposeInternal = function() {
251 if (this.onDisposeCallbacks_) {
252 while (this.onDisposeCallbacks_.length) {
253 this.onDisposeCallbacks_.shift()();
254 }
255 }
256};
257
258
259/**
260 * Returns True if we can verify the object is disposed.
261 * Calls {@code isDisposed} on the argument if it supports it. If obj
262 * is not an object with an isDisposed() method, return false.
263 * @param {*} obj The object to investigate.
264 * @return {boolean} True if we can verify the object is disposed.
265 */
266goog.Disposable.isDisposed = function(obj) {
267 if (obj && typeof obj.isDisposed == 'function') {
268 return obj.isDisposed();
269 }
270 return false;
271};
272
273
274/**
275 * Calls {@code dispose} on the argument if it supports it. If obj is not an
276 * object with a dispose() method, this is a no-op.
277 * @param {*} obj The object to dispose of.
278 */
279goog.dispose = function(obj) {
280 if (obj && typeof obj.dispose == 'function') {
281 obj.dispose();
282 }
283};
284
285
286/**
287 * Calls {@code dispose} on each member of the list that supports it. (If the
288 * member is an ArrayLike, then {@code goog.disposeAll()} will be called
289 * recursively on each of its members.) If the member is not an object with a
290 * {@code dispose()} method, then it is ignored.
291 * @param {...*} var_args The list.
292 */
293goog.disposeAll = function(var_args) {
294 for (var i = 0, len = arguments.length; i < len; ++i) {
295 var disposable = arguments[i];
296 if (goog.isArrayLike(disposable)) {
297 goog.disposeAll.apply(null, disposable);
298 } else {
299 goog.dispose(disposable);
300 }
301 }
302};