michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: module.metadata = { michael@0: "stability": "experimental" michael@0: }; michael@0: michael@0: michael@0: const { Class } = require("./heritage"); michael@0: const { Observer, subscribe, unsubscribe, observe } = require("./observer"); michael@0: const { isWeak, WeakReference } = require("./reference"); michael@0: const method = require("../../method/core"); michael@0: michael@0: const unloadSubject = require('@loader/unload'); michael@0: const addonUnloadTopic = "sdk:loader:destroy"; michael@0: michael@0: michael@0: michael@0: const uninstall = method("disposable/uninstall"); michael@0: exports.uninstall = uninstall; michael@0: michael@0: michael@0: const shutdown = method("disposable/shutdown"); michael@0: exports.shutdown = shutdown; michael@0: michael@0: const disable = method("disposable/disable"); michael@0: exports.disable = disable; michael@0: michael@0: const upgrade = method("disposable/upgrade"); michael@0: exports.upgrade = upgrade; michael@0: michael@0: const downgrade = method("disposable/downgrade"); michael@0: exports.downgrade = downgrade; michael@0: michael@0: const unload = method("disposable/unload"); michael@0: exports.unload = unload; michael@0: michael@0: const dispose = method("disposable/dispose"); michael@0: exports.dispose = dispose; michael@0: dispose.define(Object, object => object.dispose()); michael@0: michael@0: michael@0: const setup = method("disposable/setup"); michael@0: exports.setup = setup; michael@0: setup.define(Object, (object, ...args) => object.setup(...args)); michael@0: michael@0: michael@0: // Set's up disposable instance. michael@0: const setupDisposable = disposable => { michael@0: subscribe(disposable, addonUnloadTopic, isWeak(disposable)); michael@0: }; michael@0: michael@0: // Tears down disposable instance. michael@0: const disposeDisposable = disposable => { michael@0: unsubscribe(disposable, addonUnloadTopic); michael@0: }; michael@0: michael@0: // Base type that takes care of disposing it's instances on add-on unload. michael@0: // Also makes sure to remove unload listener if it's already being disposed. michael@0: const Disposable = Class({ michael@0: implements: [Observer], michael@0: initialize: function(...args) { michael@0: // First setup instance before initializing it's disposal. If instance michael@0: // fails to initialize then there is no instance to be disposed at the michael@0: // unload. michael@0: setup(this, ...args); michael@0: setupDisposable(this); michael@0: }, michael@0: destroy: function(reason) { michael@0: // Destroying disposable removes unload handler so that attempt to dispose michael@0: // won't be made at unload & delegates to dispose. michael@0: disposeDisposable(this); michael@0: unload(this, reason); michael@0: }, michael@0: setup: function() { michael@0: // Implement your initialize logic here. michael@0: }, michael@0: dispose: function() { michael@0: // Implement your cleanup logic here. michael@0: } michael@0: }); michael@0: exports.Disposable = Disposable; michael@0: michael@0: // Disposable instances observe add-on unload notifications in michael@0: // order to trigger `unload` on them. michael@0: observe.define(Disposable, (disposable, subject, topic, data) => { michael@0: const isUnloadTopic = topic === addonUnloadTopic; michael@0: const isUnloadSubject = subject.wrappedJSObject === unloadSubject; michael@0: if (isUnloadTopic && isUnloadSubject) { michael@0: unsubscribe(disposable, topic); michael@0: unload(disposable); michael@0: } michael@0: }); michael@0: michael@0: const unloaders = { michael@0: destroy: dispose, michael@0: uninstall: uninstall, michael@0: shutdown: shutdown, michael@0: disable: disable, michael@0: upgrade: upgrade, michael@0: downgrade: downgrade michael@0: } michael@0: const unloaded = new WeakMap(); michael@0: unload.define(Disposable, (disposable, reason) => { michael@0: if (!unloaded.get(disposable)) { michael@0: unloaded.set(disposable, true); michael@0: // Pick an unload handler associated with an unload michael@0: // reason (falling back to destroy if not found) and michael@0: // delegate unloading to it. michael@0: const unload = unloaders[reason] || unloaders.destroy; michael@0: unload(disposable); michael@0: } michael@0: }); michael@0: michael@0: michael@0: // If add-on is disabled munally, it's being upgraded, downgraded michael@0: // or uniststalled `dispose` is invoked to undo any changes that michael@0: // has being done by it in this session. michael@0: disable.define(Disposable, dispose); michael@0: downgrade.define(Disposable, dispose); michael@0: upgrade.define(Disposable, dispose); michael@0: uninstall.define(Disposable, dispose); michael@0: michael@0: // If application is shut down no dispose is invoked as undo-ing michael@0: // changes made by instance is likely to just waste of resources & michael@0: // increase shutdown time. Although specefic components may choose michael@0: // to implement shutdown handler that does something better. michael@0: shutdown.define(Disposable, disposable => {}); michael@0: