Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | const Cc = Components.classes; |
michael@0 | 8 | const Ci = Components.interfaces; |
michael@0 | 9 | const Cr = Components.results; |
michael@0 | 10 | const Cu = Components.utils; |
michael@0 | 11 | |
michael@0 | 12 | // Cannot use Services.appinfo here, or else xpcshell-tests will blow up, as |
michael@0 | 13 | // most tests later register different nsIAppInfo implementations, which |
michael@0 | 14 | // wouldn't be reflected in Services.appinfo anymore, as the lazy getter |
michael@0 | 15 | // underlying it would have been initialized if we used it here. |
michael@0 | 16 | if ("@mozilla.org/xre/app-info;1" in Cc) { |
michael@0 | 17 | let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); |
michael@0 | 18 | if (runtime.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { |
michael@0 | 19 | // Refuse to run in child processes. |
michael@0 | 20 | throw new Error("You cannot use the AddonManager in child processes!"); |
michael@0 | 21 | } |
michael@0 | 22 | } |
michael@0 | 23 | |
michael@0 | 24 | |
michael@0 | 25 | const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion"; |
michael@0 | 26 | const PREF_DEFAULT_PROVIDERS_ENABLED = "extensions.defaultProviders.enabled"; |
michael@0 | 27 | const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled"; |
michael@0 | 28 | const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion"; |
michael@0 | 29 | const PREF_EM_LAST_PLATFORM_VERSION = "extensions.lastPlatformVersion"; |
michael@0 | 30 | const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault"; |
michael@0 | 31 | const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; |
michael@0 | 32 | const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; |
michael@0 | 33 | const PREF_EM_UPDATE_BACKGROUND_URL = "extensions.update.background.url"; |
michael@0 | 34 | const PREF_APP_UPDATE_ENABLED = "app.update.enabled"; |
michael@0 | 35 | const PREF_APP_UPDATE_AUTO = "app.update.auto"; |
michael@0 | 36 | const PREF_EM_HOTFIX_ID = "extensions.hotfix.id"; |
michael@0 | 37 | const PREF_EM_HOTFIX_LASTVERSION = "extensions.hotfix.lastVersion"; |
michael@0 | 38 | const PREF_EM_HOTFIX_URL = "extensions.hotfix.url"; |
michael@0 | 39 | const PREF_EM_CERT_CHECKATTRIBUTES = "extensions.hotfix.cert.checkAttributes"; |
michael@0 | 40 | const PREF_EM_HOTFIX_CERTS = "extensions.hotfix.certs."; |
michael@0 | 41 | const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; |
michael@0 | 42 | const PREF_SELECTED_LOCALE = "general.useragent.locale"; |
michael@0 | 43 | const UNKNOWN_XPCOM_ABI = "unknownABI"; |
michael@0 | 44 | |
michael@0 | 45 | const UPDATE_REQUEST_VERSION = 2; |
michael@0 | 46 | const CATEGORY_UPDATE_PARAMS = "extension-update-params"; |
michael@0 | 47 | |
michael@0 | 48 | const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist"; |
michael@0 | 49 | |
michael@0 | 50 | const KEY_PROFILEDIR = "ProfD"; |
michael@0 | 51 | const KEY_APPDIR = "XCurProcD"; |
michael@0 | 52 | const FILE_BLOCKLIST = "blocklist.xml"; |
michael@0 | 53 | |
michael@0 | 54 | const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi; |
michael@0 | 55 | const PREF_EM_CHECK_COMPATIBILITY_BASE = "extensions.checkCompatibility"; |
michael@0 | 56 | #ifdef MOZ_COMPATIBILITY_NIGHTLY |
michael@0 | 57 | var PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + ".nightly"; |
michael@0 | 58 | #else |
michael@0 | 59 | var PREF_EM_CHECK_COMPATIBILITY; |
michael@0 | 60 | #endif |
michael@0 | 61 | |
michael@0 | 62 | const TOOLKIT_ID = "toolkit@mozilla.org"; |
michael@0 | 63 | |
michael@0 | 64 | const VALID_TYPES_REGEXP = /^[\w\-]+$/; |
michael@0 | 65 | |
michael@0 | 66 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 67 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 68 | Cu.import("resource://gre/modules/AsyncShutdown.jsm"); |
michael@0 | 69 | |
michael@0 | 70 | XPCOMUtils.defineLazyModuleGetter(this, "Promise", |
michael@0 | 71 | "resource://gre/modules/Promise.jsm"); |
michael@0 | 72 | XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", |
michael@0 | 73 | "resource://gre/modules/addons/AddonRepository.jsm"); |
michael@0 | 74 | XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", |
michael@0 | 75 | "resource://gre/modules/FileUtils.jsm"); |
michael@0 | 76 | |
michael@0 | 77 | XPCOMUtils.defineLazyGetter(this, "CertUtils", function certUtilsLazyGetter() { |
michael@0 | 78 | let certUtils = {}; |
michael@0 | 79 | Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils); |
michael@0 | 80 | return certUtils; |
michael@0 | 81 | }); |
michael@0 | 82 | |
michael@0 | 83 | |
michael@0 | 84 | this.EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ]; |
michael@0 | 85 | |
michael@0 | 86 | const CATEGORY_PROVIDER_MODULE = "addon-provider-module"; |
michael@0 | 87 | |
michael@0 | 88 | // A list of providers to load by default |
michael@0 | 89 | const DEFAULT_PROVIDERS = [ |
michael@0 | 90 | "resource://gre/modules/addons/XPIProvider.jsm", |
michael@0 | 91 | "resource://gre/modules/LightweightThemeManager.jsm" |
michael@0 | 92 | ]; |
michael@0 | 93 | |
michael@0 | 94 | Cu.import("resource://gre/modules/Log.jsm"); |
michael@0 | 95 | // Configure a logger at the parent 'addons' level to format |
michael@0 | 96 | // messages for all the modules under addons.* |
michael@0 | 97 | const PARENT_LOGGER_ID = "addons"; |
michael@0 | 98 | let parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID); |
michael@0 | 99 | parentLogger.level = Log.Level.Warn; |
michael@0 | 100 | let formatter = new Log.BasicFormatter(); |
michael@0 | 101 | // Set parent logger (and its children) to append to |
michael@0 | 102 | // the Javascript section of the Browser Console |
michael@0 | 103 | parentLogger.addAppender(new Log.ConsoleAppender(formatter)); |
michael@0 | 104 | // Set parent logger (and its children) to |
michael@0 | 105 | // also append to standard out |
michael@0 | 106 | parentLogger.addAppender(new Log.DumpAppender(formatter)); |
michael@0 | 107 | |
michael@0 | 108 | // Create a new logger (child of 'addons' logger) |
michael@0 | 109 | // for use by the Addons Manager |
michael@0 | 110 | const LOGGER_ID = "addons.manager"; |
michael@0 | 111 | let logger = Log.repository.getLogger(LOGGER_ID); |
michael@0 | 112 | |
michael@0 | 113 | // Provide the ability to enable/disable logging |
michael@0 | 114 | // messages at runtime. |
michael@0 | 115 | // If the "extensions.logging.enabled" preference is |
michael@0 | 116 | // missing or 'false', messages at the WARNING and higher |
michael@0 | 117 | // severity should be logged to the JS console and standard error. |
michael@0 | 118 | // If "extensions.logging.enabled" is set to 'true', messages |
michael@0 | 119 | // at DEBUG and higher should go to JS console and standard error. |
michael@0 | 120 | const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; |
michael@0 | 121 | const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; |
michael@0 | 122 | |
michael@0 | 123 | /** |
michael@0 | 124 | * Preference listener which listens for a change in the |
michael@0 | 125 | * "extensions.logging.enabled" preference and changes the logging level of the |
michael@0 | 126 | * parent 'addons' level logger accordingly. |
michael@0 | 127 | */ |
michael@0 | 128 | var PrefObserver = { |
michael@0 | 129 | init: function PrefObserver_init() { |
michael@0 | 130 | Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false); |
michael@0 | 131 | Services.obs.addObserver(this, "xpcom-shutdown", false); |
michael@0 | 132 | this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED); |
michael@0 | 133 | }, |
michael@0 | 134 | |
michael@0 | 135 | observe: function PrefObserver_observe(aSubject, aTopic, aData) { |
michael@0 | 136 | if (aTopic == "xpcom-shutdown") { |
michael@0 | 137 | Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this); |
michael@0 | 138 | Services.obs.removeObserver(this, "xpcom-shutdown"); |
michael@0 | 139 | } |
michael@0 | 140 | else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) { |
michael@0 | 141 | let debugLogEnabled = false; |
michael@0 | 142 | try { |
michael@0 | 143 | debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED); |
michael@0 | 144 | } |
michael@0 | 145 | catch (e) { |
michael@0 | 146 | } |
michael@0 | 147 | if (debugLogEnabled) { |
michael@0 | 148 | parentLogger.level = Log.Level.Debug; |
michael@0 | 149 | } |
michael@0 | 150 | else { |
michael@0 | 151 | parentLogger.level = Log.Level.Warn; |
michael@0 | 152 | } |
michael@0 | 153 | } |
michael@0 | 154 | } |
michael@0 | 155 | }; |
michael@0 | 156 | |
michael@0 | 157 | PrefObserver.init(); |
michael@0 | 158 | |
michael@0 | 159 | /** |
michael@0 | 160 | * Calls a callback method consuming any thrown exception. Any parameters after |
michael@0 | 161 | * the callback parameter will be passed to the callback. |
michael@0 | 162 | * |
michael@0 | 163 | * @param aCallback |
michael@0 | 164 | * The callback method to call |
michael@0 | 165 | */ |
michael@0 | 166 | function safeCall(aCallback, ...aArgs) { |
michael@0 | 167 | try { |
michael@0 | 168 | aCallback.apply(null, aArgs); |
michael@0 | 169 | } |
michael@0 | 170 | catch (e) { |
michael@0 | 171 | logger.warn("Exception calling callback", e); |
michael@0 | 172 | } |
michael@0 | 173 | } |
michael@0 | 174 | |
michael@0 | 175 | /** |
michael@0 | 176 | * Calls a method on a provider if it exists and consumes any thrown exception. |
michael@0 | 177 | * Any parameters after the dflt parameter are passed to the provider's method. |
michael@0 | 178 | * |
michael@0 | 179 | * @param aProvider |
michael@0 | 180 | * The provider to call |
michael@0 | 181 | * @param aMethod |
michael@0 | 182 | * The method name to call |
michael@0 | 183 | * @param aDefault |
michael@0 | 184 | * A default return value if the provider does not implement the named |
michael@0 | 185 | * method or throws an error. |
michael@0 | 186 | * @return the return value from the provider or dflt if the provider does not |
michael@0 | 187 | * implement method or throws an error |
michael@0 | 188 | */ |
michael@0 | 189 | function callProvider(aProvider, aMethod, aDefault, ...aArgs) { |
michael@0 | 190 | if (!(aMethod in aProvider)) |
michael@0 | 191 | return aDefault; |
michael@0 | 192 | |
michael@0 | 193 | try { |
michael@0 | 194 | return aProvider[aMethod].apply(aProvider, aArgs); |
michael@0 | 195 | } |
michael@0 | 196 | catch (e) { |
michael@0 | 197 | logger.error("Exception calling provider " + aMethod, e); |
michael@0 | 198 | return aDefault; |
michael@0 | 199 | } |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | /** |
michael@0 | 203 | * Gets the currently selected locale for display. |
michael@0 | 204 | * @return the selected locale or "en-US" if none is selected |
michael@0 | 205 | */ |
michael@0 | 206 | function getLocale() { |
michael@0 | 207 | try { |
michael@0 | 208 | if (Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE)) |
michael@0 | 209 | return Services.locale.getLocaleComponentForUserAgent(); |
michael@0 | 210 | } |
michael@0 | 211 | catch (e) { } |
michael@0 | 212 | |
michael@0 | 213 | try { |
michael@0 | 214 | let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE, |
michael@0 | 215 | Ci.nsIPrefLocalizedString); |
michael@0 | 216 | if (locale) |
michael@0 | 217 | return locale; |
michael@0 | 218 | } |
michael@0 | 219 | catch (e) { } |
michael@0 | 220 | |
michael@0 | 221 | try { |
michael@0 | 222 | return Services.prefs.getCharPref(PREF_SELECTED_LOCALE); |
michael@0 | 223 | } |
michael@0 | 224 | catch (e) { } |
michael@0 | 225 | |
michael@0 | 226 | return "en-US"; |
michael@0 | 227 | } |
michael@0 | 228 | |
michael@0 | 229 | /** |
michael@0 | 230 | * A helper class to repeatedly call a listener with each object in an array |
michael@0 | 231 | * optionally checking whether the object has a method in it. |
michael@0 | 232 | * |
michael@0 | 233 | * @param aObjects |
michael@0 | 234 | * The array of objects to iterate through |
michael@0 | 235 | * @param aMethod |
michael@0 | 236 | * An optional method name, if not null any objects without this method |
michael@0 | 237 | * will not be passed to the listener |
michael@0 | 238 | * @param aListener |
michael@0 | 239 | * A listener implementing nextObject and noMoreObjects methods. The |
michael@0 | 240 | * former will be called with the AsyncObjectCaller as the first |
michael@0 | 241 | * parameter and the object as the second. noMoreObjects will be passed |
michael@0 | 242 | * just the AsyncObjectCaller |
michael@0 | 243 | */ |
michael@0 | 244 | function AsyncObjectCaller(aObjects, aMethod, aListener) { |
michael@0 | 245 | this.objects = aObjects.slice(0); |
michael@0 | 246 | this.method = aMethod; |
michael@0 | 247 | this.listener = aListener; |
michael@0 | 248 | |
michael@0 | 249 | this.callNext(); |
michael@0 | 250 | } |
michael@0 | 251 | |
michael@0 | 252 | AsyncObjectCaller.prototype = { |
michael@0 | 253 | objects: null, |
michael@0 | 254 | method: null, |
michael@0 | 255 | listener: null, |
michael@0 | 256 | |
michael@0 | 257 | /** |
michael@0 | 258 | * Passes the next object to the listener or calls noMoreObjects if there |
michael@0 | 259 | * are none left. |
michael@0 | 260 | */ |
michael@0 | 261 | callNext: function AOC_callNext() { |
michael@0 | 262 | if (this.objects.length == 0) { |
michael@0 | 263 | this.listener.noMoreObjects(this); |
michael@0 | 264 | return; |
michael@0 | 265 | } |
michael@0 | 266 | |
michael@0 | 267 | let object = this.objects.shift(); |
michael@0 | 268 | if (!this.method || this.method in object) |
michael@0 | 269 | this.listener.nextObject(this, object); |
michael@0 | 270 | else |
michael@0 | 271 | this.callNext(); |
michael@0 | 272 | } |
michael@0 | 273 | }; |
michael@0 | 274 | |
michael@0 | 275 | /** |
michael@0 | 276 | * This represents an author of an add-on (e.g. creator or developer) |
michael@0 | 277 | * |
michael@0 | 278 | * @param aName |
michael@0 | 279 | * The name of the author |
michael@0 | 280 | * @param aURL |
michael@0 | 281 | * The URL of the author's profile page |
michael@0 | 282 | */ |
michael@0 | 283 | function AddonAuthor(aName, aURL) { |
michael@0 | 284 | this.name = aName; |
michael@0 | 285 | this.url = aURL; |
michael@0 | 286 | } |
michael@0 | 287 | |
michael@0 | 288 | AddonAuthor.prototype = { |
michael@0 | 289 | name: null, |
michael@0 | 290 | url: null, |
michael@0 | 291 | |
michael@0 | 292 | // Returns the author's name, defaulting to the empty string |
michael@0 | 293 | toString: function AddonAuthor_toString() { |
michael@0 | 294 | return this.name || ""; |
michael@0 | 295 | } |
michael@0 | 296 | } |
michael@0 | 297 | |
michael@0 | 298 | /** |
michael@0 | 299 | * This represents an screenshot for an add-on |
michael@0 | 300 | * |
michael@0 | 301 | * @param aURL |
michael@0 | 302 | * The URL to the full version of the screenshot |
michael@0 | 303 | * @param aWidth |
michael@0 | 304 | * The width in pixels of the screenshot |
michael@0 | 305 | * @param aHeight |
michael@0 | 306 | * The height in pixels of the screenshot |
michael@0 | 307 | * @param aThumbnailURL |
michael@0 | 308 | * The URL to the thumbnail version of the screenshot |
michael@0 | 309 | * @param aThumbnailWidth |
michael@0 | 310 | * The width in pixels of the thumbnail version of the screenshot |
michael@0 | 311 | * @param aThumbnailHeight |
michael@0 | 312 | * The height in pixels of the thumbnail version of the screenshot |
michael@0 | 313 | * @param aCaption |
michael@0 | 314 | * The caption of the screenshot |
michael@0 | 315 | */ |
michael@0 | 316 | function AddonScreenshot(aURL, aWidth, aHeight, aThumbnailURL, |
michael@0 | 317 | aThumbnailWidth, aThumbnailHeight, aCaption) { |
michael@0 | 318 | this.url = aURL; |
michael@0 | 319 | if (aWidth) this.width = aWidth; |
michael@0 | 320 | if (aHeight) this.height = aHeight; |
michael@0 | 321 | if (aThumbnailURL) this.thumbnailURL = aThumbnailURL; |
michael@0 | 322 | if (aThumbnailWidth) this.thumbnailWidth = aThumbnailWidth; |
michael@0 | 323 | if (aThumbnailHeight) this.thumbnailHeight = aThumbnailHeight; |
michael@0 | 324 | if (aCaption) this.caption = aCaption; |
michael@0 | 325 | } |
michael@0 | 326 | |
michael@0 | 327 | AddonScreenshot.prototype = { |
michael@0 | 328 | url: null, |
michael@0 | 329 | width: null, |
michael@0 | 330 | height: null, |
michael@0 | 331 | thumbnailURL: null, |
michael@0 | 332 | thumbnailWidth: null, |
michael@0 | 333 | thumbnailHeight: null, |
michael@0 | 334 | caption: null, |
michael@0 | 335 | |
michael@0 | 336 | // Returns the screenshot URL, defaulting to the empty string |
michael@0 | 337 | toString: function AddonScreenshot_toString() { |
michael@0 | 338 | return this.url || ""; |
michael@0 | 339 | } |
michael@0 | 340 | } |
michael@0 | 341 | |
michael@0 | 342 | |
michael@0 | 343 | /** |
michael@0 | 344 | * This represents a compatibility override for an addon. |
michael@0 | 345 | * |
michael@0 | 346 | * @param aType |
michael@0 | 347 | * Overrride type - "compatible" or "incompatible" |
michael@0 | 348 | * @param aMinVersion |
michael@0 | 349 | * Minimum version of the addon to match |
michael@0 | 350 | * @param aMaxVersion |
michael@0 | 351 | * Maximum version of the addon to match |
michael@0 | 352 | * @param aAppID |
michael@0 | 353 | * Application ID used to match appMinVersion and appMaxVersion |
michael@0 | 354 | * @param aAppMinVersion |
michael@0 | 355 | * Minimum version of the application to match |
michael@0 | 356 | * @param aAppMaxVersion |
michael@0 | 357 | * Maximum version of the application to match |
michael@0 | 358 | */ |
michael@0 | 359 | function AddonCompatibilityOverride(aType, aMinVersion, aMaxVersion, aAppID, |
michael@0 | 360 | aAppMinVersion, aAppMaxVersion) { |
michael@0 | 361 | this.type = aType; |
michael@0 | 362 | this.minVersion = aMinVersion; |
michael@0 | 363 | this.maxVersion = aMaxVersion; |
michael@0 | 364 | this.appID = aAppID; |
michael@0 | 365 | this.appMinVersion = aAppMinVersion; |
michael@0 | 366 | this.appMaxVersion = aAppMaxVersion; |
michael@0 | 367 | } |
michael@0 | 368 | |
michael@0 | 369 | AddonCompatibilityOverride.prototype = { |
michael@0 | 370 | /** |
michael@0 | 371 | * Type of override - "incompatible" or "compatible". |
michael@0 | 372 | * Only "incompatible" is supported for now. |
michael@0 | 373 | */ |
michael@0 | 374 | type: null, |
michael@0 | 375 | |
michael@0 | 376 | /** |
michael@0 | 377 | * Min version of the addon to match. |
michael@0 | 378 | */ |
michael@0 | 379 | minVersion: null, |
michael@0 | 380 | |
michael@0 | 381 | /** |
michael@0 | 382 | * Max version of the addon to match. |
michael@0 | 383 | */ |
michael@0 | 384 | maxVersion: null, |
michael@0 | 385 | |
michael@0 | 386 | /** |
michael@0 | 387 | * Application ID to match. |
michael@0 | 388 | */ |
michael@0 | 389 | appID: null, |
michael@0 | 390 | |
michael@0 | 391 | /** |
michael@0 | 392 | * Min version of the application to match. |
michael@0 | 393 | */ |
michael@0 | 394 | appMinVersion: null, |
michael@0 | 395 | |
michael@0 | 396 | /** |
michael@0 | 397 | * Max version of the application to match. |
michael@0 | 398 | */ |
michael@0 | 399 | appMaxVersion: null |
michael@0 | 400 | }; |
michael@0 | 401 | |
michael@0 | 402 | |
michael@0 | 403 | /** |
michael@0 | 404 | * A type of add-on, used by the UI to determine how to display different types |
michael@0 | 405 | * of add-ons. |
michael@0 | 406 | * |
michael@0 | 407 | * @param aID |
michael@0 | 408 | * The add-on type ID |
michael@0 | 409 | * @param aLocaleURI |
michael@0 | 410 | * The URI of a localized properties file to get the displayable name |
michael@0 | 411 | * for the type from |
michael@0 | 412 | * @param aLocaleKey |
michael@0 | 413 | * The key for the string in the properties file or the actual display |
michael@0 | 414 | * name if aLocaleURI is null. Include %ID% to include the type ID in |
michael@0 | 415 | * the key |
michael@0 | 416 | * @param aViewType |
michael@0 | 417 | * The optional type of view to use in the UI |
michael@0 | 418 | * @param aUIPriority |
michael@0 | 419 | * The priority is used by the UI to list the types in order. Lower |
michael@0 | 420 | * values push the type higher in the list. |
michael@0 | 421 | * @param aFlags |
michael@0 | 422 | * An option set of flags that customize the display of the add-on in |
michael@0 | 423 | * the UI. |
michael@0 | 424 | */ |
michael@0 | 425 | function AddonType(aID, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags) { |
michael@0 | 426 | if (!aID) |
michael@0 | 427 | throw Components.Exception("An AddonType must have an ID", Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 428 | |
michael@0 | 429 | if (aViewType && aUIPriority === undefined) |
michael@0 | 430 | throw Components.Exception("An AddonType with a defined view must have a set UI priority", |
michael@0 | 431 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 432 | |
michael@0 | 433 | if (!aLocaleKey) |
michael@0 | 434 | throw Components.Exception("An AddonType must have a displayable name", |
michael@0 | 435 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 436 | |
michael@0 | 437 | this.id = aID; |
michael@0 | 438 | this.uiPriority = aUIPriority; |
michael@0 | 439 | this.viewType = aViewType; |
michael@0 | 440 | this.flags = aFlags; |
michael@0 | 441 | |
michael@0 | 442 | if (aLocaleURI) { |
michael@0 | 443 | this.__defineGetter__("name", function nameGetter() { |
michael@0 | 444 | delete this.name; |
michael@0 | 445 | let bundle = Services.strings.createBundle(aLocaleURI); |
michael@0 | 446 | this.name = bundle.GetStringFromName(aLocaleKey.replace("%ID%", aID)); |
michael@0 | 447 | return this.name; |
michael@0 | 448 | }); |
michael@0 | 449 | } |
michael@0 | 450 | else { |
michael@0 | 451 | this.name = aLocaleKey; |
michael@0 | 452 | } |
michael@0 | 453 | } |
michael@0 | 454 | |
michael@0 | 455 | var gStarted = false; |
michael@0 | 456 | var gStartupComplete = false; |
michael@0 | 457 | var gCheckCompatibility = true; |
michael@0 | 458 | var gStrictCompatibility = true; |
michael@0 | 459 | var gCheckUpdateSecurityDefault = true; |
michael@0 | 460 | var gCheckUpdateSecurity = gCheckUpdateSecurityDefault; |
michael@0 | 461 | var gUpdateEnabled = true; |
michael@0 | 462 | var gAutoUpdateDefault = true; |
michael@0 | 463 | var gHotfixID = null; |
michael@0 | 464 | |
michael@0 | 465 | /** |
michael@0 | 466 | * This is the real manager, kept here rather than in AddonManager to keep its |
michael@0 | 467 | * contents hidden from API users. |
michael@0 | 468 | */ |
michael@0 | 469 | var AddonManagerInternal = { |
michael@0 | 470 | managerListeners: [], |
michael@0 | 471 | installListeners: [], |
michael@0 | 472 | addonListeners: [], |
michael@0 | 473 | typeListeners: [], |
michael@0 | 474 | providers: [], |
michael@0 | 475 | types: {}, |
michael@0 | 476 | startupChanges: {}, |
michael@0 | 477 | // Store telemetry details per addon provider |
michael@0 | 478 | telemetryDetails: {}, |
michael@0 | 479 | |
michael@0 | 480 | |
michael@0 | 481 | // A read-only wrapper around the types dictionary |
michael@0 | 482 | typesProxy: Proxy.create({ |
michael@0 | 483 | getOwnPropertyDescriptor: function typesProxy_getOwnPropertyDescriptor(aName) { |
michael@0 | 484 | if (!(aName in AddonManagerInternal.types)) |
michael@0 | 485 | return undefined; |
michael@0 | 486 | |
michael@0 | 487 | return { |
michael@0 | 488 | value: AddonManagerInternal.types[aName].type, |
michael@0 | 489 | writable: false, |
michael@0 | 490 | configurable: false, |
michael@0 | 491 | enumerable: true |
michael@0 | 492 | } |
michael@0 | 493 | }, |
michael@0 | 494 | |
michael@0 | 495 | getPropertyDescriptor: function typesProxy_getPropertyDescriptor(aName) { |
michael@0 | 496 | return this.getOwnPropertyDescriptor(aName); |
michael@0 | 497 | }, |
michael@0 | 498 | |
michael@0 | 499 | getOwnPropertyNames: function typesProxy_getOwnPropertyNames() { |
michael@0 | 500 | return Object.keys(AddonManagerInternal.types); |
michael@0 | 501 | }, |
michael@0 | 502 | |
michael@0 | 503 | getPropertyNames: function typesProxy_getPropertyNames() { |
michael@0 | 504 | return this.getOwnPropertyNames(); |
michael@0 | 505 | }, |
michael@0 | 506 | |
michael@0 | 507 | delete: function typesProxy_delete(aName) { |
michael@0 | 508 | // Not allowed to delete properties |
michael@0 | 509 | return false; |
michael@0 | 510 | }, |
michael@0 | 511 | |
michael@0 | 512 | defineProperty: function typesProxy_defineProperty(aName, aProperty) { |
michael@0 | 513 | // Ignore attempts to define properties |
michael@0 | 514 | }, |
michael@0 | 515 | |
michael@0 | 516 | fix: function typesProxy_fix(){ |
michael@0 | 517 | return undefined; |
michael@0 | 518 | }, |
michael@0 | 519 | |
michael@0 | 520 | // Despite MDC's claims to the contrary, it is required that this trap |
michael@0 | 521 | // be defined |
michael@0 | 522 | enumerate: function typesProxy_enumerate() { |
michael@0 | 523 | // All properties are enumerable |
michael@0 | 524 | return this.getPropertyNames(); |
michael@0 | 525 | } |
michael@0 | 526 | }), |
michael@0 | 527 | |
michael@0 | 528 | recordTimestamp: function AMI_recordTimestamp(name, value) { |
michael@0 | 529 | this.TelemetryTimestamps.add(name, value); |
michael@0 | 530 | }, |
michael@0 | 531 | |
michael@0 | 532 | validateBlocklist: function AMI_validateBlocklist() { |
michael@0 | 533 | let appBlocklist = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); |
michael@0 | 534 | |
michael@0 | 535 | // If there is no application shipped blocklist then there is nothing to do |
michael@0 | 536 | if (!appBlocklist.exists()) |
michael@0 | 537 | return; |
michael@0 | 538 | |
michael@0 | 539 | let profileBlocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]); |
michael@0 | 540 | |
michael@0 | 541 | // If there is no blocklist in the profile then copy the application shipped |
michael@0 | 542 | // one there |
michael@0 | 543 | if (!profileBlocklist.exists()) { |
michael@0 | 544 | try { |
michael@0 | 545 | appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST); |
michael@0 | 546 | } |
michael@0 | 547 | catch (e) { |
michael@0 | 548 | logger.warn("Failed to copy the application shipped blocklist to the profile", e); |
michael@0 | 549 | } |
michael@0 | 550 | return; |
michael@0 | 551 | } |
michael@0 | 552 | |
michael@0 | 553 | let fileStream = Cc["@mozilla.org/network/file-input-stream;1"]. |
michael@0 | 554 | createInstance(Ci.nsIFileInputStream); |
michael@0 | 555 | try { |
michael@0 | 556 | let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]. |
michael@0 | 557 | createInstance(Ci.nsIConverterInputStream); |
michael@0 | 558 | fileStream.init(appBlocklist, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); |
michael@0 | 559 | cstream.init(fileStream, "UTF-8", 0, 0); |
michael@0 | 560 | |
michael@0 | 561 | let data = ""; |
michael@0 | 562 | let str = {}; |
michael@0 | 563 | let read = 0; |
michael@0 | 564 | do { |
michael@0 | 565 | read = cstream.readString(0xffffffff, str); |
michael@0 | 566 | data += str.value; |
michael@0 | 567 | } while (read != 0); |
michael@0 | 568 | |
michael@0 | 569 | let parser = Cc["@mozilla.org/xmlextras/domparser;1"]. |
michael@0 | 570 | createInstance(Ci.nsIDOMParser); |
michael@0 | 571 | var doc = parser.parseFromString(data, "text/xml"); |
michael@0 | 572 | } |
michael@0 | 573 | catch (e) { |
michael@0 | 574 | logger.warn("Application shipped blocklist could not be loaded", e); |
michael@0 | 575 | return; |
michael@0 | 576 | } |
michael@0 | 577 | finally { |
michael@0 | 578 | try { |
michael@0 | 579 | fileStream.close(); |
michael@0 | 580 | } |
michael@0 | 581 | catch (e) { |
michael@0 | 582 | logger.warn("Unable to close blocklist file stream", e); |
michael@0 | 583 | } |
michael@0 | 584 | } |
michael@0 | 585 | |
michael@0 | 586 | // If the namespace is incorrect then ignore the application shipped |
michael@0 | 587 | // blocklist |
michael@0 | 588 | if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) { |
michael@0 | 589 | logger.warn("Application shipped blocklist has an unexpected namespace (" + |
michael@0 | 590 | doc.documentElement.namespaceURI + ")"); |
michael@0 | 591 | return; |
michael@0 | 592 | } |
michael@0 | 593 | |
michael@0 | 594 | // If there is no lastupdate information then ignore the application shipped |
michael@0 | 595 | // blocklist |
michael@0 | 596 | if (!doc.documentElement.hasAttribute("lastupdate")) |
michael@0 | 597 | return; |
michael@0 | 598 | |
michael@0 | 599 | // If the application shipped blocklist is older than the profile blocklist |
michael@0 | 600 | // then do nothing |
michael@0 | 601 | if (doc.documentElement.getAttribute("lastupdate") <= |
michael@0 | 602 | profileBlocklist.lastModifiedTime) |
michael@0 | 603 | return; |
michael@0 | 604 | |
michael@0 | 605 | // Otherwise copy the application shipped blocklist to the profile |
michael@0 | 606 | try { |
michael@0 | 607 | appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST); |
michael@0 | 608 | } |
michael@0 | 609 | catch (e) { |
michael@0 | 610 | logger.warn("Failed to copy the application shipped blocklist to the profile", e); |
michael@0 | 611 | } |
michael@0 | 612 | }, |
michael@0 | 613 | |
michael@0 | 614 | /** |
michael@0 | 615 | * Initializes the AddonManager, loading any known providers and initializing |
michael@0 | 616 | * them. |
michael@0 | 617 | */ |
michael@0 | 618 | startup: function AMI_startup() { |
michael@0 | 619 | try { |
michael@0 | 620 | if (gStarted) |
michael@0 | 621 | return; |
michael@0 | 622 | |
michael@0 | 623 | this.recordTimestamp("AMI_startup_begin"); |
michael@0 | 624 | |
michael@0 | 625 | // clear this for xpcshell test restarts |
michael@0 | 626 | for (let provider in this.telemetryDetails) |
michael@0 | 627 | delete this.telemetryDetails[provider]; |
michael@0 | 628 | |
michael@0 | 629 | let appChanged = undefined; |
michael@0 | 630 | |
michael@0 | 631 | let oldAppVersion = null; |
michael@0 | 632 | try { |
michael@0 | 633 | oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION); |
michael@0 | 634 | appChanged = Services.appinfo.version != oldAppVersion; |
michael@0 | 635 | } |
michael@0 | 636 | catch (e) { } |
michael@0 | 637 | |
michael@0 | 638 | let oldPlatformVersion = null; |
michael@0 | 639 | try { |
michael@0 | 640 | oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION); |
michael@0 | 641 | } |
michael@0 | 642 | catch (e) { } |
michael@0 | 643 | |
michael@0 | 644 | if (appChanged !== false) { |
michael@0 | 645 | logger.debug("Application has been upgraded"); |
michael@0 | 646 | Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION, |
michael@0 | 647 | Services.appinfo.version); |
michael@0 | 648 | Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION, |
michael@0 | 649 | Services.appinfo.platformVersion); |
michael@0 | 650 | Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, |
michael@0 | 651 | (appChanged === undefined ? 0 : -1)); |
michael@0 | 652 | this.validateBlocklist(); |
michael@0 | 653 | } |
michael@0 | 654 | |
michael@0 | 655 | #ifndef MOZ_COMPATIBILITY_NIGHTLY |
michael@0 | 656 | PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + "." + |
michael@0 | 657 | Services.appinfo.version.replace(BRANCH_REGEXP, "$1"); |
michael@0 | 658 | #endif |
michael@0 | 659 | |
michael@0 | 660 | try { |
michael@0 | 661 | gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY); |
michael@0 | 662 | } catch (e) {} |
michael@0 | 663 | Services.prefs.addObserver(PREF_EM_CHECK_COMPATIBILITY, this, false); |
michael@0 | 664 | |
michael@0 | 665 | try { |
michael@0 | 666 | gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY); |
michael@0 | 667 | } catch (e) {} |
michael@0 | 668 | Services.prefs.addObserver(PREF_EM_STRICT_COMPATIBILITY, this, false); |
michael@0 | 669 | |
michael@0 | 670 | try { |
michael@0 | 671 | let defaultBranch = Services.prefs.getDefaultBranch(""); |
michael@0 | 672 | gCheckUpdateSecurityDefault = defaultBranch.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); |
michael@0 | 673 | } catch(e) {} |
michael@0 | 674 | |
michael@0 | 675 | try { |
michael@0 | 676 | gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); |
michael@0 | 677 | } catch (e) {} |
michael@0 | 678 | Services.prefs.addObserver(PREF_EM_CHECK_UPDATE_SECURITY, this, false); |
michael@0 | 679 | |
michael@0 | 680 | try { |
michael@0 | 681 | gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED); |
michael@0 | 682 | } catch (e) {} |
michael@0 | 683 | Services.prefs.addObserver(PREF_EM_UPDATE_ENABLED, this, false); |
michael@0 | 684 | |
michael@0 | 685 | try { |
michael@0 | 686 | gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT); |
michael@0 | 687 | } catch (e) {} |
michael@0 | 688 | Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this, false); |
michael@0 | 689 | |
michael@0 | 690 | try { |
michael@0 | 691 | gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); |
michael@0 | 692 | } catch (e) {} |
michael@0 | 693 | Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this, false); |
michael@0 | 694 | |
michael@0 | 695 | let defaultProvidersEnabled = true; |
michael@0 | 696 | try { |
michael@0 | 697 | defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED); |
michael@0 | 698 | } catch (e) {} |
michael@0 | 699 | AddonManagerPrivate.recordSimpleMeasure("default_providers", defaultProvidersEnabled); |
michael@0 | 700 | |
michael@0 | 701 | // Ensure all default providers have had a chance to register themselves |
michael@0 | 702 | if (defaultProvidersEnabled) { |
michael@0 | 703 | for (let url of DEFAULT_PROVIDERS) { |
michael@0 | 704 | try { |
michael@0 | 705 | let scope = {}; |
michael@0 | 706 | Components.utils.import(url, scope); |
michael@0 | 707 | // Sanity check - make sure the provider exports a symbol that |
michael@0 | 708 | // has a 'startup' method |
michael@0 | 709 | let syms = Object.keys(scope); |
michael@0 | 710 | if ((syms.length < 1) || |
michael@0 | 711 | (typeof scope[syms[0]].startup != "function")) { |
michael@0 | 712 | logger.warn("Provider " + url + " has no startup()"); |
michael@0 | 713 | AddonManagerPrivate.recordException("AMI", "provider " + url, "no startup()"); |
michael@0 | 714 | } |
michael@0 | 715 | logger.debug("Loaded provider scope for " + url + ": " + Object.keys(scope).toSource()); |
michael@0 | 716 | } |
michael@0 | 717 | catch (e) { |
michael@0 | 718 | AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e); |
michael@0 | 719 | logger.error("Exception loading default provider \"" + url + "\"", e); |
michael@0 | 720 | } |
michael@0 | 721 | }; |
michael@0 | 722 | } |
michael@0 | 723 | |
michael@0 | 724 | // Load any providers registered in the category manager |
michael@0 | 725 | let catman = Cc["@mozilla.org/categorymanager;1"]. |
michael@0 | 726 | getService(Ci.nsICategoryManager); |
michael@0 | 727 | let entries = catman.enumerateCategory(CATEGORY_PROVIDER_MODULE); |
michael@0 | 728 | while (entries.hasMoreElements()) { |
michael@0 | 729 | let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data; |
michael@0 | 730 | let url = catman.getCategoryEntry(CATEGORY_PROVIDER_MODULE, entry); |
michael@0 | 731 | |
michael@0 | 732 | try { |
michael@0 | 733 | Components.utils.import(url, {}); |
michael@0 | 734 | } |
michael@0 | 735 | catch (e) { |
michael@0 | 736 | AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e); |
michael@0 | 737 | logger.error("Exception loading provider " + entry + " from category \"" + |
michael@0 | 738 | url + "\"", e); |
michael@0 | 739 | } |
michael@0 | 740 | } |
michael@0 | 741 | |
michael@0 | 742 | // Register our shutdown handler with the AsyncShutdown manager |
michael@0 | 743 | AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down providers", |
michael@0 | 744 | this.shutdown.bind(this)); |
michael@0 | 745 | |
michael@0 | 746 | // Once we start calling providers we must allow all normal methods to work. |
michael@0 | 747 | gStarted = true; |
michael@0 | 748 | |
michael@0 | 749 | this.callProviders("startup", appChanged, oldAppVersion, |
michael@0 | 750 | oldPlatformVersion); |
michael@0 | 751 | |
michael@0 | 752 | // If this is a new profile just pretend that there were no changes |
michael@0 | 753 | if (appChanged === undefined) { |
michael@0 | 754 | for (let type in this.startupChanges) |
michael@0 | 755 | delete this.startupChanges[type]; |
michael@0 | 756 | } |
michael@0 | 757 | |
michael@0 | 758 | gStartupComplete = true; |
michael@0 | 759 | this.recordTimestamp("AMI_startup_end"); |
michael@0 | 760 | } |
michael@0 | 761 | catch (e) { |
michael@0 | 762 | logger.error("startup failed", e); |
michael@0 | 763 | AddonManagerPrivate.recordException("AMI", "startup failed", e); |
michael@0 | 764 | } |
michael@0 | 765 | }, |
michael@0 | 766 | |
michael@0 | 767 | /** |
michael@0 | 768 | * Registers a new AddonProvider. |
michael@0 | 769 | * |
michael@0 | 770 | * @param aProvider |
michael@0 | 771 | * The provider to register |
michael@0 | 772 | * @param aTypes |
michael@0 | 773 | * An optional array of add-on types |
michael@0 | 774 | */ |
michael@0 | 775 | registerProvider: function AMI_registerProvider(aProvider, aTypes) { |
michael@0 | 776 | if (!aProvider || typeof aProvider != "object") |
michael@0 | 777 | throw Components.Exception("aProvider must be specified", |
michael@0 | 778 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 779 | |
michael@0 | 780 | if (aTypes && !Array.isArray(aTypes)) |
michael@0 | 781 | throw Components.Exception("aTypes must be an array or null", |
michael@0 | 782 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 783 | |
michael@0 | 784 | this.providers.push(aProvider); |
michael@0 | 785 | |
michael@0 | 786 | if (aTypes) { |
michael@0 | 787 | aTypes.forEach(function(aType) { |
michael@0 | 788 | if (!(aType.id in this.types)) { |
michael@0 | 789 | if (!VALID_TYPES_REGEXP.test(aType.id)) { |
michael@0 | 790 | logger.warn("Ignoring invalid type " + aType.id); |
michael@0 | 791 | return; |
michael@0 | 792 | } |
michael@0 | 793 | |
michael@0 | 794 | this.types[aType.id] = { |
michael@0 | 795 | type: aType, |
michael@0 | 796 | providers: [aProvider] |
michael@0 | 797 | }; |
michael@0 | 798 | |
michael@0 | 799 | let typeListeners = this.typeListeners.slice(0); |
michael@0 | 800 | for (let listener of typeListeners) { |
michael@0 | 801 | safeCall(function listenerSafeCall() { |
michael@0 | 802 | listener.onTypeAdded(aType); |
michael@0 | 803 | }); |
michael@0 | 804 | } |
michael@0 | 805 | } |
michael@0 | 806 | else { |
michael@0 | 807 | this.types[aType.id].providers.push(aProvider); |
michael@0 | 808 | } |
michael@0 | 809 | }, this); |
michael@0 | 810 | } |
michael@0 | 811 | |
michael@0 | 812 | // If we're registering after startup call this provider's startup. |
michael@0 | 813 | if (gStarted) |
michael@0 | 814 | callProvider(aProvider, "startup"); |
michael@0 | 815 | }, |
michael@0 | 816 | |
michael@0 | 817 | /** |
michael@0 | 818 | * Unregisters an AddonProvider. |
michael@0 | 819 | * |
michael@0 | 820 | * @param aProvider |
michael@0 | 821 | * The provider to unregister |
michael@0 | 822 | */ |
michael@0 | 823 | unregisterProvider: function AMI_unregisterProvider(aProvider) { |
michael@0 | 824 | if (!aProvider || typeof aProvider != "object") |
michael@0 | 825 | throw Components.Exception("aProvider must be specified", |
michael@0 | 826 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 827 | |
michael@0 | 828 | let pos = 0; |
michael@0 | 829 | while (pos < this.providers.length) { |
michael@0 | 830 | if (this.providers[pos] == aProvider) |
michael@0 | 831 | this.providers.splice(pos, 1); |
michael@0 | 832 | else |
michael@0 | 833 | pos++; |
michael@0 | 834 | } |
michael@0 | 835 | |
michael@0 | 836 | for (let type in this.types) { |
michael@0 | 837 | this.types[type].providers = this.types[type].providers.filter(function filterProvider(p) p != aProvider); |
michael@0 | 838 | if (this.types[type].providers.length == 0) { |
michael@0 | 839 | let oldType = this.types[type].type; |
michael@0 | 840 | delete this.types[type]; |
michael@0 | 841 | |
michael@0 | 842 | let typeListeners = this.typeListeners.slice(0); |
michael@0 | 843 | for (let listener of typeListeners) { |
michael@0 | 844 | safeCall(function listenerSafeCall() { |
michael@0 | 845 | listener.onTypeRemoved(oldType); |
michael@0 | 846 | }); |
michael@0 | 847 | } |
michael@0 | 848 | } |
michael@0 | 849 | } |
michael@0 | 850 | |
michael@0 | 851 | // If we're unregistering after startup call this provider's shutdown. |
michael@0 | 852 | if (gStarted) |
michael@0 | 853 | callProvider(aProvider, "shutdown"); |
michael@0 | 854 | }, |
michael@0 | 855 | |
michael@0 | 856 | /** |
michael@0 | 857 | * Calls a method on all registered providers if it exists and consumes any |
michael@0 | 858 | * thrown exception. Return values are ignored. Any parameters after the |
michael@0 | 859 | * method parameter are passed to the provider's method. |
michael@0 | 860 | * |
michael@0 | 861 | * @param aMethod |
michael@0 | 862 | * The method name to call |
michael@0 | 863 | * @see callProvider |
michael@0 | 864 | */ |
michael@0 | 865 | callProviders: function AMI_callProviders(aMethod, ...aArgs) { |
michael@0 | 866 | if (!aMethod || typeof aMethod != "string") |
michael@0 | 867 | throw Components.Exception("aMethod must be a non-empty string", |
michael@0 | 868 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 869 | |
michael@0 | 870 | let providers = this.providers.slice(0); |
michael@0 | 871 | for (let provider of providers) { |
michael@0 | 872 | try { |
michael@0 | 873 | if (aMethod in provider) |
michael@0 | 874 | provider[aMethod].apply(provider, aArgs); |
michael@0 | 875 | } |
michael@0 | 876 | catch (e) { |
michael@0 | 877 | AddonManagerPrivate.recordException("AMI", "provider " + aMethod, e); |
michael@0 | 878 | logger.error("Exception calling provider " + aMethod, e); |
michael@0 | 879 | } |
michael@0 | 880 | } |
michael@0 | 881 | }, |
michael@0 | 882 | |
michael@0 | 883 | /** |
michael@0 | 884 | * Calls a method on all registered providers, if the provider implements |
michael@0 | 885 | * the method. The called method is expected to return a promise, and |
michael@0 | 886 | * callProvidersAsync returns a promise that resolves when every provider |
michael@0 | 887 | * method has either resolved or rejected. Rejection reasons are logged |
michael@0 | 888 | * but otherwise ignored. Return values are ignored. Any parameters after the |
michael@0 | 889 | * method parameter are passed to the provider's method. |
michael@0 | 890 | * |
michael@0 | 891 | * @param aMethod |
michael@0 | 892 | * The method name to call |
michael@0 | 893 | * @see callProvider |
michael@0 | 894 | */ |
michael@0 | 895 | callProvidersAsync: function AMI_callProviders(aMethod, ...aArgs) { |
michael@0 | 896 | if (!aMethod || typeof aMethod != "string") |
michael@0 | 897 | throw Components.Exception("aMethod must be a non-empty string", |
michael@0 | 898 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 899 | |
michael@0 | 900 | let allProviders = []; |
michael@0 | 901 | |
michael@0 | 902 | let providers = this.providers.slice(0); |
michael@0 | 903 | for (let provider of providers) { |
michael@0 | 904 | try { |
michael@0 | 905 | if (aMethod in provider) { |
michael@0 | 906 | // Resolve a new promise with the result of the method, to handle both |
michael@0 | 907 | // methods that return values (or nothing) and methods that return promises. |
michael@0 | 908 | let providerResult = provider[aMethod].apply(provider, aArgs); |
michael@0 | 909 | let nextPromise = Promise.resolve(providerResult); |
michael@0 | 910 | // Log and swallow the errors from methods that do return promises. |
michael@0 | 911 | nextPromise = nextPromise.then( |
michael@0 | 912 | null, |
michael@0 | 913 | e => logger.error("Exception calling provider " + aMethod, e)); |
michael@0 | 914 | allProviders.push(nextPromise); |
michael@0 | 915 | } |
michael@0 | 916 | } |
michael@0 | 917 | catch (e) { |
michael@0 | 918 | logger.error("Exception calling provider " + aMethod, e); |
michael@0 | 919 | } |
michael@0 | 920 | } |
michael@0 | 921 | // Because we use promise.then to catch and log all errors above, Promise.all() |
michael@0 | 922 | // will never exit early because of a rejection. |
michael@0 | 923 | return Promise.all(allProviders); |
michael@0 | 924 | }, |
michael@0 | 925 | |
michael@0 | 926 | /** |
michael@0 | 927 | * Shuts down the addon manager and all registered providers, this must clean |
michael@0 | 928 | * up everything in order for automated tests to fake restarts. |
michael@0 | 929 | * @return Promise{null} that resolves when all providers and dependent modules |
michael@0 | 930 | * have finished shutting down |
michael@0 | 931 | */ |
michael@0 | 932 | shutdown: function AMI_shutdown() { |
michael@0 | 933 | logger.debug("shutdown"); |
michael@0 | 934 | // Clean up listeners |
michael@0 | 935 | Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this); |
michael@0 | 936 | Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this); |
michael@0 | 937 | Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this); |
michael@0 | 938 | Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this); |
michael@0 | 939 | Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this); |
michael@0 | 940 | Services.prefs.removeObserver(PREF_EM_HOTFIX_ID, this); |
michael@0 | 941 | |
michael@0 | 942 | // Only shut down providers if they've been started. Shut down |
michael@0 | 943 | // AddonRepository after providers (if any). |
michael@0 | 944 | let shuttingDown = null; |
michael@0 | 945 | if (gStarted) { |
michael@0 | 946 | shuttingDown = this.callProvidersAsync("shutdown") |
michael@0 | 947 | .then(null, |
michael@0 | 948 | err => logger.error("Failure during async provider shutdown", err)) |
michael@0 | 949 | .then(() => AddonRepository.shutdown()); |
michael@0 | 950 | } |
michael@0 | 951 | else { |
michael@0 | 952 | shuttingDown = AddonRepository.shutdown(); |
michael@0 | 953 | } |
michael@0 | 954 | |
michael@0 | 955 | shuttingDown.then(val => logger.debug("Async provider shutdown done"), |
michael@0 | 956 | err => logger.error("Failure during AddonRepository shutdown", err)) |
michael@0 | 957 | .then(() => { |
michael@0 | 958 | this.managerListeners.splice(0, this.managerListeners.length); |
michael@0 | 959 | this.installListeners.splice(0, this.installListeners.length); |
michael@0 | 960 | this.addonListeners.splice(0, this.addonListeners.length); |
michael@0 | 961 | this.typeListeners.splice(0, this.typeListeners.length); |
michael@0 | 962 | for (let type in this.startupChanges) |
michael@0 | 963 | delete this.startupChanges[type]; |
michael@0 | 964 | gStarted = false; |
michael@0 | 965 | gStartupComplete = false; |
michael@0 | 966 | }); |
michael@0 | 967 | return shuttingDown; |
michael@0 | 968 | }, |
michael@0 | 969 | |
michael@0 | 970 | /** |
michael@0 | 971 | * Notified when a preference we're interested in has changed. |
michael@0 | 972 | * |
michael@0 | 973 | * @see nsIObserver |
michael@0 | 974 | */ |
michael@0 | 975 | observe: function AMI_observe(aSubject, aTopic, aData) { |
michael@0 | 976 | switch (aData) { |
michael@0 | 977 | case PREF_EM_CHECK_COMPATIBILITY: { |
michael@0 | 978 | let oldValue = gCheckCompatibility; |
michael@0 | 979 | try { |
michael@0 | 980 | gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY); |
michael@0 | 981 | } catch(e) { |
michael@0 | 982 | gCheckCompatibility = true; |
michael@0 | 983 | } |
michael@0 | 984 | |
michael@0 | 985 | this.callManagerListeners("onCompatibilityModeChanged"); |
michael@0 | 986 | |
michael@0 | 987 | if (gCheckCompatibility != oldValue) |
michael@0 | 988 | this.updateAddonAppDisabledStates(); |
michael@0 | 989 | |
michael@0 | 990 | break; |
michael@0 | 991 | } |
michael@0 | 992 | case PREF_EM_STRICT_COMPATIBILITY: { |
michael@0 | 993 | let oldValue = gStrictCompatibility; |
michael@0 | 994 | try { |
michael@0 | 995 | gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY); |
michael@0 | 996 | } catch(e) { |
michael@0 | 997 | gStrictCompatibility = true; |
michael@0 | 998 | } |
michael@0 | 999 | |
michael@0 | 1000 | this.callManagerListeners("onCompatibilityModeChanged"); |
michael@0 | 1001 | |
michael@0 | 1002 | if (gStrictCompatibility != oldValue) |
michael@0 | 1003 | this.updateAddonAppDisabledStates(); |
michael@0 | 1004 | |
michael@0 | 1005 | break; |
michael@0 | 1006 | } |
michael@0 | 1007 | case PREF_EM_CHECK_UPDATE_SECURITY: { |
michael@0 | 1008 | let oldValue = gCheckUpdateSecurity; |
michael@0 | 1009 | try { |
michael@0 | 1010 | gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); |
michael@0 | 1011 | } catch(e) { |
michael@0 | 1012 | gCheckUpdateSecurity = true; |
michael@0 | 1013 | } |
michael@0 | 1014 | |
michael@0 | 1015 | this.callManagerListeners("onCheckUpdateSecurityChanged"); |
michael@0 | 1016 | |
michael@0 | 1017 | if (gCheckUpdateSecurity != oldValue) |
michael@0 | 1018 | this.updateAddonAppDisabledStates(); |
michael@0 | 1019 | |
michael@0 | 1020 | break; |
michael@0 | 1021 | } |
michael@0 | 1022 | case PREF_EM_UPDATE_ENABLED: { |
michael@0 | 1023 | let oldValue = gUpdateEnabled; |
michael@0 | 1024 | try { |
michael@0 | 1025 | gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED); |
michael@0 | 1026 | } catch(e) { |
michael@0 | 1027 | gUpdateEnabled = true; |
michael@0 | 1028 | } |
michael@0 | 1029 | |
michael@0 | 1030 | this.callManagerListeners("onUpdateModeChanged"); |
michael@0 | 1031 | break; |
michael@0 | 1032 | } |
michael@0 | 1033 | case PREF_EM_AUTOUPDATE_DEFAULT: { |
michael@0 | 1034 | let oldValue = gAutoUpdateDefault; |
michael@0 | 1035 | try { |
michael@0 | 1036 | gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT); |
michael@0 | 1037 | } catch(e) { |
michael@0 | 1038 | gAutoUpdateDefault = true; |
michael@0 | 1039 | } |
michael@0 | 1040 | |
michael@0 | 1041 | this.callManagerListeners("onUpdateModeChanged"); |
michael@0 | 1042 | break; |
michael@0 | 1043 | } |
michael@0 | 1044 | case PREF_EM_HOTFIX_ID: { |
michael@0 | 1045 | try { |
michael@0 | 1046 | gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); |
michael@0 | 1047 | } catch(e) { |
michael@0 | 1048 | gHotfixID = null; |
michael@0 | 1049 | } |
michael@0 | 1050 | break; |
michael@0 | 1051 | } |
michael@0 | 1052 | } |
michael@0 | 1053 | }, |
michael@0 | 1054 | |
michael@0 | 1055 | /** |
michael@0 | 1056 | * Replaces %...% strings in an addon url (update and updateInfo) with |
michael@0 | 1057 | * appropriate values. |
michael@0 | 1058 | * |
michael@0 | 1059 | * @param aAddon |
michael@0 | 1060 | * The Addon representing the add-on |
michael@0 | 1061 | * @param aUri |
michael@0 | 1062 | * The string representation of the URI to escape |
michael@0 | 1063 | * @param aAppVersion |
michael@0 | 1064 | * The optional application version to use for %APP_VERSION% |
michael@0 | 1065 | * @return The appropriately escaped URI. |
michael@0 | 1066 | */ |
michael@0 | 1067 | escapeAddonURI: function AMI_escapeAddonURI(aAddon, aUri, aAppVersion) |
michael@0 | 1068 | { |
michael@0 | 1069 | if (!aAddon || typeof aAddon != "object") |
michael@0 | 1070 | throw Components.Exception("aAddon must be an Addon object", |
michael@0 | 1071 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1072 | |
michael@0 | 1073 | if (!aUri || typeof aUri != "string") |
michael@0 | 1074 | throw Components.Exception("aUri must be a non-empty string", |
michael@0 | 1075 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1076 | |
michael@0 | 1077 | if (aAppVersion && typeof aAppVersion != "string") |
michael@0 | 1078 | throw Components.Exception("aAppVersion must be a string or null", |
michael@0 | 1079 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1080 | |
michael@0 | 1081 | var addonStatus = aAddon.userDisabled || aAddon.softDisabled ? "userDisabled" |
michael@0 | 1082 | : "userEnabled"; |
michael@0 | 1083 | |
michael@0 | 1084 | if (!aAddon.isCompatible) |
michael@0 | 1085 | addonStatus += ",incompatible"; |
michael@0 | 1086 | if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) |
michael@0 | 1087 | addonStatus += ",blocklisted"; |
michael@0 | 1088 | if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) |
michael@0 | 1089 | addonStatus += ",softblocked"; |
michael@0 | 1090 | |
michael@0 | 1091 | try { |
michael@0 | 1092 | var xpcomABI = Services.appinfo.XPCOMABI; |
michael@0 | 1093 | } catch (ex) { |
michael@0 | 1094 | xpcomABI = UNKNOWN_XPCOM_ABI; |
michael@0 | 1095 | } |
michael@0 | 1096 | |
michael@0 | 1097 | let uri = aUri.replace(/%ITEM_ID%/g, aAddon.id); |
michael@0 | 1098 | uri = uri.replace(/%ITEM_VERSION%/g, aAddon.version); |
michael@0 | 1099 | uri = uri.replace(/%ITEM_STATUS%/g, addonStatus); |
michael@0 | 1100 | uri = uri.replace(/%APP_ID%/g, Services.appinfo.ID); |
michael@0 | 1101 | uri = uri.replace(/%APP_VERSION%/g, aAppVersion ? aAppVersion : |
michael@0 | 1102 | Services.appinfo.version); |
michael@0 | 1103 | uri = uri.replace(/%REQ_VERSION%/g, UPDATE_REQUEST_VERSION); |
michael@0 | 1104 | uri = uri.replace(/%APP_OS%/g, Services.appinfo.OS); |
michael@0 | 1105 | uri = uri.replace(/%APP_ABI%/g, xpcomABI); |
michael@0 | 1106 | uri = uri.replace(/%APP_LOCALE%/g, getLocale()); |
michael@0 | 1107 | uri = uri.replace(/%CURRENT_APP_VERSION%/g, Services.appinfo.version); |
michael@0 | 1108 | |
michael@0 | 1109 | // Replace custom parameters (names of custom parameters must have at |
michael@0 | 1110 | // least 3 characters to prevent lookups for something like %D0%C8) |
michael@0 | 1111 | var catMan = null; |
michael@0 | 1112 | uri = uri.replace(/%(\w{3,})%/g, function parameterReplace(aMatch, aParam) { |
michael@0 | 1113 | if (!catMan) { |
michael@0 | 1114 | catMan = Cc["@mozilla.org/categorymanager;1"]. |
michael@0 | 1115 | getService(Ci.nsICategoryManager); |
michael@0 | 1116 | } |
michael@0 | 1117 | |
michael@0 | 1118 | try { |
michael@0 | 1119 | var contractID = catMan.getCategoryEntry(CATEGORY_UPDATE_PARAMS, aParam); |
michael@0 | 1120 | var paramHandler = Cc[contractID].getService(Ci.nsIPropertyBag2); |
michael@0 | 1121 | return paramHandler.getPropertyAsAString(aParam); |
michael@0 | 1122 | } |
michael@0 | 1123 | catch(e) { |
michael@0 | 1124 | return aMatch; |
michael@0 | 1125 | } |
michael@0 | 1126 | }); |
michael@0 | 1127 | |
michael@0 | 1128 | // escape() does not properly encode + symbols in any embedded FVF strings. |
michael@0 | 1129 | return uri.replace(/\+/g, "%2B"); |
michael@0 | 1130 | }, |
michael@0 | 1131 | |
michael@0 | 1132 | /** |
michael@0 | 1133 | * Performs a background update check by starting an update for all add-ons |
michael@0 | 1134 | * that can be updated. |
michael@0 | 1135 | */ |
michael@0 | 1136 | backgroundUpdateCheck: function AMI_backgroundUpdateCheck() { |
michael@0 | 1137 | if (!gStarted) |
michael@0 | 1138 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1139 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1140 | |
michael@0 | 1141 | let hotfixID = this.hotfixID; |
michael@0 | 1142 | |
michael@0 | 1143 | let checkHotfix = hotfixID && |
michael@0 | 1144 | Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) && |
michael@0 | 1145 | Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO); |
michael@0 | 1146 | |
michael@0 | 1147 | if (!this.updateEnabled && !checkHotfix) |
michael@0 | 1148 | return; |
michael@0 | 1149 | |
michael@0 | 1150 | Services.obs.notifyObservers(null, "addons-background-update-start", null); |
michael@0 | 1151 | |
michael@0 | 1152 | // Start this from one to ensure the whole of this function completes before |
michael@0 | 1153 | // we can send the complete notification. Some parts can in some cases |
michael@0 | 1154 | // complete synchronously before later parts have a chance to increment |
michael@0 | 1155 | // pendingUpdates. |
michael@0 | 1156 | let pendingUpdates = 1; |
michael@0 | 1157 | |
michael@0 | 1158 | function notifyComplete() { |
michael@0 | 1159 | if (--pendingUpdates == 0) { |
michael@0 | 1160 | Services.obs.notifyObservers(null, |
michael@0 | 1161 | "addons-background-update-complete", |
michael@0 | 1162 | null); |
michael@0 | 1163 | } |
michael@0 | 1164 | } |
michael@0 | 1165 | |
michael@0 | 1166 | if (this.updateEnabled) { |
michael@0 | 1167 | let scope = {}; |
michael@0 | 1168 | Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope); |
michael@0 | 1169 | scope.LightweightThemeManager.updateCurrentTheme(); |
michael@0 | 1170 | |
michael@0 | 1171 | pendingUpdates++; |
michael@0 | 1172 | this.getAllAddons(function getAddonsCallback(aAddons) { |
michael@0 | 1173 | // If there is a known hotfix then exclude it from the list of add-ons to update. |
michael@0 | 1174 | var ids = [a.id for each (a in aAddons) if (a.id != hotfixID)]; |
michael@0 | 1175 | |
michael@0 | 1176 | // Repopulate repository cache first, to ensure compatibility overrides |
michael@0 | 1177 | // are up to date before checking for addon updates. |
michael@0 | 1178 | AddonRepository.backgroundUpdateCheck( |
michael@0 | 1179 | ids, function BUC_backgroundUpdateCheckCallback() { |
michael@0 | 1180 | pendingUpdates += aAddons.length; |
michael@0 | 1181 | aAddons.forEach(function BUC_forEachCallback(aAddon) { |
michael@0 | 1182 | if (aAddon.id == hotfixID) { |
michael@0 | 1183 | notifyComplete(); |
michael@0 | 1184 | return; |
michael@0 | 1185 | } |
michael@0 | 1186 | |
michael@0 | 1187 | // Check all add-ons for updates so that any compatibility updates will |
michael@0 | 1188 | // be applied |
michael@0 | 1189 | aAddon.findUpdates({ |
michael@0 | 1190 | onUpdateAvailable: function BUC_onUpdateAvailable(aAddon, aInstall) { |
michael@0 | 1191 | // Start installing updates when the add-on can be updated and |
michael@0 | 1192 | // background updates should be applied. |
michael@0 | 1193 | if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE && |
michael@0 | 1194 | AddonManager.shouldAutoUpdate(aAddon)) { |
michael@0 | 1195 | aInstall.install(); |
michael@0 | 1196 | } |
michael@0 | 1197 | }, |
michael@0 | 1198 | |
michael@0 | 1199 | onUpdateFinished: notifyComplete |
michael@0 | 1200 | }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); |
michael@0 | 1201 | }); |
michael@0 | 1202 | |
michael@0 | 1203 | notifyComplete(); |
michael@0 | 1204 | }); |
michael@0 | 1205 | }); |
michael@0 | 1206 | } |
michael@0 | 1207 | |
michael@0 | 1208 | if (checkHotfix) { |
michael@0 | 1209 | var hotfixVersion = ""; |
michael@0 | 1210 | try { |
michael@0 | 1211 | hotfixVersion = Services.prefs.getCharPref(PREF_EM_HOTFIX_LASTVERSION); |
michael@0 | 1212 | } |
michael@0 | 1213 | catch (e) { } |
michael@0 | 1214 | |
michael@0 | 1215 | let url = null; |
michael@0 | 1216 | if (Services.prefs.getPrefType(PREF_EM_HOTFIX_URL) == Ci.nsIPrefBranch.PREF_STRING) |
michael@0 | 1217 | url = Services.prefs.getCharPref(PREF_EM_HOTFIX_URL); |
michael@0 | 1218 | else |
michael@0 | 1219 | url = Services.prefs.getCharPref(PREF_EM_UPDATE_BACKGROUND_URL); |
michael@0 | 1220 | |
michael@0 | 1221 | // Build the URI from a fake add-on data. |
michael@0 | 1222 | url = AddonManager.escapeAddonURI({ |
michael@0 | 1223 | id: hotfixID, |
michael@0 | 1224 | version: hotfixVersion, |
michael@0 | 1225 | userDisabled: false, |
michael@0 | 1226 | appDisabled: false |
michael@0 | 1227 | }, url); |
michael@0 | 1228 | |
michael@0 | 1229 | pendingUpdates++; |
michael@0 | 1230 | Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); |
michael@0 | 1231 | AddonUpdateChecker.checkForUpdates(hotfixID, null, url, { |
michael@0 | 1232 | onUpdateCheckComplete: function BUC_onUpdateCheckComplete(aUpdates) { |
michael@0 | 1233 | let update = AddonUpdateChecker.getNewestCompatibleUpdate(aUpdates); |
michael@0 | 1234 | if (!update) { |
michael@0 | 1235 | notifyComplete(); |
michael@0 | 1236 | return; |
michael@0 | 1237 | } |
michael@0 | 1238 | |
michael@0 | 1239 | // If the available version isn't newer than the last installed |
michael@0 | 1240 | // version then ignore it. |
michael@0 | 1241 | if (Services.vc.compare(hotfixVersion, update.version) >= 0) { |
michael@0 | 1242 | notifyComplete(); |
michael@0 | 1243 | return; |
michael@0 | 1244 | } |
michael@0 | 1245 | |
michael@0 | 1246 | logger.debug("Downloading hotfix version " + update.version); |
michael@0 | 1247 | AddonManager.getInstallForURL(update.updateURL, |
michael@0 | 1248 | function BUC_getInstallForURL(aInstall) { |
michael@0 | 1249 | aInstall.addListener({ |
michael@0 | 1250 | onDownloadEnded: function BUC_onDownloadEnded(aInstall) { |
michael@0 | 1251 | try { |
michael@0 | 1252 | if (!Services.prefs.getBoolPref(PREF_EM_CERT_CHECKATTRIBUTES)) |
michael@0 | 1253 | return; |
michael@0 | 1254 | } |
michael@0 | 1255 | catch (e) { |
michael@0 | 1256 | // By default don't do certificate checks. |
michael@0 | 1257 | return; |
michael@0 | 1258 | } |
michael@0 | 1259 | |
michael@0 | 1260 | try { |
michael@0 | 1261 | CertUtils.validateCert(aInstall.certificate, |
michael@0 | 1262 | CertUtils.readCertPrefs(PREF_EM_HOTFIX_CERTS)); |
michael@0 | 1263 | } |
michael@0 | 1264 | catch (e) { |
michael@0 | 1265 | logger.warn("The hotfix add-on was not signed by the expected " + |
michael@0 | 1266 | "certificate and so will not be installed."); |
michael@0 | 1267 | aInstall.cancel(); |
michael@0 | 1268 | } |
michael@0 | 1269 | }, |
michael@0 | 1270 | |
michael@0 | 1271 | onInstallEnded: function BUC_onInstallEnded(aInstall) { |
michael@0 | 1272 | // Remember the last successfully installed version. |
michael@0 | 1273 | Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION, |
michael@0 | 1274 | aInstall.version); |
michael@0 | 1275 | }, |
michael@0 | 1276 | |
michael@0 | 1277 | onInstallCancelled: function BUC_onInstallCancelled(aInstall) { |
michael@0 | 1278 | // Revert to the previous version if the installation was |
michael@0 | 1279 | // cancelled. |
michael@0 | 1280 | Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION, |
michael@0 | 1281 | hotfixVersion); |
michael@0 | 1282 | } |
michael@0 | 1283 | }); |
michael@0 | 1284 | |
michael@0 | 1285 | aInstall.install(); |
michael@0 | 1286 | |
michael@0 | 1287 | notifyComplete(); |
michael@0 | 1288 | }, "application/x-xpinstall", update.updateHash, null, |
michael@0 | 1289 | null, update.version); |
michael@0 | 1290 | }, |
michael@0 | 1291 | |
michael@0 | 1292 | onUpdateCheckError: notifyComplete |
michael@0 | 1293 | }); |
michael@0 | 1294 | } |
michael@0 | 1295 | |
michael@0 | 1296 | notifyComplete(); |
michael@0 | 1297 | }, |
michael@0 | 1298 | |
michael@0 | 1299 | /** |
michael@0 | 1300 | * Adds a add-on to the list of detected changes for this startup. If |
michael@0 | 1301 | * addStartupChange is called multiple times for the same add-on in the same |
michael@0 | 1302 | * startup then only the most recent change will be remembered. |
michael@0 | 1303 | * |
michael@0 | 1304 | * @param aType |
michael@0 | 1305 | * The type of change as a string. Providers can define their own |
michael@0 | 1306 | * types of changes or use the existing defined STARTUP_CHANGE_* |
michael@0 | 1307 | * constants |
michael@0 | 1308 | * @param aID |
michael@0 | 1309 | * The ID of the add-on |
michael@0 | 1310 | */ |
michael@0 | 1311 | addStartupChange: function AMI_addStartupChange(aType, aID) { |
michael@0 | 1312 | if (!aType || typeof aType != "string") |
michael@0 | 1313 | throw Components.Exception("aType must be a non-empty string", |
michael@0 | 1314 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1315 | |
michael@0 | 1316 | if (!aID || typeof aID != "string") |
michael@0 | 1317 | throw Components.Exception("aID must be a non-empty string", |
michael@0 | 1318 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1319 | |
michael@0 | 1320 | if (gStartupComplete) |
michael@0 | 1321 | return; |
michael@0 | 1322 | |
michael@0 | 1323 | // Ensure that an ID is only listed in one type of change |
michael@0 | 1324 | for (let type in this.startupChanges) |
michael@0 | 1325 | this.removeStartupChange(type, aID); |
michael@0 | 1326 | |
michael@0 | 1327 | if (!(aType in this.startupChanges)) |
michael@0 | 1328 | this.startupChanges[aType] = []; |
michael@0 | 1329 | this.startupChanges[aType].push(aID); |
michael@0 | 1330 | }, |
michael@0 | 1331 | |
michael@0 | 1332 | /** |
michael@0 | 1333 | * Removes a startup change for an add-on. |
michael@0 | 1334 | * |
michael@0 | 1335 | * @param aType |
michael@0 | 1336 | * The type of change |
michael@0 | 1337 | * @param aID |
michael@0 | 1338 | * The ID of the add-on |
michael@0 | 1339 | */ |
michael@0 | 1340 | removeStartupChange: function AMI_removeStartupChange(aType, aID) { |
michael@0 | 1341 | if (!aType || typeof aType != "string") |
michael@0 | 1342 | throw Components.Exception("aType must be a non-empty string", |
michael@0 | 1343 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1344 | |
michael@0 | 1345 | if (!aID || typeof aID != "string") |
michael@0 | 1346 | throw Components.Exception("aID must be a non-empty string", |
michael@0 | 1347 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1348 | |
michael@0 | 1349 | if (gStartupComplete) |
michael@0 | 1350 | return; |
michael@0 | 1351 | |
michael@0 | 1352 | if (!(aType in this.startupChanges)) |
michael@0 | 1353 | return; |
michael@0 | 1354 | |
michael@0 | 1355 | this.startupChanges[aType] = this.startupChanges[aType].filter( |
michael@0 | 1356 | function filterItem(aItem) aItem != aID); |
michael@0 | 1357 | }, |
michael@0 | 1358 | |
michael@0 | 1359 | /** |
michael@0 | 1360 | * Calls all registered AddonManagerListeners with an event. Any parameters |
michael@0 | 1361 | * after the method parameter are passed to the listener. |
michael@0 | 1362 | * |
michael@0 | 1363 | * @param aMethod |
michael@0 | 1364 | * The method on the listeners to call |
michael@0 | 1365 | */ |
michael@0 | 1366 | callManagerListeners: function AMI_callManagerListeners(aMethod, ...aArgs) { |
michael@0 | 1367 | if (!gStarted) |
michael@0 | 1368 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1369 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1370 | |
michael@0 | 1371 | if (!aMethod || typeof aMethod != "string") |
michael@0 | 1372 | throw Components.Exception("aMethod must be a non-empty string", |
michael@0 | 1373 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1374 | |
michael@0 | 1375 | let managerListeners = this.managerListeners.slice(0); |
michael@0 | 1376 | for (let listener of managerListeners) { |
michael@0 | 1377 | try { |
michael@0 | 1378 | if (aMethod in listener) |
michael@0 | 1379 | listener[aMethod].apply(listener, aArgs); |
michael@0 | 1380 | } |
michael@0 | 1381 | catch (e) { |
michael@0 | 1382 | logger.warn("AddonManagerListener threw exception when calling " + aMethod, e); |
michael@0 | 1383 | } |
michael@0 | 1384 | } |
michael@0 | 1385 | }, |
michael@0 | 1386 | |
michael@0 | 1387 | /** |
michael@0 | 1388 | * Calls all registered InstallListeners with an event. Any parameters after |
michael@0 | 1389 | * the extraListeners parameter are passed to the listener. |
michael@0 | 1390 | * |
michael@0 | 1391 | * @param aMethod |
michael@0 | 1392 | * The method on the listeners to call |
michael@0 | 1393 | * @param aExtraListeners |
michael@0 | 1394 | * An optional array of extra InstallListeners to also call |
michael@0 | 1395 | * @return false if any of the listeners returned false, true otherwise |
michael@0 | 1396 | */ |
michael@0 | 1397 | callInstallListeners: function AMI_callInstallListeners(aMethod, |
michael@0 | 1398 | aExtraListeners, ...aArgs) { |
michael@0 | 1399 | if (!gStarted) |
michael@0 | 1400 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1401 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1402 | |
michael@0 | 1403 | if (!aMethod || typeof aMethod != "string") |
michael@0 | 1404 | throw Components.Exception("aMethod must be a non-empty string", |
michael@0 | 1405 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1406 | |
michael@0 | 1407 | if (aExtraListeners && !Array.isArray(aExtraListeners)) |
michael@0 | 1408 | throw Components.Exception("aExtraListeners must be an array or null", |
michael@0 | 1409 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1410 | |
michael@0 | 1411 | let result = true; |
michael@0 | 1412 | let listeners; |
michael@0 | 1413 | if (aExtraListeners) |
michael@0 | 1414 | listeners = aExtraListeners.concat(this.installListeners); |
michael@0 | 1415 | else |
michael@0 | 1416 | listeners = this.installListeners.slice(0); |
michael@0 | 1417 | |
michael@0 | 1418 | for (let listener of listeners) { |
michael@0 | 1419 | try { |
michael@0 | 1420 | if (aMethod in listener) { |
michael@0 | 1421 | if (listener[aMethod].apply(listener, aArgs) === false) |
michael@0 | 1422 | result = false; |
michael@0 | 1423 | } |
michael@0 | 1424 | } |
michael@0 | 1425 | catch (e) { |
michael@0 | 1426 | logger.warn("InstallListener threw exception when calling " + aMethod, e); |
michael@0 | 1427 | } |
michael@0 | 1428 | } |
michael@0 | 1429 | return result; |
michael@0 | 1430 | }, |
michael@0 | 1431 | |
michael@0 | 1432 | /** |
michael@0 | 1433 | * Calls all registered AddonListeners with an event. Any parameters after |
michael@0 | 1434 | * the method parameter are passed to the listener. |
michael@0 | 1435 | * |
michael@0 | 1436 | * @param aMethod |
michael@0 | 1437 | * The method on the listeners to call |
michael@0 | 1438 | */ |
michael@0 | 1439 | callAddonListeners: function AMI_callAddonListeners(aMethod, ...aArgs) { |
michael@0 | 1440 | if (!gStarted) |
michael@0 | 1441 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1442 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1443 | |
michael@0 | 1444 | if (!aMethod || typeof aMethod != "string") |
michael@0 | 1445 | throw Components.Exception("aMethod must be a non-empty string", |
michael@0 | 1446 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1447 | |
michael@0 | 1448 | let addonListeners = this.addonListeners.slice(0); |
michael@0 | 1449 | for (let listener of addonListeners) { |
michael@0 | 1450 | try { |
michael@0 | 1451 | if (aMethod in listener) |
michael@0 | 1452 | listener[aMethod].apply(listener, aArgs); |
michael@0 | 1453 | } |
michael@0 | 1454 | catch (e) { |
michael@0 | 1455 | logger.warn("AddonListener threw exception when calling " + aMethod, e); |
michael@0 | 1456 | } |
michael@0 | 1457 | } |
michael@0 | 1458 | }, |
michael@0 | 1459 | |
michael@0 | 1460 | /** |
michael@0 | 1461 | * Notifies all providers that an add-on has been enabled when that type of |
michael@0 | 1462 | * add-on only supports a single add-on being enabled at a time. This allows |
michael@0 | 1463 | * the providers to disable theirs if necessary. |
michael@0 | 1464 | * |
michael@0 | 1465 | * @param aID |
michael@0 | 1466 | * The ID of the enabled add-on |
michael@0 | 1467 | * @param aType |
michael@0 | 1468 | * The type of the enabled add-on |
michael@0 | 1469 | * @param aPendingRestart |
michael@0 | 1470 | * A boolean indicating if the change will only take place the next |
michael@0 | 1471 | * time the application is restarted |
michael@0 | 1472 | */ |
michael@0 | 1473 | notifyAddonChanged: function AMI_notifyAddonChanged(aID, aType, aPendingRestart) { |
michael@0 | 1474 | if (!gStarted) |
michael@0 | 1475 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1476 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1477 | |
michael@0 | 1478 | if (aID && typeof aID != "string") |
michael@0 | 1479 | throw Components.Exception("aID must be a string or null", |
michael@0 | 1480 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1481 | |
michael@0 | 1482 | if (!aType || typeof aType != "string") |
michael@0 | 1483 | throw Components.Exception("aType must be a non-empty string", |
michael@0 | 1484 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1485 | |
michael@0 | 1486 | this.callProviders("addonChanged", aID, aType, aPendingRestart); |
michael@0 | 1487 | }, |
michael@0 | 1488 | |
michael@0 | 1489 | /** |
michael@0 | 1490 | * Notifies all providers they need to update the appDisabled property for |
michael@0 | 1491 | * their add-ons in response to an application change such as a blocklist |
michael@0 | 1492 | * update. |
michael@0 | 1493 | */ |
michael@0 | 1494 | updateAddonAppDisabledStates: function AMI_updateAddonAppDisabledStates() { |
michael@0 | 1495 | if (!gStarted) |
michael@0 | 1496 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1497 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1498 | |
michael@0 | 1499 | this.callProviders("updateAddonAppDisabledStates"); |
michael@0 | 1500 | }, |
michael@0 | 1501 | |
michael@0 | 1502 | /** |
michael@0 | 1503 | * Notifies all providers that the repository has updated its data for |
michael@0 | 1504 | * installed add-ons. |
michael@0 | 1505 | * |
michael@0 | 1506 | * @param aCallback |
michael@0 | 1507 | * Function to call when operation is complete. |
michael@0 | 1508 | */ |
michael@0 | 1509 | updateAddonRepositoryData: function AMI_updateAddonRepositoryData(aCallback) { |
michael@0 | 1510 | if (!gStarted) |
michael@0 | 1511 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1512 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1513 | |
michael@0 | 1514 | if (typeof aCallback != "function") |
michael@0 | 1515 | throw Components.Exception("aCallback must be a function", |
michael@0 | 1516 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1517 | |
michael@0 | 1518 | new AsyncObjectCaller(this.providers, "updateAddonRepositoryData", { |
michael@0 | 1519 | nextObject: function updateAddonRepositoryData_nextObject(aCaller, aProvider) { |
michael@0 | 1520 | callProvider(aProvider, |
michael@0 | 1521 | "updateAddonRepositoryData", |
michael@0 | 1522 | null, |
michael@0 | 1523 | aCaller.callNext.bind(aCaller)); |
michael@0 | 1524 | }, |
michael@0 | 1525 | noMoreObjects: function updateAddonRepositoryData_noMoreObjects(aCaller) { |
michael@0 | 1526 | safeCall(aCallback); |
michael@0 | 1527 | // only tests should care about this |
michael@0 | 1528 | Services.obs.notifyObservers(null, "TEST:addon-repository-data-updated", null); |
michael@0 | 1529 | } |
michael@0 | 1530 | }); |
michael@0 | 1531 | }, |
michael@0 | 1532 | |
michael@0 | 1533 | /** |
michael@0 | 1534 | * Asynchronously gets an AddonInstall for a URL. |
michael@0 | 1535 | * |
michael@0 | 1536 | * @param aUrl |
michael@0 | 1537 | * The string represenation of the URL the add-on is located at |
michael@0 | 1538 | * @param aCallback |
michael@0 | 1539 | * A callback to pass the AddonInstall to |
michael@0 | 1540 | * @param aMimetype |
michael@0 | 1541 | * The mimetype of the add-on |
michael@0 | 1542 | * @param aHash |
michael@0 | 1543 | * An optional hash of the add-on |
michael@0 | 1544 | * @param aName |
michael@0 | 1545 | * An optional placeholder name while the add-on is being downloaded |
michael@0 | 1546 | * @param aIcons |
michael@0 | 1547 | * Optional placeholder icons while the add-on is being downloaded |
michael@0 | 1548 | * @param aVersion |
michael@0 | 1549 | * An optional placeholder version while the add-on is being downloaded |
michael@0 | 1550 | * @param aLoadGroup |
michael@0 | 1551 | * An optional nsILoadGroup to associate any network requests with |
michael@0 | 1552 | * @throws if the aUrl, aCallback or aMimetype arguments are not specified |
michael@0 | 1553 | */ |
michael@0 | 1554 | getInstallForURL: function AMI_getInstallForURL(aUrl, aCallback, aMimetype, |
michael@0 | 1555 | aHash, aName, aIcons, |
michael@0 | 1556 | aVersion, aLoadGroup) { |
michael@0 | 1557 | if (!gStarted) |
michael@0 | 1558 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1559 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1560 | |
michael@0 | 1561 | if (!aUrl || typeof aUrl != "string") |
michael@0 | 1562 | throw Components.Exception("aURL must be a non-empty string", |
michael@0 | 1563 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1564 | |
michael@0 | 1565 | if (typeof aCallback != "function") |
michael@0 | 1566 | throw Components.Exception("aCallback must be a function", |
michael@0 | 1567 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1568 | |
michael@0 | 1569 | if (!aMimetype || typeof aMimetype != "string") |
michael@0 | 1570 | throw Components.Exception("aMimetype must be a non-empty string", |
michael@0 | 1571 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1572 | |
michael@0 | 1573 | if (aHash && typeof aHash != "string") |
michael@0 | 1574 | throw Components.Exception("aHash must be a string or null", |
michael@0 | 1575 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1576 | |
michael@0 | 1577 | if (aName && typeof aName != "string") |
michael@0 | 1578 | throw Components.Exception("aName must be a string or null", |
michael@0 | 1579 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1580 | |
michael@0 | 1581 | if (aIcons) { |
michael@0 | 1582 | if (typeof aIcons == "string") |
michael@0 | 1583 | aIcons = { "32": aIcons }; |
michael@0 | 1584 | else if (typeof aIcons != "object") |
michael@0 | 1585 | throw Components.Exception("aIcons must be a string, an object or null", |
michael@0 | 1586 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1587 | } else { |
michael@0 | 1588 | aIcons = {}; |
michael@0 | 1589 | } |
michael@0 | 1590 | |
michael@0 | 1591 | if (aVersion && typeof aVersion != "string") |
michael@0 | 1592 | throw Components.Exception("aVersion must be a string or null", |
michael@0 | 1593 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1594 | |
michael@0 | 1595 | if (aLoadGroup && (!(aLoadGroup instanceof Ci.nsILoadGroup))) |
michael@0 | 1596 | throw Components.Exception("aLoadGroup must be a nsILoadGroup or null", |
michael@0 | 1597 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1598 | |
michael@0 | 1599 | let providers = this.providers.slice(0); |
michael@0 | 1600 | for (let provider of providers) { |
michael@0 | 1601 | if (callProvider(provider, "supportsMimetype", false, aMimetype)) { |
michael@0 | 1602 | callProvider(provider, "getInstallForURL", null, |
michael@0 | 1603 | aUrl, aHash, aName, aIcons, aVersion, aLoadGroup, |
michael@0 | 1604 | function getInstallForURL_safeCall(aInstall) { |
michael@0 | 1605 | safeCall(aCallback, aInstall); |
michael@0 | 1606 | }); |
michael@0 | 1607 | return; |
michael@0 | 1608 | } |
michael@0 | 1609 | } |
michael@0 | 1610 | safeCall(aCallback, null); |
michael@0 | 1611 | }, |
michael@0 | 1612 | |
michael@0 | 1613 | /** |
michael@0 | 1614 | * Asynchronously gets an AddonInstall for an nsIFile. |
michael@0 | 1615 | * |
michael@0 | 1616 | * @param aFile |
michael@0 | 1617 | * The nsIFile where the add-on is located |
michael@0 | 1618 | * @param aCallback |
michael@0 | 1619 | * A callback to pass the AddonInstall to |
michael@0 | 1620 | * @param aMimetype |
michael@0 | 1621 | * An optional mimetype hint for the add-on |
michael@0 | 1622 | * @throws if the aFile or aCallback arguments are not specified |
michael@0 | 1623 | */ |
michael@0 | 1624 | getInstallForFile: function AMI_getInstallForFile(aFile, aCallback, aMimetype) { |
michael@0 | 1625 | if (!gStarted) |
michael@0 | 1626 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1627 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1628 | |
michael@0 | 1629 | if (!(aFile instanceof Ci.nsIFile)) |
michael@0 | 1630 | throw Components.Exception("aFile must be a nsIFile", |
michael@0 | 1631 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1632 | |
michael@0 | 1633 | if (typeof aCallback != "function") |
michael@0 | 1634 | throw Components.Exception("aCallback must be a function", |
michael@0 | 1635 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1636 | |
michael@0 | 1637 | if (aMimetype && typeof aMimetype != "string") |
michael@0 | 1638 | throw Components.Exception("aMimetype must be a string or null", |
michael@0 | 1639 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1640 | |
michael@0 | 1641 | new AsyncObjectCaller(this.providers, "getInstallForFile", { |
michael@0 | 1642 | nextObject: function getInstallForFile_nextObject(aCaller, aProvider) { |
michael@0 | 1643 | callProvider(aProvider, "getInstallForFile", null, aFile, |
michael@0 | 1644 | function getInstallForFile_safeCall(aInstall) { |
michael@0 | 1645 | if (aInstall) |
michael@0 | 1646 | safeCall(aCallback, aInstall); |
michael@0 | 1647 | else |
michael@0 | 1648 | aCaller.callNext(); |
michael@0 | 1649 | }); |
michael@0 | 1650 | }, |
michael@0 | 1651 | |
michael@0 | 1652 | noMoreObjects: function getInstallForFile_noMoreObjects(aCaller) { |
michael@0 | 1653 | safeCall(aCallback, null); |
michael@0 | 1654 | } |
michael@0 | 1655 | }); |
michael@0 | 1656 | }, |
michael@0 | 1657 | |
michael@0 | 1658 | /** |
michael@0 | 1659 | * Asynchronously gets all current AddonInstalls optionally limiting to a list |
michael@0 | 1660 | * of types. |
michael@0 | 1661 | * |
michael@0 | 1662 | * @param aTypes |
michael@0 | 1663 | * An optional array of types to retrieve. Each type is a string name |
michael@0 | 1664 | * @param aCallback |
michael@0 | 1665 | * A callback which will be passed an array of AddonInstalls |
michael@0 | 1666 | * @throws If the aCallback argument is not specified |
michael@0 | 1667 | */ |
michael@0 | 1668 | getInstallsByTypes: function AMI_getInstallsByTypes(aTypes, aCallback) { |
michael@0 | 1669 | if (!gStarted) |
michael@0 | 1670 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1671 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1672 | |
michael@0 | 1673 | if (aTypes && !Array.isArray(aTypes)) |
michael@0 | 1674 | throw Components.Exception("aTypes must be an array or null", |
michael@0 | 1675 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1676 | |
michael@0 | 1677 | if (typeof aCallback != "function") |
michael@0 | 1678 | throw Components.Exception("aCallback must be a function", |
michael@0 | 1679 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1680 | |
michael@0 | 1681 | let installs = []; |
michael@0 | 1682 | |
michael@0 | 1683 | new AsyncObjectCaller(this.providers, "getInstallsByTypes", { |
michael@0 | 1684 | nextObject: function getInstallsByTypes_nextObject(aCaller, aProvider) { |
michael@0 | 1685 | callProvider(aProvider, "getInstallsByTypes", null, aTypes, |
michael@0 | 1686 | function getInstallsByTypes_safeCall(aProviderInstalls) { |
michael@0 | 1687 | installs = installs.concat(aProviderInstalls); |
michael@0 | 1688 | aCaller.callNext(); |
michael@0 | 1689 | }); |
michael@0 | 1690 | }, |
michael@0 | 1691 | |
michael@0 | 1692 | noMoreObjects: function getInstallsByTypes_noMoreObjects(aCaller) { |
michael@0 | 1693 | safeCall(aCallback, installs); |
michael@0 | 1694 | } |
michael@0 | 1695 | }); |
michael@0 | 1696 | }, |
michael@0 | 1697 | |
michael@0 | 1698 | /** |
michael@0 | 1699 | * Asynchronously gets all current AddonInstalls. |
michael@0 | 1700 | * |
michael@0 | 1701 | * @param aCallback |
michael@0 | 1702 | * A callback which will be passed an array of AddonInstalls |
michael@0 | 1703 | */ |
michael@0 | 1704 | getAllInstalls: function AMI_getAllInstalls(aCallback) { |
michael@0 | 1705 | if (!gStarted) |
michael@0 | 1706 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1707 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1708 | |
michael@0 | 1709 | this.getInstallsByTypes(null, aCallback); |
michael@0 | 1710 | }, |
michael@0 | 1711 | |
michael@0 | 1712 | /** |
michael@0 | 1713 | * Synchronously map a URI to the corresponding Addon ID. |
michael@0 | 1714 | * |
michael@0 | 1715 | * Mappable URIs are limited to in-application resources belonging to the |
michael@0 | 1716 | * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc. |
michael@0 | 1717 | * but do not include URIs from meta data, such as the add-on homepage. |
michael@0 | 1718 | * |
michael@0 | 1719 | * @param aURI |
michael@0 | 1720 | * nsIURI to map to an addon id |
michael@0 | 1721 | * @return string containing the Addon ID or null |
michael@0 | 1722 | * @see amIAddonManager.mapURIToAddonID |
michael@0 | 1723 | */ |
michael@0 | 1724 | mapURIToAddonID: function AMI_mapURIToAddonID(aURI) { |
michael@0 | 1725 | if (!(aURI instanceof Ci.nsIURI)) { |
michael@0 | 1726 | throw Components.Exception("aURI is not a nsIURI", |
michael@0 | 1727 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1728 | } |
michael@0 | 1729 | // Try all providers |
michael@0 | 1730 | let providers = this.providers.slice(0); |
michael@0 | 1731 | for (let provider of providers) { |
michael@0 | 1732 | var id = callProvider(provider, "mapURIToAddonID", null, aURI); |
michael@0 | 1733 | if (id !== null) { |
michael@0 | 1734 | return id; |
michael@0 | 1735 | } |
michael@0 | 1736 | } |
michael@0 | 1737 | |
michael@0 | 1738 | return null; |
michael@0 | 1739 | }, |
michael@0 | 1740 | |
michael@0 | 1741 | /** |
michael@0 | 1742 | * Checks whether installation is enabled for a particular mimetype. |
michael@0 | 1743 | * |
michael@0 | 1744 | * @param aMimetype |
michael@0 | 1745 | * The mimetype to check |
michael@0 | 1746 | * @return true if installation is enabled for the mimetype |
michael@0 | 1747 | */ |
michael@0 | 1748 | isInstallEnabled: function AMI_isInstallEnabled(aMimetype) { |
michael@0 | 1749 | if (!gStarted) |
michael@0 | 1750 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1751 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1752 | |
michael@0 | 1753 | if (!aMimetype || typeof aMimetype != "string") |
michael@0 | 1754 | throw Components.Exception("aMimetype must be a non-empty string", |
michael@0 | 1755 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1756 | |
michael@0 | 1757 | let providers = this.providers.slice(0); |
michael@0 | 1758 | for (let provider of providers) { |
michael@0 | 1759 | if (callProvider(provider, "supportsMimetype", false, aMimetype) && |
michael@0 | 1760 | callProvider(provider, "isInstallEnabled")) |
michael@0 | 1761 | return true; |
michael@0 | 1762 | } |
michael@0 | 1763 | return false; |
michael@0 | 1764 | }, |
michael@0 | 1765 | |
michael@0 | 1766 | /** |
michael@0 | 1767 | * Checks whether a particular source is allowed to install add-ons of a |
michael@0 | 1768 | * given mimetype. |
michael@0 | 1769 | * |
michael@0 | 1770 | * @param aMimetype |
michael@0 | 1771 | * The mimetype of the add-on |
michael@0 | 1772 | * @param aURI |
michael@0 | 1773 | * The optional nsIURI of the source |
michael@0 | 1774 | * @return true if the source is allowed to install this mimetype |
michael@0 | 1775 | */ |
michael@0 | 1776 | isInstallAllowed: function AMI_isInstallAllowed(aMimetype, aURI) { |
michael@0 | 1777 | if (!gStarted) |
michael@0 | 1778 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1779 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1780 | |
michael@0 | 1781 | if (!aMimetype || typeof aMimetype != "string") |
michael@0 | 1782 | throw Components.Exception("aMimetype must be a non-empty string", |
michael@0 | 1783 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1784 | |
michael@0 | 1785 | if (aURI && !(aURI instanceof Ci.nsIURI)) |
michael@0 | 1786 | throw Components.Exception("aURI must be a nsIURI or null", |
michael@0 | 1787 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1788 | |
michael@0 | 1789 | let providers = this.providers.slice(0); |
michael@0 | 1790 | for (let provider of providers) { |
michael@0 | 1791 | if (callProvider(provider, "supportsMimetype", false, aMimetype) && |
michael@0 | 1792 | callProvider(provider, "isInstallAllowed", null, aURI)) |
michael@0 | 1793 | return true; |
michael@0 | 1794 | } |
michael@0 | 1795 | return false; |
michael@0 | 1796 | }, |
michael@0 | 1797 | |
michael@0 | 1798 | /** |
michael@0 | 1799 | * Starts installation of an array of AddonInstalls notifying the registered |
michael@0 | 1800 | * web install listener of blocked or started installs. |
michael@0 | 1801 | * |
michael@0 | 1802 | * @param aMimetype |
michael@0 | 1803 | * The mimetype of add-ons being installed |
michael@0 | 1804 | * @param aSource |
michael@0 | 1805 | * The optional nsIDOMWindow that started the installs |
michael@0 | 1806 | * @param aURI |
michael@0 | 1807 | * The optional nsIURI that started the installs |
michael@0 | 1808 | * @param aInstalls |
michael@0 | 1809 | * The array of AddonInstalls to be installed |
michael@0 | 1810 | */ |
michael@0 | 1811 | installAddonsFromWebpage: function AMI_installAddonsFromWebpage(aMimetype, |
michael@0 | 1812 | aSource, |
michael@0 | 1813 | aURI, |
michael@0 | 1814 | aInstalls) { |
michael@0 | 1815 | if (!gStarted) |
michael@0 | 1816 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1817 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1818 | |
michael@0 | 1819 | if (!aMimetype || typeof aMimetype != "string") |
michael@0 | 1820 | throw Components.Exception("aMimetype must be a non-empty string", |
michael@0 | 1821 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1822 | |
michael@0 | 1823 | if (aSource && !(aSource instanceof Ci.nsIDOMWindow)) |
michael@0 | 1824 | throw Components.Exception("aSource must be a nsIDOMWindow or null", |
michael@0 | 1825 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1826 | |
michael@0 | 1827 | if (aURI && !(aURI instanceof Ci.nsIURI)) |
michael@0 | 1828 | throw Components.Exception("aURI must be a nsIURI or null", |
michael@0 | 1829 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1830 | |
michael@0 | 1831 | if (!Array.isArray(aInstalls)) |
michael@0 | 1832 | throw Components.Exception("aInstalls must be an array", |
michael@0 | 1833 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1834 | |
michael@0 | 1835 | if (!("@mozilla.org/addons/web-install-listener;1" in Cc)) { |
michael@0 | 1836 | logger.warn("No web installer available, cancelling all installs"); |
michael@0 | 1837 | aInstalls.forEach(function(aInstall) { |
michael@0 | 1838 | aInstall.cancel(); |
michael@0 | 1839 | }); |
michael@0 | 1840 | return; |
michael@0 | 1841 | } |
michael@0 | 1842 | |
michael@0 | 1843 | try { |
michael@0 | 1844 | let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"]. |
michael@0 | 1845 | getService(Ci.amIWebInstallListener); |
michael@0 | 1846 | |
michael@0 | 1847 | if (!this.isInstallEnabled(aMimetype, aURI)) { |
michael@0 | 1848 | weblistener.onWebInstallDisabled(aSource, aURI, aInstalls, |
michael@0 | 1849 | aInstalls.length); |
michael@0 | 1850 | } |
michael@0 | 1851 | else if (!this.isInstallAllowed(aMimetype, aURI)) { |
michael@0 | 1852 | if (weblistener.onWebInstallBlocked(aSource, aURI, aInstalls, |
michael@0 | 1853 | aInstalls.length)) { |
michael@0 | 1854 | aInstalls.forEach(function(aInstall) { |
michael@0 | 1855 | aInstall.install(); |
michael@0 | 1856 | }); |
michael@0 | 1857 | } |
michael@0 | 1858 | } |
michael@0 | 1859 | else if (weblistener.onWebInstallRequested(aSource, aURI, aInstalls, |
michael@0 | 1860 | aInstalls.length)) { |
michael@0 | 1861 | aInstalls.forEach(function(aInstall) { |
michael@0 | 1862 | aInstall.install(); |
michael@0 | 1863 | }); |
michael@0 | 1864 | } |
michael@0 | 1865 | } |
michael@0 | 1866 | catch (e) { |
michael@0 | 1867 | // In the event that the weblistener throws during instantiation or when |
michael@0 | 1868 | // calling onWebInstallBlocked or onWebInstallRequested all of the |
michael@0 | 1869 | // installs should get cancelled. |
michael@0 | 1870 | logger.warn("Failure calling web installer", e); |
michael@0 | 1871 | aInstalls.forEach(function(aInstall) { |
michael@0 | 1872 | aInstall.cancel(); |
michael@0 | 1873 | }); |
michael@0 | 1874 | } |
michael@0 | 1875 | }, |
michael@0 | 1876 | |
michael@0 | 1877 | /** |
michael@0 | 1878 | * Adds a new InstallListener if the listener is not already registered. |
michael@0 | 1879 | * |
michael@0 | 1880 | * @param aListener |
michael@0 | 1881 | * The InstallListener to add |
michael@0 | 1882 | */ |
michael@0 | 1883 | addInstallListener: function AMI_addInstallListener(aListener) { |
michael@0 | 1884 | if (!aListener || typeof aListener != "object") |
michael@0 | 1885 | throw Components.Exception("aListener must be a InstallListener object", |
michael@0 | 1886 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1887 | |
michael@0 | 1888 | if (!this.installListeners.some(function addInstallListener_matchListener(i) { |
michael@0 | 1889 | return i == aListener; })) |
michael@0 | 1890 | this.installListeners.push(aListener); |
michael@0 | 1891 | }, |
michael@0 | 1892 | |
michael@0 | 1893 | /** |
michael@0 | 1894 | * Removes an InstallListener if the listener is registered. |
michael@0 | 1895 | * |
michael@0 | 1896 | * @param aListener |
michael@0 | 1897 | * The InstallListener to remove |
michael@0 | 1898 | */ |
michael@0 | 1899 | removeInstallListener: function AMI_removeInstallListener(aListener) { |
michael@0 | 1900 | if (!aListener || typeof aListener != "object") |
michael@0 | 1901 | throw Components.Exception("aListener must be a InstallListener object", |
michael@0 | 1902 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1903 | |
michael@0 | 1904 | let pos = 0; |
michael@0 | 1905 | while (pos < this.installListeners.length) { |
michael@0 | 1906 | if (this.installListeners[pos] == aListener) |
michael@0 | 1907 | this.installListeners.splice(pos, 1); |
michael@0 | 1908 | else |
michael@0 | 1909 | pos++; |
michael@0 | 1910 | } |
michael@0 | 1911 | }, |
michael@0 | 1912 | |
michael@0 | 1913 | /** |
michael@0 | 1914 | * Asynchronously gets an add-on with a specific ID. |
michael@0 | 1915 | * |
michael@0 | 1916 | * @param aID |
michael@0 | 1917 | * The ID of the add-on to retrieve |
michael@0 | 1918 | * @param aCallback |
michael@0 | 1919 | * The callback to pass the retrieved add-on to |
michael@0 | 1920 | * @throws if the aID or aCallback arguments are not specified |
michael@0 | 1921 | */ |
michael@0 | 1922 | getAddonByID: function AMI_getAddonByID(aID, aCallback) { |
michael@0 | 1923 | if (!gStarted) |
michael@0 | 1924 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1925 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1926 | |
michael@0 | 1927 | if (!aID || typeof aID != "string") |
michael@0 | 1928 | throw Components.Exception("aID must be a non-empty string", |
michael@0 | 1929 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1930 | |
michael@0 | 1931 | if (typeof aCallback != "function") |
michael@0 | 1932 | throw Components.Exception("aCallback must be a function", |
michael@0 | 1933 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1934 | |
michael@0 | 1935 | new AsyncObjectCaller(this.providers, "getAddonByID", { |
michael@0 | 1936 | nextObject: function getAddonByID_nextObject(aCaller, aProvider) { |
michael@0 | 1937 | callProvider(aProvider, "getAddonByID", null, aID, |
michael@0 | 1938 | function getAddonByID_safeCall(aAddon) { |
michael@0 | 1939 | if (aAddon) |
michael@0 | 1940 | safeCall(aCallback, aAddon); |
michael@0 | 1941 | else |
michael@0 | 1942 | aCaller.callNext(); |
michael@0 | 1943 | }); |
michael@0 | 1944 | }, |
michael@0 | 1945 | |
michael@0 | 1946 | noMoreObjects: function getAddonByID_noMoreObjects(aCaller) { |
michael@0 | 1947 | safeCall(aCallback, null); |
michael@0 | 1948 | } |
michael@0 | 1949 | }); |
michael@0 | 1950 | }, |
michael@0 | 1951 | |
michael@0 | 1952 | /** |
michael@0 | 1953 | * Asynchronously get an add-on with a specific Sync GUID. |
michael@0 | 1954 | * |
michael@0 | 1955 | * @param aGUID |
michael@0 | 1956 | * String GUID of add-on to retrieve |
michael@0 | 1957 | * @param aCallback |
michael@0 | 1958 | * The callback to pass the retrieved add-on to. |
michael@0 | 1959 | * @throws if the aGUID or aCallback arguments are not specified |
michael@0 | 1960 | */ |
michael@0 | 1961 | getAddonBySyncGUID: function AMI_getAddonBySyncGUID(aGUID, aCallback) { |
michael@0 | 1962 | if (!gStarted) |
michael@0 | 1963 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 1964 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1965 | |
michael@0 | 1966 | if (!aGUID || typeof aGUID != "string") |
michael@0 | 1967 | throw Components.Exception("aGUID must be a non-empty string", |
michael@0 | 1968 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1969 | |
michael@0 | 1970 | if (typeof aCallback != "function") |
michael@0 | 1971 | throw Components.Exception("aCallback must be a function", |
michael@0 | 1972 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 1973 | |
michael@0 | 1974 | new AsyncObjectCaller(this.providers, "getAddonBySyncGUID", { |
michael@0 | 1975 | nextObject: function getAddonBySyncGUID_nextObject(aCaller, aProvider) { |
michael@0 | 1976 | callProvider(aProvider, "getAddonBySyncGUID", null, aGUID, |
michael@0 | 1977 | function getAddonBySyncGUID_safeCall(aAddon) { |
michael@0 | 1978 | if (aAddon) { |
michael@0 | 1979 | safeCall(aCallback, aAddon); |
michael@0 | 1980 | } else { |
michael@0 | 1981 | aCaller.callNext(); |
michael@0 | 1982 | } |
michael@0 | 1983 | }); |
michael@0 | 1984 | }, |
michael@0 | 1985 | |
michael@0 | 1986 | noMoreObjects: function getAddonBySyncGUID_noMoreObjects(aCaller) { |
michael@0 | 1987 | safeCall(aCallback, null); |
michael@0 | 1988 | } |
michael@0 | 1989 | }); |
michael@0 | 1990 | }, |
michael@0 | 1991 | |
michael@0 | 1992 | /** |
michael@0 | 1993 | * Asynchronously gets an array of add-ons. |
michael@0 | 1994 | * |
michael@0 | 1995 | * @param aIDs |
michael@0 | 1996 | * The array of IDs to retrieve |
michael@0 | 1997 | * @param aCallback |
michael@0 | 1998 | * The callback to pass an array of Addons to |
michael@0 | 1999 | * @throws if the aID or aCallback arguments are not specified |
michael@0 | 2000 | */ |
michael@0 | 2001 | getAddonsByIDs: function AMI_getAddonsByIDs(aIDs, aCallback) { |
michael@0 | 2002 | if (!gStarted) |
michael@0 | 2003 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 2004 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 2005 | |
michael@0 | 2006 | if (!Array.isArray(aIDs)) |
michael@0 | 2007 | throw Components.Exception("aIDs must be an array", |
michael@0 | 2008 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 2009 | |
michael@0 | 2010 | if (typeof aCallback != "function") |
michael@0 | 2011 | throw Components.Exception("aCallback must be a function", |
michael@0 | 2012 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 2013 | |
michael@0 | 2014 | let addons = []; |
michael@0 | 2015 | |
michael@0 | 2016 | new AsyncObjectCaller(aIDs, null, { |
michael@0 | 2017 | nextObject: function getAddonsByIDs_nextObject(aCaller, aID) { |
michael@0 | 2018 | AddonManagerInternal.getAddonByID(aID, |
michael@0 | 2019 | function getAddonsByIDs_getAddonByID(aAddon) { |
michael@0 | 2020 | addons.push(aAddon); |
michael@0 | 2021 | aCaller.callNext(); |
michael@0 | 2022 | }); |
michael@0 | 2023 | }, |
michael@0 | 2024 | |
michael@0 | 2025 | noMoreObjects: function getAddonsByIDs_noMoreObjects(aCaller) { |
michael@0 | 2026 | safeCall(aCallback, addons); |
michael@0 | 2027 | } |
michael@0 | 2028 | }); |
michael@0 | 2029 | }, |
michael@0 | 2030 | |
michael@0 | 2031 | /** |
michael@0 | 2032 | * Asynchronously gets add-ons of specific types. |
michael@0 | 2033 | * |
michael@0 | 2034 | * @param aTypes |
michael@0 | 2035 | * An optional array of types to retrieve. Each type is a string name |
michael@0 | 2036 | * @param aCallback |
michael@0 | 2037 | * The callback to pass an array of Addons to. |
michael@0 | 2038 | * @throws if the aCallback argument is not specified |
michael@0 | 2039 | */ |
michael@0 | 2040 | getAddonsByTypes: function AMI_getAddonsByTypes(aTypes, aCallback) { |
michael@0 | 2041 | if (!gStarted) |
michael@0 | 2042 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 2043 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 2044 | |
michael@0 | 2045 | if (aTypes && !Array.isArray(aTypes)) |
michael@0 | 2046 | throw Components.Exception("aTypes must be an array or null", |
michael@0 | 2047 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 2048 | |
michael@0 | 2049 | if (typeof aCallback != "function") |
michael@0 | 2050 | throw Components.Exception("aCallback must be a function", |
michael@0 | 2051 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 2052 | |
michael@0 | 2053 | let addons = []; |
michael@0 | 2054 | |
michael@0 | 2055 | new AsyncObjectCaller(this.providers, "getAddonsByTypes", { |
michael@0 | 2056 | nextObject: function getAddonsByTypes_nextObject(aCaller, aProvider) { |
michael@0 | 2057 | callProvider(aProvider, "getAddonsByTypes", null, aTypes, |
michael@0 | 2058 | function getAddonsByTypes_concatAddons(aProviderAddons) { |
michael@0 | 2059 | addons = addons.concat(aProviderAddons); |
michael@0 | 2060 | aCaller.callNext(); |
michael@0 | 2061 | }); |
michael@0 | 2062 | }, |
michael@0 | 2063 | |
michael@0 | 2064 | noMoreObjects: function getAddonsByTypes_noMoreObjects(aCaller) { |
michael@0 | 2065 | safeCall(aCallback, addons); |
michael@0 | 2066 | } |
michael@0 | 2067 | }); |
michael@0 | 2068 | }, |
michael@0 | 2069 | |
michael@0 | 2070 | /** |
michael@0 | 2071 | * Asynchronously gets all installed add-ons. |
michael@0 | 2072 | * |
michael@0 | 2073 | * @param aCallback |
michael@0 | 2074 | * A callback which will be passed an array of Addons |
michael@0 | 2075 | */ |
michael@0 | 2076 | getAllAddons: function AMI_getAllAddons(aCallback) { |
michael@0 | 2077 | if (!gStarted) |
michael@0 | 2078 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 2079 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 2080 | |
michael@0 | 2081 | if (typeof aCallback != "function") |
michael@0 | 2082 | throw Components.Exception("aCallback must be a function", |
michael@0 | 2083 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 2084 | |
michael@0 | 2085 | this.getAddonsByTypes(null, aCallback); |
michael@0 | 2086 | }, |
michael@0 | 2087 | |
michael@0 | 2088 | /** |
michael@0 | 2089 | * Asynchronously gets add-ons that have operations waiting for an application |
michael@0 | 2090 | * restart to complete. |
michael@0 | 2091 | * |
michael@0 | 2092 | * @param aTypes |
michael@0 | 2093 | * An optional array of types to retrieve. Each type is a string name |
michael@0 | 2094 | * @param aCallback |
michael@0 | 2095 | * The callback to pass the array of Addons to |
michael@0 | 2096 | * @throws if the aCallback argument is not specified |
michael@0 | 2097 | */ |
michael@0 | 2098 | getAddonsWithOperationsByTypes: |
michael@0 | 2099 | function AMI_getAddonsWithOperationsByTypes(aTypes, aCallback) { |
michael@0 | 2100 | if (!gStarted) |
michael@0 | 2101 | throw Components.Exception("AddonManager is not initialized", |
michael@0 | 2102 | Cr.NS_ERROR_NOT_INITIALIZED); |
michael@0 | 2103 | |
michael@0 | 2104 | if (aTypes && !Array.isArray(aTypes)) |
michael@0 | 2105 | throw Components.Exception("aTypes must be an array or null", |
michael@0 | 2106 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 2107 | |
michael@0 | 2108 | if (typeof aCallback != "function") |
michael@0 | 2109 | throw Components.Exception("aCallback must be a function", |
michael@0 | 2110 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 2111 | |
michael@0 | 2112 | let addons = []; |
michael@0 | 2113 | |
michael@0 | 2114 | new AsyncObjectCaller(this.providers, "getAddonsWithOperationsByTypes", { |
michael@0 | 2115 | nextObject: function getAddonsWithOperationsByTypes_nextObject |
michael@0 | 2116 | (aCaller, aProvider) { |
michael@0 | 2117 | callProvider(aProvider, "getAddonsWithOperationsByTypes", null, aTypes, |
michael@0 | 2118 | function getAddonsWithOperationsByTypes_concatAddons |
michael@0 | 2119 | (aProviderAddons) { |
michael@0 | 2120 | addons = addons.concat(aProviderAddons); |
michael@0 | 2121 | aCaller.callNext(); |
michael@0 | 2122 | }); |
michael@0 | 2123 | }, |
michael@0 | 2124 | |
michael@0 | 2125 | noMoreObjects: function getAddonsWithOperationsByTypes_noMoreObjects(caller) { |
michael@0 | 2126 | safeCall(aCallback, addons); |
michael@0 | 2127 | } |
michael@0 | 2128 | }); |
michael@0 | 2129 | }, |
michael@0 | 2130 | |
michael@0 | 2131 | /** |
michael@0 | 2132 | * Adds a new AddonManagerListener if the listener is not already registered. |
michael@0 | 2133 | * |
michael@0 | 2134 | * @param aListener |
michael@0 | 2135 | * The listener to add |
michael@0 | 2136 | */ |
michael@0 | 2137 | addManagerListener: function AMI_addManagerListener(aListener) { |
michael@0 | 2138 | if (!aListener || typeof aListener != "object") |
michael@0 | 2139 | throw Components.Exception("aListener must be an AddonManagerListener object", |
michael@0 | 2140 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 2141 | |
michael@0 | 2142 | if (!this.managerListeners.some(function addManagerListener_matchListener(i) { |
michael@0 | 2143 | return i == aListener; })) |
michael@0 | 2144 | this.managerListeners.push(aListener); |
michael@0 | 2145 | }, |
michael@0 | 2146 | |
michael@0 | 2147 | /** |
michael@0 | 2148 | * Removes an AddonManagerListener if the listener is registered. |
michael@0 | 2149 | * |
michael@0 | 2150 | * @param aListener |
michael@0 | 2151 | * The listener to remove |
michael@0 | 2152 | */ |
michael@0 | 2153 | removeManagerListener: function AMI_removeManagerListener(aListener) { |
michael@0 | 2154 | if (!aListener || typeof aListener != "object") |
michael@0 | 2155 | throw Components.Exception("aListener must be an AddonManagerListener object", |
michael@0 | 2156 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 2157 | |
michael@0 | 2158 | let pos = 0; |
michael@0 | 2159 | while (pos < this.managerListeners.length) { |
michael@0 | 2160 | if (this.managerListeners[pos] == aListener) |
michael@0 | 2161 | this.managerListeners.splice(pos, 1); |
michael@0 | 2162 | else |
michael@0 | 2163 | pos++; |
michael@0 | 2164 | } |
michael@0 | 2165 | }, |
michael@0 | 2166 | |
michael@0 | 2167 | /** |
michael@0 | 2168 | * Adds a new AddonListener if the listener is not already registered. |
michael@0 | 2169 | * |
michael@0 | 2170 | * @param aListener |
michael@0 | 2171 | * The AddonListener to add |
michael@0 | 2172 | */ |
michael@0 | 2173 | addAddonListener: function AMI_addAddonListener(aListener) { |
michael@0 | 2174 | if (!aListener || typeof aListener != "object") |
michael@0 | 2175 | throw Components.Exception("aListener must be an AddonListener object", |
michael@0 | 2176 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 2177 | |
michael@0 | 2178 | if (!this.addonListeners.some(function addAddonListener_matchListener(i) { |
michael@0 | 2179 | return i == aListener; })) |
michael@0 | 2180 | this.addonListeners.push(aListener); |
michael@0 | 2181 | }, |
michael@0 | 2182 | |
michael@0 | 2183 | /** |
michael@0 | 2184 | * Removes an AddonListener if the listener is registered. |
michael@0 | 2185 | * |
michael@0 | 2186 | * @param aListener |
michael@0 | 2187 | * The AddonListener to remove |
michael@0 | 2188 | */ |
michael@0 | 2189 | removeAddonListener: function AMI_removeAddonListener(aListener) { |
michael@0 | 2190 | if (!aListener || typeof aListener != "object") |
michael@0 | 2191 | throw Components.Exception("aListener must be an AddonListener object", |
michael@0 | 2192 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 2193 | |
michael@0 | 2194 | let pos = 0; |
michael@0 | 2195 | while (pos < this.addonListeners.length) { |
michael@0 | 2196 | if (this.addonListeners[pos] == aListener) |
michael@0 | 2197 | this.addonListeners.splice(pos, 1); |
michael@0 | 2198 | else |
michael@0 | 2199 | pos++; |
michael@0 | 2200 | } |
michael@0 | 2201 | }, |
michael@0 | 2202 | |
michael@0 | 2203 | /** |
michael@0 | 2204 | * Adds a new TypeListener if the listener is not already registered. |
michael@0 | 2205 | * |
michael@0 | 2206 | * @param aListener |
michael@0 | 2207 | * The TypeListener to add |
michael@0 | 2208 | */ |
michael@0 | 2209 | addTypeListener: function AMI_addTypeListener(aListener) { |
michael@0 | 2210 | if (!aListener || typeof aListener != "object") |
michael@0 | 2211 | throw Components.Exception("aListener must be a TypeListener object", |
michael@0 | 2212 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 2213 | |
michael@0 | 2214 | if (!this.typeListeners.some(function addTypeListener_matchListener(i) { |
michael@0 | 2215 | return i == aListener; })) |
michael@0 | 2216 | this.typeListeners.push(aListener); |
michael@0 | 2217 | }, |
michael@0 | 2218 | |
michael@0 | 2219 | /** |
michael@0 | 2220 | * Removes an TypeListener if the listener is registered. |
michael@0 | 2221 | * |
michael@0 | 2222 | * @param aListener |
michael@0 | 2223 | * The TypeListener to remove |
michael@0 | 2224 | */ |
michael@0 | 2225 | removeTypeListener: function AMI_removeTypeListener(aListener) { |
michael@0 | 2226 | if (!aListener || typeof aListener != "object") |
michael@0 | 2227 | throw Components.Exception("aListener must be a TypeListener object", |
michael@0 | 2228 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 2229 | |
michael@0 | 2230 | let pos = 0; |
michael@0 | 2231 | while (pos < this.typeListeners.length) { |
michael@0 | 2232 | if (this.typeListeners[pos] == aListener) |
michael@0 | 2233 | this.typeListeners.splice(pos, 1); |
michael@0 | 2234 | else |
michael@0 | 2235 | pos++; |
michael@0 | 2236 | } |
michael@0 | 2237 | }, |
michael@0 | 2238 | |
michael@0 | 2239 | get addonTypes() { |
michael@0 | 2240 | return this.typesProxy; |
michael@0 | 2241 | }, |
michael@0 | 2242 | |
michael@0 | 2243 | get autoUpdateDefault() { |
michael@0 | 2244 | return gAutoUpdateDefault; |
michael@0 | 2245 | }, |
michael@0 | 2246 | |
michael@0 | 2247 | set autoUpdateDefault(aValue) { |
michael@0 | 2248 | aValue = !!aValue; |
michael@0 | 2249 | if (aValue != gAutoUpdateDefault) |
michael@0 | 2250 | Services.prefs.setBoolPref(PREF_EM_AUTOUPDATE_DEFAULT, aValue); |
michael@0 | 2251 | return aValue; |
michael@0 | 2252 | }, |
michael@0 | 2253 | |
michael@0 | 2254 | get checkCompatibility() { |
michael@0 | 2255 | return gCheckCompatibility; |
michael@0 | 2256 | }, |
michael@0 | 2257 | |
michael@0 | 2258 | set checkCompatibility(aValue) { |
michael@0 | 2259 | aValue = !!aValue; |
michael@0 | 2260 | if (aValue != gCheckCompatibility) { |
michael@0 | 2261 | if (!aValue) |
michael@0 | 2262 | Services.prefs.setBoolPref(PREF_EM_CHECK_COMPATIBILITY, false); |
michael@0 | 2263 | else |
michael@0 | 2264 | Services.prefs.clearUserPref(PREF_EM_CHECK_COMPATIBILITY); |
michael@0 | 2265 | } |
michael@0 | 2266 | return aValue; |
michael@0 | 2267 | }, |
michael@0 | 2268 | |
michael@0 | 2269 | get strictCompatibility() { |
michael@0 | 2270 | return gStrictCompatibility; |
michael@0 | 2271 | }, |
michael@0 | 2272 | |
michael@0 | 2273 | set strictCompatibility(aValue) { |
michael@0 | 2274 | aValue = !!aValue; |
michael@0 | 2275 | if (aValue != gStrictCompatibility) |
michael@0 | 2276 | Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, aValue); |
michael@0 | 2277 | return aValue; |
michael@0 | 2278 | }, |
michael@0 | 2279 | |
michael@0 | 2280 | get checkUpdateSecurityDefault() { |
michael@0 | 2281 | return gCheckUpdateSecurityDefault; |
michael@0 | 2282 | }, |
michael@0 | 2283 | |
michael@0 | 2284 | get checkUpdateSecurity() { |
michael@0 | 2285 | return gCheckUpdateSecurity; |
michael@0 | 2286 | }, |
michael@0 | 2287 | |
michael@0 | 2288 | set checkUpdateSecurity(aValue) { |
michael@0 | 2289 | aValue = !!aValue; |
michael@0 | 2290 | if (aValue != gCheckUpdateSecurity) { |
michael@0 | 2291 | if (aValue != gCheckUpdateSecurityDefault) |
michael@0 | 2292 | Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, aValue); |
michael@0 | 2293 | else |
michael@0 | 2294 | Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY); |
michael@0 | 2295 | } |
michael@0 | 2296 | return aValue; |
michael@0 | 2297 | }, |
michael@0 | 2298 | |
michael@0 | 2299 | get updateEnabled() { |
michael@0 | 2300 | return gUpdateEnabled; |
michael@0 | 2301 | }, |
michael@0 | 2302 | |
michael@0 | 2303 | set updateEnabled(aValue) { |
michael@0 | 2304 | aValue = !!aValue; |
michael@0 | 2305 | if (aValue != gUpdateEnabled) |
michael@0 | 2306 | Services.prefs.setBoolPref(PREF_EM_UPDATE_ENABLED, aValue); |
michael@0 | 2307 | return aValue; |
michael@0 | 2308 | }, |
michael@0 | 2309 | |
michael@0 | 2310 | get hotfixID() { |
michael@0 | 2311 | return gHotfixID; |
michael@0 | 2312 | }, |
michael@0 | 2313 | }; |
michael@0 | 2314 | |
michael@0 | 2315 | /** |
michael@0 | 2316 | * Should not be used outside of core Mozilla code. This is a private API for |
michael@0 | 2317 | * the startup and platform integration code to use. Refer to the methods on |
michael@0 | 2318 | * AddonManagerInternal for documentation however note that these methods are |
michael@0 | 2319 | * subject to change at any time. |
michael@0 | 2320 | */ |
michael@0 | 2321 | this.AddonManagerPrivate = { |
michael@0 | 2322 | startup: function AMP_startup() { |
michael@0 | 2323 | AddonManagerInternal.startup(); |
michael@0 | 2324 | }, |
michael@0 | 2325 | |
michael@0 | 2326 | registerProvider: function AMP_registerProvider(aProvider, aTypes) { |
michael@0 | 2327 | AddonManagerInternal.registerProvider(aProvider, aTypes); |
michael@0 | 2328 | }, |
michael@0 | 2329 | |
michael@0 | 2330 | unregisterProvider: function AMP_unregisterProvider(aProvider) { |
michael@0 | 2331 | AddonManagerInternal.unregisterProvider(aProvider); |
michael@0 | 2332 | }, |
michael@0 | 2333 | |
michael@0 | 2334 | backgroundUpdateCheck: function AMP_backgroundUpdateCheck() { |
michael@0 | 2335 | AddonManagerInternal.backgroundUpdateCheck(); |
michael@0 | 2336 | }, |
michael@0 | 2337 | |
michael@0 | 2338 | addStartupChange: function AMP_addStartupChange(aType, aID) { |
michael@0 | 2339 | AddonManagerInternal.addStartupChange(aType, aID); |
michael@0 | 2340 | }, |
michael@0 | 2341 | |
michael@0 | 2342 | removeStartupChange: function AMP_removeStartupChange(aType, aID) { |
michael@0 | 2343 | AddonManagerInternal.removeStartupChange(aType, aID); |
michael@0 | 2344 | }, |
michael@0 | 2345 | |
michael@0 | 2346 | notifyAddonChanged: function AMP_notifyAddonChanged(aID, aType, aPendingRestart) { |
michael@0 | 2347 | AddonManagerInternal.notifyAddonChanged(aID, aType, aPendingRestart); |
michael@0 | 2348 | }, |
michael@0 | 2349 | |
michael@0 | 2350 | updateAddonAppDisabledStates: function AMP_updateAddonAppDisabledStates() { |
michael@0 | 2351 | AddonManagerInternal.updateAddonAppDisabledStates(); |
michael@0 | 2352 | }, |
michael@0 | 2353 | |
michael@0 | 2354 | updateAddonRepositoryData: function AMP_updateAddonRepositoryData(aCallback) { |
michael@0 | 2355 | AddonManagerInternal.updateAddonRepositoryData(aCallback); |
michael@0 | 2356 | }, |
michael@0 | 2357 | |
michael@0 | 2358 | callInstallListeners: function AMP_callInstallListeners(...aArgs) { |
michael@0 | 2359 | return AddonManagerInternal.callInstallListeners.apply(AddonManagerInternal, |
michael@0 | 2360 | aArgs); |
michael@0 | 2361 | }, |
michael@0 | 2362 | |
michael@0 | 2363 | callAddonListeners: function AMP_callAddonListeners(...aArgs) { |
michael@0 | 2364 | AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, aArgs); |
michael@0 | 2365 | }, |
michael@0 | 2366 | |
michael@0 | 2367 | AddonAuthor: AddonAuthor, |
michael@0 | 2368 | |
michael@0 | 2369 | AddonScreenshot: AddonScreenshot, |
michael@0 | 2370 | |
michael@0 | 2371 | AddonCompatibilityOverride: AddonCompatibilityOverride, |
michael@0 | 2372 | |
michael@0 | 2373 | AddonType: AddonType, |
michael@0 | 2374 | |
michael@0 | 2375 | recordTimestamp: function AMP_recordTimestamp(name, value) { |
michael@0 | 2376 | AddonManagerInternal.recordTimestamp(name, value); |
michael@0 | 2377 | }, |
michael@0 | 2378 | |
michael@0 | 2379 | _simpleMeasures: {}, |
michael@0 | 2380 | recordSimpleMeasure: function AMP_recordSimpleMeasure(name, value) { |
michael@0 | 2381 | this._simpleMeasures[name] = value; |
michael@0 | 2382 | }, |
michael@0 | 2383 | |
michael@0 | 2384 | recordException: function AMP_recordException(aModule, aContext, aException) { |
michael@0 | 2385 | let report = { |
michael@0 | 2386 | module: aModule, |
michael@0 | 2387 | context: aContext |
michael@0 | 2388 | }; |
michael@0 | 2389 | |
michael@0 | 2390 | if (typeof aException == "number") { |
michael@0 | 2391 | report.message = Components.Exception("", aException).name; |
michael@0 | 2392 | } |
michael@0 | 2393 | else { |
michael@0 | 2394 | report.message = aException.toString(); |
michael@0 | 2395 | if (aException.fileName) { |
michael@0 | 2396 | report.file = aException.fileName; |
michael@0 | 2397 | report.line = aException.lineNumber; |
michael@0 | 2398 | } |
michael@0 | 2399 | } |
michael@0 | 2400 | |
michael@0 | 2401 | this._simpleMeasures.exception = report; |
michael@0 | 2402 | }, |
michael@0 | 2403 | |
michael@0 | 2404 | getSimpleMeasures: function AMP_getSimpleMeasures() { |
michael@0 | 2405 | return this._simpleMeasures; |
michael@0 | 2406 | }, |
michael@0 | 2407 | |
michael@0 | 2408 | getTelemetryDetails: function AMP_getTelemetryDetails() { |
michael@0 | 2409 | return AddonManagerInternal.telemetryDetails; |
michael@0 | 2410 | }, |
michael@0 | 2411 | |
michael@0 | 2412 | setTelemetryDetails: function AMP_setTelemetryDetails(aProvider, aDetails) { |
michael@0 | 2413 | AddonManagerInternal.telemetryDetails[aProvider] = aDetails; |
michael@0 | 2414 | }, |
michael@0 | 2415 | |
michael@0 | 2416 | // Start a timer, record a simple measure of the time interval when |
michael@0 | 2417 | // timer.done() is called |
michael@0 | 2418 | simpleTimer: function(aName) { |
michael@0 | 2419 | let startTime = Date.now(); |
michael@0 | 2420 | return { |
michael@0 | 2421 | done: () => this.recordSimpleMeasure(aName, Date.now() - startTime) |
michael@0 | 2422 | }; |
michael@0 | 2423 | }, |
michael@0 | 2424 | |
michael@0 | 2425 | /** |
michael@0 | 2426 | * Helper to call update listeners when no update is available. |
michael@0 | 2427 | * |
michael@0 | 2428 | * This can be used as an implementation for Addon.findUpdates() when |
michael@0 | 2429 | * no update mechanism is available. |
michael@0 | 2430 | */ |
michael@0 | 2431 | callNoUpdateListeners: function (addon, listener, reason, appVersion, platformVersion) { |
michael@0 | 2432 | if ("onNoCompatibilityUpdateAvailable" in listener) { |
michael@0 | 2433 | safeCall(listener.onNoCompatibilityUpdateAvailable.bind(listener), addon); |
michael@0 | 2434 | } |
michael@0 | 2435 | if ("onNoUpdateAvailable" in listener) { |
michael@0 | 2436 | safeCall(listener.onNoUpdateAvailable.bind(listener), addon); |
michael@0 | 2437 | } |
michael@0 | 2438 | if ("onUpdateFinished" in listener) { |
michael@0 | 2439 | safeCall(listener.onUpdateFinished.bind(listener), addon); |
michael@0 | 2440 | } |
michael@0 | 2441 | }, |
michael@0 | 2442 | }; |
michael@0 | 2443 | |
michael@0 | 2444 | /** |
michael@0 | 2445 | * This is the public API that UI and developers should be calling. All methods |
michael@0 | 2446 | * just forward to AddonManagerInternal. |
michael@0 | 2447 | */ |
michael@0 | 2448 | this.AddonManager = { |
michael@0 | 2449 | // Constants for the AddonInstall.state property |
michael@0 | 2450 | // The install is available for download. |
michael@0 | 2451 | STATE_AVAILABLE: 0, |
michael@0 | 2452 | // The install is being downloaded. |
michael@0 | 2453 | STATE_DOWNLOADING: 1, |
michael@0 | 2454 | // The install is checking for compatibility information. |
michael@0 | 2455 | STATE_CHECKING: 2, |
michael@0 | 2456 | // The install is downloaded and ready to install. |
michael@0 | 2457 | STATE_DOWNLOADED: 3, |
michael@0 | 2458 | // The download failed. |
michael@0 | 2459 | STATE_DOWNLOAD_FAILED: 4, |
michael@0 | 2460 | // The add-on is being installed. |
michael@0 | 2461 | STATE_INSTALLING: 5, |
michael@0 | 2462 | // The add-on has been installed. |
michael@0 | 2463 | STATE_INSTALLED: 6, |
michael@0 | 2464 | // The install failed. |
michael@0 | 2465 | STATE_INSTALL_FAILED: 7, |
michael@0 | 2466 | // The install has been cancelled. |
michael@0 | 2467 | STATE_CANCELLED: 8, |
michael@0 | 2468 | |
michael@0 | 2469 | // Constants representing different types of errors while downloading an |
michael@0 | 2470 | // add-on. |
michael@0 | 2471 | // The download failed due to network problems. |
michael@0 | 2472 | ERROR_NETWORK_FAILURE: -1, |
michael@0 | 2473 | // The downloaded file did not match the provided hash. |
michael@0 | 2474 | ERROR_INCORRECT_HASH: -2, |
michael@0 | 2475 | // The downloaded file seems to be corrupted in some way. |
michael@0 | 2476 | ERROR_CORRUPT_FILE: -3, |
michael@0 | 2477 | // An error occured trying to write to the filesystem. |
michael@0 | 2478 | ERROR_FILE_ACCESS: -4, |
michael@0 | 2479 | |
michael@0 | 2480 | // These must be kept in sync with AddonUpdateChecker. |
michael@0 | 2481 | // No error was encountered. |
michael@0 | 2482 | UPDATE_STATUS_NO_ERROR: 0, |
michael@0 | 2483 | // The update check timed out |
michael@0 | 2484 | UPDATE_STATUS_TIMEOUT: -1, |
michael@0 | 2485 | // There was an error while downloading the update information. |
michael@0 | 2486 | UPDATE_STATUS_DOWNLOAD_ERROR: -2, |
michael@0 | 2487 | // The update information was malformed in some way. |
michael@0 | 2488 | UPDATE_STATUS_PARSE_ERROR: -3, |
michael@0 | 2489 | // The update information was not in any known format. |
michael@0 | 2490 | UPDATE_STATUS_UNKNOWN_FORMAT: -4, |
michael@0 | 2491 | // The update information was not correctly signed or there was an SSL error. |
michael@0 | 2492 | UPDATE_STATUS_SECURITY_ERROR: -5, |
michael@0 | 2493 | // The update was cancelled. |
michael@0 | 2494 | UPDATE_STATUS_CANCELLED: -6, |
michael@0 | 2495 | |
michael@0 | 2496 | // Constants to indicate why an update check is being performed |
michael@0 | 2497 | // Update check has been requested by the user. |
michael@0 | 2498 | UPDATE_WHEN_USER_REQUESTED: 1, |
michael@0 | 2499 | // Update check is necessary to see if the Addon is compatibile with a new |
michael@0 | 2500 | // version of the application. |
michael@0 | 2501 | UPDATE_WHEN_NEW_APP_DETECTED: 2, |
michael@0 | 2502 | // Update check is necessary because a new application has been installed. |
michael@0 | 2503 | UPDATE_WHEN_NEW_APP_INSTALLED: 3, |
michael@0 | 2504 | // Update check is a regular background update check. |
michael@0 | 2505 | UPDATE_WHEN_PERIODIC_UPDATE: 16, |
michael@0 | 2506 | // Update check is needed to check an Addon that is being installed. |
michael@0 | 2507 | UPDATE_WHEN_ADDON_INSTALLED: 17, |
michael@0 | 2508 | |
michael@0 | 2509 | // Constants for operations in Addon.pendingOperations |
michael@0 | 2510 | // Indicates that the Addon has no pending operations. |
michael@0 | 2511 | PENDING_NONE: 0, |
michael@0 | 2512 | // Indicates that the Addon will be enabled after the application restarts. |
michael@0 | 2513 | PENDING_ENABLE: 1, |
michael@0 | 2514 | // Indicates that the Addon will be disabled after the application restarts. |
michael@0 | 2515 | PENDING_DISABLE: 2, |
michael@0 | 2516 | // Indicates that the Addon will be uninstalled after the application restarts. |
michael@0 | 2517 | PENDING_UNINSTALL: 4, |
michael@0 | 2518 | // Indicates that the Addon will be installed after the application restarts. |
michael@0 | 2519 | PENDING_INSTALL: 8, |
michael@0 | 2520 | PENDING_UPGRADE: 16, |
michael@0 | 2521 | |
michael@0 | 2522 | // Constants for operations in Addon.operationsRequiringRestart |
michael@0 | 2523 | // Indicates that restart isn't required for any operation. |
michael@0 | 2524 | OP_NEEDS_RESTART_NONE: 0, |
michael@0 | 2525 | // Indicates that restart is required for enabling the addon. |
michael@0 | 2526 | OP_NEEDS_RESTART_ENABLE: 1, |
michael@0 | 2527 | // Indicates that restart is required for disabling the addon. |
michael@0 | 2528 | OP_NEEDS_RESTART_DISABLE: 2, |
michael@0 | 2529 | // Indicates that restart is required for uninstalling the addon. |
michael@0 | 2530 | OP_NEEDS_RESTART_UNINSTALL: 4, |
michael@0 | 2531 | // Indicates that restart is required for installing the addon. |
michael@0 | 2532 | OP_NEEDS_RESTART_INSTALL: 8, |
michael@0 | 2533 | |
michael@0 | 2534 | // Constants for permissions in Addon.permissions. |
michael@0 | 2535 | // Indicates that the Addon can be uninstalled. |
michael@0 | 2536 | PERM_CAN_UNINSTALL: 1, |
michael@0 | 2537 | // Indicates that the Addon can be enabled by the user. |
michael@0 | 2538 | PERM_CAN_ENABLE: 2, |
michael@0 | 2539 | // Indicates that the Addon can be disabled by the user. |
michael@0 | 2540 | PERM_CAN_DISABLE: 4, |
michael@0 | 2541 | // Indicates that the Addon can be upgraded. |
michael@0 | 2542 | PERM_CAN_UPGRADE: 8, |
michael@0 | 2543 | // Indicates that the Addon can be set to be optionally enabled |
michael@0 | 2544 | // on a case-by-case basis. |
michael@0 | 2545 | PERM_CAN_ASK_TO_ACTIVATE: 16, |
michael@0 | 2546 | |
michael@0 | 2547 | // General descriptions of where items are installed. |
michael@0 | 2548 | // Installed in this profile. |
michael@0 | 2549 | SCOPE_PROFILE: 1, |
michael@0 | 2550 | // Installed for all of this user's profiles. |
michael@0 | 2551 | SCOPE_USER: 2, |
michael@0 | 2552 | // Installed and owned by the application. |
michael@0 | 2553 | SCOPE_APPLICATION: 4, |
michael@0 | 2554 | // Installed for all users of the computer. |
michael@0 | 2555 | SCOPE_SYSTEM: 8, |
michael@0 | 2556 | // The combination of all scopes. |
michael@0 | 2557 | SCOPE_ALL: 15, |
michael@0 | 2558 | |
michael@0 | 2559 | // 1-15 are different built-in views for the add-on type |
michael@0 | 2560 | VIEW_TYPE_LIST: "list", |
michael@0 | 2561 | |
michael@0 | 2562 | TYPE_UI_HIDE_EMPTY: 16, |
michael@0 | 2563 | // Indicates that this add-on type supports the ask-to-activate state. |
michael@0 | 2564 | // That is, add-ons of this type can be set to be optionally enabled |
michael@0 | 2565 | // on a case-by-case basis. |
michael@0 | 2566 | TYPE_SUPPORTS_ASK_TO_ACTIVATE: 32, |
michael@0 | 2567 | |
michael@0 | 2568 | // Constants for Addon.applyBackgroundUpdates. |
michael@0 | 2569 | // Indicates that the Addon should not update automatically. |
michael@0 | 2570 | AUTOUPDATE_DISABLE: 0, |
michael@0 | 2571 | // Indicates that the Addon should update automatically only if |
michael@0 | 2572 | // that's the global default. |
michael@0 | 2573 | AUTOUPDATE_DEFAULT: 1, |
michael@0 | 2574 | // Indicates that the Addon should update automatically. |
michael@0 | 2575 | AUTOUPDATE_ENABLE: 2, |
michael@0 | 2576 | |
michael@0 | 2577 | // Constants for how Addon options should be shown. |
michael@0 | 2578 | // Options will be opened in a new window |
michael@0 | 2579 | OPTIONS_TYPE_DIALOG: 1, |
michael@0 | 2580 | // Options will be displayed within the AM detail view |
michael@0 | 2581 | OPTIONS_TYPE_INLINE: 2, |
michael@0 | 2582 | // Options will be displayed in a new tab, if possible |
michael@0 | 2583 | OPTIONS_TYPE_TAB: 3, |
michael@0 | 2584 | // Same as OPTIONS_TYPE_INLINE, but no Preferences button will be shown. |
michael@0 | 2585 | // Used to indicate that only non-interactive information will be shown. |
michael@0 | 2586 | OPTIONS_TYPE_INLINE_INFO: 4, |
michael@0 | 2587 | |
michael@0 | 2588 | // Constants for displayed or hidden options notifications |
michael@0 | 2589 | // Options notification will be displayed |
michael@0 | 2590 | OPTIONS_NOTIFICATION_DISPLAYED: "addon-options-displayed", |
michael@0 | 2591 | // Options notification will be hidden |
michael@0 | 2592 | OPTIONS_NOTIFICATION_HIDDEN: "addon-options-hidden", |
michael@0 | 2593 | |
michael@0 | 2594 | // Constants for getStartupChanges, addStartupChange and removeStartupChange |
michael@0 | 2595 | // Add-ons that were detected as installed during startup. Doesn't include |
michael@0 | 2596 | // add-ons that were pending installation the last time the application ran. |
michael@0 | 2597 | STARTUP_CHANGE_INSTALLED: "installed", |
michael@0 | 2598 | // Add-ons that were detected as changed during startup. This includes an |
michael@0 | 2599 | // add-on moving to a different location, changing version or just having |
michael@0 | 2600 | // been detected as possibly changed. |
michael@0 | 2601 | STARTUP_CHANGE_CHANGED: "changed", |
michael@0 | 2602 | // Add-ons that were detected as uninstalled during startup. Doesn't include |
michael@0 | 2603 | // add-ons that were pending uninstallation the last time the application ran. |
michael@0 | 2604 | STARTUP_CHANGE_UNINSTALLED: "uninstalled", |
michael@0 | 2605 | // Add-ons that were detected as disabled during startup, normally because of |
michael@0 | 2606 | // an application change making an add-on incompatible. Doesn't include |
michael@0 | 2607 | // add-ons that were pending being disabled the last time the application ran. |
michael@0 | 2608 | STARTUP_CHANGE_DISABLED: "disabled", |
michael@0 | 2609 | // Add-ons that were detected as enabled during startup, normally because of |
michael@0 | 2610 | // an application change making an add-on compatible. Doesn't include |
michael@0 | 2611 | // add-ons that were pending being enabled the last time the application ran. |
michael@0 | 2612 | STARTUP_CHANGE_ENABLED: "enabled", |
michael@0 | 2613 | |
michael@0 | 2614 | // Constants for the Addon.userDisabled property |
michael@0 | 2615 | // Indicates that the userDisabled state of this add-on is currently |
michael@0 | 2616 | // ask-to-activate. That is, it can be conditionally enabled on a |
michael@0 | 2617 | // case-by-case basis. |
michael@0 | 2618 | STATE_ASK_TO_ACTIVATE: "askToActivate", |
michael@0 | 2619 | |
michael@0 | 2620 | #ifdef MOZ_EM_DEBUG |
michael@0 | 2621 | get __AddonManagerInternal__() { |
michael@0 | 2622 | return AddonManagerInternal; |
michael@0 | 2623 | }, |
michael@0 | 2624 | #endif |
michael@0 | 2625 | |
michael@0 | 2626 | getInstallForURL: function AM_getInstallForURL(aUrl, aCallback, aMimetype, |
michael@0 | 2627 | aHash, aName, aIcons, |
michael@0 | 2628 | aVersion, aLoadGroup) { |
michael@0 | 2629 | AddonManagerInternal.getInstallForURL(aUrl, aCallback, aMimetype, aHash, |
michael@0 | 2630 | aName, aIcons, aVersion, aLoadGroup); |
michael@0 | 2631 | }, |
michael@0 | 2632 | |
michael@0 | 2633 | getInstallForFile: function AM_getInstallForFile(aFile, aCallback, aMimetype) { |
michael@0 | 2634 | AddonManagerInternal.getInstallForFile(aFile, aCallback, aMimetype); |
michael@0 | 2635 | }, |
michael@0 | 2636 | |
michael@0 | 2637 | /** |
michael@0 | 2638 | * Gets an array of add-on IDs that changed during the most recent startup. |
michael@0 | 2639 | * |
michael@0 | 2640 | * @param aType |
michael@0 | 2641 | * The type of startup change to get |
michael@0 | 2642 | * @return An array of add-on IDs |
michael@0 | 2643 | */ |
michael@0 | 2644 | getStartupChanges: function AM_getStartupChanges(aType) { |
michael@0 | 2645 | if (!(aType in AddonManagerInternal.startupChanges)) |
michael@0 | 2646 | return []; |
michael@0 | 2647 | return AddonManagerInternal.startupChanges[aType].slice(0); |
michael@0 | 2648 | }, |
michael@0 | 2649 | |
michael@0 | 2650 | getAddonByID: function AM_getAddonByID(aID, aCallback) { |
michael@0 | 2651 | AddonManagerInternal.getAddonByID(aID, aCallback); |
michael@0 | 2652 | }, |
michael@0 | 2653 | |
michael@0 | 2654 | getAddonBySyncGUID: function AM_getAddonBySyncGUID(aGUID, aCallback) { |
michael@0 | 2655 | AddonManagerInternal.getAddonBySyncGUID(aGUID, aCallback); |
michael@0 | 2656 | }, |
michael@0 | 2657 | |
michael@0 | 2658 | getAddonsByIDs: function AM_getAddonsByIDs(aIDs, aCallback) { |
michael@0 | 2659 | AddonManagerInternal.getAddonsByIDs(aIDs, aCallback); |
michael@0 | 2660 | }, |
michael@0 | 2661 | |
michael@0 | 2662 | getAddonsWithOperationsByTypes: |
michael@0 | 2663 | function AM_getAddonsWithOperationsByTypes(aTypes, aCallback) { |
michael@0 | 2664 | AddonManagerInternal.getAddonsWithOperationsByTypes(aTypes, aCallback); |
michael@0 | 2665 | }, |
michael@0 | 2666 | |
michael@0 | 2667 | getAddonsByTypes: function AM_getAddonsByTypes(aTypes, aCallback) { |
michael@0 | 2668 | AddonManagerInternal.getAddonsByTypes(aTypes, aCallback); |
michael@0 | 2669 | }, |
michael@0 | 2670 | |
michael@0 | 2671 | getAllAddons: function AM_getAllAddons(aCallback) { |
michael@0 | 2672 | AddonManagerInternal.getAllAddons(aCallback); |
michael@0 | 2673 | }, |
michael@0 | 2674 | |
michael@0 | 2675 | getInstallsByTypes: function AM_getInstallsByTypes(aTypes, aCallback) { |
michael@0 | 2676 | AddonManagerInternal.getInstallsByTypes(aTypes, aCallback); |
michael@0 | 2677 | }, |
michael@0 | 2678 | |
michael@0 | 2679 | getAllInstalls: function AM_getAllInstalls(aCallback) { |
michael@0 | 2680 | AddonManagerInternal.getAllInstalls(aCallback); |
michael@0 | 2681 | }, |
michael@0 | 2682 | |
michael@0 | 2683 | mapURIToAddonID: function AM_mapURIToAddonID(aURI) { |
michael@0 | 2684 | return AddonManagerInternal.mapURIToAddonID(aURI); |
michael@0 | 2685 | }, |
michael@0 | 2686 | |
michael@0 | 2687 | isInstallEnabled: function AM_isInstallEnabled(aType) { |
michael@0 | 2688 | return AddonManagerInternal.isInstallEnabled(aType); |
michael@0 | 2689 | }, |
michael@0 | 2690 | |
michael@0 | 2691 | isInstallAllowed: function AM_isInstallAllowed(aType, aUri) { |
michael@0 | 2692 | return AddonManagerInternal.isInstallAllowed(aType, aUri); |
michael@0 | 2693 | }, |
michael@0 | 2694 | |
michael@0 | 2695 | installAddonsFromWebpage: function AM_installAddonsFromWebpage(aType, aSource, |
michael@0 | 2696 | aUri, aInstalls) { |
michael@0 | 2697 | AddonManagerInternal.installAddonsFromWebpage(aType, aSource, aUri, aInstalls); |
michael@0 | 2698 | }, |
michael@0 | 2699 | |
michael@0 | 2700 | addManagerListener: function AM_addManagerListener(aListener) { |
michael@0 | 2701 | AddonManagerInternal.addManagerListener(aListener); |
michael@0 | 2702 | }, |
michael@0 | 2703 | |
michael@0 | 2704 | removeManagerListener: function AM_removeManagerListener(aListener) { |
michael@0 | 2705 | AddonManagerInternal.removeManagerListener(aListener); |
michael@0 | 2706 | }, |
michael@0 | 2707 | |
michael@0 | 2708 | addInstallListener: function AM_addInstallListener(aListener) { |
michael@0 | 2709 | AddonManagerInternal.addInstallListener(aListener); |
michael@0 | 2710 | }, |
michael@0 | 2711 | |
michael@0 | 2712 | removeInstallListener: function AM_removeInstallListener(aListener) { |
michael@0 | 2713 | AddonManagerInternal.removeInstallListener(aListener); |
michael@0 | 2714 | }, |
michael@0 | 2715 | |
michael@0 | 2716 | addAddonListener: function AM_addAddonListener(aListener) { |
michael@0 | 2717 | AddonManagerInternal.addAddonListener(aListener); |
michael@0 | 2718 | }, |
michael@0 | 2719 | |
michael@0 | 2720 | removeAddonListener: function AM_removeAddonListener(aListener) { |
michael@0 | 2721 | AddonManagerInternal.removeAddonListener(aListener); |
michael@0 | 2722 | }, |
michael@0 | 2723 | |
michael@0 | 2724 | addTypeListener: function AM_addTypeListener(aListener) { |
michael@0 | 2725 | AddonManagerInternal.addTypeListener(aListener); |
michael@0 | 2726 | }, |
michael@0 | 2727 | |
michael@0 | 2728 | removeTypeListener: function AM_removeTypeListener(aListener) { |
michael@0 | 2729 | AddonManagerInternal.removeTypeListener(aListener); |
michael@0 | 2730 | }, |
michael@0 | 2731 | |
michael@0 | 2732 | get addonTypes() { |
michael@0 | 2733 | return AddonManagerInternal.addonTypes; |
michael@0 | 2734 | }, |
michael@0 | 2735 | |
michael@0 | 2736 | /** |
michael@0 | 2737 | * Determines whether an Addon should auto-update or not. |
michael@0 | 2738 | * |
michael@0 | 2739 | * @param aAddon |
michael@0 | 2740 | * The Addon representing the add-on |
michael@0 | 2741 | * @return true if the addon should auto-update, false otherwise. |
michael@0 | 2742 | */ |
michael@0 | 2743 | shouldAutoUpdate: function AM_shouldAutoUpdate(aAddon) { |
michael@0 | 2744 | if (!aAddon || typeof aAddon != "object") |
michael@0 | 2745 | throw Components.Exception("aAddon must be specified", |
michael@0 | 2746 | Cr.NS_ERROR_INVALID_ARG); |
michael@0 | 2747 | |
michael@0 | 2748 | if (!("applyBackgroundUpdates" in aAddon)) |
michael@0 | 2749 | return false; |
michael@0 | 2750 | if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE) |
michael@0 | 2751 | return true; |
michael@0 | 2752 | if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE) |
michael@0 | 2753 | return false; |
michael@0 | 2754 | return this.autoUpdateDefault; |
michael@0 | 2755 | }, |
michael@0 | 2756 | |
michael@0 | 2757 | get checkCompatibility() { |
michael@0 | 2758 | return AddonManagerInternal.checkCompatibility; |
michael@0 | 2759 | }, |
michael@0 | 2760 | |
michael@0 | 2761 | set checkCompatibility(aValue) { |
michael@0 | 2762 | AddonManagerInternal.checkCompatibility = aValue; |
michael@0 | 2763 | }, |
michael@0 | 2764 | |
michael@0 | 2765 | get strictCompatibility() { |
michael@0 | 2766 | return AddonManagerInternal.strictCompatibility; |
michael@0 | 2767 | }, |
michael@0 | 2768 | |
michael@0 | 2769 | set strictCompatibility(aValue) { |
michael@0 | 2770 | AddonManagerInternal.strictCompatibility = aValue; |
michael@0 | 2771 | }, |
michael@0 | 2772 | |
michael@0 | 2773 | get checkUpdateSecurityDefault() { |
michael@0 | 2774 | return AddonManagerInternal.checkUpdateSecurityDefault; |
michael@0 | 2775 | }, |
michael@0 | 2776 | |
michael@0 | 2777 | get checkUpdateSecurity() { |
michael@0 | 2778 | return AddonManagerInternal.checkUpdateSecurity; |
michael@0 | 2779 | }, |
michael@0 | 2780 | |
michael@0 | 2781 | set checkUpdateSecurity(aValue) { |
michael@0 | 2782 | AddonManagerInternal.checkUpdateSecurity = aValue; |
michael@0 | 2783 | }, |
michael@0 | 2784 | |
michael@0 | 2785 | get updateEnabled() { |
michael@0 | 2786 | return AddonManagerInternal.updateEnabled; |
michael@0 | 2787 | }, |
michael@0 | 2788 | |
michael@0 | 2789 | set updateEnabled(aValue) { |
michael@0 | 2790 | AddonManagerInternal.updateEnabled = aValue; |
michael@0 | 2791 | }, |
michael@0 | 2792 | |
michael@0 | 2793 | get autoUpdateDefault() { |
michael@0 | 2794 | return AddonManagerInternal.autoUpdateDefault; |
michael@0 | 2795 | }, |
michael@0 | 2796 | |
michael@0 | 2797 | set autoUpdateDefault(aValue) { |
michael@0 | 2798 | AddonManagerInternal.autoUpdateDefault = aValue; |
michael@0 | 2799 | }, |
michael@0 | 2800 | |
michael@0 | 2801 | get hotfixID() { |
michael@0 | 2802 | return AddonManagerInternal.hotfixID; |
michael@0 | 2803 | }, |
michael@0 | 2804 | |
michael@0 | 2805 | escapeAddonURI: function AM_escapeAddonURI(aAddon, aUri, aAppVersion) { |
michael@0 | 2806 | return AddonManagerInternal.escapeAddonURI(aAddon, aUri, aAppVersion); |
michael@0 | 2807 | } |
michael@0 | 2808 | }; |
michael@0 | 2809 | |
michael@0 | 2810 | // load the timestamps module into AddonManagerInternal |
michael@0 | 2811 | Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", AddonManagerInternal); |
michael@0 | 2812 | Object.freeze(AddonManagerInternal); |
michael@0 | 2813 | Object.freeze(AddonManagerPrivate); |
michael@0 | 2814 | Object.freeze(AddonManager); |