|
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 |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 /** |
|
6 * This file contains middleware to reconcile state of AddonManager for |
|
7 * purposes of tracking events for Sync. The content in this file exists |
|
8 * because AddonManager does not have a getChangesSinceX() API and adding |
|
9 * that functionality properly was deemed too time-consuming at the time |
|
10 * add-on sync was originally written. If/when AddonManager adds this API, |
|
11 * this file can go away and the add-ons engine can be rewritten to use it. |
|
12 * |
|
13 * It was decided to have this tracking functionality exist in a separate |
|
14 * standalone file so it could be more easily understood, tested, and |
|
15 * hopefully ported. |
|
16 */ |
|
17 |
|
18 "use strict"; |
|
19 |
|
20 const Cu = Components.utils; |
|
21 |
|
22 Cu.import("resource://gre/modules/Log.jsm"); |
|
23 Cu.import("resource://services-sync/util.js"); |
|
24 Cu.import("resource://gre/modules/AddonManager.jsm"); |
|
25 |
|
26 const DEFAULT_STATE_FILE = "addonsreconciler"; |
|
27 |
|
28 this.CHANGE_INSTALLED = 1; |
|
29 this.CHANGE_UNINSTALLED = 2; |
|
30 this.CHANGE_ENABLED = 3; |
|
31 this.CHANGE_DISABLED = 4; |
|
32 |
|
33 this.EXPORTED_SYMBOLS = ["AddonsReconciler", "CHANGE_INSTALLED", |
|
34 "CHANGE_UNINSTALLED", "CHANGE_ENABLED", |
|
35 "CHANGE_DISABLED"]; |
|
36 /** |
|
37 * Maintains state of add-ons. |
|
38 * |
|
39 * State is maintained in 2 data structures, an object mapping add-on IDs |
|
40 * to metadata and an array of changes over time. The object mapping can be |
|
41 * thought of as a minimal copy of data from AddonManager which is needed for |
|
42 * Sync. The array is effectively a log of changes over time. |
|
43 * |
|
44 * The data structures are persisted to disk by serializing to a JSON file in |
|
45 * the current profile. The data structures are updated by 2 mechanisms. First, |
|
46 * they can be refreshed from the global state of the AddonManager. This is a |
|
47 * sure-fire way of ensuring the reconciler is up to date. Second, the |
|
48 * reconciler adds itself as an AddonManager listener. When it receives change |
|
49 * notifications, it updates its internal state incrementally. |
|
50 * |
|
51 * The internal state is persisted to a JSON file in the profile directory. |
|
52 * |
|
53 * An instance of this is bound to an AddonsEngine instance. In reality, it |
|
54 * likely exists as a singleton. To AddonsEngine, it functions as a store and |
|
55 * an entity which emits events for tracking. |
|
56 * |
|
57 * The usage pattern for instances of this class is: |
|
58 * |
|
59 * let reconciler = new AddonsReconciler(); |
|
60 * reconciler.loadState(null, function(error) { ... }); |
|
61 * |
|
62 * // At this point, your instance should be ready to use. |
|
63 * |
|
64 * When you are finished with the instance, please call: |
|
65 * |
|
66 * reconciler.stopListening(); |
|
67 * reconciler.saveState(...); |
|
68 * |
|
69 * There are 2 classes of listeners in the AddonManager: AddonListener and |
|
70 * InstallListener. This class is a listener for both (member functions just |
|
71 * get called directly). |
|
72 * |
|
73 * When an add-on is installed, listeners are called in the following order: |
|
74 * |
|
75 * IL.onInstallStarted, AL.onInstalling, IL.onInstallEnded, AL.onInstalled |
|
76 * |
|
77 * For non-restartless add-ons, an application restart may occur between |
|
78 * IL.onInstallEnded and AL.onInstalled. Unfortunately, Sync likely will |
|
79 * not be loaded when AL.onInstalled is fired shortly after application |
|
80 * start, so it won't see this event. Therefore, for add-ons requiring a |
|
81 * restart, Sync treats the IL.onInstallEnded event as good enough to |
|
82 * indicate an install. For restartless add-ons, Sync assumes AL.onInstalled |
|
83 * will follow shortly after IL.onInstallEnded and thus it ignores |
|
84 * IL.onInstallEnded. |
|
85 * |
|
86 * The listeners can also see events related to the download of the add-on. |
|
87 * This class isn't interested in those. However, there are failure events, |
|
88 * IL.onDownloadFailed and IL.onDownloadCanceled which get called if a |
|
89 * download doesn't complete successfully. |
|
90 * |
|
91 * For uninstalls, we see AL.onUninstalling then AL.onUninstalled. Like |
|
92 * installs, the events could be separated by an application restart and Sync |
|
93 * may not see the onUninstalled event. Again, if we require a restart, we |
|
94 * react to onUninstalling. If not, we assume we'll get onUninstalled. |
|
95 * |
|
96 * Enabling and disabling work by sending: |
|
97 * |
|
98 * AL.onEnabling, AL.onEnabled |
|
99 * AL.onDisabling, AL.onDisabled |
|
100 * |
|
101 * Again, they may be separated by a restart, so we heed the requiresRestart |
|
102 * flag. |
|
103 * |
|
104 * Actions can be undone. All undoable actions notify the same |
|
105 * AL.onOperationCancelled event. We treat this event like any other. |
|
106 * |
|
107 * Restartless add-ons have interesting behavior during uninstall. These |
|
108 * add-ons are first disabled then they are actually uninstalled. So, we will |
|
109 * see AL.onDisabling and AL.onDisabled. The onUninstalling and onUninstalled |
|
110 * events only come after the Addon Manager is closed or another view is |
|
111 * switched to. In the case of Sync performing the uninstall, the uninstall |
|
112 * events will occur immediately. However, we still see disabling events and |
|
113 * heed them like they were normal. In the end, the state is proper. |
|
114 */ |
|
115 this.AddonsReconciler = function AddonsReconciler() { |
|
116 this._log = Log.repository.getLogger("Sync.AddonsReconciler"); |
|
117 let level = Svc.Prefs.get("log.logger.addonsreconciler", "Debug"); |
|
118 this._log.level = Log.Level[level]; |
|
119 |
|
120 Svc.Obs.add("xpcom-shutdown", this.stopListening, this); |
|
121 }; |
|
122 AddonsReconciler.prototype = { |
|
123 /** Flag indicating whether we are listening to AddonManager events. */ |
|
124 _listening: false, |
|
125 |
|
126 /** |
|
127 * Whether state has been loaded from a file. |
|
128 * |
|
129 * State is loaded on demand if an operation requires it. |
|
130 */ |
|
131 _stateLoaded: false, |
|
132 |
|
133 /** |
|
134 * Define this as false if the reconciler should not persist state |
|
135 * to disk when handling events. |
|
136 * |
|
137 * This allows test code to avoid spinning to write during observer |
|
138 * notifications and xpcom shutdown, which appears to cause hangs on WinXP |
|
139 * (Bug 873861). |
|
140 */ |
|
141 _shouldPersist: true, |
|
142 |
|
143 /** Log logger instance */ |
|
144 _log: null, |
|
145 |
|
146 /** |
|
147 * Container for add-on metadata. |
|
148 * |
|
149 * Keys are add-on IDs. Values are objects which describe the state of the |
|
150 * add-on. This is a minimal mirror of data that can be queried from |
|
151 * AddonManager. In some cases, we retain data longer than AddonManager. |
|
152 */ |
|
153 _addons: {}, |
|
154 |
|
155 /** |
|
156 * List of add-on changes over time. |
|
157 * |
|
158 * Each element is an array of [time, change, id]. |
|
159 */ |
|
160 _changes: [], |
|
161 |
|
162 /** |
|
163 * Objects subscribed to changes made to this instance. |
|
164 */ |
|
165 _listeners: [], |
|
166 |
|
167 /** |
|
168 * Accessor for add-ons in this object. |
|
169 * |
|
170 * Returns an object mapping add-on IDs to objects containing metadata. |
|
171 */ |
|
172 get addons() { |
|
173 this._ensureStateLoaded(); |
|
174 return this._addons; |
|
175 }, |
|
176 |
|
177 /** |
|
178 * Load reconciler state from a file. |
|
179 * |
|
180 * The path is relative to the weave directory in the profile. If no |
|
181 * path is given, the default one is used. |
|
182 * |
|
183 * If the file does not exist or there was an error parsing the file, the |
|
184 * state will be transparently defined as empty. |
|
185 * |
|
186 * @param path |
|
187 * Path to load. ".json" is appended automatically. If not defined, |
|
188 * a default path will be consulted. |
|
189 * @param callback |
|
190 * Callback to be executed upon file load. The callback receives a |
|
191 * truthy error argument signifying whether an error occurred and a |
|
192 * boolean indicating whether data was loaded. |
|
193 */ |
|
194 loadState: function loadState(path, callback) { |
|
195 let file = path || DEFAULT_STATE_FILE; |
|
196 Utils.jsonLoad(file, this, function(json) { |
|
197 this._addons = {}; |
|
198 this._changes = []; |
|
199 |
|
200 if (!json) { |
|
201 this._log.debug("No data seen in loaded file: " + file); |
|
202 if (callback) { |
|
203 callback(null, false); |
|
204 } |
|
205 |
|
206 return; |
|
207 } |
|
208 |
|
209 let version = json.version; |
|
210 if (!version || version != 1) { |
|
211 this._log.error("Could not load JSON file because version not " + |
|
212 "supported: " + version); |
|
213 if (callback) { |
|
214 callback(null, false); |
|
215 } |
|
216 |
|
217 return; |
|
218 } |
|
219 |
|
220 this._addons = json.addons; |
|
221 for each (let record in this._addons) { |
|
222 record.modified = new Date(record.modified); |
|
223 } |
|
224 |
|
225 for each (let [time, change, id] in json.changes) { |
|
226 this._changes.push([new Date(time), change, id]); |
|
227 } |
|
228 |
|
229 if (callback) { |
|
230 callback(null, true); |
|
231 } |
|
232 }); |
|
233 }, |
|
234 |
|
235 /** |
|
236 * Saves the current state to a file in the local profile. |
|
237 * |
|
238 * @param path |
|
239 * String path in profile to save to. If not defined, the default |
|
240 * will be used. |
|
241 * @param callback |
|
242 * Function to be invoked on save completion. No parameters will be |
|
243 * passed to callback. |
|
244 */ |
|
245 saveState: function saveState(path, callback) { |
|
246 let file = path || DEFAULT_STATE_FILE; |
|
247 let state = {version: 1, addons: {}, changes: []}; |
|
248 |
|
249 for (let [id, record] in Iterator(this._addons)) { |
|
250 state.addons[id] = {}; |
|
251 for (let [k, v] in Iterator(record)) { |
|
252 if (k == "modified") { |
|
253 state.addons[id][k] = v.getTime(); |
|
254 } |
|
255 else { |
|
256 state.addons[id][k] = v; |
|
257 } |
|
258 } |
|
259 } |
|
260 |
|
261 for each (let [time, change, id] in this._changes) { |
|
262 state.changes.push([time.getTime(), change, id]); |
|
263 } |
|
264 |
|
265 this._log.info("Saving reconciler state to file: " + file); |
|
266 Utils.jsonSave(file, this, state, callback); |
|
267 }, |
|
268 |
|
269 /** |
|
270 * Registers a change listener with this instance. |
|
271 * |
|
272 * Change listeners are called every time a change is recorded. The listener |
|
273 * is an object with the function "changeListener" that takes 3 arguments, |
|
274 * the Date at which the change happened, the type of change (a CHANGE_* |
|
275 * constant), and the add-on state object reflecting the current state of |
|
276 * the add-on at the time of the change. |
|
277 * |
|
278 * @param listener |
|
279 * Object containing changeListener function. |
|
280 */ |
|
281 addChangeListener: function addChangeListener(listener) { |
|
282 if (this._listeners.indexOf(listener) == -1) { |
|
283 this._log.debug("Adding change listener."); |
|
284 this._listeners.push(listener); |
|
285 } |
|
286 }, |
|
287 |
|
288 /** |
|
289 * Removes a previously-installed change listener from the instance. |
|
290 * |
|
291 * @param listener |
|
292 * Listener instance to remove. |
|
293 */ |
|
294 removeChangeListener: function removeChangeListener(listener) { |
|
295 this._listeners = this._listeners.filter(function(element) { |
|
296 if (element == listener) { |
|
297 this._log.debug("Removing change listener."); |
|
298 return false; |
|
299 } else { |
|
300 return true; |
|
301 } |
|
302 }.bind(this)); |
|
303 }, |
|
304 |
|
305 /** |
|
306 * Tells the instance to start listening for AddonManager changes. |
|
307 * |
|
308 * This is typically called automatically when Sync is loaded. |
|
309 */ |
|
310 startListening: function startListening() { |
|
311 if (this._listening) { |
|
312 return; |
|
313 } |
|
314 |
|
315 this._log.info("Registering as Add-on Manager listener."); |
|
316 AddonManager.addAddonListener(this); |
|
317 AddonManager.addInstallListener(this); |
|
318 this._listening = true; |
|
319 }, |
|
320 |
|
321 /** |
|
322 * Tells the instance to stop listening for AddonManager changes. |
|
323 * |
|
324 * The reconciler should always be listening. This should only be called when |
|
325 * the instance is being destroyed. |
|
326 * |
|
327 * This function will get called automatically on XPCOM shutdown. However, it |
|
328 * is a best practice to call it yourself. |
|
329 */ |
|
330 stopListening: function stopListening() { |
|
331 if (!this._listening) { |
|
332 return; |
|
333 } |
|
334 |
|
335 this._log.debug("Stopping listening and removing AddonManager listeners."); |
|
336 AddonManager.removeInstallListener(this); |
|
337 AddonManager.removeAddonListener(this); |
|
338 this._listening = false; |
|
339 }, |
|
340 |
|
341 /** |
|
342 * Refreshes the global state of add-ons by querying the AddonManager. |
|
343 */ |
|
344 refreshGlobalState: function refreshGlobalState(callback) { |
|
345 this._log.info("Refreshing global state from AddonManager."); |
|
346 this._ensureStateLoaded(); |
|
347 |
|
348 let installs; |
|
349 |
|
350 AddonManager.getAllAddons(function (addons) { |
|
351 let ids = {}; |
|
352 |
|
353 for each (let addon in addons) { |
|
354 ids[addon.id] = true; |
|
355 this.rectifyStateFromAddon(addon); |
|
356 } |
|
357 |
|
358 // Look for locally-defined add-ons that no longer exist and update their |
|
359 // record. |
|
360 for (let [id, addon] in Iterator(this._addons)) { |
|
361 if (id in ids) { |
|
362 continue; |
|
363 } |
|
364 |
|
365 // If the id isn't in ids, it means that the add-on has been deleted or |
|
366 // the add-on is in the process of being installed. We detect the |
|
367 // latter by seeing if an AddonInstall is found for this add-on. |
|
368 |
|
369 if (!installs) { |
|
370 let cb = Async.makeSyncCallback(); |
|
371 AddonManager.getAllInstalls(cb); |
|
372 installs = Async.waitForSyncCallback(cb); |
|
373 } |
|
374 |
|
375 let installFound = false; |
|
376 for each (let install in installs) { |
|
377 if (install.addon && install.addon.id == id && |
|
378 install.state == AddonManager.STATE_INSTALLED) { |
|
379 |
|
380 installFound = true; |
|
381 break; |
|
382 } |
|
383 } |
|
384 |
|
385 if (installFound) { |
|
386 continue; |
|
387 } |
|
388 |
|
389 if (addon.installed) { |
|
390 addon.installed = false; |
|
391 this._log.debug("Adding change because add-on not present in " + |
|
392 "Add-on Manager: " + id); |
|
393 this._addChange(new Date(), CHANGE_UNINSTALLED, addon); |
|
394 } |
|
395 } |
|
396 |
|
397 // See note for _shouldPersist. |
|
398 if (this._shouldPersist) { |
|
399 this.saveState(null, callback); |
|
400 } else { |
|
401 callback(); |
|
402 } |
|
403 }.bind(this)); |
|
404 }, |
|
405 |
|
406 /** |
|
407 * Rectifies the state of an add-on from an Addon instance. |
|
408 * |
|
409 * This basically says "given an Addon instance, assume it is truth and |
|
410 * apply changes to the local state to reflect it." |
|
411 * |
|
412 * This function could result in change listeners being called if the local |
|
413 * state differs from the passed add-on's state. |
|
414 * |
|
415 * @param addon |
|
416 * Addon instance being updated. |
|
417 */ |
|
418 rectifyStateFromAddon: function rectifyStateFromAddon(addon) { |
|
419 this._log.debug("Rectifying state for addon: " + addon.id); |
|
420 this._ensureStateLoaded(); |
|
421 |
|
422 let id = addon.id; |
|
423 let enabled = !addon.userDisabled; |
|
424 let guid = addon.syncGUID; |
|
425 let now = new Date(); |
|
426 |
|
427 if (!(id in this._addons)) { |
|
428 let record = { |
|
429 id: id, |
|
430 guid: guid, |
|
431 enabled: enabled, |
|
432 installed: true, |
|
433 modified: now, |
|
434 type: addon.type, |
|
435 scope: addon.scope, |
|
436 foreignInstall: addon.foreignInstall |
|
437 }; |
|
438 this._addons[id] = record; |
|
439 this._log.debug("Adding change because add-on not present locally: " + |
|
440 id); |
|
441 this._addChange(now, CHANGE_INSTALLED, record); |
|
442 return; |
|
443 } |
|
444 |
|
445 let record = this._addons[id]; |
|
446 |
|
447 if (!record.installed) { |
|
448 // It is possible the record is marked as uninstalled because an |
|
449 // uninstall is pending. |
|
450 if (!(addon.pendingOperations & AddonManager.PENDING_UNINSTALL)) { |
|
451 record.installed = true; |
|
452 record.modified = now; |
|
453 } |
|
454 } |
|
455 |
|
456 if (record.enabled != enabled) { |
|
457 record.enabled = enabled; |
|
458 record.modified = now; |
|
459 let change = enabled ? CHANGE_ENABLED : CHANGE_DISABLED; |
|
460 this._log.debug("Adding change because enabled state changed: " + id); |
|
461 this._addChange(new Date(), change, record); |
|
462 } |
|
463 |
|
464 if (record.guid != guid) { |
|
465 record.guid = guid; |
|
466 // We don't record a change because the Sync engine rectifies this on its |
|
467 // own. This is tightly coupled with Sync. If this code is ever lifted |
|
468 // outside of Sync, this exception should likely be removed. |
|
469 } |
|
470 }, |
|
471 |
|
472 /** |
|
473 * Record a change in add-on state. |
|
474 * |
|
475 * @param date |
|
476 * Date at which the change occurred. |
|
477 * @param change |
|
478 * The type of the change. A CHANGE_* constant. |
|
479 * @param state |
|
480 * The new state of the add-on. From this.addons. |
|
481 */ |
|
482 _addChange: function _addChange(date, change, state) { |
|
483 this._log.info("Change recorded for " + state.id); |
|
484 this._changes.push([date, change, state.id]); |
|
485 |
|
486 for each (let listener in this._listeners) { |
|
487 try { |
|
488 listener.changeListener.call(listener, date, change, state); |
|
489 } catch (ex) { |
|
490 this._log.warn("Exception calling change listener: " + |
|
491 Utils.exceptionStr(ex)); |
|
492 } |
|
493 } |
|
494 }, |
|
495 |
|
496 /** |
|
497 * Obtain the set of changes to add-ons since the date passed. |
|
498 * |
|
499 * This will return an array of arrays. Each entry in the array has the |
|
500 * elements [date, change_type, id], where |
|
501 * |
|
502 * date - Date instance representing when the change occurred. |
|
503 * change_type - One of CHANGE_* constants. |
|
504 * id - ID of add-on that changed. |
|
505 */ |
|
506 getChangesSinceDate: function getChangesSinceDate(date) { |
|
507 this._ensureStateLoaded(); |
|
508 |
|
509 let length = this._changes.length; |
|
510 for (let i = 0; i < length; i++) { |
|
511 if (this._changes[i][0] >= date) { |
|
512 return this._changes.slice(i); |
|
513 } |
|
514 } |
|
515 |
|
516 return []; |
|
517 }, |
|
518 |
|
519 /** |
|
520 * Prunes all recorded changes from before the specified Date. |
|
521 * |
|
522 * @param date |
|
523 * Entries older than this Date will be removed. |
|
524 */ |
|
525 pruneChangesBeforeDate: function pruneChangesBeforeDate(date) { |
|
526 this._ensureStateLoaded(); |
|
527 |
|
528 this._changes = this._changes.filter(function test_age(change) { |
|
529 return change[0] >= date; |
|
530 }); |
|
531 }, |
|
532 |
|
533 /** |
|
534 * Obtains the set of all known Sync GUIDs for add-ons. |
|
535 * |
|
536 * @return Object with guids as keys and values of true. |
|
537 */ |
|
538 getAllSyncGUIDs: function getAllSyncGUIDs() { |
|
539 let result = {}; |
|
540 for (let id in this.addons) { |
|
541 result[id] = true; |
|
542 } |
|
543 |
|
544 return result; |
|
545 }, |
|
546 |
|
547 /** |
|
548 * Obtain the add-on state record for an add-on by Sync GUID. |
|
549 * |
|
550 * If the add-on could not be found, returns null. |
|
551 * |
|
552 * @param guid |
|
553 * Sync GUID of add-on to retrieve. |
|
554 * @return Object on success on null on failure. |
|
555 */ |
|
556 getAddonStateFromSyncGUID: function getAddonStateFromSyncGUID(guid) { |
|
557 for each (let addon in this.addons) { |
|
558 if (addon.guid == guid) { |
|
559 return addon; |
|
560 } |
|
561 } |
|
562 |
|
563 return null; |
|
564 }, |
|
565 |
|
566 /** |
|
567 * Ensures that state is loaded before continuing. |
|
568 * |
|
569 * This is called internally by anything that accesses the internal data |
|
570 * structures. It effectively just-in-time loads serialized state. |
|
571 */ |
|
572 _ensureStateLoaded: function _ensureStateLoaded() { |
|
573 if (this._stateLoaded) { |
|
574 return; |
|
575 } |
|
576 |
|
577 let cb = Async.makeSpinningCallback(); |
|
578 this.loadState(null, cb); |
|
579 cb.wait(); |
|
580 this._stateLoaded = true; |
|
581 }, |
|
582 |
|
583 /** |
|
584 * Handler that is invoked as part of the AddonManager listeners. |
|
585 */ |
|
586 _handleListener: function _handlerListener(action, addon, requiresRestart) { |
|
587 // Since this is called as an observer, we explicitly trap errors and |
|
588 // log them to ourselves so we don't see errors reported elsewhere. |
|
589 try { |
|
590 let id = addon.id; |
|
591 this._log.debug("Add-on change: " + action + " to " + id); |
|
592 |
|
593 // We assume that every event for non-restartless add-ons is |
|
594 // followed by another event and that this follow-up event is the most |
|
595 // appropriate to react to. Currently we ignore onEnabling, onDisabling, |
|
596 // and onUninstalling for non-restartless add-ons. |
|
597 if (requiresRestart === false) { |
|
598 this._log.debug("Ignoring " + action + " for restartless add-on."); |
|
599 return; |
|
600 } |
|
601 |
|
602 switch (action) { |
|
603 case "onEnabling": |
|
604 case "onEnabled": |
|
605 case "onDisabling": |
|
606 case "onDisabled": |
|
607 case "onInstalled": |
|
608 case "onInstallEnded": |
|
609 case "onOperationCancelled": |
|
610 this.rectifyStateFromAddon(addon); |
|
611 break; |
|
612 |
|
613 case "onUninstalling": |
|
614 case "onUninstalled": |
|
615 let id = addon.id; |
|
616 let addons = this.addons; |
|
617 if (id in addons) { |
|
618 let now = new Date(); |
|
619 let record = addons[id]; |
|
620 record.installed = false; |
|
621 record.modified = now; |
|
622 this._log.debug("Adding change because of uninstall listener: " + |
|
623 id); |
|
624 this._addChange(now, CHANGE_UNINSTALLED, record); |
|
625 } |
|
626 } |
|
627 |
|
628 // See note for _shouldPersist. |
|
629 if (this._shouldPersist) { |
|
630 let cb = Async.makeSpinningCallback(); |
|
631 this.saveState(null, cb); |
|
632 cb.wait(); |
|
633 } |
|
634 } |
|
635 catch (ex) { |
|
636 this._log.warn("Exception: " + Utils.exceptionStr(ex)); |
|
637 } |
|
638 }, |
|
639 |
|
640 // AddonListeners |
|
641 onEnabling: function onEnabling(addon, requiresRestart) { |
|
642 this._handleListener("onEnabling", addon, requiresRestart); |
|
643 }, |
|
644 onEnabled: function onEnabled(addon) { |
|
645 this._handleListener("onEnabled", addon); |
|
646 }, |
|
647 onDisabling: function onDisabling(addon, requiresRestart) { |
|
648 this._handleListener("onDisabling", addon, requiresRestart); |
|
649 }, |
|
650 onDisabled: function onDisabled(addon) { |
|
651 this._handleListener("onDisabled", addon); |
|
652 }, |
|
653 onInstalling: function onInstalling(addon, requiresRestart) { |
|
654 this._handleListener("onInstalling", addon, requiresRestart); |
|
655 }, |
|
656 onInstalled: function onInstalled(addon) { |
|
657 this._handleListener("onInstalled", addon); |
|
658 }, |
|
659 onUninstalling: function onUninstalling(addon, requiresRestart) { |
|
660 this._handleListener("onUninstalling", addon, requiresRestart); |
|
661 }, |
|
662 onUninstalled: function onUninstalled(addon) { |
|
663 this._handleListener("onUninstalled", addon); |
|
664 }, |
|
665 onOperationCancelled: function onOperationCancelled(addon) { |
|
666 this._handleListener("onOperationCancelled", addon); |
|
667 }, |
|
668 |
|
669 // InstallListeners |
|
670 onInstallEnded: function onInstallEnded(install, addon) { |
|
671 this._handleListener("onInstallEnded", addon); |
|
672 } |
|
673 }; |