1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/sync/modules/addonsreconciler.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,673 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +/** 1.9 + * This file contains middleware to reconcile state of AddonManager for 1.10 + * purposes of tracking events for Sync. The content in this file exists 1.11 + * because AddonManager does not have a getChangesSinceX() API and adding 1.12 + * that functionality properly was deemed too time-consuming at the time 1.13 + * add-on sync was originally written. If/when AddonManager adds this API, 1.14 + * this file can go away and the add-ons engine can be rewritten to use it. 1.15 + * 1.16 + * It was decided to have this tracking functionality exist in a separate 1.17 + * standalone file so it could be more easily understood, tested, and 1.18 + * hopefully ported. 1.19 + */ 1.20 + 1.21 +"use strict"; 1.22 + 1.23 +const Cu = Components.utils; 1.24 + 1.25 +Cu.import("resource://gre/modules/Log.jsm"); 1.26 +Cu.import("resource://services-sync/util.js"); 1.27 +Cu.import("resource://gre/modules/AddonManager.jsm"); 1.28 + 1.29 +const DEFAULT_STATE_FILE = "addonsreconciler"; 1.30 + 1.31 +this.CHANGE_INSTALLED = 1; 1.32 +this.CHANGE_UNINSTALLED = 2; 1.33 +this.CHANGE_ENABLED = 3; 1.34 +this.CHANGE_DISABLED = 4; 1.35 + 1.36 +this.EXPORTED_SYMBOLS = ["AddonsReconciler", "CHANGE_INSTALLED", 1.37 + "CHANGE_UNINSTALLED", "CHANGE_ENABLED", 1.38 + "CHANGE_DISABLED"]; 1.39 +/** 1.40 + * Maintains state of add-ons. 1.41 + * 1.42 + * State is maintained in 2 data structures, an object mapping add-on IDs 1.43 + * to metadata and an array of changes over time. The object mapping can be 1.44 + * thought of as a minimal copy of data from AddonManager which is needed for 1.45 + * Sync. The array is effectively a log of changes over time. 1.46 + * 1.47 + * The data structures are persisted to disk by serializing to a JSON file in 1.48 + * the current profile. The data structures are updated by 2 mechanisms. First, 1.49 + * they can be refreshed from the global state of the AddonManager. This is a 1.50 + * sure-fire way of ensuring the reconciler is up to date. Second, the 1.51 + * reconciler adds itself as an AddonManager listener. When it receives change 1.52 + * notifications, it updates its internal state incrementally. 1.53 + * 1.54 + * The internal state is persisted to a JSON file in the profile directory. 1.55 + * 1.56 + * An instance of this is bound to an AddonsEngine instance. In reality, it 1.57 + * likely exists as a singleton. To AddonsEngine, it functions as a store and 1.58 + * an entity which emits events for tracking. 1.59 + * 1.60 + * The usage pattern for instances of this class is: 1.61 + * 1.62 + * let reconciler = new AddonsReconciler(); 1.63 + * reconciler.loadState(null, function(error) { ... }); 1.64 + * 1.65 + * // At this point, your instance should be ready to use. 1.66 + * 1.67 + * When you are finished with the instance, please call: 1.68 + * 1.69 + * reconciler.stopListening(); 1.70 + * reconciler.saveState(...); 1.71 + * 1.72 + * There are 2 classes of listeners in the AddonManager: AddonListener and 1.73 + * InstallListener. This class is a listener for both (member functions just 1.74 + * get called directly). 1.75 + * 1.76 + * When an add-on is installed, listeners are called in the following order: 1.77 + * 1.78 + * IL.onInstallStarted, AL.onInstalling, IL.onInstallEnded, AL.onInstalled 1.79 + * 1.80 + * For non-restartless add-ons, an application restart may occur between 1.81 + * IL.onInstallEnded and AL.onInstalled. Unfortunately, Sync likely will 1.82 + * not be loaded when AL.onInstalled is fired shortly after application 1.83 + * start, so it won't see this event. Therefore, for add-ons requiring a 1.84 + * restart, Sync treats the IL.onInstallEnded event as good enough to 1.85 + * indicate an install. For restartless add-ons, Sync assumes AL.onInstalled 1.86 + * will follow shortly after IL.onInstallEnded and thus it ignores 1.87 + * IL.onInstallEnded. 1.88 + * 1.89 + * The listeners can also see events related to the download of the add-on. 1.90 + * This class isn't interested in those. However, there are failure events, 1.91 + * IL.onDownloadFailed and IL.onDownloadCanceled which get called if a 1.92 + * download doesn't complete successfully. 1.93 + * 1.94 + * For uninstalls, we see AL.onUninstalling then AL.onUninstalled. Like 1.95 + * installs, the events could be separated by an application restart and Sync 1.96 + * may not see the onUninstalled event. Again, if we require a restart, we 1.97 + * react to onUninstalling. If not, we assume we'll get onUninstalled. 1.98 + * 1.99 + * Enabling and disabling work by sending: 1.100 + * 1.101 + * AL.onEnabling, AL.onEnabled 1.102 + * AL.onDisabling, AL.onDisabled 1.103 + * 1.104 + * Again, they may be separated by a restart, so we heed the requiresRestart 1.105 + * flag. 1.106 + * 1.107 + * Actions can be undone. All undoable actions notify the same 1.108 + * AL.onOperationCancelled event. We treat this event like any other. 1.109 + * 1.110 + * Restartless add-ons have interesting behavior during uninstall. These 1.111 + * add-ons are first disabled then they are actually uninstalled. So, we will 1.112 + * see AL.onDisabling and AL.onDisabled. The onUninstalling and onUninstalled 1.113 + * events only come after the Addon Manager is closed or another view is 1.114 + * switched to. In the case of Sync performing the uninstall, the uninstall 1.115 + * events will occur immediately. However, we still see disabling events and 1.116 + * heed them like they were normal. In the end, the state is proper. 1.117 + */ 1.118 +this.AddonsReconciler = function AddonsReconciler() { 1.119 + this._log = Log.repository.getLogger("Sync.AddonsReconciler"); 1.120 + let level = Svc.Prefs.get("log.logger.addonsreconciler", "Debug"); 1.121 + this._log.level = Log.Level[level]; 1.122 + 1.123 + Svc.Obs.add("xpcom-shutdown", this.stopListening, this); 1.124 +}; 1.125 +AddonsReconciler.prototype = { 1.126 + /** Flag indicating whether we are listening to AddonManager events. */ 1.127 + _listening: false, 1.128 + 1.129 + /** 1.130 + * Whether state has been loaded from a file. 1.131 + * 1.132 + * State is loaded on demand if an operation requires it. 1.133 + */ 1.134 + _stateLoaded: false, 1.135 + 1.136 + /** 1.137 + * Define this as false if the reconciler should not persist state 1.138 + * to disk when handling events. 1.139 + * 1.140 + * This allows test code to avoid spinning to write during observer 1.141 + * notifications and xpcom shutdown, which appears to cause hangs on WinXP 1.142 + * (Bug 873861). 1.143 + */ 1.144 + _shouldPersist: true, 1.145 + 1.146 + /** Log logger instance */ 1.147 + _log: null, 1.148 + 1.149 + /** 1.150 + * Container for add-on metadata. 1.151 + * 1.152 + * Keys are add-on IDs. Values are objects which describe the state of the 1.153 + * add-on. This is a minimal mirror of data that can be queried from 1.154 + * AddonManager. In some cases, we retain data longer than AddonManager. 1.155 + */ 1.156 + _addons: {}, 1.157 + 1.158 + /** 1.159 + * List of add-on changes over time. 1.160 + * 1.161 + * Each element is an array of [time, change, id]. 1.162 + */ 1.163 + _changes: [], 1.164 + 1.165 + /** 1.166 + * Objects subscribed to changes made to this instance. 1.167 + */ 1.168 + _listeners: [], 1.169 + 1.170 + /** 1.171 + * Accessor for add-ons in this object. 1.172 + * 1.173 + * Returns an object mapping add-on IDs to objects containing metadata. 1.174 + */ 1.175 + get addons() { 1.176 + this._ensureStateLoaded(); 1.177 + return this._addons; 1.178 + }, 1.179 + 1.180 + /** 1.181 + * Load reconciler state from a file. 1.182 + * 1.183 + * The path is relative to the weave directory in the profile. If no 1.184 + * path is given, the default one is used. 1.185 + * 1.186 + * If the file does not exist or there was an error parsing the file, the 1.187 + * state will be transparently defined as empty. 1.188 + * 1.189 + * @param path 1.190 + * Path to load. ".json" is appended automatically. If not defined, 1.191 + * a default path will be consulted. 1.192 + * @param callback 1.193 + * Callback to be executed upon file load. The callback receives a 1.194 + * truthy error argument signifying whether an error occurred and a 1.195 + * boolean indicating whether data was loaded. 1.196 + */ 1.197 + loadState: function loadState(path, callback) { 1.198 + let file = path || DEFAULT_STATE_FILE; 1.199 + Utils.jsonLoad(file, this, function(json) { 1.200 + this._addons = {}; 1.201 + this._changes = []; 1.202 + 1.203 + if (!json) { 1.204 + this._log.debug("No data seen in loaded file: " + file); 1.205 + if (callback) { 1.206 + callback(null, false); 1.207 + } 1.208 + 1.209 + return; 1.210 + } 1.211 + 1.212 + let version = json.version; 1.213 + if (!version || version != 1) { 1.214 + this._log.error("Could not load JSON file because version not " + 1.215 + "supported: " + version); 1.216 + if (callback) { 1.217 + callback(null, false); 1.218 + } 1.219 + 1.220 + return; 1.221 + } 1.222 + 1.223 + this._addons = json.addons; 1.224 + for each (let record in this._addons) { 1.225 + record.modified = new Date(record.modified); 1.226 + } 1.227 + 1.228 + for each (let [time, change, id] in json.changes) { 1.229 + this._changes.push([new Date(time), change, id]); 1.230 + } 1.231 + 1.232 + if (callback) { 1.233 + callback(null, true); 1.234 + } 1.235 + }); 1.236 + }, 1.237 + 1.238 + /** 1.239 + * Saves the current state to a file in the local profile. 1.240 + * 1.241 + * @param path 1.242 + * String path in profile to save to. If not defined, the default 1.243 + * will be used. 1.244 + * @param callback 1.245 + * Function to be invoked on save completion. No parameters will be 1.246 + * passed to callback. 1.247 + */ 1.248 + saveState: function saveState(path, callback) { 1.249 + let file = path || DEFAULT_STATE_FILE; 1.250 + let state = {version: 1, addons: {}, changes: []}; 1.251 + 1.252 + for (let [id, record] in Iterator(this._addons)) { 1.253 + state.addons[id] = {}; 1.254 + for (let [k, v] in Iterator(record)) { 1.255 + if (k == "modified") { 1.256 + state.addons[id][k] = v.getTime(); 1.257 + } 1.258 + else { 1.259 + state.addons[id][k] = v; 1.260 + } 1.261 + } 1.262 + } 1.263 + 1.264 + for each (let [time, change, id] in this._changes) { 1.265 + state.changes.push([time.getTime(), change, id]); 1.266 + } 1.267 + 1.268 + this._log.info("Saving reconciler state to file: " + file); 1.269 + Utils.jsonSave(file, this, state, callback); 1.270 + }, 1.271 + 1.272 + /** 1.273 + * Registers a change listener with this instance. 1.274 + * 1.275 + * Change listeners are called every time a change is recorded. The listener 1.276 + * is an object with the function "changeListener" that takes 3 arguments, 1.277 + * the Date at which the change happened, the type of change (a CHANGE_* 1.278 + * constant), and the add-on state object reflecting the current state of 1.279 + * the add-on at the time of the change. 1.280 + * 1.281 + * @param listener 1.282 + * Object containing changeListener function. 1.283 + */ 1.284 + addChangeListener: function addChangeListener(listener) { 1.285 + if (this._listeners.indexOf(listener) == -1) { 1.286 + this._log.debug("Adding change listener."); 1.287 + this._listeners.push(listener); 1.288 + } 1.289 + }, 1.290 + 1.291 + /** 1.292 + * Removes a previously-installed change listener from the instance. 1.293 + * 1.294 + * @param listener 1.295 + * Listener instance to remove. 1.296 + */ 1.297 + removeChangeListener: function removeChangeListener(listener) { 1.298 + this._listeners = this._listeners.filter(function(element) { 1.299 + if (element == listener) { 1.300 + this._log.debug("Removing change listener."); 1.301 + return false; 1.302 + } else { 1.303 + return true; 1.304 + } 1.305 + }.bind(this)); 1.306 + }, 1.307 + 1.308 + /** 1.309 + * Tells the instance to start listening for AddonManager changes. 1.310 + * 1.311 + * This is typically called automatically when Sync is loaded. 1.312 + */ 1.313 + startListening: function startListening() { 1.314 + if (this._listening) { 1.315 + return; 1.316 + } 1.317 + 1.318 + this._log.info("Registering as Add-on Manager listener."); 1.319 + AddonManager.addAddonListener(this); 1.320 + AddonManager.addInstallListener(this); 1.321 + this._listening = true; 1.322 + }, 1.323 + 1.324 + /** 1.325 + * Tells the instance to stop listening for AddonManager changes. 1.326 + * 1.327 + * The reconciler should always be listening. This should only be called when 1.328 + * the instance is being destroyed. 1.329 + * 1.330 + * This function will get called automatically on XPCOM shutdown. However, it 1.331 + * is a best practice to call it yourself. 1.332 + */ 1.333 + stopListening: function stopListening() { 1.334 + if (!this._listening) { 1.335 + return; 1.336 + } 1.337 + 1.338 + this._log.debug("Stopping listening and removing AddonManager listeners."); 1.339 + AddonManager.removeInstallListener(this); 1.340 + AddonManager.removeAddonListener(this); 1.341 + this._listening = false; 1.342 + }, 1.343 + 1.344 + /** 1.345 + * Refreshes the global state of add-ons by querying the AddonManager. 1.346 + */ 1.347 + refreshGlobalState: function refreshGlobalState(callback) { 1.348 + this._log.info("Refreshing global state from AddonManager."); 1.349 + this._ensureStateLoaded(); 1.350 + 1.351 + let installs; 1.352 + 1.353 + AddonManager.getAllAddons(function (addons) { 1.354 + let ids = {}; 1.355 + 1.356 + for each (let addon in addons) { 1.357 + ids[addon.id] = true; 1.358 + this.rectifyStateFromAddon(addon); 1.359 + } 1.360 + 1.361 + // Look for locally-defined add-ons that no longer exist and update their 1.362 + // record. 1.363 + for (let [id, addon] in Iterator(this._addons)) { 1.364 + if (id in ids) { 1.365 + continue; 1.366 + } 1.367 + 1.368 + // If the id isn't in ids, it means that the add-on has been deleted or 1.369 + // the add-on is in the process of being installed. We detect the 1.370 + // latter by seeing if an AddonInstall is found for this add-on. 1.371 + 1.372 + if (!installs) { 1.373 + let cb = Async.makeSyncCallback(); 1.374 + AddonManager.getAllInstalls(cb); 1.375 + installs = Async.waitForSyncCallback(cb); 1.376 + } 1.377 + 1.378 + let installFound = false; 1.379 + for each (let install in installs) { 1.380 + if (install.addon && install.addon.id == id && 1.381 + install.state == AddonManager.STATE_INSTALLED) { 1.382 + 1.383 + installFound = true; 1.384 + break; 1.385 + } 1.386 + } 1.387 + 1.388 + if (installFound) { 1.389 + continue; 1.390 + } 1.391 + 1.392 + if (addon.installed) { 1.393 + addon.installed = false; 1.394 + this._log.debug("Adding change because add-on not present in " + 1.395 + "Add-on Manager: " + id); 1.396 + this._addChange(new Date(), CHANGE_UNINSTALLED, addon); 1.397 + } 1.398 + } 1.399 + 1.400 + // See note for _shouldPersist. 1.401 + if (this._shouldPersist) { 1.402 + this.saveState(null, callback); 1.403 + } else { 1.404 + callback(); 1.405 + } 1.406 + }.bind(this)); 1.407 + }, 1.408 + 1.409 + /** 1.410 + * Rectifies the state of an add-on from an Addon instance. 1.411 + * 1.412 + * This basically says "given an Addon instance, assume it is truth and 1.413 + * apply changes to the local state to reflect it." 1.414 + * 1.415 + * This function could result in change listeners being called if the local 1.416 + * state differs from the passed add-on's state. 1.417 + * 1.418 + * @param addon 1.419 + * Addon instance being updated. 1.420 + */ 1.421 + rectifyStateFromAddon: function rectifyStateFromAddon(addon) { 1.422 + this._log.debug("Rectifying state for addon: " + addon.id); 1.423 + this._ensureStateLoaded(); 1.424 + 1.425 + let id = addon.id; 1.426 + let enabled = !addon.userDisabled; 1.427 + let guid = addon.syncGUID; 1.428 + let now = new Date(); 1.429 + 1.430 + if (!(id in this._addons)) { 1.431 + let record = { 1.432 + id: id, 1.433 + guid: guid, 1.434 + enabled: enabled, 1.435 + installed: true, 1.436 + modified: now, 1.437 + type: addon.type, 1.438 + scope: addon.scope, 1.439 + foreignInstall: addon.foreignInstall 1.440 + }; 1.441 + this._addons[id] = record; 1.442 + this._log.debug("Adding change because add-on not present locally: " + 1.443 + id); 1.444 + this._addChange(now, CHANGE_INSTALLED, record); 1.445 + return; 1.446 + } 1.447 + 1.448 + let record = this._addons[id]; 1.449 + 1.450 + if (!record.installed) { 1.451 + // It is possible the record is marked as uninstalled because an 1.452 + // uninstall is pending. 1.453 + if (!(addon.pendingOperations & AddonManager.PENDING_UNINSTALL)) { 1.454 + record.installed = true; 1.455 + record.modified = now; 1.456 + } 1.457 + } 1.458 + 1.459 + if (record.enabled != enabled) { 1.460 + record.enabled = enabled; 1.461 + record.modified = now; 1.462 + let change = enabled ? CHANGE_ENABLED : CHANGE_DISABLED; 1.463 + this._log.debug("Adding change because enabled state changed: " + id); 1.464 + this._addChange(new Date(), change, record); 1.465 + } 1.466 + 1.467 + if (record.guid != guid) { 1.468 + record.guid = guid; 1.469 + // We don't record a change because the Sync engine rectifies this on its 1.470 + // own. This is tightly coupled with Sync. If this code is ever lifted 1.471 + // outside of Sync, this exception should likely be removed. 1.472 + } 1.473 + }, 1.474 + 1.475 + /** 1.476 + * Record a change in add-on state. 1.477 + * 1.478 + * @param date 1.479 + * Date at which the change occurred. 1.480 + * @param change 1.481 + * The type of the change. A CHANGE_* constant. 1.482 + * @param state 1.483 + * The new state of the add-on. From this.addons. 1.484 + */ 1.485 + _addChange: function _addChange(date, change, state) { 1.486 + this._log.info("Change recorded for " + state.id); 1.487 + this._changes.push([date, change, state.id]); 1.488 + 1.489 + for each (let listener in this._listeners) { 1.490 + try { 1.491 + listener.changeListener.call(listener, date, change, state); 1.492 + } catch (ex) { 1.493 + this._log.warn("Exception calling change listener: " + 1.494 + Utils.exceptionStr(ex)); 1.495 + } 1.496 + } 1.497 + }, 1.498 + 1.499 + /** 1.500 + * Obtain the set of changes to add-ons since the date passed. 1.501 + * 1.502 + * This will return an array of arrays. Each entry in the array has the 1.503 + * elements [date, change_type, id], where 1.504 + * 1.505 + * date - Date instance representing when the change occurred. 1.506 + * change_type - One of CHANGE_* constants. 1.507 + * id - ID of add-on that changed. 1.508 + */ 1.509 + getChangesSinceDate: function getChangesSinceDate(date) { 1.510 + this._ensureStateLoaded(); 1.511 + 1.512 + let length = this._changes.length; 1.513 + for (let i = 0; i < length; i++) { 1.514 + if (this._changes[i][0] >= date) { 1.515 + return this._changes.slice(i); 1.516 + } 1.517 + } 1.518 + 1.519 + return []; 1.520 + }, 1.521 + 1.522 + /** 1.523 + * Prunes all recorded changes from before the specified Date. 1.524 + * 1.525 + * @param date 1.526 + * Entries older than this Date will be removed. 1.527 + */ 1.528 + pruneChangesBeforeDate: function pruneChangesBeforeDate(date) { 1.529 + this._ensureStateLoaded(); 1.530 + 1.531 + this._changes = this._changes.filter(function test_age(change) { 1.532 + return change[0] >= date; 1.533 + }); 1.534 + }, 1.535 + 1.536 + /** 1.537 + * Obtains the set of all known Sync GUIDs for add-ons. 1.538 + * 1.539 + * @return Object with guids as keys and values of true. 1.540 + */ 1.541 + getAllSyncGUIDs: function getAllSyncGUIDs() { 1.542 + let result = {}; 1.543 + for (let id in this.addons) { 1.544 + result[id] = true; 1.545 + } 1.546 + 1.547 + return result; 1.548 + }, 1.549 + 1.550 + /** 1.551 + * Obtain the add-on state record for an add-on by Sync GUID. 1.552 + * 1.553 + * If the add-on could not be found, returns null. 1.554 + * 1.555 + * @param guid 1.556 + * Sync GUID of add-on to retrieve. 1.557 + * @return Object on success on null on failure. 1.558 + */ 1.559 + getAddonStateFromSyncGUID: function getAddonStateFromSyncGUID(guid) { 1.560 + for each (let addon in this.addons) { 1.561 + if (addon.guid == guid) { 1.562 + return addon; 1.563 + } 1.564 + } 1.565 + 1.566 + return null; 1.567 + }, 1.568 + 1.569 + /** 1.570 + * Ensures that state is loaded before continuing. 1.571 + * 1.572 + * This is called internally by anything that accesses the internal data 1.573 + * structures. It effectively just-in-time loads serialized state. 1.574 + */ 1.575 + _ensureStateLoaded: function _ensureStateLoaded() { 1.576 + if (this._stateLoaded) { 1.577 + return; 1.578 + } 1.579 + 1.580 + let cb = Async.makeSpinningCallback(); 1.581 + this.loadState(null, cb); 1.582 + cb.wait(); 1.583 + this._stateLoaded = true; 1.584 + }, 1.585 + 1.586 + /** 1.587 + * Handler that is invoked as part of the AddonManager listeners. 1.588 + */ 1.589 + _handleListener: function _handlerListener(action, addon, requiresRestart) { 1.590 + // Since this is called as an observer, we explicitly trap errors and 1.591 + // log them to ourselves so we don't see errors reported elsewhere. 1.592 + try { 1.593 + let id = addon.id; 1.594 + this._log.debug("Add-on change: " + action + " to " + id); 1.595 + 1.596 + // We assume that every event for non-restartless add-ons is 1.597 + // followed by another event and that this follow-up event is the most 1.598 + // appropriate to react to. Currently we ignore onEnabling, onDisabling, 1.599 + // and onUninstalling for non-restartless add-ons. 1.600 + if (requiresRestart === false) { 1.601 + this._log.debug("Ignoring " + action + " for restartless add-on."); 1.602 + return; 1.603 + } 1.604 + 1.605 + switch (action) { 1.606 + case "onEnabling": 1.607 + case "onEnabled": 1.608 + case "onDisabling": 1.609 + case "onDisabled": 1.610 + case "onInstalled": 1.611 + case "onInstallEnded": 1.612 + case "onOperationCancelled": 1.613 + this.rectifyStateFromAddon(addon); 1.614 + break; 1.615 + 1.616 + case "onUninstalling": 1.617 + case "onUninstalled": 1.618 + let id = addon.id; 1.619 + let addons = this.addons; 1.620 + if (id in addons) { 1.621 + let now = new Date(); 1.622 + let record = addons[id]; 1.623 + record.installed = false; 1.624 + record.modified = now; 1.625 + this._log.debug("Adding change because of uninstall listener: " + 1.626 + id); 1.627 + this._addChange(now, CHANGE_UNINSTALLED, record); 1.628 + } 1.629 + } 1.630 + 1.631 + // See note for _shouldPersist. 1.632 + if (this._shouldPersist) { 1.633 + let cb = Async.makeSpinningCallback(); 1.634 + this.saveState(null, cb); 1.635 + cb.wait(); 1.636 + } 1.637 + } 1.638 + catch (ex) { 1.639 + this._log.warn("Exception: " + Utils.exceptionStr(ex)); 1.640 + } 1.641 + }, 1.642 + 1.643 + // AddonListeners 1.644 + onEnabling: function onEnabling(addon, requiresRestart) { 1.645 + this._handleListener("onEnabling", addon, requiresRestart); 1.646 + }, 1.647 + onEnabled: function onEnabled(addon) { 1.648 + this._handleListener("onEnabled", addon); 1.649 + }, 1.650 + onDisabling: function onDisabling(addon, requiresRestart) { 1.651 + this._handleListener("onDisabling", addon, requiresRestart); 1.652 + }, 1.653 + onDisabled: function onDisabled(addon) { 1.654 + this._handleListener("onDisabled", addon); 1.655 + }, 1.656 + onInstalling: function onInstalling(addon, requiresRestart) { 1.657 + this._handleListener("onInstalling", addon, requiresRestart); 1.658 + }, 1.659 + onInstalled: function onInstalled(addon) { 1.660 + this._handleListener("onInstalled", addon); 1.661 + }, 1.662 + onUninstalling: function onUninstalling(addon, requiresRestart) { 1.663 + this._handleListener("onUninstalling", addon, requiresRestart); 1.664 + }, 1.665 + onUninstalled: function onUninstalled(addon) { 1.666 + this._handleListener("onUninstalled", addon); 1.667 + }, 1.668 + onOperationCancelled: function onOperationCancelled(addon) { 1.669 + this._handleListener("onOperationCancelled", addon); 1.670 + }, 1.671 + 1.672 + // InstallListeners 1.673 + onInstallEnded: function onInstallEnded(install, addon) { 1.674 + this._handleListener("onInstallEnded", addon); 1.675 + } 1.676 +};