summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/core/disposable.js
blob: 19f7eaa9fa80fa80240a3a6a4c038249b696f9f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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 => {});