diff options
Diffstat (limited to 'toolkit/jetpack/sdk/core/disposable.js')
-rw-r--r-- | toolkit/jetpack/sdk/core/disposable.js | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/core/disposable.js b/toolkit/jetpack/sdk/core/disposable.js new file mode 100644 index 000000000..19f7eaa9f --- /dev/null +++ b/toolkit/jetpack/sdk/core/disposable.js @@ -0,0 +1,186 @@ +/* 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/. */ + +"use strict"; + +module.metadata = { + "stability": "experimental" +}; + +const { Class } = require("./heritage"); +const { Observer, subscribe, unsubscribe, observe } = require("./observer"); +const { isWeak } = require("./reference"); +const SDKWeakSet = require("../lang/weak-set"); + +const method = require("../../method/core"); + +const unloadSubject = require('@loader/unload'); +const addonUnloadTopic = "sdk:loader:destroy"; + +const uninstall = method("disposable/uninstall"); +exports.uninstall = uninstall; + +const shutdown = method("disposable/shutdown"); +exports.shutdown = shutdown; + +const disable = method("disposable/disable"); +exports.disable = disable; + +const upgrade = method("disposable/upgrade"); +exports.upgrade = upgrade; + +const downgrade = method("disposable/downgrade"); +exports.downgrade = downgrade; + +const unload = method("disposable/unload"); +exports.unload = unload; + +const dispose = method("disposable/dispose"); +exports.dispose = dispose; +dispose.define(Object, object => object.dispose()); + +const setup = method("disposable/setup"); +exports.setup = setup; +setup.define(Object, (object, ...args) => object.setup(...args)); + +// DisposablesUnloadObserver is the class which subscribe the +// Observer Service to be notified when the add-on loader is +// unloading to be able to dispose all the existent disposables. +const DisposablesUnloadObserver = Class({ + implements: [Observer], + initialize: function(...args) { + // Set of the non-weak disposables registered to be disposed. + this.disposables = new Set(); + // Target of the weak disposables registered to be disposed + // (and tracked on this target using the SDK weak-set module). + this.weakDisposables = {}; + }, + subscribe(disposable) { + if (isWeak(disposable)) { + SDKWeakSet.add(this.weakDisposables, disposable); + } else { + this.disposables.add(disposable); + } + }, + unsubscribe(disposable) { + if (isWeak(disposable)) { + SDKWeakSet.remove(this.weakDisposables, disposable); + } else { + this.disposables.delete(disposable); + } + }, + tryUnloadDisposable(disposable) { + try { + if (disposable) { + unload(disposable); + } + } catch(e) { + console.error("Error unloading a", + isWeak(disposable) ? "weak disposable" : "disposable", + disposable, e); + } + }, + unloadAll() { + // Remove all the subscribed disposables. + for (let disposable of this.disposables) { + this.tryUnloadDisposable(disposable); + } + + this.disposables.clear(); + + // Remove all the subscribed weak disposables. + for (let disposable of SDKWeakSet.iterator(this.weakDisposables)) { + this.tryUnloadDisposable(disposable); + } + + SDKWeakSet.clear(this.weakDisposables); + } +}); +const disposablesUnloadObserver = new DisposablesUnloadObserver(); + +// The DisposablesUnloadObserver instance is the only object which subscribes +// the Observer Service directly, it observes add-on unload notifications in +// order to trigger `unload` on all its subscribed disposables. +observe.define(DisposablesUnloadObserver, (obj, subject, topic, data) => { + const isUnloadTopic = topic === addonUnloadTopic; + const isUnloadSubject = subject.wrappedJSObject === unloadSubject; + if (isUnloadTopic && isUnloadSubject) { + unsubscribe(disposablesUnloadObserver, addonUnloadTopic); + disposablesUnloadObserver.unloadAll(); + } +}); + +subscribe(disposablesUnloadObserver, addonUnloadTopic, false); + +// Set's up disposable instance. +const setupDisposable = disposable => { + disposablesUnloadObserver.subscribe(disposable); +}; +exports.setupDisposable = setupDisposable; + +// Tears down disposable instance. +const disposeDisposable = disposable => { + disposablesUnloadObserver.unsubscribe(disposable); +}; +exports.disposeDisposable = disposeDisposable; + +// Base type that takes care of disposing it's instances on add-on unload. +// Also makes sure to remove unload listener if it's already being disposed. +const Disposable = Class({ + initialize: function(...args) { + // First setup instance before initializing it's disposal. If instance + // fails to initialize then there is no instance to be disposed at the + // unload. + setup(this, ...args); + setupDisposable(this); + }, + destroy: function(reason) { + // Destroying disposable removes unload handler so that attempt to dispose + // won't be made at unload & delegates to dispose. + disposeDisposable(this); + unload(this, reason); + }, + setup: function() { + // Implement your initialize logic here. + }, + dispose: function() { + // Implement your cleanup logic here. + } +}); +exports.Disposable = Disposable; + +const unloaders = { + destroy: dispose, + uninstall: uninstall, + shutdown: shutdown, + disable: disable, + upgrade: upgrade, + downgrade: downgrade +}; + +const unloaded = new WeakMap(); +unload.define(Disposable, (disposable, reason) => { + if (!unloaded.get(disposable)) { + unloaded.set(disposable, true); + // Pick an unload handler associated with an unload + // reason (falling back to destroy if not found) and + // delegate unloading to it. + const unload = unloaders[reason] || unloaders.destroy; + unload(disposable); + } +}); + +// If add-on is disabled manually, it's being upgraded, downgraded +// or uninstalled `dispose` is invoked to undo any changes that +// has being done by it in this session. +disable.define(Disposable, dispose); +downgrade.define(Disposable, dispose); +upgrade.define(Disposable, dispose); +uninstall.define(Disposable, dispose); + +// If application is shut down no dispose is invoked as undo-ing +// changes made by instance is likely to just waste of resources & +// increase shutdown time. Although specefic components may choose +// to implement shutdown handler that does something better. +shutdown.define(Disposable, disposable => {}); |