From 1e0da1994d03786571f4f97399595c5910f3a4c2 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Wed, 13 Feb 2019 19:25:03 +0100 Subject: Remove the WebExtension Add-on Manager from our tree. Tag #936 --- .../mozapps/webextensions/AddonContentPolicy.cpp | 478 - toolkit/mozapps/webextensions/AddonContentPolicy.h | 22 - toolkit/mozapps/webextensions/AddonManager.jsm | 3666 -------- .../mozapps/webextensions/AddonManagerWebAPI.cpp | 171 - toolkit/mozapps/webextensions/AddonManagerWebAPI.h | 33 - toolkit/mozapps/webextensions/AddonPathService.cpp | 258 - toolkit/mozapps/webextensions/AddonPathService.h | 55 - .../mozapps/webextensions/GMPInstallManager.jsm | 523 -- .../webextensions/LightweightThemeManager.jsm | 909 -- toolkit/mozapps/webextensions/addonManager.js | 296 - toolkit/mozapps/webextensions/amInstallTrigger.js | 240 - toolkit/mozapps/webextensions/amWebAPI.js | 269 - .../mozapps/webextensions/amWebInstallListener.js | 348 - toolkit/mozapps/webextensions/content/about.js | 103 - toolkit/mozapps/webextensions/content/eula.js | 25 - .../mozapps/webextensions/content/extensions.css | 270 - .../mozapps/webextensions/content/extensions.js | 3827 -------- .../mozapps/webextensions/content/extensions.xml | 2008 ----- .../mozapps/webextensions/content/extensions.xul | 715 -- toolkit/mozapps/webextensions/content/newaddon.xul | 67 - toolkit/mozapps/webextensions/content/setting.xml | 486 -- toolkit/mozapps/webextensions/content/update.js | 663 -- toolkit/mozapps/webextensions/content/update.xul | 194 - toolkit/mozapps/webextensions/extensions.manifest | 18 - .../internal/APIExtensionBootstrap.js | 39 - .../webextensions/internal/AddonConstants.jsm | 31 - .../webextensions/internal/AddonRepository.jsm | 1988 ----- .../internal/AddonRepository_SQLiteMigrator.jsm | 522 -- .../mozapps/webextensions/internal/GMPProvider.jsm | 699 -- .../internal/LightweightThemeImageOptimizer.jsm | 180 - .../webextensions/internal/PluginProvider.jsm | 600 -- .../internal/WebExtensionBootstrap.js | 39 - .../mozapps/webextensions/internal/XPIProvider.jsm | 9217 -------------------- .../webextensions/internal/XPIProviderUtils.js | 2239 ----- toolkit/mozapps/webextensions/internal/moz.build | 31 - toolkit/mozapps/webextensions/jar.mn | 35 - toolkit/mozapps/webextensions/moz.build | 57 - 37 files changed, 31321 deletions(-) delete mode 100644 toolkit/mozapps/webextensions/AddonContentPolicy.cpp delete mode 100644 toolkit/mozapps/webextensions/AddonContentPolicy.h delete mode 100644 toolkit/mozapps/webextensions/AddonManager.jsm delete mode 100644 toolkit/mozapps/webextensions/AddonManagerWebAPI.cpp delete mode 100644 toolkit/mozapps/webextensions/AddonManagerWebAPI.h delete mode 100644 toolkit/mozapps/webextensions/AddonPathService.cpp delete mode 100644 toolkit/mozapps/webextensions/AddonPathService.h delete mode 100644 toolkit/mozapps/webextensions/GMPInstallManager.jsm delete mode 100644 toolkit/mozapps/webextensions/LightweightThemeManager.jsm delete mode 100644 toolkit/mozapps/webextensions/addonManager.js delete mode 100644 toolkit/mozapps/webextensions/amInstallTrigger.js delete mode 100644 toolkit/mozapps/webextensions/amWebAPI.js delete mode 100644 toolkit/mozapps/webextensions/amWebInstallListener.js delete mode 100644 toolkit/mozapps/webextensions/content/about.js delete mode 100644 toolkit/mozapps/webextensions/content/eula.js delete mode 100644 toolkit/mozapps/webextensions/content/extensions.css delete mode 100644 toolkit/mozapps/webextensions/content/extensions.js delete mode 100644 toolkit/mozapps/webextensions/content/extensions.xml delete mode 100644 toolkit/mozapps/webextensions/content/extensions.xul delete mode 100644 toolkit/mozapps/webextensions/content/newaddon.xul delete mode 100644 toolkit/mozapps/webextensions/content/setting.xml delete mode 100644 toolkit/mozapps/webextensions/content/update.js delete mode 100644 toolkit/mozapps/webextensions/content/update.xul delete mode 100644 toolkit/mozapps/webextensions/extensions.manifest delete mode 100644 toolkit/mozapps/webextensions/internal/APIExtensionBootstrap.js delete mode 100644 toolkit/mozapps/webextensions/internal/AddonConstants.jsm delete mode 100644 toolkit/mozapps/webextensions/internal/AddonRepository.jsm delete mode 100644 toolkit/mozapps/webextensions/internal/AddonRepository_SQLiteMigrator.jsm delete mode 100644 toolkit/mozapps/webextensions/internal/GMPProvider.jsm delete mode 100644 toolkit/mozapps/webextensions/internal/LightweightThemeImageOptimizer.jsm delete mode 100644 toolkit/mozapps/webextensions/internal/PluginProvider.jsm delete mode 100644 toolkit/mozapps/webextensions/internal/WebExtensionBootstrap.js delete mode 100644 toolkit/mozapps/webextensions/internal/XPIProvider.jsm delete mode 100644 toolkit/mozapps/webextensions/internal/XPIProviderUtils.js delete mode 100644 toolkit/mozapps/webextensions/internal/moz.build delete mode 100644 toolkit/mozapps/webextensions/jar.mn delete mode 100644 toolkit/mozapps/webextensions/moz.build (limited to 'toolkit/mozapps') diff --git a/toolkit/mozapps/webextensions/AddonContentPolicy.cpp b/toolkit/mozapps/webextensions/AddonContentPolicy.cpp deleted file mode 100644 index 90e53b2ea..000000000 --- a/toolkit/mozapps/webextensions/AddonContentPolicy.cpp +++ /dev/null @@ -1,478 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "AddonContentPolicy.h" - -#include "mozilla/dom/nsCSPUtils.h" -#include "nsCOMPtr.h" -#include "nsContentPolicyUtils.h" -#include "nsContentTypeParser.h" -#include "nsContentUtils.h" -#include "nsIConsoleService.h" -#include "nsIContentSecurityPolicy.h" -#include "nsIContent.h" -#include "nsIDocument.h" -#include "nsIEffectiveTLDService.h" -#include "nsIScriptError.h" -#include "nsIStringBundle.h" -#include "nsIUUIDGenerator.h" -#include "nsIURI.h" -#include "nsNetCID.h" -#include "nsNetUtil.h" - -using namespace mozilla; - -/* Enforces content policies for WebExtension scopes. Currently: - * - * - Prevents loading scripts with a non-default JavaScript version. - * - Checks custom content security policies for sufficiently stringent - * script-src and object-src directives. - */ - -#define VERSIONED_JS_BLOCKED_MESSAGE \ - u"Versioned JavaScript is a non-standard, deprecated extension, and is " \ - u"not supported in WebExtension code. For alternatives, please see: " \ - u"https://developer.mozilla.org/Add-ons/WebExtensions/Tips" - -AddonContentPolicy::AddonContentPolicy() -{ -} - -AddonContentPolicy::~AddonContentPolicy() -{ -} - -NS_IMPL_ISUPPORTS(AddonContentPolicy, nsIContentPolicy, nsIAddonContentPolicy) - -static nsresult -GetWindowIDFromContext(nsISupports* aContext, uint64_t *aResult) -{ - NS_ENSURE_TRUE(aContext, NS_ERROR_FAILURE); - - nsCOMPtr content = do_QueryInterface(aContext); - NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); - - nsCOMPtr document = content->OwnerDoc(); - NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); - - nsCOMPtr window = document->GetInnerWindow(); - NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); - - *aResult = window->WindowID(); - return NS_OK; -} - -static nsresult -LogMessage(const nsAString &aMessage, nsIURI* aSourceURI, const nsAString &aSourceSample, - nsISupports* aContext) -{ - nsCOMPtr error = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); - NS_ENSURE_TRUE(error, NS_ERROR_OUT_OF_MEMORY); - - nsCString sourceName = aSourceURI->GetSpecOrDefault(); - - uint64_t windowID = 0; - GetWindowIDFromContext(aContext, &windowID); - - nsresult rv = - error->InitWithWindowID(aMessage, NS_ConvertUTF8toUTF16(sourceName), - aSourceSample, 0, 0, nsIScriptError::errorFlag, - "JavaScript", windowID); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr console = do_GetService(NS_CONSOLESERVICE_CONTRACTID); - NS_ENSURE_TRUE(console, NS_ERROR_OUT_OF_MEMORY); - - console->LogMessage(error); - return NS_OK; -} - - -// Content policy enforcement: - -NS_IMETHODIMP -AddonContentPolicy::ShouldLoad(uint32_t aContentType, - nsIURI* aContentLocation, - nsIURI* aRequestOrigin, - nsISupports* aContext, - const nsACString& aMimeTypeGuess, - nsISupports* aExtra, - nsIPrincipal* aRequestPrincipal, - int16_t* aShouldLoad) -{ - MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType), - "We should only see external content policy types here."); - - *aShouldLoad = nsIContentPolicy::ACCEPT; - - if (!aRequestOrigin) { - return NS_OK; - } - - // Only apply this policy to requests from documents loaded from - // moz-extension URLs, or to resources being loaded from moz-extension URLs. - bool equals; - if (!((NS_SUCCEEDED(aContentLocation->SchemeIs("moz-extension", &equals)) && equals) || - (NS_SUCCEEDED(aRequestOrigin->SchemeIs("moz-extension", &equals)) && equals))) { - return NS_OK; - } - - if (aContentType == nsIContentPolicy::TYPE_SCRIPT) { - NS_ConvertUTF8toUTF16 typeString(aMimeTypeGuess); - nsContentTypeParser mimeParser(typeString); - - // Reject attempts to load JavaScript scripts with a non-default version. - nsAutoString mimeType, version; - if (NS_SUCCEEDED(mimeParser.GetType(mimeType)) && - nsContentUtils::IsJavascriptMIMEType(mimeType) && - NS_SUCCEEDED(mimeParser.GetParameter("version", version))) { - *aShouldLoad = nsIContentPolicy::REJECT_REQUEST; - - LogMessage(NS_MULTILINE_LITERAL_STRING(VERSIONED_JS_BLOCKED_MESSAGE), - aRequestOrigin, typeString, aContext); - return NS_OK; - } - } - - return NS_OK; -} - -NS_IMETHODIMP -AddonContentPolicy::ShouldProcess(uint32_t aContentType, - nsIURI* aContentLocation, - nsIURI* aRequestOrigin, - nsISupports* aRequestingContext, - const nsACString& aMimeTypeGuess, - nsISupports* aExtra, - nsIPrincipal* aRequestPrincipal, - int16_t* aShouldProcess) -{ - MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType), - "We should only see external content policy types here."); - - *aShouldProcess = nsIContentPolicy::ACCEPT; - return NS_OK; -} - - -// CSP Validation: - -static const char* allowedSchemes[] = { - "blob", - "filesystem", - nullptr -}; - -static const char* allowedHostSchemes[] = { - "https", - "moz-extension", - nullptr -}; - -/** - * Validates a CSP directive to ensure that it is sufficiently stringent. - * In particular, ensures that: - * - * - No remote sources are allowed other than from https: schemes - * - * - No remote sources specify host wildcards for generic domains - * (*.blogspot.com, *.com, *) - * - * - All remote sources and local extension sources specify a host - * - * - No scheme sources are allowed other than blob:, filesystem:, - * moz-extension:, and https: - * - * - No keyword sources are allowed other than 'none', 'self', 'unsafe-eval', - * and hash sources. - */ -class CSPValidator final : public nsCSPSrcVisitor { - public: - CSPValidator(nsAString& aURL, CSPDirective aDirective, bool aDirectiveRequired = true) : - mURL(aURL), - mDirective(CSP_CSPDirectiveToString(aDirective)), - mFoundSelf(false) - { - // Start with the default error message for a missing directive, since no - // visitors will be called if the directive isn't present. - if (aDirectiveRequired) { - FormatError("csp.error.missing-directive"); - } - } - - // Visitors - - bool visitSchemeSrc(const nsCSPSchemeSrc& src) override - { - nsAutoString scheme; - src.getScheme(scheme); - - if (SchemeInList(scheme, allowedHostSchemes)) { - FormatError("csp.error.missing-host", scheme); - return false; - } - if (!SchemeInList(scheme, allowedSchemes)) { - FormatError("csp.error.illegal-protocol", scheme); - return false; - } - return true; - }; - - bool visitHostSrc(const nsCSPHostSrc& src) override - { - nsAutoString scheme, host; - - src.getScheme(scheme); - src.getHost(host); - - if (scheme.LowerCaseEqualsLiteral("https")) { - if (!HostIsAllowed(host)) { - FormatError("csp.error.illegal-host-wildcard", scheme); - return false; - } - } else if (scheme.LowerCaseEqualsLiteral("moz-extension")) { - // The CSP parser silently converts 'self' keywords to the origin - // URL, so we need to reconstruct the URL to see if it was present. - if (!mFoundSelf) { - nsAutoString url(u"moz-extension://"); - url.Append(host); - - mFoundSelf = url.Equals(mURL); - } - - if (host.IsEmpty() || host.EqualsLiteral("*")) { - FormatError("csp.error.missing-host", scheme); - return false; - } - } else if (!SchemeInList(scheme, allowedSchemes)) { - FormatError("csp.error.illegal-protocol", scheme); - return false; - } - - return true; - }; - - bool visitKeywordSrc(const nsCSPKeywordSrc& src) override - { - switch (src.getKeyword()) { - case CSP_NONE: - case CSP_SELF: - case CSP_UNSAFE_EVAL: - return true; - - default: - NS_ConvertASCIItoUTF16 keyword(CSP_EnumToKeyword(src.getKeyword())); - - FormatError("csp.error.illegal-keyword", keyword); - return false; - } - }; - - bool visitNonceSrc(const nsCSPNonceSrc& src) override - { - FormatError("csp.error.illegal-keyword", NS_LITERAL_STRING("'nonce-*'")); - return false; - }; - - bool visitHashSrc(const nsCSPHashSrc& src) override - { - return true; - }; - - // Accessors - - inline nsAString& GetError() - { - return mError; - }; - - inline bool FoundSelf() - { - return mFoundSelf; - }; - - - // Formatters - - template - inline void FormatError(const char* aName, const T ...aParams) - { - const char16_t* params[] = { mDirective.get(), aParams.get()... }; - FormatErrorParams(aName, params, MOZ_ARRAY_LENGTH(params)); - }; - - private: - // Validators - - bool HostIsAllowed(nsAString& host) - { - if (host.First() == '*') { - if (host.EqualsLiteral("*") || host[1] != '.') { - return false; - } - - host.Cut(0, 2); - - nsCOMPtr tldService = - do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); - - if (!tldService) { - return false; - } - - NS_ConvertUTF16toUTF8 cHost(host); - nsAutoCString publicSuffix; - - nsresult rv = tldService->GetPublicSuffixFromHost(cHost, publicSuffix); - - return NS_SUCCEEDED(rv) && !cHost.Equals(publicSuffix); - } - - return true; - }; - - bool SchemeInList(nsAString& scheme, const char** schemes) - { - for (; *schemes; schemes++) { - if (scheme.LowerCaseEqualsASCII(*schemes)) { - return true; - } - } - return false; - }; - - - // Formatters - - already_AddRefed - GetStringBundle() - { - nsCOMPtr sbs = - mozilla::services::GetStringBundleService(); - NS_ENSURE_TRUE(sbs, nullptr); - - nsCOMPtr stringBundle; - sbs->CreateBundle("chrome://global/locale/extensions.properties", - getter_AddRefs(stringBundle)); - - return stringBundle.forget(); - }; - - void FormatErrorParams(const char* aName, const char16_t** aParams, int32_t aLength) - { - nsresult rv = NS_ERROR_FAILURE; - - nsCOMPtr stringBundle = GetStringBundle(); - - if (stringBundle) { - NS_ConvertASCIItoUTF16 name(aName); - - rv = stringBundle->FormatStringFromName(name.get(), aParams, aLength, - getter_Copies(mError)); - } - - if (NS_WARN_IF(NS_FAILED(rv))) { - mError.AssignLiteral("An unexpected error occurred"); - } - }; - - - // Data members - - nsAutoString mURL; - NS_ConvertASCIItoUTF16 mDirective; - nsXPIDLString mError; - - bool mFoundSelf; -}; - -/** - * Validates a custom content security policy string for use by an add-on. - * In particular, ensures that: - * - * - Both object-src and script-src directives are present, and meet - * the policies required by the CSPValidator class - * - * - The script-src directive includes the source 'self' - */ -NS_IMETHODIMP -AddonContentPolicy::ValidateAddonCSP(const nsAString& aPolicyString, - nsAString& aResult) -{ - nsresult rv; - - // Validate against a randomly-generated extension origin. - // There is no add-on-specific behavior in the CSP code, beyond the ability - // for add-ons to specify a custom policy, but the parser requires a valid - // origin in order to operate correctly. - nsAutoString url(u"moz-extension://"); - { - nsCOMPtr uuidgen = services::GetUUIDGenerator(); - NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE); - - nsID id; - rv = uuidgen->GenerateUUIDInPlace(&id); - NS_ENSURE_SUCCESS(rv, rv); - - char idString[NSID_LENGTH]; - id.ToProvidedString(idString); - - MOZ_RELEASE_ASSERT(idString[0] == '{' && idString[NSID_LENGTH - 2] == '}', - "UUID generator did not return a valid UUID"); - - url.AppendASCII(idString + 1, NSID_LENGTH - 3); - } - - - RefPtr principal = - BasePrincipal::CreateCodebasePrincipal(NS_ConvertUTF16toUTF8(url)); - - nsCOMPtr csp; - rv = principal->EnsureCSP(nullptr, getter_AddRefs(csp)); - NS_ENSURE_SUCCESS(rv, rv); - - - csp->AppendPolicy(aPolicyString, false, false); - - const nsCSPPolicy* policy = csp->GetPolicy(0); - if (!policy) { - CSPValidator validator(url, nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE); - aResult.Assign(validator.GetError()); - return NS_OK; - } - - bool haveValidDefaultSrc = false; - { - CSPDirective directive = nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; - CSPValidator validator(url, directive); - - haveValidDefaultSrc = policy->visitDirectiveSrcs(directive, &validator); - } - - aResult.SetIsVoid(true); - { - CSPDirective directive = nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE; - CSPValidator validator(url, directive, !haveValidDefaultSrc); - - if (!policy->visitDirectiveSrcs(directive, &validator)) { - aResult.Assign(validator.GetError()); - } else if (!validator.FoundSelf()) { - validator.FormatError("csp.error.missing-source", NS_LITERAL_STRING("'self'")); - aResult.Assign(validator.GetError()); - } - } - - if (aResult.IsVoid()) { - CSPDirective directive = nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE; - CSPValidator validator(url, directive, !haveValidDefaultSrc); - - if (!policy->visitDirectiveSrcs(directive, &validator)) { - aResult.Assign(validator.GetError()); - } - } - - return NS_OK; -} diff --git a/toolkit/mozapps/webextensions/AddonContentPolicy.h b/toolkit/mozapps/webextensions/AddonContentPolicy.h deleted file mode 100644 index 4c8af4828..000000000 --- a/toolkit/mozapps/webextensions/AddonContentPolicy.h +++ /dev/null @@ -1,22 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsIContentPolicy.h" -#include "nsIAddonPolicyService.h" - -class AddonContentPolicy : public nsIContentPolicy, - public nsIAddonContentPolicy -{ -protected: - virtual ~AddonContentPolicy(); - -public: - AddonContentPolicy(); - - NS_DECL_ISUPPORTS - NS_DECL_NSICONTENTPOLICY - NS_DECL_NSIADDONCONTENTPOLICY -}; diff --git a/toolkit/mozapps/webextensions/AddonManager.jsm b/toolkit/mozapps/webextensions/AddonManager.jsm deleted file mode 100644 index a3bcbb504..000000000 --- a/toolkit/mozapps/webextensions/AddonManager.jsm +++ /dev/null @@ -1,3666 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; - -// Cannot use Services.appinfo here, or else xpcshell-tests will blow up, as -// most tests later register different nsIAppInfo implementations, which -// wouldn't be reflected in Services.appinfo anymore, as the lazy getter -// underlying it would have been initialized if we used it here. -if ("@mozilla.org/xre/app-info;1" in Cc) { - let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); - if (runtime.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { - // Refuse to run in child processes. - throw new Error("You cannot use the AddonManager in child processes!"); - } -} - -Cu.import("resource://gre/modules/AppConstants.jsm"); - -const MOZ_COMPATIBILITY_NIGHTLY = !['aurora', 'beta', 'release', 'esr'].includes(AppConstants.MOZ_UPDATE_CHANNEL); - -const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion"; -const PREF_DEFAULT_PROVIDERS_ENABLED = "extensions.defaultProviders.enabled"; -const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled"; -const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion"; -const PREF_EM_LAST_PLATFORM_VERSION = "extensions.lastPlatformVersion"; -const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault"; -const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; -const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; -const PREF_EM_UPDATE_BACKGROUND_URL = "extensions.update.background.url"; -const PREF_APP_UPDATE_ENABLED = "app.update.enabled"; -const PREF_APP_UPDATE_AUTO = "app.update.auto"; -const PREF_EM_HOTFIX_ID = "extensions.hotfix.id"; -const PREF_EM_HOTFIX_LASTVERSION = "extensions.hotfix.lastVersion"; -const PREF_EM_HOTFIX_URL = "extensions.hotfix.url"; -const PREF_EM_CERT_CHECKATTRIBUTES = "extensions.hotfix.cert.checkAttributes"; -const PREF_EM_HOTFIX_CERTS = "extensions.hotfix.certs."; -const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; -const PREF_SELECTED_LOCALE = "general.useragent.locale"; -const UNKNOWN_XPCOM_ABI = "unknownABI"; - -const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion"; -const PREF_WEBAPI_TESTING = "extensions.webapi.testing"; - -const UPDATE_REQUEST_VERSION = 2; -const CATEGORY_UPDATE_PARAMS = "extension-update-params"; - -const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist"; - -const KEY_PROFILEDIR = "ProfD"; -const KEY_APPDIR = "XCurProcD"; -const FILE_BLOCKLIST = "blocklist.xml"; - -const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi; -const PREF_EM_CHECK_COMPATIBILITY = "extensions.enableCompatibilityChecking"; - -const TOOLKIT_ID = "toolkit@mozilla.org"; - -const VALID_TYPES_REGEXP = /^[\w\-]+$/; - -const WEBAPI_INSTALL_HOSTS = ["addons.mozilla.org", "testpilot.firefox.com"]; -const WEBAPI_TEST_INSTALL_HOSTS = [ - "addons.allizom.org", "addons-dev.allizom.org", - "testpilot.stage.mozaws.net", "testpilot.dev.mozaws.net", - "example.com", -]; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/AsyncShutdown.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/Promise.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", - "resource://gre/modules/addons/AddonRepository.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Extension", - "resource://gre/modules/Extension.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); - -XPCOMUtils.defineLazyGetter(this, "CertUtils", function() { - let certUtils = {}; - Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils); - return certUtils; -}); - -const INTEGER = /^[1-9]\d*$/; - -this.EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ]; - -const CATEGORY_PROVIDER_MODULE = "addon-provider-module"; - -// A list of providers to load by default -const DEFAULT_PROVIDERS = [ - "resource://gre/modules/addons/XPIProvider.jsm", - "resource://gre/modules/LightweightThemeManager.jsm" -]; - -Cu.import("resource://gre/modules/Log.jsm"); -// Configure a logger at the parent 'addons' level to format -// messages for all the modules under addons.* -const PARENT_LOGGER_ID = "addons"; -var parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID); -parentLogger.level = Log.Level.Warn; -var formatter = new Log.BasicFormatter(); -// Set parent logger (and its children) to append to -// the Javascript section of the Browser Console -parentLogger.addAppender(new Log.ConsoleAppender(formatter)); -// Set parent logger (and its children) to -// also append to standard out -parentLogger.addAppender(new Log.DumpAppender(formatter)); - -// Create a new logger (child of 'addons' logger) -// for use by the Addons Manager -const LOGGER_ID = "addons.manager"; -var logger = Log.repository.getLogger(LOGGER_ID); - -// Provide the ability to enable/disable logging -// messages at runtime. -// If the "extensions.logging.enabled" preference is -// missing or 'false', messages at the WARNING and higher -// severity should be logged to the JS console and standard error. -// If "extensions.logging.enabled" is set to 'true', messages -// at DEBUG and higher should go to JS console and standard error. -const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; -const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; - -const UNNAMED_PROVIDER = ""; -function providerName(aProvider) { - return aProvider.name || UNNAMED_PROVIDER; -} - -/** - * Preference listener which listens for a change in the - * "extensions.logging.enabled" preference and changes the logging level of the - * parent 'addons' level logger accordingly. - */ -var PrefObserver = { - init: function() { - Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false); - Services.obs.addObserver(this, "xpcom-shutdown", false); - this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED); - }, - - observe: function(aSubject, aTopic, aData) { - if (aTopic == "xpcom-shutdown") { - Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this); - Services.obs.removeObserver(this, "xpcom-shutdown"); - } - else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) { - let debugLogEnabled = false; - try { - debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED); - } - catch (e) { - } - if (debugLogEnabled) { - parentLogger.level = Log.Level.Debug; - } - else { - parentLogger.level = Log.Level.Warn; - } - } - } -}; - -PrefObserver.init(); - -/** - * Calls a callback method consuming any thrown exception. Any parameters after - * the callback parameter will be passed to the callback. - * - * @param aCallback - * The callback method to call - */ -function safeCall(aCallback, ...aArgs) { - try { - aCallback.apply(null, aArgs); - } - catch (e) { - logger.warn("Exception calling callback", e); - } -} - -/** - * Creates a function that will call the passed callback catching and logging - * any exceptions. - * - * @param aCallback - * The callback method to call - */ -function makeSafe(aCallback) { - return function(...aArgs) { - safeCall(aCallback, ...aArgs); - } -} - -/** - * Report an exception thrown by a provider API method. - */ -function reportProviderError(aProvider, aMethod, aError) { - let method = `provider ${providerName(aProvider)}.${aMethod}`; - AddonManagerPrivate.recordException("AMI", method, aError); - logger.error("Exception calling " + method, aError); -} - -/** - * Calls a method on a provider if it exists and consumes any thrown exception. - * Any parameters after the aDefault parameter are passed to the provider's method. - * - * @param aProvider - * The provider to call - * @param aMethod - * The method name to call - * @param aDefault - * A default return value if the provider does not implement the named - * method or throws an error. - * @return the return value from the provider, or aDefault if the provider does not - * implement method or throws an error - */ -function callProvider(aProvider, aMethod, aDefault, ...aArgs) { - if (!(aMethod in aProvider)) - return aDefault; - - try { - return aProvider[aMethod].apply(aProvider, aArgs); - } - catch (e) { - reportProviderError(aProvider, aMethod, e); - return aDefault; - } -} - -/** - * Calls a method on a provider if it exists and consumes any thrown exception. - * Parameters after aMethod are passed to aProvider.aMethod(). - * The last parameter must be a callback function. - * If the provider does not implement the method, or the method throws, calls - * the callback with 'undefined'. - * - * @param aProvider - * The provider to call - * @param aMethod - * The method name to call - */ -function callProviderAsync(aProvider, aMethod, ...aArgs) { - let callback = aArgs[aArgs.length - 1]; - if (!(aMethod in aProvider)) { - callback(undefined); - return undefined; - } - try { - return aProvider[aMethod].apply(aProvider, aArgs); - } - catch (e) { - reportProviderError(aProvider, aMethod, e); - callback(undefined); - return undefined; - } -} - -/** - * Calls a method on a provider if it exists and consumes any thrown exception. - * Parameters after aMethod are passed to aProvider.aMethod() and an additional - * callback is added for the provider to return a result to. - * - * @param aProvider - * The provider to call - * @param aMethod - * The method name to call - * @return {Promise} - * @resolves The result the provider returns, or |undefined| if the provider - * does not implement the method or the method throws. - * @rejects Never - */ -function promiseCallProvider(aProvider, aMethod, ...aArgs) { - return new Promise(resolve => { - callProviderAsync(aProvider, aMethod, ...aArgs, resolve); - }); -} - -/** - * Gets the currently selected locale for display. - * @return the selected locale or "en-US" if none is selected - */ -function getLocale() { - try { - if (Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE)) - return Services.locale.getLocaleComponentForUserAgent(); - } - catch (e) { } - - try { - let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE, - Ci.nsIPrefLocalizedString); - if (locale) - return locale; - } - catch (e) { } - - try { - return Services.prefs.getCharPref(PREF_SELECTED_LOCALE); - } - catch (e) { } - - return "en-US"; -} - -function webAPIForAddon(addon) { - if (!addon) { - return null; - } - - let result = {}; - - // By default just pass through any plain property, the webidl will - // control access. Also filter out private properties, regular Addon - // objects are okay but MockAddon used in tests has non-serializable - // private properties. - for (let prop in addon) { - if (prop[0] != "_" && typeof(addon[prop]) != "function") { - result[prop] = addon[prop]; - } - } - - // A few properties are computed for a nicer API - result.isEnabled = !addon.userDisabled; - result.canUninstall = Boolean(addon.permissions & AddonManager.PERM_CAN_UNINSTALL); - - return result; -} - -/** - * A helper class to repeatedly call a listener with each object in an array - * optionally checking whether the object has a method in it. - * - * @param aObjects - * The array of objects to iterate through - * @param aMethod - * An optional method name, if not null any objects without this method - * will not be passed to the listener - * @param aListener - * A listener implementing nextObject and noMoreObjects methods. The - * former will be called with the AsyncObjectCaller as the first - * parameter and the object as the second. noMoreObjects will be passed - * just the AsyncObjectCaller - */ -function AsyncObjectCaller(aObjects, aMethod, aListener) { - this.objects = [...aObjects]; - this.method = aMethod; - this.listener = aListener; - - this.callNext(); -} - -AsyncObjectCaller.prototype = { - objects: null, - method: null, - listener: null, - - /** - * Passes the next object to the listener or calls noMoreObjects if there - * are none left. - */ - callNext: function() { - if (this.objects.length == 0) { - this.listener.noMoreObjects(this); - return; - } - - let object = this.objects.shift(); - if (!this.method || this.method in object) - this.listener.nextObject(this, object); - else - this.callNext(); - } -}; - -/** - * Listens for a browser changing origin and cancels the installs that were - * started by it. - */ -function BrowserListener(aBrowser, aInstallingPrincipal, aInstalls) { - this.browser = aBrowser; - this.principal = aInstallingPrincipal; - this.installs = aInstalls; - this.installCount = aInstalls.length; - - aBrowser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION); - Services.obs.addObserver(this, "message-manager-close", true); - - for (let install of this.installs) - install.addListener(this); - - this.registered = true; -} - -BrowserListener.prototype = { - browser: null, - installs: null, - installCount: null, - registered: false, - - unregister: function() { - if (!this.registered) - return; - this.registered = false; - - Services.obs.removeObserver(this, "message-manager-close"); - // The browser may have already been detached - if (this.browser.removeProgressListener) - this.browser.removeProgressListener(this); - - for (let install of this.installs) - install.removeListener(this); - this.installs = null; - }, - - cancelInstalls: function() { - for (let install of this.installs) { - try { - install.cancel(); - } - catch (e) { - // Some installs may have already failed or been cancelled, ignore these - } - } - }, - - observe: function(subject, topic, data) { - if (subject != this.browser.messageManager) - return; - - // The browser's message manager has closed and so the browser is - // going away, cancel all installs - this.cancelInstalls(); - }, - - onLocationChange: function(webProgress, request, location) { - if (this.browser.contentPrincipal && this.principal.subsumes(this.browser.contentPrincipal)) - return; - - // The browser has navigated to a new origin so cancel all installs - this.cancelInstalls(); - }, - - onDownloadCancelled: function(install) { - // Don't need to hear more events from this install - install.removeListener(this); - - // Once all installs have ended unregister everything - if (--this.installCount == 0) - this.unregister(); - }, - - onDownloadFailed: function(install) { - this.onDownloadCancelled(install); - }, - - onInstallFailed: function(install) { - this.onDownloadCancelled(install); - }, - - onInstallEnded: function(install) { - this.onDownloadCancelled(install); - }, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, - Ci.nsIWebProgressListener, - Ci.nsIObserver]) -}; - -/** - * This represents an author of an add-on (e.g. creator or developer) - * - * @param aName - * The name of the author - * @param aURL - * The URL of the author's profile page - */ -function AddonAuthor(aName, aURL) { - this.name = aName; - this.url = aURL; -} - -AddonAuthor.prototype = { - name: null, - url: null, - - // Returns the author's name, defaulting to the empty string - toString: function() { - return this.name || ""; - } -} - -/** - * This represents an screenshot for an add-on - * - * @param aURL - * The URL to the full version of the screenshot - * @param aWidth - * The width in pixels of the screenshot - * @param aHeight - * The height in pixels of the screenshot - * @param aThumbnailURL - * The URL to the thumbnail version of the screenshot - * @param aThumbnailWidth - * The width in pixels of the thumbnail version of the screenshot - * @param aThumbnailHeight - * The height in pixels of the thumbnail version of the screenshot - * @param aCaption - * The caption of the screenshot - */ -function AddonScreenshot(aURL, aWidth, aHeight, aThumbnailURL, - aThumbnailWidth, aThumbnailHeight, aCaption) { - this.url = aURL; - if (aWidth) this.width = aWidth; - if (aHeight) this.height = aHeight; - if (aThumbnailURL) this.thumbnailURL = aThumbnailURL; - if (aThumbnailWidth) this.thumbnailWidth = aThumbnailWidth; - if (aThumbnailHeight) this.thumbnailHeight = aThumbnailHeight; - if (aCaption) this.caption = aCaption; -} - -AddonScreenshot.prototype = { - url: null, - width: null, - height: null, - thumbnailURL: null, - thumbnailWidth: null, - thumbnailHeight: null, - caption: null, - - // Returns the screenshot URL, defaulting to the empty string - toString: function() { - return this.url || ""; - } -} - - -/** - * This represents a compatibility override for an addon. - * - * @param aType - * Overrride type - "compatible" or "incompatible" - * @param aMinVersion - * Minimum version of the addon to match - * @param aMaxVersion - * Maximum version of the addon to match - * @param aAppID - * Application ID used to match appMinVersion and appMaxVersion - * @param aAppMinVersion - * Minimum version of the application to match - * @param aAppMaxVersion - * Maximum version of the application to match - */ -function AddonCompatibilityOverride(aType, aMinVersion, aMaxVersion, aAppID, - aAppMinVersion, aAppMaxVersion) { - this.type = aType; - this.minVersion = aMinVersion; - this.maxVersion = aMaxVersion; - this.appID = aAppID; - this.appMinVersion = aAppMinVersion; - this.appMaxVersion = aAppMaxVersion; -} - -AddonCompatibilityOverride.prototype = { - /** - * Type of override - "incompatible" or "compatible". - * Only "incompatible" is supported for now. - */ - type: null, - - /** - * Min version of the addon to match. - */ - minVersion: null, - - /** - * Max version of the addon to match. - */ - maxVersion: null, - - /** - * Application ID to match. - */ - appID: null, - - /** - * Min version of the application to match. - */ - appMinVersion: null, - - /** - * Max version of the application to match. - */ - appMaxVersion: null -}; - - -/** - * A type of add-on, used by the UI to determine how to display different types - * of add-ons. - * - * @param aID - * The add-on type ID - * @param aLocaleURI - * The URI of a localized properties file to get the displayable name - * for the type from - * @param aLocaleKey - * The key for the string in the properties file or the actual display - * name if aLocaleURI is null. Include %ID% to include the type ID in - * the key - * @param aViewType - * The optional type of view to use in the UI - * @param aUIPriority - * The priority is used by the UI to list the types in order. Lower - * values push the type higher in the list. - * @param aFlags - * An option set of flags that customize the display of the add-on in - * the UI. - */ -function AddonType(aID, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags) { - if (!aID) - throw Components.Exception("An AddonType must have an ID", Cr.NS_ERROR_INVALID_ARG); - - if (aViewType && aUIPriority === undefined) - throw Components.Exception("An AddonType with a defined view must have a set UI priority", - Cr.NS_ERROR_INVALID_ARG); - - if (!aLocaleKey) - throw Components.Exception("An AddonType must have a displayable name", - Cr.NS_ERROR_INVALID_ARG); - - this.id = aID; - this.uiPriority = aUIPriority; - this.viewType = aViewType; - this.flags = aFlags; - - if (aLocaleURI) { - XPCOMUtils.defineLazyGetter(this, "name", () => { - let bundle = Services.strings.createBundle(aLocaleURI); - return bundle.GetStringFromName(aLocaleKey.replace("%ID%", aID)); - }); - } - else { - this.name = aLocaleKey; - } -} - -var gStarted = false; -var gStartupComplete = false; -var gCheckCompatibility = true; -var gStrictCompatibility = true; -var gCheckUpdateSecurityDefault = true; -var gCheckUpdateSecurity = gCheckUpdateSecurityDefault; -var gUpdateEnabled = true; -var gAutoUpdateDefault = true; -var gHotfixID = null; -var gWebExtensionsMinPlatformVersion = null; -var gShutdownBarrier = null; -var gRepoShutdownState = ""; -var gShutdownInProgress = false; -var gPluginPageListener = null; - -/** - * This is the real manager, kept here rather than in AddonManager to keep its - * contents hidden from API users. - */ -var AddonManagerInternal = { - managerListeners: [], - installListeners: [], - addonListeners: [], - typeListeners: [], - pendingProviders: new Set(), - providers: new Set(), - providerShutdowns: new Map(), - types: {}, - startupChanges: {}, - // Store telemetry details per addon provider - telemetryDetails: {}, - upgradeListeners: new Map(), - - recordTimestamp: function(name, value) { - this.TelemetryTimestamps.add(name, value); - }, - - validateBlocklist: function() { - let appBlocklist = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); - - // If there is no application shipped blocklist then there is nothing to do - if (!appBlocklist.exists()) - return; - - let profileBlocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]); - - // If there is no blocklist in the profile then copy the application shipped - // one there - if (!profileBlocklist.exists()) { - try { - appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST); - } - catch (e) { - logger.warn("Failed to copy the application shipped blocklist to the profile", e); - } - return; - } - - let fileStream = Cc["@mozilla.org/network/file-input-stream;1"]. - createInstance(Ci.nsIFileInputStream); - try { - let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]. - createInstance(Ci.nsIConverterInputStream); - fileStream.init(appBlocklist, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); - cstream.init(fileStream, "UTF-8", 0, 0); - - let data = ""; - let str = {}; - let read = 0; - do { - read = cstream.readString(0xffffffff, str); - data += str.value; - } while (read != 0); - - let parser = Cc["@mozilla.org/xmlextras/domparser;1"]. - createInstance(Ci.nsIDOMParser); - var doc = parser.parseFromString(data, "text/xml"); - } - catch (e) { - logger.warn("Application shipped blocklist could not be loaded", e); - return; - } - finally { - try { - fileStream.close(); - } - catch (e) { - logger.warn("Unable to close blocklist file stream", e); - } - } - - // If the namespace is incorrect then ignore the application shipped - // blocklist - if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) { - logger.warn("Application shipped blocklist has an unexpected namespace (" + - doc.documentElement.namespaceURI + ")"); - return; - } - - // If there is no lastupdate information then ignore the application shipped - // blocklist - if (!doc.documentElement.hasAttribute("lastupdate")) - return; - - // If the application shipped blocklist is older than the profile blocklist - // then do nothing - if (doc.documentElement.getAttribute("lastupdate") <= - profileBlocklist.lastModifiedTime) - return; - - // Otherwise copy the application shipped blocklist to the profile - try { - appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST); - } - catch (e) { - logger.warn("Failed to copy the application shipped blocklist to the profile", e); - } - }, - - /** - * Start up a provider, and register its shutdown hook if it has one - */ - _startProvider(aProvider, aAppChanged, aOldAppVersion, aOldPlatformVersion) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - logger.debug(`Starting provider: ${providerName(aProvider)}`); - callProvider(aProvider, "startup", null, aAppChanged, aOldAppVersion, aOldPlatformVersion); - if ('shutdown' in aProvider) { - let name = providerName(aProvider); - let AMProviderShutdown = () => { - // If the provider has been unregistered, it will have been removed from - // this.providers. If it hasn't been unregistered, then this is a normal - // shutdown - and we move it to this.pendingProviders incase we're - // running in a test that will start AddonManager again. - if (this.providers.has(aProvider)) { - this.providers.delete(aProvider); - this.pendingProviders.add(aProvider); - } - - return new Promise((resolve, reject) => { - logger.debug("Calling shutdown blocker for " + name); - resolve(aProvider.shutdown()); - }) - .catch(err => { - logger.warn("Failure during shutdown of " + name, err); - AddonManagerPrivate.recordException("AMI", "Async shutdown of " + name, err); - }); - }; - logger.debug("Registering shutdown blocker for " + name); - this.providerShutdowns.set(aProvider, AMProviderShutdown); - AddonManager.shutdown.addBlocker(name, AMProviderShutdown); - } - - this.pendingProviders.delete(aProvider); - this.providers.add(aProvider); - logger.debug(`Provider finished startup: ${providerName(aProvider)}`); - }, - - _getProviderByName(aName) { - for (let provider of this.providers) { - if (providerName(provider) == aName) - return provider; - } - return undefined; - }, - - /** - * Initializes the AddonManager, loading any known providers and initializing - * them. - */ - startup: function() { - try { - if (gStarted) - return; - - this.recordTimestamp("AMI_startup_begin"); - - // clear this for xpcshell test restarts - for (let provider in this.telemetryDetails) - delete this.telemetryDetails[provider]; - - let appChanged = undefined; - - let oldAppVersion = null; - try { - oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION); - appChanged = Services.appinfo.version != oldAppVersion; - } - catch (e) { } - - Extension.browserUpdated = appChanged; - - let oldPlatformVersion = null; - try { - oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION); - } - catch (e) { } - - if (appChanged !== false) { - logger.debug("Application has been upgraded"); - Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION, - Services.appinfo.version); - Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION, - Services.appinfo.platformVersion); - Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, - (appChanged === undefined ? 0 : -1)); - this.validateBlocklist(); - } - - try { - gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY); - } catch (e) {} - Services.prefs.addObserver(PREF_EM_CHECK_COMPATIBILITY, this, false); - - try { - gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY); - } catch (e) {} - Services.prefs.addObserver(PREF_EM_STRICT_COMPATIBILITY, this, false); - - try { - let defaultBranch = Services.prefs.getDefaultBranch(""); - gCheckUpdateSecurityDefault = defaultBranch.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); - } catch (e) {} - - try { - gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); - } catch (e) {} - Services.prefs.addObserver(PREF_EM_CHECK_UPDATE_SECURITY, this, false); - - try { - gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED); - } catch (e) {} - Services.prefs.addObserver(PREF_EM_UPDATE_ENABLED, this, false); - - try { - gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT); - } catch (e) {} - Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this, false); - - try { - gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); - } catch (e) {} - Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this, false); - - try { - gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); - } catch (e) {} - Services.prefs.addObserver(PREF_MIN_WEBEXT_PLATFORM_VERSION, this, false); - - let defaultProvidersEnabled = true; - try { - defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED); - } catch (e) {} - AddonManagerPrivate.recordSimpleMeasure("default_providers", defaultProvidersEnabled); - - // Ensure all default providers have had a chance to register themselves - if (defaultProvidersEnabled) { - for (let url of DEFAULT_PROVIDERS) { - try { - let scope = {}; - Components.utils.import(url, scope); - // Sanity check - make sure the provider exports a symbol that - // has a 'startup' method - let syms = Object.keys(scope); - if ((syms.length < 1) || - (typeof scope[syms[0]].startup != "function")) { - logger.warn("Provider " + url + " has no startup()"); - AddonManagerPrivate.recordException("AMI", "provider " + url, "no startup()"); - } - logger.debug("Loaded provider scope for " + url + ": " + Object.keys(scope).toSource()); - } - catch (e) { - AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e); - logger.error("Exception loading default provider \"" + url + "\"", e); - } - } - } - - // Load any providers registered in the category manager - let catman = Cc["@mozilla.org/categorymanager;1"]. - getService(Ci.nsICategoryManager); - let entries = catman.enumerateCategory(CATEGORY_PROVIDER_MODULE); - while (entries.hasMoreElements()) { - let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data; - let url = catman.getCategoryEntry(CATEGORY_PROVIDER_MODULE, entry); - - try { - Components.utils.import(url, {}); - logger.debug(`Loaded provider scope for ${url}`); - } - catch (e) { - AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e); - logger.error("Exception loading provider " + entry + " from category \"" + - url + "\"", e); - } - } - - // Register our shutdown handler with the AsyncShutdown manager - gShutdownBarrier = new AsyncShutdown.Barrier("AddonManager: Waiting for providers to shut down."); - AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down.", - this.shutdownManager.bind(this), - {fetchState: this.shutdownState.bind(this)}); - - // Once we start calling providers we must allow all normal methods to work. - gStarted = true; - - for (let provider of this.pendingProviders) { - this._startProvider(provider, appChanged, oldAppVersion, oldPlatformVersion); - } - - // If this is a new profile just pretend that there were no changes - if (appChanged === undefined) { - for (let type in this.startupChanges) - delete this.startupChanges[type]; - } - - // Support for remote about:plugins. Note that this module isn't loaded - // at the top because Services.appinfo is defined late in tests. - let { RemotePages } = Cu.import("resource://gre/modules/RemotePageManager.jsm", {}); - - gPluginPageListener = new RemotePages("about:plugins"); - gPluginPageListener.addMessageListener("RequestPlugins", this.requestPlugins); - - gStartupComplete = true; - this.recordTimestamp("AMI_startup_end"); - } - catch (e) { - logger.error("startup failed", e); - AddonManagerPrivate.recordException("AMI", "startup failed", e); - } - - logger.debug("Completed startup sequence"); - this.callManagerListeners("onStartup"); - }, - - /** - * Registers a new AddonProvider. - * - * @param aProvider - * The provider to register - * @param aTypes - * An optional array of add-on types - */ - registerProvider: function(aProvider, aTypes) { - if (!aProvider || typeof aProvider != "object") - throw Components.Exception("aProvider must be specified", - Cr.NS_ERROR_INVALID_ARG); - - if (aTypes && !Array.isArray(aTypes)) - throw Components.Exception("aTypes must be an array or null", - Cr.NS_ERROR_INVALID_ARG); - - this.pendingProviders.add(aProvider); - - if (aTypes) { - for (let type of aTypes) { - if (!(type.id in this.types)) { - if (!VALID_TYPES_REGEXP.test(type.id)) { - logger.warn("Ignoring invalid type " + type.id); - return; - } - - this.types[type.id] = { - type: type, - providers: [aProvider] - }; - - let typeListeners = this.typeListeners.slice(0); - for (let listener of typeListeners) - safeCall(() => listener.onTypeAdded(type)); - } - else { - this.types[type.id].providers.push(aProvider); - } - } - } - - // If we're registering after startup call this provider's startup. - if (gStarted) { - this._startProvider(aProvider); - } - }, - - /** - * Unregisters an AddonProvider. - * - * @param aProvider - * The provider to unregister - * @return Whatever the provider's 'shutdown' method returns (if anything). - * For providers that have async shutdown methods returning Promises, - * the caller should wait for that Promise to resolve. - */ - unregisterProvider: function(aProvider) { - if (!aProvider || typeof aProvider != "object") - throw Components.Exception("aProvider must be specified", - Cr.NS_ERROR_INVALID_ARG); - - this.providers.delete(aProvider); - // The test harness will unregister XPIProvider *after* shutdown, which is - // after the provider will have been moved from providers to - // pendingProviders. - this.pendingProviders.delete(aProvider); - - for (let type in this.types) { - this.types[type].providers = this.types[type].providers.filter(p => p != aProvider); - if (this.types[type].providers.length == 0) { - let oldType = this.types[type].type; - delete this.types[type]; - - let typeListeners = this.typeListeners.slice(0); - for (let listener of typeListeners) - safeCall(() => listener.onTypeRemoved(oldType)); - } - } - - // If we're unregistering after startup but before shutting down, - // remove the blocker for this provider's shutdown and call it. - // If we're already shutting down, just let gShutdownBarrier call it to avoid races. - if (gStarted && !gShutdownInProgress) { - logger.debug("Unregistering shutdown blocker for " + providerName(aProvider)); - let shutter = this.providerShutdowns.get(aProvider); - if (shutter) { - this.providerShutdowns.delete(aProvider); - gShutdownBarrier.client.removeBlocker(shutter); - return shutter(); - } - } - return undefined; - }, - - /** - * Mark a provider as safe to access via AddonManager APIs, before its - * startup has completed. - * - * Normally a provider isn't marked as safe until after its (synchronous) - * startup() method has returned. Until a provider has been marked safe, - * it won't be used by any of the AddonManager APIs. markProviderSafe() - * allows a provider to mark itself as safe during its startup; this can be - * useful if the provider wants to perform tasks that block startup, which - * happen after its required initialization tasks and therefore when the - * provider is in a safe state. - * - * @param aProvider Provider object to mark safe - */ - markProviderSafe: function(aProvider) { - if (!gStarted) { - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - } - - if (!aProvider || typeof aProvider != "object") { - throw Components.Exception("aProvider must be specified", - Cr.NS_ERROR_INVALID_ARG); - } - - if (!this.pendingProviders.has(aProvider)) { - return; - } - - this.pendingProviders.delete(aProvider); - this.providers.add(aProvider); - }, - - /** - * Calls a method on all registered providers if it exists and consumes any - * thrown exception. Return values are ignored. Any parameters after the - * method parameter are passed to the provider's method. - * WARNING: Do not use for asynchronous calls; callProviders() does not - * invoke callbacks if provider methods throw synchronous exceptions. - * - * @param aMethod - * The method name to call - * @see callProvider - */ - callProviders: function(aMethod, ...aArgs) { - if (!aMethod || typeof aMethod != "string") - throw Components.Exception("aMethod must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - let providers = [...this.providers]; - for (let provider of providers) { - try { - if (aMethod in provider) - provider[aMethod].apply(provider, aArgs); - } - catch (e) { - reportProviderError(provider, aMethod, e); - } - } - }, - - /** - * Report the current state of asynchronous shutdown - */ - shutdownState() { - let state = []; - if (gShutdownBarrier) { - state.push({ - name: gShutdownBarrier.client.name, - state: gShutdownBarrier.state - }); - } - state.push({ - name: "AddonRepository: async shutdown", - state: gRepoShutdownState - }); - return state; - }, - - /** - * Shuts down the addon manager and all registered providers, this must clean - * up everything in order for automated tests to fake restarts. - * @return Promise{null} that resolves when all providers and dependent modules - * have finished shutting down - */ - shutdownManager: Task.async(function*() { - logger.debug("shutdown"); - this.callManagerListeners("onShutdown"); - - gRepoShutdownState = "pending"; - gShutdownInProgress = true; - // Clean up listeners - Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this); - Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this); - Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this); - Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this); - Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this); - Services.prefs.removeObserver(PREF_EM_HOTFIX_ID, this); - gPluginPageListener.destroy(); - gPluginPageListener = null; - - let savedError = null; - // Only shut down providers if they've been started. - if (gStarted) { - try { - yield gShutdownBarrier.wait(); - } - catch (err) { - savedError = err; - logger.error("Failure during wait for shutdown barrier", err); - AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonManager providers", err); - } - } - - // Shut down AddonRepository after providers (if any). - try { - gRepoShutdownState = "in progress"; - yield AddonRepository.shutdown(); - gRepoShutdownState = "done"; - } - catch (err) { - savedError = err; - logger.error("Failure during AddonRepository shutdown", err); - AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonRepository", err); - } - - logger.debug("Async provider shutdown done"); - this.managerListeners.splice(0, this.managerListeners.length); - this.installListeners.splice(0, this.installListeners.length); - this.addonListeners.splice(0, this.addonListeners.length); - this.typeListeners.splice(0, this.typeListeners.length); - this.providerShutdowns.clear(); - for (let type in this.startupChanges) - delete this.startupChanges[type]; - gStarted = false; - gStartupComplete = false; - gShutdownBarrier = null; - gShutdownInProgress = false; - if (savedError) { - throw savedError; - } - }), - - requestPlugins: function({ target: port }) { - // Lists all the properties that plugins.html needs - const NEEDED_PROPS = ["name", "pluginLibraries", "pluginFullpath", "version", - "isActive", "blocklistState", "description", - "pluginMimeTypes"]; - function filterProperties(plugin) { - let filtered = {}; - for (let prop of NEEDED_PROPS) { - filtered[prop] = plugin[prop]; - } - return filtered; - } - - AddonManager.getAddonsByTypes(["plugin"], function(aPlugins) { - port.sendAsyncMessage("PluginList", aPlugins.map(filterProperties)); - }); - }, - - /** - * Notified when a preference we're interested in has changed. - * - * @see nsIObserver - */ - observe: function(aSubject, aTopic, aData) { - switch (aData) { - case PREF_EM_CHECK_COMPATIBILITY: { - let oldValue = gCheckCompatibility; - try { - gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY); - } catch (e) { - gCheckCompatibility = true; - } - - this.callManagerListeners("onCompatibilityModeChanged"); - - if (gCheckCompatibility != oldValue) - this.updateAddonAppDisabledStates(); - - break; - } - case PREF_EM_STRICT_COMPATIBILITY: { - let oldValue = gStrictCompatibility; - try { - gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY); - } catch (e) { - gStrictCompatibility = true; - } - - this.callManagerListeners("onCompatibilityModeChanged"); - - if (gStrictCompatibility != oldValue) - this.updateAddonAppDisabledStates(); - - break; - } - case PREF_EM_CHECK_UPDATE_SECURITY: { - let oldValue = gCheckUpdateSecurity; - try { - gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); - } catch (e) { - gCheckUpdateSecurity = true; - } - - this.callManagerListeners("onCheckUpdateSecurityChanged"); - - if (gCheckUpdateSecurity != oldValue) - this.updateAddonAppDisabledStates(); - - break; - } - case PREF_EM_UPDATE_ENABLED: { - let oldValue = gUpdateEnabled; - try { - gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED); - } catch (e) { - gUpdateEnabled = true; - } - - this.callManagerListeners("onUpdateModeChanged"); - break; - } - case PREF_EM_AUTOUPDATE_DEFAULT: { - let oldValue = gAutoUpdateDefault; - try { - gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT); - } catch (e) { - gAutoUpdateDefault = true; - } - - this.callManagerListeners("onUpdateModeChanged"); - break; - } - case PREF_EM_HOTFIX_ID: { - try { - gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); - } catch (e) { - gHotfixID = null; - } - break; - } - case PREF_MIN_WEBEXT_PLATFORM_VERSION: { - gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); - break; - } - } - }, - - /** - * Replaces %...% strings in an addon url (update and updateInfo) with - * appropriate values. - * - * @param aAddon - * The Addon representing the add-on - * @param aUri - * The string representation of the URI to escape - * @param aAppVersion - * The optional application version to use for %APP_VERSION% - * @return The appropriately escaped URI. - */ - escapeAddonURI: function(aAddon, aUri, aAppVersion) - { - if (!aAddon || typeof aAddon != "object") - throw Components.Exception("aAddon must be an Addon object", - Cr.NS_ERROR_INVALID_ARG); - - if (!aUri || typeof aUri != "string") - throw Components.Exception("aUri must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (aAppVersion && typeof aAppVersion != "string") - throw Components.Exception("aAppVersion must be a string or null", - Cr.NS_ERROR_INVALID_ARG); - - var addonStatus = aAddon.userDisabled || aAddon.softDisabled ? "userDisabled" - : "userEnabled"; - - if (!aAddon.isCompatible) - addonStatus += ",incompatible"; - if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) - addonStatus += ",blocklisted"; - if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) - addonStatus += ",softblocked"; - - try { - var xpcomABI = Services.appinfo.XPCOMABI; - } catch (ex) { - xpcomABI = UNKNOWN_XPCOM_ABI; - } - - let uri = aUri.replace(/%ITEM_ID%/g, aAddon.id); - uri = uri.replace(/%ITEM_VERSION%/g, aAddon.version); - uri = uri.replace(/%ITEM_STATUS%/g, addonStatus); - uri = uri.replace(/%APP_ID%/g, Services.appinfo.ID); - uri = uri.replace(/%APP_VERSION%/g, aAppVersion ? aAppVersion : - Services.appinfo.version); - uri = uri.replace(/%REQ_VERSION%/g, UPDATE_REQUEST_VERSION); - uri = uri.replace(/%APP_OS%/g, Services.appinfo.OS); - uri = uri.replace(/%APP_ABI%/g, xpcomABI); - uri = uri.replace(/%APP_LOCALE%/g, getLocale()); - uri = uri.replace(/%CURRENT_APP_VERSION%/g, Services.appinfo.version); - - // Replace custom parameters (names of custom parameters must have at - // least 3 characters to prevent lookups for something like %D0%C8) - var catMan = null; - uri = uri.replace(/%(\w{3,})%/g, function(aMatch, aParam) { - if (!catMan) { - catMan = Cc["@mozilla.org/categorymanager;1"]. - getService(Ci.nsICategoryManager); - } - - try { - var contractID = catMan.getCategoryEntry(CATEGORY_UPDATE_PARAMS, aParam); - var paramHandler = Cc[contractID].getService(Ci.nsIPropertyBag2); - return paramHandler.getPropertyAsAString(aParam); - } - catch (e) { - return aMatch; - } - }); - - // escape() does not properly encode + symbols in any embedded FVF strings. - return uri.replace(/\+/g, "%2B"); - }, - - /** - * Performs a background update check by starting an update for all add-ons - * that can be updated. - * @return Promise{null} Resolves when the background update check is complete - * (the resulting addon installations may still be in progress). - */ - backgroundUpdateCheck: function() { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - let buPromise = Task.spawn(function*() { - let hotfixID = this.hotfixID; - - let appUpdateEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) && - Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO); - let checkHotfix = hotfixID && appUpdateEnabled; - - logger.debug("Background update check beginning"); - - Services.obs.notifyObservers(null, "addons-background-update-start", null); - - if (this.updateEnabled) { - let scope = {}; - Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope); - scope.LightweightThemeManager.updateCurrentTheme(); - - let allAddons = yield new Promise((resolve, reject) => this.getAllAddons(resolve)); - - // Repopulate repository cache first, to ensure compatibility overrides - // are up to date before checking for addon updates. - yield AddonRepository.backgroundUpdateCheck(); - - // Keep track of all the async add-on updates happening in parallel - let updates = []; - - for (let addon of allAddons) { - if (addon.id == hotfixID) { - continue; - } - - // Check all add-ons for updates so that any compatibility updates will - // be applied - updates.push(new Promise((resolve, reject) => { - addon.findUpdates({ - onUpdateAvailable: function(aAddon, aInstall) { - // Start installing updates when the add-on can be updated and - // background updates should be applied. - logger.debug("Found update for add-on ${id}", aAddon); - if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE && - AddonManager.shouldAutoUpdate(aAddon)) { - // XXX we really should resolve when this install is done, - // not when update-available check completes, no? - logger.debug(`Starting upgrade install of ${aAddon.id}`); - aInstall.install(); - } - }, - - onUpdateFinished: aAddon => { logger.debug("onUpdateFinished for ${id}", aAddon); resolve(); } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - })); - } - yield Promise.all(updates); - } - - if (checkHotfix) { - var hotfixVersion = ""; - try { - hotfixVersion = Services.prefs.getCharPref(PREF_EM_HOTFIX_LASTVERSION); - } - catch (e) { } - - let url = null; - if (Services.prefs.getPrefType(PREF_EM_HOTFIX_URL) == Ci.nsIPrefBranch.PREF_STRING) - url = Services.prefs.getCharPref(PREF_EM_HOTFIX_URL); - else - url = Services.prefs.getCharPref(PREF_EM_UPDATE_BACKGROUND_URL); - - // Build the URI from a fake add-on data. - url = AddonManager.escapeAddonURI({ - id: hotfixID, - version: hotfixVersion, - userDisabled: false, - appDisabled: false - }, url); - - Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); - let update = null; - try { - let foundUpdates = yield new Promise((resolve, reject) => { - AddonUpdateChecker.checkForUpdates(hotfixID, null, url, { - onUpdateCheckComplete: resolve, - onUpdateCheckError: reject - }); - }); - update = AddonUpdateChecker.getNewestCompatibleUpdate(foundUpdates); - } catch (e) { - // AUC.checkForUpdates already logged the error - } - - // Check that we have a hotfix update, and it's newer than the one we already - // have installed (if any) - if (update) { - if (Services.vc.compare(hotfixVersion, update.version) < 0) { - logger.debug("Downloading hotfix version " + update.version); - let aInstall = yield new Promise((resolve, reject) => - AddonManager.getInstallForURL(update.updateURL, resolve, - "application/x-xpinstall", update.updateHash, null, - null, update.version)); - - aInstall.addListener({ - onDownloadEnded: function(aInstall) { - if (aInstall.addon.id != hotfixID) { - logger.warn("The downloaded hotfix add-on did not have the " + - "expected ID and so will not be installed."); - aInstall.cancel(); - return; - } - - // If XPIProvider has reported the hotfix as properly signed then - // there is nothing more to do here - if (aInstall.addon.signedState == AddonManager.SIGNEDSTATE_SIGNED) - return; - - try { - if (!Services.prefs.getBoolPref(PREF_EM_CERT_CHECKATTRIBUTES)) - return; - } - catch (e) { - // By default don't do certificate checks. - return; - } - - try { - CertUtils.validateCert(aInstall.certificate, - CertUtils.readCertPrefs(PREF_EM_HOTFIX_CERTS)); - } - catch (e) { - logger.warn("The hotfix add-on was not signed by the expected " + - "certificate and so will not be installed.", e); - aInstall.cancel(); - } - }, - - onInstallEnded: function(aInstall) { - // Remember the last successfully installed version. - Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION, - aInstall.version); - }, - - onInstallCancelled: function(aInstall) { - // Revert to the previous version if the installation was - // cancelled. - Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION, - hotfixVersion); - } - }); - - aInstall.install(); - } - } - } - - if (appUpdateEnabled) { - try { - yield AddonManagerInternal._getProviderByName("XPIProvider").updateSystemAddons(); - } - catch (e) { - logger.warn("Failed to update system addons", e); - } - } - - logger.debug("Background update check complete"); - Services.obs.notifyObservers(null, - "addons-background-update-complete", - null); - }.bind(this)); - // Fork the promise chain so we can log the error and let our caller see it too. - buPromise.then(null, e => logger.warn("Error in background update", e)); - return buPromise; - }, - - /** - * Adds a add-on to the list of detected changes for this startup. If - * addStartupChange is called multiple times for the same add-on in the same - * startup then only the most recent change will be remembered. - * - * @param aType - * The type of change as a string. Providers can define their own - * types of changes or use the existing defined STARTUP_CHANGE_* - * constants - * @param aID - * The ID of the add-on - */ - addStartupChange: function(aType, aID) { - if (!aType || typeof aType != "string") - throw Components.Exception("aType must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (!aID || typeof aID != "string") - throw Components.Exception("aID must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (gStartupComplete) - return; - logger.debug("Registering startup change '" + aType + "' for " + aID); - - // Ensure that an ID is only listed in one type of change - for (let type in this.startupChanges) - this.removeStartupChange(type, aID); - - if (!(aType in this.startupChanges)) - this.startupChanges[aType] = []; - this.startupChanges[aType].push(aID); - }, - - /** - * Removes a startup change for an add-on. - * - * @param aType - * The type of change - * @param aID - * The ID of the add-on - */ - removeStartupChange: function(aType, aID) { - if (!aType || typeof aType != "string") - throw Components.Exception("aType must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (!aID || typeof aID != "string") - throw Components.Exception("aID must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (gStartupComplete) - return; - - if (!(aType in this.startupChanges)) - return; - - this.startupChanges[aType] = this.startupChanges[aType].filter(aItem => aItem != aID); - }, - - /** - * Calls all registered AddonManagerListeners with an event. Any parameters - * after the method parameter are passed to the listener. - * - * @param aMethod - * The method on the listeners to call - */ - callManagerListeners: function(aMethod, ...aArgs) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aMethod || typeof aMethod != "string") - throw Components.Exception("aMethod must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - let managerListeners = this.managerListeners.slice(0); - for (let listener of managerListeners) { - try { - if (aMethod in listener) - listener[aMethod].apply(listener, aArgs); - } - catch (e) { - logger.warn("AddonManagerListener threw exception when calling " + aMethod, e); - } - } - }, - - /** - * Calls all registered InstallListeners with an event. Any parameters after - * the extraListeners parameter are passed to the listener. - * - * @param aMethod - * The method on the listeners to call - * @param aExtraListeners - * An optional array of extra InstallListeners to also call - * @return false if any of the listeners returned false, true otherwise - */ - callInstallListeners: function(aMethod, - aExtraListeners, ...aArgs) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aMethod || typeof aMethod != "string") - throw Components.Exception("aMethod must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (aExtraListeners && !Array.isArray(aExtraListeners)) - throw Components.Exception("aExtraListeners must be an array or null", - Cr.NS_ERROR_INVALID_ARG); - - let result = true; - let listeners; - if (aExtraListeners) - listeners = aExtraListeners.concat(this.installListeners); - else - listeners = this.installListeners.slice(0); - - for (let listener of listeners) { - try { - if (aMethod in listener) { - if (listener[aMethod].apply(listener, aArgs) === false) - result = false; - } - } - catch (e) { - logger.warn("InstallListener threw exception when calling " + aMethod, e); - } - } - return result; - }, - - /** - * Calls all registered AddonListeners with an event. Any parameters after - * the method parameter are passed to the listener. - * - * @param aMethod - * The method on the listeners to call - */ - callAddonListeners: function(aMethod, ...aArgs) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aMethod || typeof aMethod != "string") - throw Components.Exception("aMethod must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - let addonListeners = this.addonListeners.slice(0); - for (let listener of addonListeners) { - try { - if (aMethod in listener) - listener[aMethod].apply(listener, aArgs); - } - catch (e) { - logger.warn("AddonListener threw exception when calling " + aMethod, e); - } - } - }, - - /** - * Notifies all providers that an add-on has been enabled when that type of - * add-on only supports a single add-on being enabled at a time. This allows - * the providers to disable theirs if necessary. - * - * @param aID - * The ID of the enabled add-on - * @param aType - * The type of the enabled add-on - * @param aPendingRestart - * A boolean indicating if the change will only take place the next - * time the application is restarted - */ - notifyAddonChanged: function(aID, aType, aPendingRestart) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (aID && typeof aID != "string") - throw Components.Exception("aID must be a string or null", - Cr.NS_ERROR_INVALID_ARG); - - if (!aType || typeof aType != "string") - throw Components.Exception("aType must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - // Temporary hack until bug 520124 lands. - // We can get here during synchronous startup, at which point it's - // considered unsafe (and therefore disallowed by AddonManager.jsm) to - // access providers that haven't been initialized yet. Since this is when - // XPIProvider is starting up, XPIProvider can't access itself via APIs - // going through AddonManager.jsm. Furthermore, LightweightThemeManager may - // not be initialized until after XPIProvider is, and therefore would also - // be unaccessible during XPIProvider startup. Thankfully, these are the - // only two uses of this API, and we know it's safe to use this API with - // both providers; so we have this hack to allow bypassing the normal - // safetey guard. - // The notifyAddonChanged/addonChanged API will be unneeded and therefore - // removed by bug 520124, so this is a temporary quick'n'dirty hack. - let providers = [...this.providers, ...this.pendingProviders]; - for (let provider of providers) { - callProvider(provider, "addonChanged", null, aID, aType, aPendingRestart); - } - }, - - /** - * Notifies all providers they need to update the appDisabled property for - * their add-ons in response to an application change such as a blocklist - * update. - */ - updateAddonAppDisabledStates: function() { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - this.callProviders("updateAddonAppDisabledStates"); - }, - - /** - * Notifies all providers that the repository has updated its data for - * installed add-ons. - * - * @param aCallback - * Function to call when operation is complete. - */ - updateAddonRepositoryData: function(aCallback) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - new AsyncObjectCaller(this.providers, "updateAddonRepositoryData", { - nextObject: function(aCaller, aProvider) { - callProviderAsync(aProvider, "updateAddonRepositoryData", - aCaller.callNext.bind(aCaller)); - }, - noMoreObjects: function(aCaller) { - safeCall(aCallback); - // only tests should care about this - Services.obs.notifyObservers(null, "TEST:addon-repository-data-updated", null); - } - }); - }, - - /** - * Asynchronously gets an AddonInstall for a URL. - * - * @param aUrl - * The string represenation of the URL the add-on is located at - * @param aCallback - * A callback to pass the AddonInstall to - * @param aMimetype - * The mimetype of the add-on - * @param aHash - * An optional hash of the add-on - * @param aName - * An optional placeholder name while the add-on is being downloaded - * @param aIcons - * Optional placeholder icons while the add-on is being downloaded - * @param aVersion - * An optional placeholder version while the add-on is being downloaded - * @param aLoadGroup - * An optional nsILoadGroup to associate any network requests with - * @throws if the aUrl, aCallback or aMimetype arguments are not specified - */ - getInstallForURL: function(aUrl, aCallback, aMimetype, - aHash, aName, aIcons, - aVersion, aBrowser) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aUrl || typeof aUrl != "string") - throw Components.Exception("aURL must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - if (!aMimetype || typeof aMimetype != "string") - throw Components.Exception("aMimetype must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (aHash && typeof aHash != "string") - throw Components.Exception("aHash must be a string or null", - Cr.NS_ERROR_INVALID_ARG); - - if (aName && typeof aName != "string") - throw Components.Exception("aName must be a string or null", - Cr.NS_ERROR_INVALID_ARG); - - if (aIcons) { - if (typeof aIcons == "string") - aIcons = { "32": aIcons }; - else if (typeof aIcons != "object") - throw Components.Exception("aIcons must be a string, an object or null", - Cr.NS_ERROR_INVALID_ARG); - } else { - aIcons = {}; - } - - if (aVersion && typeof aVersion != "string") - throw Components.Exception("aVersion must be a string or null", - Cr.NS_ERROR_INVALID_ARG); - - if (aBrowser && (!(aBrowser instanceof Ci.nsIDOMElement))) - throw Components.Exception("aBrowser must be a nsIDOMElement or null", - Cr.NS_ERROR_INVALID_ARG); - - let providers = [...this.providers]; - for (let provider of providers) { - if (callProvider(provider, "supportsMimetype", false, aMimetype)) { - callProviderAsync(provider, "getInstallForURL", - aUrl, aHash, aName, aIcons, aVersion, aBrowser, - function getInstallForURL_safeCall(aInstall) { - safeCall(aCallback, aInstall); - }); - return; - } - } - safeCall(aCallback, null); - }, - - /** - * Asynchronously gets an AddonInstall for an nsIFile. - * - * @param aFile - * The nsIFile where the add-on is located - * @param aCallback - * A callback to pass the AddonInstall to - * @param aMimetype - * An optional mimetype hint for the add-on - * @throws if the aFile or aCallback arguments are not specified - */ - getInstallForFile: function(aFile, aCallback, aMimetype) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!(aFile instanceof Ci.nsIFile)) - throw Components.Exception("aFile must be a nsIFile", - Cr.NS_ERROR_INVALID_ARG); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - if (aMimetype && typeof aMimetype != "string") - throw Components.Exception("aMimetype must be a string or null", - Cr.NS_ERROR_INVALID_ARG); - - new AsyncObjectCaller(this.providers, "getInstallForFile", { - nextObject: function(aCaller, aProvider) { - callProviderAsync(aProvider, "getInstallForFile", aFile, - function(aInstall) { - if (aInstall) - safeCall(aCallback, aInstall); - else - aCaller.callNext(); - }); - }, - - noMoreObjects: function(aCaller) { - safeCall(aCallback, null); - } - }); - }, - - /** - * Asynchronously gets all current AddonInstalls optionally limiting to a list - * of types. - * - * @param aTypes - * An optional array of types to retrieve. Each type is a string name - * @param aCallback - * A callback which will be passed an array of AddonInstalls - * @throws If the aCallback argument is not specified - */ - getInstallsByTypes: function(aTypes, aCallback) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (aTypes && !Array.isArray(aTypes)) - throw Components.Exception("aTypes must be an array or null", - Cr.NS_ERROR_INVALID_ARG); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - let installs = []; - - new AsyncObjectCaller(this.providers, "getInstallsByTypes", { - nextObject: function(aCaller, aProvider) { - callProviderAsync(aProvider, "getInstallsByTypes", aTypes, - function(aProviderInstalls) { - if (aProviderInstalls) { - installs = installs.concat(aProviderInstalls); - } - aCaller.callNext(); - }); - }, - - noMoreObjects: function(aCaller) { - safeCall(aCallback, installs); - } - }); - }, - - /** - * Asynchronously gets all current AddonInstalls. - * - * @param aCallback - * A callback which will be passed an array of AddonInstalls - */ - getAllInstalls: function(aCallback) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - this.getInstallsByTypes(null, aCallback); - }, - - /** - * Synchronously map a URI to the corresponding Addon ID. - * - * Mappable URIs are limited to in-application resources belonging to the - * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc. - * but do not include URIs from meta data, such as the add-on homepage. - * - * @param aURI - * nsIURI to map to an addon id - * @return string containing the Addon ID or null - * @see amIAddonManager.mapURIToAddonID - */ - mapURIToAddonID: function(aURI) { - if (!(aURI instanceof Ci.nsIURI)) { - throw Components.Exception("aURI is not a nsIURI", - Cr.NS_ERROR_INVALID_ARG); - } - - // Try all providers - let providers = [...this.providers]; - for (let provider of providers) { - var id = callProvider(provider, "mapURIToAddonID", null, aURI); - if (id !== null) { - return id; - } - } - - return null; - }, - - /** - * Checks whether installation is enabled for a particular mimetype. - * - * @param aMimetype - * The mimetype to check - * @return true if installation is enabled for the mimetype - */ - isInstallEnabled: function(aMimetype) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aMimetype || typeof aMimetype != "string") - throw Components.Exception("aMimetype must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - let providers = [...this.providers]; - for (let provider of providers) { - if (callProvider(provider, "supportsMimetype", false, aMimetype) && - callProvider(provider, "isInstallEnabled")) - return true; - } - return false; - }, - - /** - * Checks whether a particular source is allowed to install add-ons of a - * given mimetype. - * - * @param aMimetype - * The mimetype of the add-on - * @param aInstallingPrincipal - * The nsIPrincipal that initiated the install - * @return true if the source is allowed to install this mimetype - */ - isInstallAllowed: function(aMimetype, aInstallingPrincipal) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aMimetype || typeof aMimetype != "string") - throw Components.Exception("aMimetype must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal)) - throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal", - Cr.NS_ERROR_INVALID_ARG); - - let providers = [...this.providers]; - for (let provider of providers) { - if (callProvider(provider, "supportsMimetype", false, aMimetype) && - callProvider(provider, "isInstallAllowed", null, aInstallingPrincipal)) - return true; - } - return false; - }, - - /** - * Starts installation of an array of AddonInstalls notifying the registered - * web install listener of blocked or started installs. - * - * @param aMimetype - * The mimetype of add-ons being installed - * @param aBrowser - * The optional browser element that started the installs - * @param aInstallingPrincipal - * The nsIPrincipal that initiated the install - * @param aInstalls - * The array of AddonInstalls to be installed - */ - installAddonsFromWebpage: function(aMimetype, aBrowser, - aInstallingPrincipal, aInstalls) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aMimetype || typeof aMimetype != "string") - throw Components.Exception("aMimetype must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (aBrowser && !(aBrowser instanceof Ci.nsIDOMElement)) - throw Components.Exception("aSource must be a nsIDOMElement, or null", - Cr.NS_ERROR_INVALID_ARG); - - if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal)) - throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal", - Cr.NS_ERROR_INVALID_ARG); - - if (!Array.isArray(aInstalls)) - throw Components.Exception("aInstalls must be an array", - Cr.NS_ERROR_INVALID_ARG); - - if (!("@mozilla.org/addons/web-install-listener;1" in Cc)) { - logger.warn("No web installer available, cancelling all installs"); - for (let install of aInstalls) - install.cancel(); - return; - } - - // When a chrome in-content UI has loaded a inside to host a - // website we want to do our security checks on the inner-browser but - // notify front-end that install events came from the outer-browser (the - // main tab's browser). Check this by seeing if the browser we've been - // passed is in a content type docshell and if so get the outer-browser. - let topBrowser = aBrowser; - let docShell = aBrowser.ownerDocument.defaultView - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell) - .QueryInterface(Ci.nsIDocShellTreeItem); - if (docShell.itemType == Ci.nsIDocShellTreeItem.typeContent) - topBrowser = docShell.chromeEventHandler; - - try { - let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"]. - getService(Ci.amIWebInstallListener); - - if (!this.isInstallEnabled(aMimetype)) { - for (let install of aInstalls) - install.cancel(); - - weblistener.onWebInstallDisabled(topBrowser, aInstallingPrincipal.URI, - aInstalls, aInstalls.length); - return; - } - else if (!aBrowser.contentPrincipal || !aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)) { - for (let install of aInstalls) - install.cancel(); - - if (weblistener instanceof Ci.amIWebInstallListener2) { - weblistener.onWebInstallOriginBlocked(topBrowser, aInstallingPrincipal.URI, - aInstalls, aInstalls.length); - } - return; - } - - // The installs may start now depending on the web install listener, - // listen for the browser navigating to a new origin and cancel the - // installs in that case. - new BrowserListener(aBrowser, aInstallingPrincipal, aInstalls); - - if (!this.isInstallAllowed(aMimetype, aInstallingPrincipal)) { - if (weblistener.onWebInstallBlocked(topBrowser, aInstallingPrincipal.URI, - aInstalls, aInstalls.length)) { - for (let install of aInstalls) - install.install(); - } - } - else if (weblistener.onWebInstallRequested(topBrowser, aInstallingPrincipal.URI, - aInstalls, aInstalls.length)) { - for (let install of aInstalls) - install.install(); - } - } - catch (e) { - // In the event that the weblistener throws during instantiation or when - // calling onWebInstallBlocked or onWebInstallRequested all of the - // installs should get cancelled. - logger.warn("Failure calling web installer", e); - for (let install of aInstalls) - install.cancel(); - } - }, - - /** - * Adds a new InstallListener if the listener is not already registered. - * - * @param aListener - * The InstallListener to add - */ - addInstallListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be a InstallListener object", - Cr.NS_ERROR_INVALID_ARG); - - if (!this.installListeners.some(function(i) { - return i == aListener; })) - this.installListeners.push(aListener); - }, - - /** - * Removes an InstallListener if the listener is registered. - * - * @param aListener - * The InstallListener to remove - */ - removeInstallListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be a InstallListener object", - Cr.NS_ERROR_INVALID_ARG); - - let pos = 0; - while (pos < this.installListeners.length) { - if (this.installListeners[pos] == aListener) - this.installListeners.splice(pos, 1); - else - pos++; - } - }, - /* - * Adds new or overrides existing UpgradeListener. - * - * @param aInstanceID - * The instance ID of an addon to register a listener for. - * @param aCallback - * The callback to invoke when updates are available for this addon. - * @throws if there is no addon matching the instanceID - */ - addUpgradeListener: function(aInstanceID, aCallback) { - if (!aInstanceID || typeof aInstanceID != "symbol") - throw Components.Exception("aInstanceID must be a symbol", - Cr.NS_ERROR_INVALID_ARG); - - if (!aCallback || typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - this.getAddonByInstanceID(aInstanceID).then(wrapper => { - if (!wrapper) { - throw Error("No addon matching instanceID:", aInstanceID.toString()); - } - let addonId = wrapper.addonId(); - logger.debug(`Registering upgrade listener for ${addonId}`); - this.upgradeListeners.set(addonId, aCallback); - }); - }, - - /** - * Removes an UpgradeListener if the listener is registered. - * - * @param aInstanceID - * The instance ID of the addon to remove - */ - removeUpgradeListener: function(aInstanceID) { - if (!aInstanceID || typeof aInstanceID != "symbol") - throw Components.Exception("aInstanceID must be a symbol", - Cr.NS_ERROR_INVALID_ARG); - - this.getAddonByInstanceID(aInstanceID).then(addon => { - if (!addon) { - throw Error("No addon for instanceID:", aInstanceID.toString()); - } - if (this.upgradeListeners.has(addon.id)) { - this.upgradeListeners.delete(addon.id); - } else { - throw Error("No upgrade listener registered for addon ID:", addon.id); - } - }); - }, - - /** - * Installs a temporary add-on from a local file or directory. - * @param aFile - * An nsIFile for the file or directory of the add-on to be - * temporarily installed. - * @return a Promise that rejects if the add-on is not a valid restartless - * add-on or if the same ID is already temporarily installed. - */ - installTemporaryAddon: function(aFile) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!(aFile instanceof Ci.nsIFile)) - throw Components.Exception("aFile must be a nsIFile", - Cr.NS_ERROR_INVALID_ARG); - - return AddonManagerInternal._getProviderByName("XPIProvider") - .installTemporaryAddon(aFile); - }, - - installAddonFromSources: function(aFile) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!(aFile instanceof Ci.nsIFile)) - throw Components.Exception("aFile must be a nsIFile", - Cr.NS_ERROR_INVALID_ARG); - - return AddonManagerInternal._getProviderByName("XPIProvider") - .installAddonFromSources(aFile); - }, - - /** - * Returns an Addon corresponding to an instance ID. - * @param aInstanceID - * An Addon Instance ID symbol - * @return {Promise} - * @resolves The found Addon or null if no such add-on exists. - * @rejects Never - * @throws if the aInstanceID argument is not specified - * or the AddonManager is not initialized - */ - getAddonByInstanceID: function(aInstanceID) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aInstanceID || typeof aInstanceID != "symbol") - throw Components.Exception("aInstanceID must be a Symbol()", - Cr.NS_ERROR_INVALID_ARG); - - return AddonManagerInternal._getProviderByName("XPIProvider") - .getAddonByInstanceID(aInstanceID); - }, - - /** - * Gets an icon from the icon set provided by the add-on - * that is closest to the specified size. - * - * The optional window parameter will be used to determine - * the screen resolution and select a more appropriate icon. - * Calling this method with 48px on retina screens will try to - * match an icon of size 96px. - * - * @param aAddon - * An addon object, meaning: - * An object with either an icons property that is a key-value - * list of icon size and icon URL, or an object having an iconURL - * and icon64URL property. - * @param aSize - * Ideal icon size in pixels - * @param aWindow - * Optional window object for determining the correct scale. - * @return {String} The absolute URL of the icon or null if the addon doesn't have icons - */ - getPreferredIconURL: function(aAddon, aSize, aWindow = undefined) { - if (aWindow && aWindow.devicePixelRatio) { - aSize *= aWindow.devicePixelRatio; - } - - let icons = aAddon.icons; - - // certain addon-types only have iconURLs - if (!icons) { - icons = {}; - if (aAddon.iconURL) { - icons[32] = aAddon.iconURL; - icons[48] = aAddon.iconURL; - } - if (aAddon.icon64URL) { - icons[64] = aAddon.icon64URL; - } - } - - // quick return if the exact size was found - if (icons[aSize]) { - return icons[aSize]; - } - - let bestSize = null; - - for (let size of Object.keys(icons)) { - if (!INTEGER.test(size)) { - throw Components.Exception("Invalid icon size, must be an integer", - Cr.NS_ERROR_ILLEGAL_VALUE); - } - - size = parseInt(size, 10); - - if (!bestSize) { - bestSize = size; - continue; - } - - if (size > aSize && bestSize > aSize) { - // If both best size and current size are larger than the wanted size then choose - // the one closest to the wanted size - bestSize = Math.min(bestSize, size); - } - else { - // Otherwise choose the largest of the two so we'll prefer sizes as close to below aSize - // or above aSize - bestSize = Math.max(bestSize, size); - } - } - - return icons[bestSize] || null; - }, - - /** - * Asynchronously gets an add-on with a specific ID. - * - * @param aID - * The ID of the add-on to retrieve - * @return {Promise} - * @resolves The found Addon or null if no such add-on exists. - * @rejects Never - * @throws if the aID argument is not specified - */ - getAddonByID: function(aID) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aID || typeof aID != "string") - throw Components.Exception("aID must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - let promises = Array.from(this.providers, - p => promiseCallProvider(p, "getAddonByID", aID)); - return Promise.all(promises).then(aAddons => { - return aAddons.find(a => !!a) || null; - }); - }, - - /** - * Asynchronously get an add-on with a specific Sync GUID. - * - * @param aGUID - * String GUID of add-on to retrieve - * @param aCallback - * The callback to pass the retrieved add-on to. - * @throws if the aGUID or aCallback arguments are not specified - */ - getAddonBySyncGUID: function(aGUID, aCallback) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aGUID || typeof aGUID != "string") - throw Components.Exception("aGUID must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - new AsyncObjectCaller(this.providers, "getAddonBySyncGUID", { - nextObject: function(aCaller, aProvider) { - callProviderAsync(aProvider, "getAddonBySyncGUID", aGUID, - function(aAddon) { - if (aAddon) { - safeCall(aCallback, aAddon); - } else { - aCaller.callNext(); - } - }); - }, - - noMoreObjects: function(aCaller) { - safeCall(aCallback, null); - } - }); - }, - - /** - * Asynchronously gets an array of add-ons. - * - * @param aIDs - * The array of IDs to retrieve - * @return {Promise} - * @resolves The array of found add-ons. - * @rejects Never - * @throws if the aIDs argument is not specified - */ - getAddonsByIDs: function(aIDs) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!Array.isArray(aIDs)) - throw Components.Exception("aIDs must be an array", - Cr.NS_ERROR_INVALID_ARG); - - let promises = aIDs.map(a => AddonManagerInternal.getAddonByID(a)); - return Promise.all(promises); - }, - - /** - * Asynchronously gets add-ons of specific types. - * - * @param aTypes - * An optional array of types to retrieve. Each type is a string name - * @param aCallback - * The callback to pass an array of Addons to. - * @throws if the aCallback argument is not specified - */ - getAddonsByTypes: function(aTypes, aCallback) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (aTypes && !Array.isArray(aTypes)) - throw Components.Exception("aTypes must be an array or null", - Cr.NS_ERROR_INVALID_ARG); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - let addons = []; - - new AsyncObjectCaller(this.providers, "getAddonsByTypes", { - nextObject: function(aCaller, aProvider) { - callProviderAsync(aProvider, "getAddonsByTypes", aTypes, - function(aProviderAddons) { - if (aProviderAddons) { - addons = addons.concat(aProviderAddons); - } - aCaller.callNext(); - }); - }, - - noMoreObjects: function(aCaller) { - safeCall(aCallback, addons); - } - }); - }, - - /** - * Asynchronously gets all installed add-ons. - * - * @param aCallback - * A callback which will be passed an array of Addons - */ - getAllAddons: function(aCallback) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - this.getAddonsByTypes(null, aCallback); - }, - - /** - * Asynchronously gets add-ons that have operations waiting for an application - * restart to complete. - * - * @param aTypes - * An optional array of types to retrieve. Each type is a string name - * @param aCallback - * The callback to pass the array of Addons to - * @throws if the aCallback argument is not specified - */ - getAddonsWithOperationsByTypes: function(aTypes, aCallback) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (aTypes && !Array.isArray(aTypes)) - throw Components.Exception("aTypes must be an array or null", - Cr.NS_ERROR_INVALID_ARG); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - let addons = []; - - new AsyncObjectCaller(this.providers, "getAddonsWithOperationsByTypes", { - nextObject: function getAddonsWithOperationsByTypes_nextObject - (aCaller, aProvider) { - callProviderAsync(aProvider, "getAddonsWithOperationsByTypes", aTypes, - function getAddonsWithOperationsByTypes_concatAddons - (aProviderAddons) { - if (aProviderAddons) { - addons = addons.concat(aProviderAddons); - } - aCaller.callNext(); - }); - }, - - noMoreObjects: function(caller) { - safeCall(aCallback, addons); - } - }); - }, - - /** - * Adds a new AddonManagerListener if the listener is not already registered. - * - * @param aListener - * The listener to add - */ - addManagerListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be an AddonManagerListener object", - Cr.NS_ERROR_INVALID_ARG); - - if (!this.managerListeners.some(i => i == aListener)) - this.managerListeners.push(aListener); - }, - - /** - * Removes an AddonManagerListener if the listener is registered. - * - * @param aListener - * The listener to remove - */ - removeManagerListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be an AddonManagerListener object", - Cr.NS_ERROR_INVALID_ARG); - - let pos = 0; - while (pos < this.managerListeners.length) { - if (this.managerListeners[pos] == aListener) - this.managerListeners.splice(pos, 1); - else - pos++; - } - }, - - /** - * Adds a new AddonListener if the listener is not already registered. - * - * @param aListener - * The AddonListener to add - */ - addAddonListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be an AddonListener object", - Cr.NS_ERROR_INVALID_ARG); - - if (!this.addonListeners.some(i => i == aListener)) - this.addonListeners.push(aListener); - }, - - /** - * Removes an AddonListener if the listener is registered. - * - * @param aListener - * The AddonListener to remove - */ - removeAddonListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be an AddonListener object", - Cr.NS_ERROR_INVALID_ARG); - - let pos = 0; - while (pos < this.addonListeners.length) { - if (this.addonListeners[pos] == aListener) - this.addonListeners.splice(pos, 1); - else - pos++; - } - }, - - /** - * Adds a new TypeListener if the listener is not already registered. - * - * @param aListener - * The TypeListener to add - */ - addTypeListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be a TypeListener object", - Cr.NS_ERROR_INVALID_ARG); - - if (!this.typeListeners.some(i => i == aListener)) - this.typeListeners.push(aListener); - }, - - /** - * Removes an TypeListener if the listener is registered. - * - * @param aListener - * The TypeListener to remove - */ - removeTypeListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be a TypeListener object", - Cr.NS_ERROR_INVALID_ARG); - - let pos = 0; - while (pos < this.typeListeners.length) { - if (this.typeListeners[pos] == aListener) - this.typeListeners.splice(pos, 1); - else - pos++; - } - }, - - get addonTypes() { - // A read-only wrapper around the types dictionary - return new Proxy(this.types, { - defineProperty(target, property, descriptor) { - // Not allowed to define properties - return false; - }, - - deleteProperty(target, property) { - // Not allowed to delete properties - return false; - }, - - get(target, property, receiver) { - if (!target.hasOwnProperty(property)) - return undefined; - - return target[property].type; - }, - - getOwnPropertyDescriptor(target, property) { - if (!target.hasOwnProperty(property)) - return undefined; - - return { - value: target[property].type, - writable: false, - // Claim configurability to maintain the proxy invariants. - configurable: true, - enumerable: true - } - }, - - preventExtensions(target) { - // Not allowed to prevent adding new properties - return false; - }, - - set(target, property, value, receiver) { - // Not allowed to set properties - return false; - }, - - setPrototypeOf(target, prototype) { - // Not allowed to change prototype - return false; - } - }); - }, - - get autoUpdateDefault() { - return gAutoUpdateDefault; - }, - - set autoUpdateDefault(aValue) { - aValue = !!aValue; - if (aValue != gAutoUpdateDefault) - Services.prefs.setBoolPref(PREF_EM_AUTOUPDATE_DEFAULT, aValue); - return aValue; - }, - - get checkCompatibility() { - return gCheckCompatibility; - }, - - set checkCompatibility(aValue) { - aValue = !!aValue; - if (aValue != gCheckCompatibility) { - if (!aValue) - Services.prefs.setBoolPref(PREF_EM_CHECK_COMPATIBILITY, false); - else - Services.prefs.clearUserPref(PREF_EM_CHECK_COMPATIBILITY); - } - return aValue; - }, - - get strictCompatibility() { - return gStrictCompatibility; - }, - - set strictCompatibility(aValue) { - aValue = !!aValue; - if (aValue != gStrictCompatibility) - Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, aValue); - return aValue; - }, - - get checkUpdateSecurityDefault() { - return gCheckUpdateSecurityDefault; - }, - - get checkUpdateSecurity() { - return gCheckUpdateSecurity; - }, - - set checkUpdateSecurity(aValue) { - aValue = !!aValue; - if (aValue != gCheckUpdateSecurity) { - if (aValue != gCheckUpdateSecurityDefault) - Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, aValue); - else - Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY); - } - return aValue; - }, - - get updateEnabled() { - return gUpdateEnabled; - }, - - set updateEnabled(aValue) { - aValue = !!aValue; - if (aValue != gUpdateEnabled) - Services.prefs.setBoolPref(PREF_EM_UPDATE_ENABLED, aValue); - return aValue; - }, - - get hotfixID() { - return gHotfixID; - }, - - webAPI: { - // installs maps integer ids to AddonInstall instances. - installs: new Map(), - nextInstall: 0, - - sendEvent: null, - setEventHandler(fn) { - this.sendEvent = fn; - }, - - getAddonByID(target, id) { - return new Promise(resolve => { - AddonManager.getAddonByID(id, (addon) => { - resolve(webAPIForAddon(addon)); - }); - }); - }, - - // helper to copy (and convert) the properties we care about - copyProps(install, obj) { - obj.state = AddonManager.stateToString(install.state); - obj.error = AddonManager.errorToString(install.error); - obj.progress = install.progress; - obj.maxProgress = install.maxProgress; - }, - - makeListener(id, mm) { - const events = [ - "onDownloadStarted", - "onDownloadProgress", - "onDownloadEnded", - "onDownloadCancelled", - "onDownloadFailed", - "onInstallStarted", - "onInstallEnded", - "onInstallCancelled", - "onInstallFailed", - ]; - - let listener = {}; - events.forEach(event => { - listener[event] = (install) => { - let data = {event, id}; - AddonManager.webAPI.copyProps(install, data); - this.sendEvent(mm, data); - } - }); - return listener; - }, - - forgetInstall(id) { - let info = this.installs.get(id); - if (!info) { - throw new Error(`forgetInstall cannot find ${id}`); - } - info.install.removeListener(info.listener); - this.installs.delete(id); - }, - - createInstall(target, options) { - // Throw an appropriate error if the given URL is not valid - // as an installation source. Return silently if it is okay. - function checkInstallUrl(url) { - let host = Services.io.newURI(options.url, null, null).host; - if (WEBAPI_INSTALL_HOSTS.includes(host)) { - return; - } - if (Services.prefs.getBoolPref(PREF_WEBAPI_TESTING) - && WEBAPI_TEST_INSTALL_HOSTS.includes(host)) { - return; - } - - throw new Error(`Install from ${host} not permitted`); - } - - return new Promise((resolve, reject) => { - try { - checkInstallUrl(options.url); - } catch (err) { - reject({message: err.message}); - return; - } - - let newInstall = install => { - let id = this.nextInstall++; - let listener = this.makeListener(id, target.messageManager); - install.addListener(listener); - - this.installs.set(id, {install, target, listener}); - - let result = {id}; - this.copyProps(install, result); - resolve(result); - }; - AddonManager.getInstallForURL(options.url, newInstall, "application/x-xpinstall", options.hash); - }); - }, - - addonUninstall(target, id) { - return new Promise(resolve => { - AddonManager.getAddonByID(id, addon => { - if (!addon) { - resolve(false); - } - - try { - addon.uninstall(); - resolve(true); - } catch (err) { - Cu.reportError(err); - resolve(false); - } - }); - }); - }, - - addonSetEnabled(target, id, value) { - return new Promise((resolve, reject) => { - AddonManager.getAddonByID(id, addon => { - if (!addon) { - reject({message: `No such addon ${id}`}); - } - addon.userDisabled = !value; - resolve(); - }); - }); - }, - - addonInstallDoInstall(target, id) { - let state = this.installs.get(id); - if (!state) { - return Promise.reject(`invalid id ${id}`); - } - return Promise.resolve(state.install.install()); - }, - - addonInstallCancel(target, id) { - let state = this.installs.get(id); - if (!state) { - return Promise.reject(`invalid id ${id}`); - } - return Promise.resolve(state.install.cancel()); - }, - - clearInstalls(ids) { - for (let id of ids) { - this.forgetInstall(id); - } - }, - - clearInstallsFrom(mm) { - for (let [id, info] of this.installs) { - if (info.target.messageManager == mm) { - this.forgetInstall(id); - } - } - }, - }, -}; - -/** - * Should not be used outside of core Mozilla code. This is a private API for - * the startup and platform integration code to use. Refer to the methods on - * AddonManagerInternal for documentation however note that these methods are - * subject to change at any time. - */ -this.AddonManagerPrivate = { - startup: function() { - AddonManagerInternal.startup(); - }, - - registerProvider: function(aProvider, aTypes) { - AddonManagerInternal.registerProvider(aProvider, aTypes); - }, - - unregisterProvider: function(aProvider) { - AddonManagerInternal.unregisterProvider(aProvider); - }, - - markProviderSafe: function(aProvider) { - AddonManagerInternal.markProviderSafe(aProvider); - }, - - backgroundUpdateCheck: function() { - return AddonManagerInternal.backgroundUpdateCheck(); - }, - - backgroundUpdateTimerHandler() { - // Don't call through to the real update check if no checks are enabled. - let checkHotfix = AddonManagerInternal.hotfixID && - Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) && - Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO); - - if (!AddonManagerInternal.updateEnabled && !checkHotfix) { - logger.info("Skipping background update check"); - return; - } - // Don't return the promise here, since the caller doesn't care. - AddonManagerInternal.backgroundUpdateCheck(); - }, - - addStartupChange: function(aType, aID) { - AddonManagerInternal.addStartupChange(aType, aID); - }, - - removeStartupChange: function(aType, aID) { - AddonManagerInternal.removeStartupChange(aType, aID); - }, - - notifyAddonChanged: function(aID, aType, aPendingRestart) { - AddonManagerInternal.notifyAddonChanged(aID, aType, aPendingRestart); - }, - - updateAddonAppDisabledStates: function() { - AddonManagerInternal.updateAddonAppDisabledStates(); - }, - - updateAddonRepositoryData: function(aCallback) { - AddonManagerInternal.updateAddonRepositoryData(aCallback); - }, - - callInstallListeners: function(...aArgs) { - return AddonManagerInternal.callInstallListeners.apply(AddonManagerInternal, - aArgs); - }, - - callAddonListeners: function(...aArgs) { - AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, aArgs); - }, - - AddonAuthor: AddonAuthor, - - AddonScreenshot: AddonScreenshot, - - AddonCompatibilityOverride: AddonCompatibilityOverride, - - AddonType: AddonType, - - recordTimestamp: function(name, value) { - AddonManagerInternal.recordTimestamp(name, value); - }, - - _simpleMeasures: {}, - recordSimpleMeasure: function(name, value) { - this._simpleMeasures[name] = value; - }, - - recordException: function(aModule, aContext, aException) { - let report = { - module: aModule, - context: aContext - }; - - if (typeof aException == "number") { - report.message = Components.Exception("", aException).name; - } - else { - report.message = aException.toString(); - if (aException.fileName) { - report.file = aException.fileName; - report.line = aException.lineNumber; - } - } - - this._simpleMeasures.exception = report; - }, - - getSimpleMeasures: function() { - return this._simpleMeasures; - }, - - getTelemetryDetails: function() { - return AddonManagerInternal.telemetryDetails; - }, - - setTelemetryDetails: function(aProvider, aDetails) { - AddonManagerInternal.telemetryDetails[aProvider] = aDetails; - }, - - // Start a timer, record a simple measure of the time interval when - // timer.done() is called - simpleTimer: function(aName) { - let startTime = Cu.now(); - return { - done: () => this.recordSimpleMeasure(aName, Math.round(Cu.now() - startTime)) - }; - }, - - /** - * Helper to call update listeners when no update is available. - * - * This can be used as an implementation for Addon.findUpdates() when - * no update mechanism is available. - */ - callNoUpdateListeners: function(addon, listener, reason, appVersion, platformVersion) { - if ("onNoCompatibilityUpdateAvailable" in listener) { - safeCall(listener.onNoCompatibilityUpdateAvailable.bind(listener), addon); - } - if ("onNoUpdateAvailable" in listener) { - safeCall(listener.onNoUpdateAvailable.bind(listener), addon); - } - if ("onUpdateFinished" in listener) { - safeCall(listener.onUpdateFinished.bind(listener), addon); - } - }, - - get webExtensionsMinPlatformVersion() { - return gWebExtensionsMinPlatformVersion; - }, - - hasUpgradeListener: function(aId) { - return AddonManagerInternal.upgradeListeners.has(aId); - }, - - getUpgradeListener: function(aId) { - return AddonManagerInternal.upgradeListeners.get(aId); - }, -}; - -/** - * This is the public API that UI and developers should be calling. All methods - * just forward to AddonManagerInternal. - */ -this.AddonManager = { - // Constants for the AddonInstall.state property - // These will show up as AddonManager.STATE_* (eg, STATE_AVAILABLE) - _states: new Map([ - // The install is available for download. - ["STATE_AVAILABLE", 0], - // The install is being downloaded. - ["STATE_DOWNLOADING", 1], - // The install is checking for compatibility information. - ["STATE_CHECKING", 2], - // The install is downloaded and ready to install. - ["STATE_DOWNLOADED", 3], - // The download failed. - ["STATE_DOWNLOAD_FAILED", 4], - // The install has been postponed. - ["STATE_POSTPONED", 5], - // The add-on is being installed. - ["STATE_INSTALLING", 6], - // The add-on has been installed. - ["STATE_INSTALLED", 7], - // The install failed. - ["STATE_INSTALL_FAILED", 8], - // The install has been cancelled. - ["STATE_CANCELLED", 9], - ]), - - // Constants representing different types of errors while downloading an - // add-on. - // These will show up as AddonManager.ERROR_* (eg, ERROR_NETWORK_FAILURE) - _errors: new Map([ - // The download failed due to network problems. - ["ERROR_NETWORK_FAILURE", -1], - // The downloaded file did not match the provided hash. - ["ERROR_INCORRECT_HASH", -2], - // The downloaded file seems to be corrupted in some way. - ["ERROR_CORRUPT_FILE", -3], - // An error occured trying to write to the filesystem. - ["ERROR_FILE_ACCESS", -4], - // The add-on must be signed and isn't. - ["ERROR_SIGNEDSTATE_REQUIRED", -5], - // The downloaded add-on had a different type than expected. - ["ERROR_UNEXPECTED_ADDON_TYPE", -6], - // The addon did not have the expected ID - ["ERROR_INCORRECT_ID", -7], - ]), - - // These must be kept in sync with AddonUpdateChecker. - // No error was encountered. - UPDATE_STATUS_NO_ERROR: 0, - // The update check timed out - UPDATE_STATUS_TIMEOUT: -1, - // There was an error while downloading the update information. - UPDATE_STATUS_DOWNLOAD_ERROR: -2, - // The update information was malformed in some way. - UPDATE_STATUS_PARSE_ERROR: -3, - // The update information was not in any known format. - UPDATE_STATUS_UNKNOWN_FORMAT: -4, - // The update information was not correctly signed or there was an SSL error. - UPDATE_STATUS_SECURITY_ERROR: -5, - // The update was cancelled. - UPDATE_STATUS_CANCELLED: -6, - - // Constants to indicate why an update check is being performed - // Update check has been requested by the user. - UPDATE_WHEN_USER_REQUESTED: 1, - // Update check is necessary to see if the Addon is compatibile with a new - // version of the application. - UPDATE_WHEN_NEW_APP_DETECTED: 2, - // Update check is necessary because a new application has been installed. - UPDATE_WHEN_NEW_APP_INSTALLED: 3, - // Update check is a regular background update check. - UPDATE_WHEN_PERIODIC_UPDATE: 16, - // Update check is needed to check an Addon that is being installed. - UPDATE_WHEN_ADDON_INSTALLED: 17, - - // Constants for operations in Addon.pendingOperations - // Indicates that the Addon has no pending operations. - PENDING_NONE: 0, - // Indicates that the Addon will be enabled after the application restarts. - PENDING_ENABLE: 1, - // Indicates that the Addon will be disabled after the application restarts. - PENDING_DISABLE: 2, - // Indicates that the Addon will be uninstalled after the application restarts. - PENDING_UNINSTALL: 4, - // Indicates that the Addon will be installed after the application restarts. - PENDING_INSTALL: 8, - PENDING_UPGRADE: 16, - - // Constants for operations in Addon.operationsRequiringRestart - // Indicates that restart isn't required for any operation. - OP_NEEDS_RESTART_NONE: 0, - // Indicates that restart is required for enabling the addon. - OP_NEEDS_RESTART_ENABLE: 1, - // Indicates that restart is required for disabling the addon. - OP_NEEDS_RESTART_DISABLE: 2, - // Indicates that restart is required for uninstalling the addon. - OP_NEEDS_RESTART_UNINSTALL: 4, - // Indicates that restart is required for installing the addon. - OP_NEEDS_RESTART_INSTALL: 8, - - // Constants for permissions in Addon.permissions. - // Indicates that the Addon can be uninstalled. - PERM_CAN_UNINSTALL: 1, - // Indicates that the Addon can be enabled by the user. - PERM_CAN_ENABLE: 2, - // Indicates that the Addon can be disabled by the user. - PERM_CAN_DISABLE: 4, - // Indicates that the Addon can be upgraded. - PERM_CAN_UPGRADE: 8, - // Indicates that the Addon can be set to be optionally enabled - // on a case-by-case basis. - PERM_CAN_ASK_TO_ACTIVATE: 16, - - // General descriptions of where items are installed. - // Installed in this profile. - SCOPE_PROFILE: 1, - // Installed for all of this user's profiles. - SCOPE_USER: 2, - // Installed and owned by the application. - SCOPE_APPLICATION: 4, - // Installed for all users of the computer. - SCOPE_SYSTEM: 8, - // Installed temporarily - SCOPE_TEMPORARY: 16, - // The combination of all scopes. - SCOPE_ALL: 31, - - // Add-on type is expected to be displayed in the UI in a list. - VIEW_TYPE_LIST: "list", - - // Constants describing how add-on types behave. - - // If no add-ons of a type are installed, then the category for that add-on - // type should be hidden in the UI. - TYPE_UI_HIDE_EMPTY: 16, - // Indicates that this add-on type supports the ask-to-activate state. - // That is, add-ons of this type can be set to be optionally enabled - // on a case-by-case basis. - TYPE_SUPPORTS_ASK_TO_ACTIVATE: 32, - // The add-on type natively supports undo for restartless uninstalls. - // If this flag is not specified, the UI is expected to handle this via - // disabling the add-on, and performing the actual uninstall at a later time. - TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL: 64, - - // Constants for Addon.applyBackgroundUpdates. - // Indicates that the Addon should not update automatically. - AUTOUPDATE_DISABLE: 0, - // Indicates that the Addon should update automatically only if - // that's the global default. - AUTOUPDATE_DEFAULT: 1, - // Indicates that the Addon should update automatically. - AUTOUPDATE_ENABLE: 2, - - // Constants for how Addon options should be shown. - // Options will be opened in a new window - OPTIONS_TYPE_DIALOG: 1, - // Options will be displayed within the AM detail view - OPTIONS_TYPE_INLINE: 2, - // Options will be displayed in a new tab, if possible - OPTIONS_TYPE_TAB: 3, - // Same as OPTIONS_TYPE_INLINE, but no Preferences button will be shown. - // Used to indicate that only non-interactive information will be shown. - OPTIONS_TYPE_INLINE_INFO: 4, - // Similar to OPTIONS_TYPE_INLINE, but rather than generating inline - // options from a specially-formatted XUL file, the contents of the - // file are simply displayed in an inline element. - OPTIONS_TYPE_INLINE_BROWSER: 5, - - // Constants for displayed or hidden options notifications - // Options notification will be displayed - OPTIONS_NOTIFICATION_DISPLAYED: "addon-options-displayed", - // Options notification will be hidden - OPTIONS_NOTIFICATION_HIDDEN: "addon-options-hidden", - - // Constants for getStartupChanges, addStartupChange and removeStartupChange - // Add-ons that were detected as installed during startup. Doesn't include - // add-ons that were pending installation the last time the application ran. - STARTUP_CHANGE_INSTALLED: "installed", - // Add-ons that were detected as changed during startup. This includes an - // add-on moving to a different location, changing version or just having - // been detected as possibly changed. - STARTUP_CHANGE_CHANGED: "changed", - // Add-ons that were detected as uninstalled during startup. Doesn't include - // add-ons that were pending uninstallation the last time the application ran. - STARTUP_CHANGE_UNINSTALLED: "uninstalled", - // Add-ons that were detected as disabled during startup, normally because of - // an application change making an add-on incompatible. Doesn't include - // add-ons that were pending being disabled the last time the application ran. - STARTUP_CHANGE_DISABLED: "disabled", - // Add-ons that were detected as enabled during startup, normally because of - // an application change making an add-on compatible. Doesn't include - // add-ons that were pending being enabled the last time the application ran. - STARTUP_CHANGE_ENABLED: "enabled", - - // Constants for Addon.signedState. Any states that should cause an add-on - // to be unusable in builds that require signing should have negative values. - // Add-on signing is not required, e.g. because the pref is disabled. - SIGNEDSTATE_NOT_REQUIRED: undefined, - // Add-on is signed but signature verification has failed. - SIGNEDSTATE_BROKEN: -2, - // Add-on may be signed but by an certificate that doesn't chain to our - // our trusted certificate. - SIGNEDSTATE_UNKNOWN: -1, - // Add-on is unsigned. - SIGNEDSTATE_MISSING: 0, - // Add-on is preliminarily reviewed. - SIGNEDSTATE_PRELIMINARY: 1, - // Add-on is fully reviewed. - SIGNEDSTATE_SIGNED: 2, - // Add-on is system add-on. - SIGNEDSTATE_SYSTEM: 3, - - // Constants for the Addon.userDisabled property - // Indicates that the userDisabled state of this add-on is currently - // ask-to-activate. That is, it can be conditionally enabled on a - // case-by-case basis. - STATE_ASK_TO_ACTIVATE: "askToActivate", - - get __AddonManagerInternal__() { - return AppConstants.DEBUG ? AddonManagerInternal : undefined; - }, - - get isReady() { - return gStartupComplete && !gShutdownInProgress; - }, - - init() { - this._stateToString = new Map(); - for (let [name, value] of this._states) { - this[name] = value; - this._stateToString.set(value, name); - } - this._errorToString = new Map(); - for (let [name, value] of this._errors) { - this[name] = value; - this._errorToString.set(value, name); - } - }, - - stateToString(state) { - return this._stateToString.get(state); - }, - - errorToString(err) { - return err ? this._errorToString.get(err) : null; - }, - - getInstallForURL: function(aUrl, aCallback, aMimetype, - aHash, aName, aIcons, - aVersion, aBrowser) { - AddonManagerInternal.getInstallForURL(aUrl, aCallback, aMimetype, aHash, - aName, aIcons, aVersion, aBrowser); - }, - - getInstallForFile: function(aFile, aCallback, aMimetype) { - AddonManagerInternal.getInstallForFile(aFile, aCallback, aMimetype); - }, - - /** - * Gets an array of add-on IDs that changed during the most recent startup. - * - * @param aType - * The type of startup change to get - * @return An array of add-on IDs - */ - getStartupChanges: function(aType) { - if (!(aType in AddonManagerInternal.startupChanges)) - return []; - return AddonManagerInternal.startupChanges[aType].slice(0); - }, - - getAddonByID: function(aID, aCallback) { - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - AddonManagerInternal.getAddonByID(aID) - .then(makeSafe(aCallback)) - .catch(logger.error); - }, - - getAddonBySyncGUID: function(aGUID, aCallback) { - AddonManagerInternal.getAddonBySyncGUID(aGUID, aCallback); - }, - - getAddonsByIDs: function(aIDs, aCallback) { - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - AddonManagerInternal.getAddonsByIDs(aIDs) - .then(makeSafe(aCallback)) - .catch(logger.error); - }, - - getAddonsWithOperationsByTypes: function(aTypes, aCallback) { - AddonManagerInternal.getAddonsWithOperationsByTypes(aTypes, aCallback); - }, - - getAddonsByTypes: function(aTypes, aCallback) { - AddonManagerInternal.getAddonsByTypes(aTypes, aCallback); - }, - - getAllAddons: function(aCallback) { - AddonManagerInternal.getAllAddons(aCallback); - }, - - getInstallsByTypes: function(aTypes, aCallback) { - AddonManagerInternal.getInstallsByTypes(aTypes, aCallback); - }, - - getAllInstalls: function(aCallback) { - AddonManagerInternal.getAllInstalls(aCallback); - }, - - mapURIToAddonID: function(aURI) { - return AddonManagerInternal.mapURIToAddonID(aURI); - }, - - isInstallEnabled: function(aType) { - return AddonManagerInternal.isInstallEnabled(aType); - }, - - isInstallAllowed: function(aType, aInstallingPrincipal) { - return AddonManagerInternal.isInstallAllowed(aType, aInstallingPrincipal); - }, - - installAddonsFromWebpage: function(aType, aBrowser, aInstallingPrincipal, - aInstalls) { - AddonManagerInternal.installAddonsFromWebpage(aType, aBrowser, - aInstallingPrincipal, - aInstalls); - }, - - installTemporaryAddon: function(aDirectory) { - return AddonManagerInternal.installTemporaryAddon(aDirectory); - }, - - installAddonFromSources: function(aDirectory) { - return AddonManagerInternal.installAddonFromSources(aDirectory); - }, - - getAddonByInstanceID: function(aInstanceID) { - return AddonManagerInternal.getAddonByInstanceID(aInstanceID); - }, - - addManagerListener: function(aListener) { - AddonManagerInternal.addManagerListener(aListener); - }, - - removeManagerListener: function(aListener) { - AddonManagerInternal.removeManagerListener(aListener); - }, - - addInstallListener: function(aListener) { - AddonManagerInternal.addInstallListener(aListener); - }, - - removeInstallListener: function(aListener) { - AddonManagerInternal.removeInstallListener(aListener); - }, - - getUpgradeListener: function(aId) { - return AddonManagerInternal.upgradeListeners.get(aId); - }, - - addUpgradeListener: function(aInstanceID, aCallback) { - AddonManagerInternal.addUpgradeListener(aInstanceID, aCallback); - }, - - removeUpgradeListener: function(aInstanceID) { - AddonManagerInternal.removeUpgradeListener(aInstanceID); - }, - - addAddonListener: function(aListener) { - AddonManagerInternal.addAddonListener(aListener); - }, - - removeAddonListener: function(aListener) { - AddonManagerInternal.removeAddonListener(aListener); - }, - - addTypeListener: function(aListener) { - AddonManagerInternal.addTypeListener(aListener); - }, - - removeTypeListener: function(aListener) { - AddonManagerInternal.removeTypeListener(aListener); - }, - - get addonTypes() { - return AddonManagerInternal.addonTypes; - }, - - /** - * Determines whether an Addon should auto-update or not. - * - * @param aAddon - * The Addon representing the add-on - * @return true if the addon should auto-update, false otherwise. - */ - shouldAutoUpdate: function(aAddon) { - if (!aAddon || typeof aAddon != "object") - throw Components.Exception("aAddon must be specified", - Cr.NS_ERROR_INVALID_ARG); - - if (!("applyBackgroundUpdates" in aAddon)) - return false; - if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE) - return true; - if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE) - return false; - return this.autoUpdateDefault; - }, - - get checkCompatibility() { - return AddonManagerInternal.checkCompatibility; - }, - - set checkCompatibility(aValue) { - AddonManagerInternal.checkCompatibility = aValue; - }, - - get strictCompatibility() { - return AddonManagerInternal.strictCompatibility; - }, - - set strictCompatibility(aValue) { - AddonManagerInternal.strictCompatibility = aValue; - }, - - get checkUpdateSecurityDefault() { - return AddonManagerInternal.checkUpdateSecurityDefault; - }, - - get checkUpdateSecurity() { - return AddonManagerInternal.checkUpdateSecurity; - }, - - set checkUpdateSecurity(aValue) { - AddonManagerInternal.checkUpdateSecurity = aValue; - }, - - get updateEnabled() { - return AddonManagerInternal.updateEnabled; - }, - - set updateEnabled(aValue) { - AddonManagerInternal.updateEnabled = aValue; - }, - - get autoUpdateDefault() { - return AddonManagerInternal.autoUpdateDefault; - }, - - set autoUpdateDefault(aValue) { - AddonManagerInternal.autoUpdateDefault = aValue; - }, - - get hotfixID() { - return AddonManagerInternal.hotfixID; - }, - - escapeAddonURI: function(aAddon, aUri, aAppVersion) { - return AddonManagerInternal.escapeAddonURI(aAddon, aUri, aAppVersion); - }, - - getPreferredIconURL: function(aAddon, aSize, aWindow = undefined) { - return AddonManagerInternal.getPreferredIconURL(aAddon, aSize, aWindow); - }, - - get webAPI() { - return AddonManagerInternal.webAPI; - }, - - get shutdown() { - return gShutdownBarrier.client; - }, -}; - -this.AddonManager.init(); - -// load the timestamps module into AddonManagerInternal -Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", AddonManagerInternal); -Object.freeze(AddonManagerInternal); -Object.freeze(AddonManagerPrivate); -Object.freeze(AddonManager); diff --git a/toolkit/mozapps/webextensions/AddonManagerWebAPI.cpp b/toolkit/mozapps/webextensions/AddonManagerWebAPI.cpp deleted file mode 100644 index 3f2a7a529..000000000 --- a/toolkit/mozapps/webextensions/AddonManagerWebAPI.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "AddonManagerWebAPI.h" - -#include "mozilla/dom/Navigator.h" -#include "mozilla/dom/NavigatorBinding.h" - -#include "mozilla/Preferences.h" -#include "nsGlobalWindow.h" - -#include "nsIDocShell.h" -#include "nsIScriptObjectPrincipal.h" - -namespace mozilla { -using namespace mozilla::dom; - -static bool -IsValidHost(const nsACString& host) { - // This is ugly, but Preferences.h doesn't have support - // for default prefs or locked prefs - nsCOMPtr prefService (do_GetService(NS_PREFSERVICE_CONTRACTID)); - nsCOMPtr prefs; - if (prefService) { - prefService->GetDefaultBranch(nullptr, getter_AddRefs(prefs)); - bool isEnabled; - if (NS_SUCCEEDED(prefs->GetBoolPref("xpinstall.enabled", &isEnabled)) && !isEnabled) { - bool isLocked; - prefs->PrefIsLocked("xpinstall.enabled", &isLocked); - if (isLocked) { - return false; - } - } - } - - if (host.Equals("addons.mozilla.org") || - host.Equals("discovery.addons.mozilla.org") || - host.Equals("testpilot.firefox.com")) { - return true; - } - - // When testing allow access to the developer sites. - if (Preferences::GetBool("extensions.webapi.testing", false)) { - if (host.LowerCaseEqualsLiteral("addons.allizom.org") || - host.LowerCaseEqualsLiteral("discovery.addons.allizom.org") || - host.LowerCaseEqualsLiteral("addons-dev.allizom.org") || - host.LowerCaseEqualsLiteral("discovery.addons-dev.allizom.org") || - host.LowerCaseEqualsLiteral("testpilot.stage.mozaws.net") || - host.LowerCaseEqualsLiteral("testpilot.dev.mozaws.net") || - host.LowerCaseEqualsLiteral("example.com")) { - return true; - } - } - - return false; -} - -// Checks if the given uri is secure and matches one of the hosts allowed to -// access the API. -bool -AddonManagerWebAPI::IsValidSite(nsIURI* uri) -{ - if (!uri) { - return false; - } - - bool isSecure; - nsresult rv = uri->SchemeIs("https", &isSecure); - if (NS_FAILED(rv) || !isSecure) { - return false; - } - - nsAutoCString host; - rv = uri->GetHost(host); - if (NS_FAILED(rv)) { - return false; - } - - return IsValidHost(host); -} - -bool -AddonManagerWebAPI::IsAPIEnabled(JSContext* cx, JSObject* obj) -{ - nsGlobalWindow* global = xpc::WindowGlobalOrNull(obj); - if (!global) { - return false; - } - - nsCOMPtr win = global->AsInner(); - if (!win) { - return false; - } - - // Check that the current window and all parent frames are allowed access to - // the API. - while (win) { - nsCOMPtr sop = do_QueryInterface(win); - if (!sop) { - return false; - } - - nsCOMPtr principal = sop->GetPrincipal(); - if (!principal) { - return false; - } - - // Reaching a window with a system principal means we have reached - // privileged UI of some kind so stop at this point and allow access. - if (principal->GetIsSystemPrincipal()) { - return true; - } - - nsCOMPtr docShell = win->GetDocShell(); - if (!docShell) { - // This window has been torn down so don't allow access to the API. - return false; - } - - if (!IsValidSite(win->GetDocumentURI())) { - return false; - } - - // Checks whether there is a parent frame of the same type. This won't cross - // mozbrowser or chrome boundaries. - nsCOMPtr parent; - nsresult rv = docShell->GetSameTypeParent(getter_AddRefs(parent)); - if (NS_FAILED(rv)) { - return false; - } - - if (!parent) { - // No parent means we've hit a mozbrowser or chrome boundary so allow - // access to the API. - return true; - } - - nsIDocument* doc = win->GetDoc(); - if (!doc) { - return false; - } - - doc = doc->GetParentDocument(); - if (!doc) { - // Getting here means something has been torn down so fail safe. - return false; - } - - - win = doc->GetInnerWindow(); - } - - // Found a document with no inner window, don't grant access to the API. - return false; -} - -namespace dom { - -bool -AddonManagerPermissions::IsHostPermitted(const GlobalObject& /*unused*/, const nsAString& host) -{ - return IsValidHost(NS_ConvertUTF16toUTF8(host)); -} - -} // namespace mozilla::dom - - -} // namespace mozilla diff --git a/toolkit/mozapps/webextensions/AddonManagerWebAPI.h b/toolkit/mozapps/webextensions/AddonManagerWebAPI.h deleted file mode 100644 index 6830bc91f..000000000 --- a/toolkit/mozapps/webextensions/AddonManagerWebAPI.h +++ /dev/null @@ -1,33 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef addonmanagerwebapi_h_ -#define addonmanagerwebapi_h_ - -#include "nsPIDOMWindow.h" - -namespace mozilla { - -class AddonManagerWebAPI { -public: - static bool IsAPIEnabled(JSContext* cx, JSObject* obj); - -private: - static bool IsValidSite(nsIURI* uri); -}; - -namespace dom { - -class AddonManagerPermissions { -public: - static bool IsHostPermitted(const GlobalObject&, const nsAString& host); -}; - -} // namespace mozilla::dom - -} // namespace mozilla - -#endif // addonmanagerwebapi_h_ diff --git a/toolkit/mozapps/webextensions/AddonPathService.cpp b/toolkit/mozapps/webextensions/AddonPathService.cpp deleted file mode 100644 index 8a405c0ea..000000000 --- a/toolkit/mozapps/webextensions/AddonPathService.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "AddonPathService.h" - -#include "amIAddonManager.h" -#include "nsIURI.h" -#include "nsXULAppAPI.h" -#include "jsapi.h" -#include "nsServiceManagerUtils.h" -#include "nsLiteralString.h" -#include "nsThreadUtils.h" -#include "nsIIOService.h" -#include "nsNetUtil.h" -#include "nsIAddonPolicyService.h" -#include "nsIFileURL.h" -#include "nsIResProtocolHandler.h" -#include "nsIChromeRegistry.h" -#include "nsIJARURI.h" -#include "nsJSUtils.h" -#include "mozilla/dom/ScriptSettings.h" -#include "mozilla/dom/ToJSValue.h" -#include "mozilla/AddonPathService.h" -#include "mozilla/Omnijar.h" - -#include - -namespace mozilla { - -struct PathEntryComparator -{ - typedef AddonPathService::PathEntry PathEntry; - - bool Equals(const PathEntry& entry1, const PathEntry& entry2) const - { - return entry1.mPath == entry2.mPath; - } - - bool LessThan(const PathEntry& entry1, const PathEntry& entry2) const - { - return entry1.mPath < entry2.mPath; - } -}; - -AddonPathService::AddonPathService() -{ -} - -AddonPathService::~AddonPathService() -{ - sInstance = nullptr; -} - -NS_IMPL_ISUPPORTS(AddonPathService, amIAddonPathService) - -AddonPathService *AddonPathService::sInstance; - -/* static */ AddonPathService* -AddonPathService::GetInstance() -{ - if (!sInstance) { - sInstance = new AddonPathService(); - } - NS_ADDREF(sInstance); - return sInstance; -} - -static JSAddonId* -ConvertAddonId(const nsAString& addonIdString) -{ - AutoSafeJSContext cx; - JS::RootedValue strv(cx); - if (!mozilla::dom::ToJSValue(cx, addonIdString, &strv)) { - return nullptr; - } - JS::RootedString str(cx, strv.toString()); - return JS::NewAddonId(cx, str); -} - -JSAddonId* -AddonPathService::Find(const nsAString& path) -{ - // Use binary search to find the nearest entry that is <= |path|. - PathEntryComparator comparator; - unsigned index = mPaths.IndexOfFirstElementGt(PathEntry(path, nullptr), comparator); - if (index == 0) { - return nullptr; - } - const PathEntry& entry = mPaths[index - 1]; - - // Return the entry's addon if its path is a prefix of |path|. - if (StringBeginsWith(path, entry.mPath)) { - return entry.mAddonId; - } - return nullptr; -} - -NS_IMETHODIMP -AddonPathService::FindAddonId(const nsAString& path, nsAString& addonIdString) -{ - if (JSAddonId* id = Find(path)) { - JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id)); - AssignJSFlatString(addonIdString, flat); - } - return NS_OK; -} - -/* static */ JSAddonId* -AddonPathService::FindAddonId(const nsAString& path) -{ - // If no service has been created, then we're not going to find anything. - if (!sInstance) { - return nullptr; - } - - return sInstance->Find(path); -} - -NS_IMETHODIMP -AddonPathService::InsertPath(const nsAString& path, const nsAString& addonIdString) -{ - JSAddonId* addonId = ConvertAddonId(addonIdString); - - // Add the new path in sorted order. - PathEntryComparator comparator; - mPaths.InsertElementSorted(PathEntry(path, addonId), comparator); - return NS_OK; -} - -NS_IMETHODIMP -AddonPathService::MapURIToAddonId(nsIURI* aURI, nsAString& addonIdString) -{ - if (JSAddonId* id = MapURIToAddonID(aURI)) { - JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id)); - AssignJSFlatString(addonIdString, flat); - } - return NS_OK; -} - -static nsresult -ResolveURI(nsIURI* aURI, nsAString& out) -{ - bool equals; - nsresult rv; - nsCOMPtr uri; - nsAutoCString spec; - - // Resolve resource:// URIs. At the end of this if/else block, we - // have both spec and uri variables identifying the same URI. - if (NS_SUCCEEDED(aURI->SchemeIs("resource", &equals)) && equals) { - nsCOMPtr ioService = do_GetIOService(&rv); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - nsCOMPtr ph; - rv = ioService->GetProtocolHandler("resource", getter_AddRefs(ph)); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - nsCOMPtr irph(do_QueryInterface(ph, &rv)); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - rv = irph->ResolveURI(aURI, spec); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - rv = ioService->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri)); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - } else if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &equals)) && equals) { - nsCOMPtr chromeReg = - mozilla::services::GetChromeRegistryService(); - if (NS_WARN_IF(!chromeReg)) - return NS_ERROR_UNEXPECTED; - - rv = chromeReg->ConvertChromeURL(aURI, getter_AddRefs(uri)); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - } else { - uri = aURI; - } - - if (NS_SUCCEEDED(uri->SchemeIs("jar", &equals)) && equals) { - nsCOMPtr jarURI = do_QueryInterface(uri, &rv); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - nsCOMPtr jarFileURI; - rv = jarURI->GetJARFile(getter_AddRefs(jarFileURI)); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - return ResolveURI(jarFileURI, out); - } - - if (NS_SUCCEEDED(uri->SchemeIs("file", &equals)) && equals) { - nsCOMPtr baseFileURL = do_QueryInterface(uri, &rv); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - nsCOMPtr file; - rv = baseFileURL->GetFile(getter_AddRefs(file)); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - return file->GetPath(out); - } - return NS_ERROR_FAILURE; -} - -JSAddonId* -MapURIToAddonID(nsIURI* aURI) -{ - if (!NS_IsMainThread() || !XRE_IsParentProcess()) { - return nullptr; - } - - bool equals; - nsresult rv; - if (NS_SUCCEEDED(aURI->SchemeIs("moz-extension", &equals)) && equals) { - nsCOMPtr service = do_GetService("@mozilla.org/addons/policy-service;1"); - if (service) { - nsString addonId; - rv = service->ExtensionURIToAddonId(aURI, addonId); - if (NS_FAILED(rv)) - return nullptr; - - return ConvertAddonId(addonId); - } - } - - nsAutoString filePath; - rv = ResolveURI(aURI, filePath); - if (NS_FAILED(rv)) - return nullptr; - - nsCOMPtr greJar = Omnijar::GetPath(Omnijar::GRE); - nsCOMPtr appJar = Omnijar::GetPath(Omnijar::APP); - if (greJar && appJar) { - nsAutoString greJarString, appJarString; - if (NS_FAILED(greJar->GetPath(greJarString)) || NS_FAILED(appJar->GetPath(appJarString))) - return nullptr; - - // If |aURI| is part of either Omnijar, then it can't be part of an - // add-on. This catches pretty much all URLs for Firefox content. - if (filePath.Equals(greJarString) || filePath.Equals(appJarString)) - return nullptr; - } - - // If it's not part of Firefox, we resort to binary searching through the - // add-on paths. - return AddonPathService::FindAddonId(filePath); -} - -} // namespace mozilla diff --git a/toolkit/mozapps/webextensions/AddonPathService.h b/toolkit/mozapps/webextensions/AddonPathService.h deleted file mode 100644 index f739b018f..000000000 --- a/toolkit/mozapps/webextensions/AddonPathService.h +++ /dev/null @@ -1,55 +0,0 @@ -//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-/ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef AddonPathService_h -#define AddonPathService_h - -#include "amIAddonPathService.h" -#include "nsString.h" -#include "nsTArray.h" - -class nsIURI; -class JSAddonId; - -namespace mozilla { - -JSAddonId* -MapURIToAddonID(nsIURI* aURI); - -class AddonPathService final : public amIAddonPathService -{ -public: - AddonPathService(); - - static AddonPathService* GetInstance(); - - JSAddonId* Find(const nsAString& path); - static JSAddonId* FindAddonId(const nsAString& path); - - NS_DECL_ISUPPORTS - NS_DECL_AMIADDONPATHSERVICE - - struct PathEntry - { - nsString mPath; - JSAddonId* mAddonId; - - PathEntry(const nsAString& aPath, JSAddonId* aAddonId) - : mPath(aPath), mAddonId(aAddonId) - {} - }; - -private: - virtual ~AddonPathService(); - - // Paths are stored sorted in order of their mPath. - nsTArray mPaths; - - static AddonPathService* sInstance; -}; - -} // namespace mozilla - -#endif diff --git a/toolkit/mozapps/webextensions/GMPInstallManager.jsm b/toolkit/mozapps/webextensions/GMPInstallManager.jsm deleted file mode 100644 index b5987ca55..000000000 --- a/toolkit/mozapps/webextensions/GMPInstallManager.jsm +++ /dev/null @@ -1,523 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -this.EXPORTED_SYMBOLS = []; - -const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu, manager: Cm} = - Components; -// 1 day default -const DEFAULT_SECONDS_BETWEEN_CHECKS = 60 * 60 * 24; - -var GMPInstallFailureReason = { - GMP_INVALID: 1, - GMP_HIDDEN: 2, - GMP_DISABLED: 3, - GMP_UPDATE_DISABLED: 4, -}; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/FileUtils.jsm"); -Cu.import("resource://gre/modules/Promise.jsm"); -Cu.import("resource://gre/modules/Preferences.jsm"); -Cu.import("resource://gre/modules/Log.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); -Cu.import("resource://gre/modules/GMPUtils.jsm"); -Cu.import("resource://gre/modules/addons/ProductAddonChecker.jsm"); - -this.EXPORTED_SYMBOLS = ["GMPInstallManager", "GMPExtractor", "GMPDownloader", - "GMPAddon"]; - -// Shared code for suppressing bad cert dialogs -XPCOMUtils.defineLazyGetter(this, "gCertUtils", function() { - let temp = { }; - Cu.import("resource://gre/modules/CertUtils.jsm", temp); - return temp; -}); - -XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils", - "resource://gre/modules/UpdateUtils.jsm"); - -function getScopedLogger(prefix) { - // `PARENT_LOGGER_ID.` being passed here effectively links this logger - // to the parentLogger. - return Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP", prefix + " "); -} - -/** - * Provides an easy API for downloading and installing GMP Addons - */ -function GMPInstallManager() { -} -/** - * Temp file name used for downloading - */ -GMPInstallManager.prototype = { - /** - * Obtains a URL with replacement of vars - */ - _getURL: function() { - let log = getScopedLogger("GMPInstallManager._getURL"); - // Use the override URL if it is specified. The override URL is just like - // the normal URL but it does not check the cert. - let url = GMPPrefs.get(GMPPrefs.KEY_URL_OVERRIDE); - if (url) { - log.info("Using override url: " + url); - } else { - url = GMPPrefs.get(GMPPrefs.KEY_URL); - log.info("Using url: " + url); - } - - url = UpdateUtils.formatUpdateURL(url); - - log.info("Using url (with replacement): " + url); - return url; - }, - /** - * Performs an addon check. - * @return a promise which will be resolved or rejected. - * The promise is resolved with an object with properties: - * gmpAddons: array of GMPAddons - * usedFallback: whether the data was collected from online or - * from fallback data within the build - * The promise is rejected with an object with properties: - * target: The XHR request object - * status: The HTTP status code - * type: Sometimes specifies type of rejection - */ - checkForAddons: function() { - let log = getScopedLogger("GMPInstallManager.checkForAddons"); - if (this._deferred) { - log.error("checkForAddons already called"); - return Promise.reject({type: "alreadycalled"}); - } - this._deferred = Promise.defer(); - let url = this._getURL(); - - let allowNonBuiltIn = true; - let certs = null; - if (!Services.prefs.prefHasUserValue(GMPPrefs.KEY_URL_OVERRIDE)) { - allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_REQUIREBUILTIN, true); - if (GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, true)) { - certs = gCertUtils.readCertPrefs(GMPPrefs.KEY_CERTS_BRANCH); - } - } - - let addonPromise = ProductAddonChecker - .getProductAddonList(url, allowNonBuiltIn, certs); - - addonPromise.then(res => { - if (!res || !res.gmpAddons) { - this._deferred.resolve({gmpAddons: []}); - } - else { - res.gmpAddons = res.gmpAddons.map(a => new GMPAddon(a)); - this._deferred.resolve(res); - } - delete this._deferred; - }, (ex) => { - this._deferred.reject(ex); - delete this._deferred; - }); - - return this._deferred.promise; - }, - /** - * Installs the specified addon and calls a callback when done. - * @param gmpAddon The GMPAddon object to install - * @return a promise which will be resolved or rejected - * The promise will resolve with an array of paths that were extracted - * The promise will reject with an error object: - * target: The XHR request object - * status: The HTTP status code - * type: A string to represent the type of error - * downloaderr, verifyerr or previouserrorencountered - */ - installAddon: function(gmpAddon) { - if (this._deferred) { - log.error("previous error encountered"); - return Promise.reject({type: "previouserrorencountered"}); - } - this.gmpDownloader = new GMPDownloader(gmpAddon); - return this.gmpDownloader.start(); - }, - _getTimeSinceLastCheck: function() { - let now = Math.round(Date.now() / 1000); - // Default to 0 here because `now - 0` will be returned later if that case - // is hit. We want a large value so a check will occur. - let lastCheck = GMPPrefs.get(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0); - // Handle clock jumps, return now since we want it to represent - // a lot of time has passed since the last check. - if (now < lastCheck) { - return now; - } - return now - lastCheck; - }, - get _isEMEEnabled() { - return GMPPrefs.get(GMPPrefs.KEY_EME_ENABLED, true); - }, - _isAddonEnabled: function(aAddon) { - return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_ENABLED, true, aAddon); - }, - _isAddonUpdateEnabled: function(aAddon) { - return this._isAddonEnabled(aAddon) && - GMPPrefs.get(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, aAddon); - }, - _updateLastCheck: function() { - let now = Math.round(Date.now() / 1000); - GMPPrefs.set(GMPPrefs.KEY_UPDATE_LAST_CHECK, now); - }, - _versionchangeOccurred: function() { - let savedBuildID = GMPPrefs.get(GMPPrefs.KEY_BUILDID, null); - let buildID = Services.appinfo.platformBuildID; - if (savedBuildID == buildID) { - return false; - } - GMPPrefs.set(GMPPrefs.KEY_BUILDID, buildID); - return true; - }, - /** - * Wrapper for checkForAddons and installAddon. - * Will only install if not already installed and will log the results. - * This will only install/update the OpenH264 and EME plugins - * @return a promise which will be resolved if all addons could be installed - * successfully, rejected otherwise. - */ - simpleCheckAndInstall: Task.async(function*() { - let log = getScopedLogger("GMPInstallManager.simpleCheckAndInstall"); - - if (this._versionchangeOccurred()) { - log.info("A version change occurred. Ignoring " + - "media.gmp-manager.lastCheck to check immediately for " + - "new or updated GMPs."); - } else { - let secondsBetweenChecks = - GMPPrefs.get(GMPPrefs.KEY_SECONDS_BETWEEN_CHECKS, - DEFAULT_SECONDS_BETWEEN_CHECKS) - let secondsSinceLast = this._getTimeSinceLastCheck(); - log.info("Last check was: " + secondsSinceLast + - " seconds ago, minimum seconds: " + secondsBetweenChecks); - if (secondsBetweenChecks > secondsSinceLast) { - log.info("Will not check for updates."); - return {status: "too-frequent-no-check"}; - } - } - - try { - let {usedFallback, gmpAddons} = yield this.checkForAddons(); - this._updateLastCheck(); - log.info("Found " + gmpAddons.length + " addons advertised."); - let addonsToInstall = gmpAddons.filter(function(gmpAddon) { - log.info("Found addon: " + gmpAddon.toString()); - - if (!gmpAddon.isValid) { - log.info("Addon |" + gmpAddon.id + "| is invalid."); - return false; - } - - if (GMPUtils.isPluginHidden(gmpAddon)) { - log.info("Addon |" + gmpAddon.id + "| has been hidden."); - return false; - } - - if (gmpAddon.isInstalled) { - log.info("Addon |" + gmpAddon.id + "| already installed."); - return false; - } - - // Do not install from fallback if already installed as it - // may be a downgrade - if (usedFallback && gmpAddon.isUpdate) { - log.info("Addon |" + gmpAddon.id + "| not installing updates based " + - "on fallback."); - return false; - } - - let addonUpdateEnabled = false; - if (GMP_PLUGIN_IDS.indexOf(gmpAddon.id) >= 0) { - if (!this._isAddonEnabled(gmpAddon.id)) { - log.info("GMP |" + gmpAddon.id + "| has been disabled; skipping check."); - } else if (!this._isAddonUpdateEnabled(gmpAddon.id)) { - log.info("Auto-update is off for " + gmpAddon.id + - ", skipping check."); - } else { - addonUpdateEnabled = true; - } - } else { - // Currently, we only support installs of OpenH264 and EME plugins. - log.info("Auto-update is off for unknown plugin '" + gmpAddon.id + - "', skipping check."); - } - - return addonUpdateEnabled; - }, this); - - if (!addonsToInstall.length) { - log.info("No new addons to install, returning"); - return {status: "nothing-new-to-install"}; - } - - let installResults = []; - let failureEncountered = false; - for (let addon of addonsToInstall) { - try { - yield this.installAddon(addon); - installResults.push({ - id: addon.id, - result: "succeeded", - }); - } catch (e) { - failureEncountered = true; - installResults.push({ - id: addon.id, - result: "failed", - }); - } - } - if (failureEncountered) { - throw {status: "failed", - results: installResults}; - } - return {status: "succeeded", - results: installResults}; - } catch (e) { - log.error("Could not check for addons", e); - throw e; - } - }), - - /** - * Makes sure everything is cleaned up - */ - uninit: function() { - let log = getScopedLogger("GMPInstallManager.uninit"); - if (this._request) { - log.info("Aborting request"); - this._request.abort(); - } - if (this._deferred) { - log.info("Rejecting deferred"); - this._deferred.reject({type: "uninitialized"}); - } - log.info("Done cleanup"); - }, - - /** - * If set to true, specifies to leave the temporary downloaded zip file. - * This is useful for tests. - */ - overrideLeaveDownloadedZip: false, -}; - -/** - * Used to construct a single GMP addon - * GMPAddon objects are returns from GMPInstallManager.checkForAddons - * GMPAddon objects can also be used in calls to GMPInstallManager.installAddon - * - * @param addon The ProductAddonChecker `addon` object - */ -function GMPAddon(addon) { - let log = getScopedLogger("GMPAddon.constructor"); - for (let name of Object.keys(addon)) { - this[name] = addon[name]; - } - log.info ("Created new addon: " + this.toString()); -} - -GMPAddon.prototype = { - /** - * Returns a string representation of the addon - */ - toString: function() { - return this.id + " (" + - "isValid: " + this.isValid + - ", isInstalled: " + this.isInstalled + - ", hashFunction: " + this.hashFunction+ - ", hashValue: " + this.hashValue + - (this.size !== undefined ? ", size: " + this.size : "" ) + - ")"; - }, - /** - * If all the fields aren't specified don't consider this addon valid - * @return true if the addon is parsed and valid - */ - get isValid() { - return this.id && this.URL && this.version && - this.hashFunction && !!this.hashValue; - }, - get isInstalled() { - return this.version && - GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION, "", this.id) === this.version; - }, - get isEME() { - return this.id == "gmp-widevinecdm" || this.id.indexOf("gmp-eme-") == 0; - }, - /** - * @return true if the addon has been previously installed and this is - * a new version, if this is a fresh install return false - */ - get isUpdate() { - return this.version && - GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION, false, this.id); - }, -}; -/** - * Constructs a GMPExtractor object which is used to extract a GMP zip - * into the specified location. (Which typically leties per platform) - * @param zipPath The path on disk of the zip file to extract - */ -function GMPExtractor(zipPath, installToDirPath) { - this.zipPath = zipPath; - this.installToDirPath = installToDirPath; -} -GMPExtractor.prototype = { - /** - * Obtains a list of all the entries in a zipfile in the format of *.*. - * This also includes files inside directories. - * - * @param zipReader the nsIZipReader to check - * @return An array of string name entries which can be used - * in nsIZipReader.extract - */ - _getZipEntries: function(zipReader) { - let entries = []; - let enumerator = zipReader.findEntries("*.*"); - while (enumerator.hasMore()) { - entries.push(enumerator.getNext()); - } - return entries; - }, - /** - * Installs the this.zipPath contents into the directory used to store GMP - * addons for the current platform. - * - * @return a promise which will be resolved or rejected - * See GMPInstallManager.installAddon for resolve/rejected info - */ - install: function() { - try { - let log = getScopedLogger("GMPExtractor.install"); - this._deferred = Promise.defer(); - log.info("Installing " + this.zipPath + "..."); - // Get the input zip file - let zipFile = Cc["@mozilla.org/file/local;1"]. - createInstance(Ci.nsIFile); - zipFile.initWithPath(this.zipPath); - - // Initialize a zipReader and obtain the entries - var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]. - createInstance(Ci.nsIZipReader); - zipReader.open(zipFile) - let entries = this._getZipEntries(zipReader); - let extractedPaths = []; - - let destDir = Cc["@mozilla.org/file/local;1"]. - createInstance(Ci.nsILocalFile); - destDir.initWithPath(this.installToDirPath); - // Make sure the destination exists - if (!destDir.exists()) { - destDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8)); - } - - // Extract each of the entries - entries.forEach(entry => { - // We don't need these types of files - if (entry.includes("__MACOSX") || - entry == "_metadata/verified_contents.json" || - entry == "imgs/icon-128x128.png") { - return; - } - let outFile = destDir.clone(); - // Do not extract into directories. Extract all files to the same - // directory. DO NOT use |OS.Path.basename()| here, as in Windows it - // does not work properly with forward slashes (which we must use here). - let outBaseName = entry.slice(entry.lastIndexOf("/") + 1); - outFile.appendRelativePath(outBaseName); - - zipReader.extract(entry, outFile); - extractedPaths.push(outFile.path); - // Ensure files are writable and executable. Otherwise we may be unable to - // execute or uninstall them. - outFile.permissions |= parseInt("0700", 8); - log.info(entry + " was successfully extracted to: " + - outFile.path); - }); - zipReader.close(); - if (!GMPInstallManager.overrideLeaveDownloadedZip) { - zipFile.remove(false); - } - - log.info(this.zipPath + " was installed successfully"); - this._deferred.resolve(extractedPaths); - } catch (e) { - if (zipReader) { - zipReader.close(); - } - this._deferred.reject({ - target: this, - status: e, - type: "exception" - }); - } - return this._deferred.promise; - } -}; - - -/** - * Constructs an object which downloads and initiates an install of - * the specified GMPAddon object. - * @param gmpAddon The addon to install. - */ -function GMPDownloader(gmpAddon) -{ - this._gmpAddon = gmpAddon; -} - -GMPDownloader.prototype = { - /** - * Starts the download process for an addon. - * @return a promise which will be resolved or rejected - * See GMPInstallManager.installAddon for resolve/rejected info - */ - start: function() { - let log = getScopedLogger("GMPDownloader"); - let gmpAddon = this._gmpAddon; - - if (!gmpAddon.isValid) { - log.info("gmpAddon is not valid, will not continue"); - return Promise.reject({ - target: this, - status: status, - type: "downloaderr" - }); - } - - return ProductAddonChecker.downloadAddon(gmpAddon).then((zipPath) => { - let path = OS.Path.join(OS.Constants.Path.profileDir, - gmpAddon.id, - gmpAddon.version); - log.info("install to directory path: " + path); - let gmpInstaller = new GMPExtractor(zipPath, path); - let installPromise = gmpInstaller.install(); - return installPromise.then(extractedPaths => { - // Success, set the prefs - let now = Math.round(Date.now() / 1000); - GMPPrefs.set(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, now, gmpAddon.id); - // Remember our ABI, so that if the profile is migrated to another - // platform or from 32 -> 64 bit, we notice and don't try to load the - // unexecutable plugin library. - GMPPrefs.set(GMPPrefs.KEY_PLUGIN_ABI, UpdateUtils.ABI, gmpAddon.id); - // Setting the version pref signals installation completion to consumers, - // if you need to set other prefs etc. do it before this. - GMPPrefs.set(GMPPrefs.KEY_PLUGIN_VERSION, gmpAddon.version, - gmpAddon.id); - return extractedPaths; - }); - }); - }, -}; diff --git a/toolkit/mozapps/webextensions/LightweightThemeManager.jsm b/toolkit/mozapps/webextensions/LightweightThemeManager.jsm deleted file mode 100644 index 5dd41831d..000000000 --- a/toolkit/mozapps/webextensions/LightweightThemeManager.jsm +++ /dev/null @@ -1,909 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -this.EXPORTED_SYMBOLS = ["LightweightThemeManager"]; - -const Cc = Components.classes; -const Ci = Components.interfaces; - -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/AddonManager.jsm"); -/* globals AddonManagerPrivate*/ -Components.utils.import("resource://gre/modules/Services.jsm"); - -const ID_SUFFIX = "@personas.mozilla.org"; -const PREF_LWTHEME_TO_SELECT = "extensions.lwThemeToSelect"; -const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; -const PREF_EM_DSS_ENABLED = "extensions.dss.enabled"; -const ADDON_TYPE = "theme"; - -const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties"; - -const STRING_TYPE_NAME = "type.%ID%.name"; - -const DEFAULT_MAX_USED_THEMES_COUNT = 30; - -const MAX_PREVIEW_SECONDS = 30; - -const MANDATORY = ["id", "name", "headerURL"]; -const OPTIONAL = ["footerURL", "textcolor", "accentcolor", "iconURL", - "previewURL", "author", "description", "homepageURL", - "updateURL", "version"]; - -const PERSIST_ENABLED = true; -const PERSIST_BYPASS_CACHE = false; -const PERSIST_FILES = { - headerURL: "lightweighttheme-header", - footerURL: "lightweighttheme-footer" -}; - -XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeImageOptimizer", - "resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest", - "resource://gre/modules/ServiceRequest.jsm"); - - -XPCOMUtils.defineLazyGetter(this, "_prefs", () => { - return Services.prefs.getBranch("lightweightThemes."); -}); - -Object.defineProperty(this, "_maxUsedThemes", { - get: function() { - delete this._maxUsedThemes; - try { - this._maxUsedThemes = _prefs.getIntPref("maxUsedThemes"); - } - catch (e) { - this._maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT; - } - return this._maxUsedThemes; - }, - - set: function(val) { - delete this._maxUsedThemes; - return this._maxUsedThemes = val; - }, - configurable: true, -}); - -// Holds the ID of the theme being enabled or disabled while sending out the -// events so cached AddonWrapper instances can return correct values for -// permissions and pendingOperations -var _themeIDBeingEnabled = null; -var _themeIDBeingDisabled = null; - -// Convert from the old storage format (in which the order of usedThemes -// was combined with isThemeSelected to determine which theme was selected) -// to the new one (where a selectedThemeID determines which theme is selected). -(function() { - let wasThemeSelected = false; - try { - wasThemeSelected = _prefs.getBoolPref("isThemeSelected"); - } catch (e) { } - - if (wasThemeSelected) { - _prefs.clearUserPref("isThemeSelected"); - let themes = []; - try { - themes = JSON.parse(_prefs.getComplexValue("usedThemes", - Ci.nsISupportsString).data); - } catch (e) { } - - if (Array.isArray(themes) && themes[0]) { - _prefs.setCharPref("selectedThemeID", themes[0].id); - } - } -})(); - -this.LightweightThemeManager = { - get name() { - return "LightweightThemeManager"; - }, - - // Themes that can be added for an application. They can't be removed, and - // will always show up at the top of the list. - _builtInThemes: new Map(), - - get usedThemes () { - let themes = []; - try { - themes = JSON.parse(_prefs.getComplexValue("usedThemes", - Ci.nsISupportsString).data); - } catch (e) { } - - themes.push(...this._builtInThemes.values()); - return themes; - }, - - get currentTheme () { - let selectedThemeID = null; - try { - selectedThemeID = _prefs.getCharPref("selectedThemeID"); - } catch (e) {} - - let data = null; - if (selectedThemeID) { - data = this.getUsedTheme(selectedThemeID); - } - return data; - }, - - get currentThemeForDisplay () { - var data = this.currentTheme; - - if (data && PERSIST_ENABLED) { - for (let key in PERSIST_FILES) { - try { - if (data[key] && _prefs.getBoolPref("persisted." + key)) - data[key] = _getLocalImageURI(PERSIST_FILES[key]).spec - + "?" + data.id + ";" + _version(data); - } catch (e) {} - } - } - - return data; - }, - - set currentTheme (aData) { - return _setCurrentTheme(aData, false); - }, - - setLocalTheme: function(aData) { - _setCurrentTheme(aData, true); - }, - - getUsedTheme: function(aId) { - var usedThemes = this.usedThemes; - for (let usedTheme of usedThemes) { - if (usedTheme.id == aId) - return usedTheme; - } - return null; - }, - - forgetUsedTheme: function(aId) { - let theme = this.getUsedTheme(aId); - if (!theme || LightweightThemeManager._builtInThemes.has(theme.id)) - return; - - let wrapper = new AddonWrapper(theme); - AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false); - - var currentTheme = this.currentTheme; - if (currentTheme && currentTheme.id == aId) { - this.themeChanged(null); - AddonManagerPrivate.notifyAddonChanged(null, ADDON_TYPE, false); - } - - _updateUsedThemes(_usedThemesExceptId(aId)); - AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper); - }, - - addBuiltInTheme: function(theme) { - if (!theme || !theme.id || this.usedThemes.some(t => t.id == theme.id)) { - throw new Error("Trying to add invalid builtIn theme"); - } - - this._builtInThemes.set(theme.id, theme); - - if (_prefs.getCharPref("selectedThemeID") == theme.id) { - this.currentTheme = theme; - } - }, - - forgetBuiltInTheme: function(id) { - if (!this._builtInThemes.has(id)) { - let currentTheme = this.currentTheme; - if (currentTheme && currentTheme.id == id) { - this.currentTheme = null; - } - } - return this._builtInThemes.delete(id); - }, - - clearBuiltInThemes: function() { - for (let id of this._builtInThemes.keys()) { - this.forgetBuiltInTheme(id); - } - }, - - previewTheme: function(aData) { - let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); - cancel.data = false; - Services.obs.notifyObservers(cancel, "lightweight-theme-preview-requested", - JSON.stringify(aData)); - if (cancel.data) - return; - - if (_previewTimer) - _previewTimer.cancel(); - else - _previewTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - _previewTimer.initWithCallback(_previewTimerCallback, - MAX_PREVIEW_SECONDS * 1000, - _previewTimer.TYPE_ONE_SHOT); - - _notifyWindows(aData); - }, - - resetPreview: function() { - if (_previewTimer) { - _previewTimer.cancel(); - _previewTimer = null; - _notifyWindows(this.currentThemeForDisplay); - } - }, - - parseTheme: function(aString, aBaseURI) { - try { - return _sanitizeTheme(JSON.parse(aString), aBaseURI, false); - } catch (e) { - return null; - } - }, - - updateCurrentTheme: function() { - try { - if (!_prefs.getBoolPref("update.enabled")) - return; - } catch (e) { - return; - } - - var theme = this.currentTheme; - if (!theme || !theme.updateURL) - return; - - var req = new ServiceRequest(); - - req.mozBackgroundRequest = true; - req.overrideMimeType("text/plain"); - req.open("GET", theme.updateURL, true); - // Prevent the request from reading from the cache. - req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; - // Prevent the request from writing to the cache. - req.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; - - req.addEventListener("load", () => { - if (req.status != 200) - return; - - let newData = this.parseTheme(req.responseText, theme.updateURL); - if (!newData || - newData.id != theme.id || - _version(newData) == _version(theme)) - return; - - var currentTheme = this.currentTheme; - if (currentTheme && currentTheme.id == theme.id) - this.currentTheme = newData; - }, false); - - req.send(null); - }, - - /** - * Switches to a new lightweight theme. - * - * @param aData - * The lightweight theme to switch to - */ - themeChanged: function(aData) { - if (_previewTimer) { - _previewTimer.cancel(); - _previewTimer = null; - } - - if (aData) { - let usedThemes = _usedThemesExceptId(aData.id); - usedThemes.unshift(aData); - _updateUsedThemes(usedThemes); - if (PERSIST_ENABLED) { - LightweightThemeImageOptimizer.purge(); - _persistImages(aData, function() { - _notifyWindows(this.currentThemeForDisplay); - }.bind(this)); - } - } - - if (aData) - _prefs.setCharPref("selectedThemeID", aData.id); - else - _prefs.setCharPref("selectedThemeID", ""); - - _notifyWindows(aData); - Services.obs.notifyObservers(null, "lightweight-theme-changed", null); - }, - - /** - * Starts the Addons provider and enables the new lightweight theme if - * necessary. - */ - startup: function() { - if (Services.prefs.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) { - let id = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); - if (id) - this.themeChanged(this.getUsedTheme(id)); - else - this.themeChanged(null); - Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT); - } - - _prefs.addObserver("", _prefObserver, false); - }, - - /** - * Shuts down the provider. - */ - shutdown: function() { - _prefs.removeObserver("", _prefObserver); - }, - - /** - * Called when a new add-on has been enabled when only one add-on of that type - * can be enabled. - * - * @param aId - * The ID of the newly enabled add-on - * @param aType - * The type of the newly enabled add-on - * @param aPendingRestart - * true if the newly enabled add-on will only become enabled after a - * restart - */ - addonChanged: function(aId, aType, aPendingRestart) { - if (aType != ADDON_TYPE) - return; - - let id = _getInternalID(aId); - let current = this.currentTheme; - - try { - let next = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); - if (id == next && aPendingRestart) - return; - - Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT); - if (next) { - AddonManagerPrivate.callAddonListeners("onOperationCancelled", - new AddonWrapper(this.getUsedTheme(next))); - } - else if (id == current.id) { - AddonManagerPrivate.callAddonListeners("onOperationCancelled", - new AddonWrapper(current)); - return; - } - } - catch (e) { - } - - if (current) { - if (current.id == id) - return; - _themeIDBeingDisabled = current.id; - let wrapper = new AddonWrapper(current); - if (aPendingRestart) { - Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, ""); - AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, true); - } - else { - AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false); - this.themeChanged(null); - AddonManagerPrivate.callAddonListeners("onDisabled", wrapper); - } - _themeIDBeingDisabled = null; - } - - if (id) { - let theme = this.getUsedTheme(id); - _themeIDBeingEnabled = id; - let wrapper = new AddonWrapper(theme); - if (aPendingRestart) { - AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, true); - Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, id); - - // Flush the preferences to disk so they survive any crash - Services.prefs.savePrefFile(null); - } - else { - AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false); - this.themeChanged(theme); - AddonManagerPrivate.callAddonListeners("onEnabled", wrapper); - } - _themeIDBeingEnabled = null; - } - }, - - /** - * Called to get an Addon with a particular ID. - * - * @param aId - * The ID of the add-on to retrieve - * @param aCallback - * A callback to pass the Addon to - */ - getAddonByID: function(aId, aCallback) { - let id = _getInternalID(aId); - if (!id) { - aCallback(null); - return; - } - - let theme = this.getUsedTheme(id); - if (!theme) { - aCallback(null); - return; - } - - aCallback(new AddonWrapper(theme)); - }, - - /** - * Called to get Addons of a particular type. - * - * @param aTypes - * An array of types to fetch. Can be null to get all types. - * @param aCallback - * A callback to pass an array of Addons to - */ - getAddonsByTypes: function(aTypes, aCallback) { - if (aTypes && aTypes.indexOf(ADDON_TYPE) == -1) { - aCallback([]); - return; - } - - aCallback(this.usedThemes.map(a => new AddonWrapper(a))); - }, -}; - -const wrapperMap = new WeakMap(); -let themeFor = wrapper => wrapperMap.get(wrapper); - -/** - * The AddonWrapper wraps lightweight theme to provide the data visible to - * consumers of the AddonManager API. - */ -function AddonWrapper(aTheme) { - wrapperMap.set(this, aTheme); -} - -AddonWrapper.prototype = { - get id() { - return themeFor(this).id + ID_SUFFIX; - }, - - get type() { - return ADDON_TYPE; - }, - - get isActive() { - let current = LightweightThemeManager.currentTheme; - if (current) - return themeFor(this).id == current.id; - return false; - }, - - get name() { - return themeFor(this).name; - }, - - get version() { - let theme = themeFor(this); - return "version" in theme ? theme.version : ""; - }, - - get creator() { - let theme = themeFor(this); - return "author" in theme ? new AddonManagerPrivate.AddonAuthor(theme.author) : null; - }, - - get screenshots() { - let url = themeFor(this).previewURL; - return [new AddonManagerPrivate.AddonScreenshot(url)]; - }, - - get pendingOperations() { - let pending = AddonManager.PENDING_NONE; - if (this.isActive == this.userDisabled) - pending |= this.isActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE; - return pending; - }, - - get operationsRequiringRestart() { - // If a non-default theme is in use then a restart will be required to - // enable lightweight themes unless dynamic theme switching is enabled - if (Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) { - try { - if (Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED)) - return AddonManager.OP_NEEDS_RESTART_NONE; - } - catch (e) { - } - return AddonManager.OP_NEEDS_RESTART_ENABLE; - } - - return AddonManager.OP_NEEDS_RESTART_NONE; - }, - - get size() { - // The size changes depending on whether the theme is in use or not, this is - // probably not worth exposing. - return null; - }, - - get permissions() { - let permissions = 0; - - // Do not allow uninstall of builtIn themes. - if (!LightweightThemeManager._builtInThemes.has(themeFor(this).id)) - permissions = AddonManager.PERM_CAN_UNINSTALL; - if (this.userDisabled) - permissions |= AddonManager.PERM_CAN_ENABLE; - else - permissions |= AddonManager.PERM_CAN_DISABLE; - return permissions; - }, - - get userDisabled() { - let id = themeFor(this).id; - if (_themeIDBeingEnabled == id) - return false; - if (_themeIDBeingDisabled == id) - return true; - - try { - let toSelect = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); - return id != toSelect; - } - catch (e) { - let current = LightweightThemeManager.currentTheme; - return !current || current.id != id; - } - }, - - set userDisabled(val) { - if (val == this.userDisabled) - return val; - - if (val) - LightweightThemeManager.currentTheme = null; - else - LightweightThemeManager.currentTheme = themeFor(this); - - return val; - }, - - // Lightweight themes are never disabled by the application - get appDisabled() { - return false; - }, - - // Lightweight themes are always compatible - get isCompatible() { - return true; - }, - - get isPlatformCompatible() { - return true; - }, - - get scope() { - return AddonManager.SCOPE_PROFILE; - }, - - get foreignInstall() { - return false; - }, - - uninstall: function() { - LightweightThemeManager.forgetUsedTheme(themeFor(this).id); - }, - - cancelUninstall: function() { - throw new Error("Theme is not marked to be uninstalled"); - }, - - findUpdates: function(listener, reason, appVersion, platformVersion) { - AddonManagerPrivate.callNoUpdateListeners(this, listener, reason, appVersion, platformVersion); - }, - - // Lightweight themes are always compatible - isCompatibleWith: function(appVersion, platformVersion) { - return true; - }, - - // Lightweight themes are always securely updated - get providesUpdatesSecurely() { - return true; - }, - - // Lightweight themes are never blocklisted - get blocklistState() { - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - } -}; - -["description", "homepageURL", "iconURL"].forEach(function(prop) { - Object.defineProperty(AddonWrapper.prototype, prop, { - get: function() { - let theme = themeFor(this); - return prop in theme ? theme[prop] : null; - }, - enumarable: true, - }); -}); - -["installDate", "updateDate"].forEach(function(prop) { - Object.defineProperty(AddonWrapper.prototype, prop, { - get: function() { - let theme = themeFor(this); - return prop in theme ? new Date(theme[prop]) : null; - }, - enumarable: true, - }); -}); - -/** - * Converts the ID used by the public AddonManager API to an lightweight theme - * ID. - * - * @param id - * The ID to be converted - * - * @return the lightweight theme ID or null if the ID was not for a lightweight - * theme. - */ -function _getInternalID(id) { - if (!id) - return null; - let len = id.length - ID_SUFFIX.length; - if (len > 0 && id.substring(len) == ID_SUFFIX) - return id.substring(0, len); - return null; -} - -function _setCurrentTheme(aData, aLocal) { - aData = _sanitizeTheme(aData, null, aLocal); - - let needsRestart = (ADDON_TYPE == "theme") && - Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN); - - let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); - cancel.data = false; - Services.obs.notifyObservers(cancel, "lightweight-theme-change-requested", - JSON.stringify(aData)); - - if (aData) { - let theme = LightweightThemeManager.getUsedTheme(aData.id); - let isInstall = !theme || theme.version != aData.version; - if (isInstall) { - aData.updateDate = Date.now(); - if (theme && "installDate" in theme) - aData.installDate = theme.installDate; - else - aData.installDate = aData.updateDate; - - var oldWrapper = theme ? new AddonWrapper(theme) : null; - var wrapper = new AddonWrapper(aData); - AddonManagerPrivate.callInstallListeners("onExternalInstall", null, - wrapper, oldWrapper, false); - AddonManagerPrivate.callAddonListeners("onInstalling", wrapper, false); - } - - let current = LightweightThemeManager.currentTheme; - let usedThemes = _usedThemesExceptId(aData.id); - if (current && current.id != aData.id) - usedThemes.splice(1, 0, aData); - else - usedThemes.unshift(aData); - _updateUsedThemes(usedThemes); - - if (isInstall) - AddonManagerPrivate.callAddonListeners("onInstalled", wrapper); - } - - if (cancel.data) - return null; - - AddonManagerPrivate.notifyAddonChanged(aData ? aData.id + ID_SUFFIX : null, - ADDON_TYPE, needsRestart); - - return LightweightThemeManager.currentTheme; -} - -function _sanitizeTheme(aData, aBaseURI, aLocal) { - if (!aData || typeof aData != "object") - return null; - - var resourceProtocols = ["http", "https", "resource"]; - if (aLocal) - resourceProtocols.push("file"); - var resourceProtocolExp = new RegExp("^(" + resourceProtocols.join("|") + "):"); - - function sanitizeProperty(prop) { - if (!(prop in aData)) - return null; - if (typeof aData[prop] != "string") - return null; - let val = aData[prop].trim(); - if (!val) - return null; - - if (!/URL$/.test(prop)) - return val; - - try { - val = _makeURI(val, aBaseURI ? _makeURI(aBaseURI) : null).spec; - if ((prop == "updateURL" ? /^https:/ : resourceProtocolExp).test(val)) - return val; - return null; - } - catch (e) { - return null; - } - } - - let result = {}; - for (let mandatoryProperty of MANDATORY) { - let val = sanitizeProperty(mandatoryProperty); - if (!val) - throw Components.results.NS_ERROR_INVALID_ARG; - result[mandatoryProperty] = val; - } - - for (let optionalProperty of OPTIONAL) { - let val = sanitizeProperty(optionalProperty); - if (!val) - continue; - result[optionalProperty] = val; - } - - return result; -} - -function _usedThemesExceptId(aId) { - return LightweightThemeManager.usedThemes.filter(function(t) { - return "id" in t && t.id != aId; - }); -} - -function _version(aThemeData) { - return aThemeData.version || ""; -} - -function _makeURI(aURL, aBaseURI) { - return Services.io.newURI(aURL, null, aBaseURI); -} - -function _updateUsedThemes(aList) { - // Remove app-specific themes before saving them to the usedThemes pref. - aList = aList.filter(theme => !LightweightThemeManager._builtInThemes.has(theme.id)); - - // Send uninstall events for all themes that need to be removed. - while (aList.length > _maxUsedThemes) { - let wrapper = new AddonWrapper(aList[aList.length - 1]); - AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false); - aList.pop(); - AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper); - } - - var str = Cc["@mozilla.org/supports-string;1"] - .createInstance(Ci.nsISupportsString); - str.data = JSON.stringify(aList); - _prefs.setComplexValue("usedThemes", Ci.nsISupportsString, str); - - Services.obs.notifyObservers(null, "lightweight-theme-list-changed", null); -} - -function _notifyWindows(aThemeData) { - Services.obs.notifyObservers(null, "lightweight-theme-styling-update", - JSON.stringify(aThemeData)); -} - -var _previewTimer; -var _previewTimerCallback = { - notify: function() { - LightweightThemeManager.resetPreview(); - } -}; - -/** - * Called when any of the lightweightThemes preferences are changed. - */ -function _prefObserver(aSubject, aTopic, aData) { - switch (aData) { - case "maxUsedThemes": - try { - _maxUsedThemes = _prefs.getIntPref(aData); - } - catch (e) { - _maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT; - } - // Update the theme list to remove any themes over the number we keep - _updateUsedThemes(LightweightThemeManager.usedThemes); - break; - } -} - -function _persistImages(aData, aCallback) { - function onSuccess(key) { - return function () { - let current = LightweightThemeManager.currentTheme; - if (current && current.id == aData.id) { - _prefs.setBoolPref("persisted." + key, true); - } - if (--numFilesToPersist == 0 && aCallback) { - aCallback(); - } - }; - } - - let numFilesToPersist = 0; - for (let key in PERSIST_FILES) { - _prefs.setBoolPref("persisted." + key, false); - if (aData[key]) { - numFilesToPersist++; - _persistImage(aData[key], PERSIST_FILES[key], onSuccess(key)); - } - } -} - -function _getLocalImageURI(localFileName) { - var localFile = Services.dirsvc.get("ProfD", Ci.nsIFile); - localFile.append(localFileName); - return Services.io.newFileURI(localFile); -} - -function _persistImage(sourceURL, localFileName, successCallback) { - if (/^(file|resource):/.test(sourceURL)) - return; - - var targetURI = _getLocalImageURI(localFileName); - var sourceURI = _makeURI(sourceURL); - - var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] - .createInstance(Ci.nsIWebBrowserPersist); - - persist.persistFlags = - Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | - Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION | - (PERSIST_BYPASS_CACHE ? - Ci.nsIWebBrowserPersist.PERSIST_FLAGS_BYPASS_CACHE : - Ci.nsIWebBrowserPersist.PERSIST_FLAGS_FROM_CACHE); - - persist.progressListener = new _persistProgressListener(successCallback); - - persist.saveURI(sourceURI, null, - null, Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE, - null, null, targetURI, null); -} - -function _persistProgressListener(successCallback) { - this.onLocationChange = function() {}; - this.onProgressChange = function() {}; - this.onStatusChange = function() {}; - this.onSecurityChange = function() {}; - this.onStateChange = function(aWebProgress, aRequest, aStateFlags, aStatus) { - if (aRequest && - aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && - aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { - try { - if (aRequest.QueryInterface(Ci.nsIHttpChannel).requestSucceeded) { - // success - successCallback(); - return; - } - } catch (e) { } - // failure - } - }; -} - -AddonManagerPrivate.registerProvider(LightweightThemeManager, [ - new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS, - STRING_TYPE_NAME, - AddonManager.VIEW_TYPE_LIST, 5000) -]); diff --git a/toolkit/mozapps/webextensions/addonManager.js b/toolkit/mozapps/webextensions/addonManager.js deleted file mode 100644 index d34cbaf62..000000000 --- a/toolkit/mozapps/webextensions/addonManager.js +++ /dev/null @@ -1,296 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * This component serves as integration between the platform and AddonManager. - * It is responsible for initializing and shutting down the AddonManager as well - * as passing new installs from webpages to the AddonManager. - */ - -"use strict"; - -const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; - -// The old XPInstall error codes -const EXECUTION_ERROR = -203; -const CANT_READ_ARCHIVE = -207; -const USER_CANCELLED = -210; -const DOWNLOAD_ERROR = -228; -const UNSUPPORTED_TYPE = -244; -const SUCCESS = 0; - -const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled"; -const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage"; -const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback"; - -const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest"; -const MSG_PROMISE_RESULT = "WebAPIPromiseResult"; -const MSG_INSTALL_EVENT = "WebAPIInstallEvent"; -const MSG_INSTALL_CLEANUP = "WebAPICleanup"; -const MSG_ADDON_EVENT_REQ = "WebAPIAddonEventRequest"; -const MSG_ADDON_EVENT = "WebAPIAddonEvent"; - -const CHILD_SCRIPT = "resource://gre/modules/addons/Content.js"; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - -var gSingleton = null; - -function amManager() { - Cu.import("resource://gre/modules/AddonManager.jsm"); - /* globals AddonManagerPrivate*/ - - Services.mm.loadFrameScript(CHILD_SCRIPT, true); - Services.mm.addMessageListener(MSG_INSTALL_ENABLED, this); - Services.mm.addMessageListener(MSG_INSTALL_ADDONS, this); - Services.mm.addMessageListener(MSG_PROMISE_REQUEST, this); - Services.mm.addMessageListener(MSG_INSTALL_CLEANUP, this); - Services.mm.addMessageListener(MSG_ADDON_EVENT_REQ, this); - - Services.obs.addObserver(this, "message-manager-close", false); - Services.obs.addObserver(this, "message-manager-disconnect", false); - - AddonManager.webAPI.setEventHandler(this.sendEvent); - - // Needed so receiveMessage can be called directly by JS callers - this.wrappedJSObject = this; -} - -amManager.prototype = { - observe: function(aSubject, aTopic, aData) { - switch (aTopic) { - case "addons-startup": - AddonManagerPrivate.startup(); - break; - - case "message-manager-close": - case "message-manager-disconnect": - this.childClosed(aSubject); - break; - } - }, - - /** - * @see amIAddonManager.idl - */ - mapURIToAddonID: function(uri, id) { - id.value = AddonManager.mapURIToAddonID(uri); - return !!id.value; - }, - - /** - * @see amIWebInstaller.idl - */ - isInstallEnabled: function(aMimetype, aReferer) { - return AddonManager.isInstallEnabled(aMimetype); - }, - - /** - * @see amIWebInstaller.idl - */ - installAddonsFromWebpage: function(aMimetype, aBrowser, aInstallingPrincipal, - aUris, aHashes, aNames, aIcons, aCallback) { - if (aUris.length == 0) - return false; - - let retval = true; - if (!AddonManager.isInstallAllowed(aMimetype, aInstallingPrincipal)) { - aCallback = null; - retval = false; - } - - let installs = []; - function buildNextInstall() { - if (aUris.length == 0) { - AddonManager.installAddonsFromWebpage(aMimetype, aBrowser, aInstallingPrincipal, installs); - return; - } - let uri = aUris.shift(); - AddonManager.getInstallForURL(uri, function(aInstall) { - function callCallback(aUri, aStatus) { - try { - aCallback.onInstallEnded(aUri, aStatus); - } - catch (e) { - Components.utils.reportError(e); - } - } - - if (aInstall) { - installs.push(aInstall); - if (aCallback) { - aInstall.addListener({ - onDownloadCancelled: function(aInstall) { - callCallback(uri, USER_CANCELLED); - }, - - onDownloadFailed: function(aInstall) { - if (aInstall.error == AddonManager.ERROR_CORRUPT_FILE) - callCallback(uri, CANT_READ_ARCHIVE); - else - callCallback(uri, DOWNLOAD_ERROR); - }, - - onInstallFailed: function(aInstall) { - callCallback(uri, EXECUTION_ERROR); - }, - - onInstallEnded: function(aInstall, aStatus) { - callCallback(uri, SUCCESS); - } - }); - } - } - else if (aCallback) { - aCallback.onInstallEnded(uri, UNSUPPORTED_TYPE); - } - buildNextInstall(); - }, aMimetype, aHashes.shift(), aNames.shift(), aIcons.shift(), null, aBrowser); - } - buildNextInstall(); - - return retval; - }, - - notify: function(aTimer) { - AddonManagerPrivate.backgroundUpdateTimerHandler(); - }, - - // Maps message manager instances for content processes to the associated - // AddonListener instances. - addonListeners: new Map(), - - _addAddonListener(target) { - if (!this.addonListeners.has(target)) { - let handler = (event, id, needsRestart) => { - target.sendAsyncMessage(MSG_ADDON_EVENT, {event, id, needsRestart}); - }; - let listener = { - onEnabling: (addon, needsRestart) => handler("onEnabling", addon.id, needsRestart), - onEnabled: (addon) => handler("onEnabled", addon.id, false), - onDisabling: (addon, needsRestart) => handler("onDisabling", addon.id, needsRestart), - onDisabled: (addon) => handler("onDisabled", addon.id, false), - onInstalling: (addon, needsRestart) => handler("onInstalling", addon.id, needsRestart), - onInstalled: (addon) => handler("onInstalled", addon.id, false), - onUninstalling: (addon, needsRestart) => handler("onUninstalling", addon.id, needsRestart), - onUninstalled: (addon) => handler("onUninstalled", addon.id, false), - onOperationCancelled: (addon) => handler("onOperationCancelled", addon.id, false), - }; - this.addonListeners.set(target, listener); - AddonManager.addAddonListener(listener); - } - }, - - _removeAddonListener(target) { - if (this.addonListeners.has(target)) { - AddonManager.removeAddonListener(this.addonListeners.get(target)); - this.addonListeners.delete(target); - } - }, - - /** - * messageManager callback function. - * - * Listens to requests from child processes for InstallTrigger - * activity, and sends back callbacks. - */ - receiveMessage: function(aMessage) { - let payload = aMessage.data; - - switch (aMessage.name) { - case MSG_INSTALL_ENABLED: - return AddonManager.isInstallEnabled(payload.mimetype); - - case MSG_INSTALL_ADDONS: { - let callback = null; - if (payload.callbackID != -1) { - let mm = aMessage.target.messageManager; - callback = { - onInstallEnded: function(url, status) { - mm.sendAsyncMessage(MSG_INSTALL_CALLBACK, { - callbackID: payload.callbackID, - url: url, - status: status - }); - }, - }; - } - - return this.installAddonsFromWebpage(payload.mimetype, - aMessage.target, payload.triggeringPrincipal, payload.uris, - payload.hashes, payload.names, payload.icons, callback); - } - - case MSG_PROMISE_REQUEST: { - let mm = aMessage.target.messageManager; - let resolve = (value) => { - mm.sendAsyncMessage(MSG_PROMISE_RESULT, { - callbackID: payload.callbackID, - resolve: value - }); - } - let reject = (value) => { - mm.sendAsyncMessage(MSG_PROMISE_RESULT, { - callbackID: payload.callbackID, - reject: value - }); - } - - let API = AddonManager.webAPI; - if (payload.type in API) { - API[payload.type](aMessage.target, ...payload.args).then(resolve, reject); - } - else { - reject("Unknown Add-on API request."); - } - break; - } - - case MSG_INSTALL_CLEANUP: { - AddonManager.webAPI.clearInstalls(payload.ids); - break; - } - - case MSG_ADDON_EVENT_REQ: { - let target = aMessage.target.messageManager; - if (payload.enabled) { - this._addAddonListener(target); - } else { - this._removeAddonListener(target); - } - } - } - return undefined; - }, - - childClosed(target) { - AddonManager.webAPI.clearInstallsFrom(target); - this._removeAddonListener(target); - }, - - sendEvent(mm, data) { - mm.sendAsyncMessage(MSG_INSTALL_EVENT, data); - }, - - classID: Components.ID("{4399533d-08d1-458c-a87a-235f74451cfa}"), - _xpcom_factory: { - createInstance: function(aOuter, aIid) { - if (aOuter != null) - throw Components.Exception("Component does not support aggregation", - Cr.NS_ERROR_NO_AGGREGATION); - - if (!gSingleton) - gSingleton = new amManager(); - return gSingleton.QueryInterface(aIid); - } - }, - QueryInterface: XPCOMUtils.generateQI([Ci.amIAddonManager, - Ci.amIWebInstaller, - Ci.nsITimerCallback, - Ci.nsIObserver, - Ci.nsIMessageListener]) -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amManager]); diff --git a/toolkit/mozapps/webextensions/amInstallTrigger.js b/toolkit/mozapps/webextensions/amInstallTrigger.js deleted file mode 100644 index 382791d32..000000000 --- a/toolkit/mozapps/webextensions/amInstallTrigger.js +++ /dev/null @@ -1,240 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Preferences.jsm"); -Cu.import("resource://gre/modules/Log.jsm"); - -const XPINSTALL_MIMETYPE = "application/x-xpinstall"; - -const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled"; -const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage"; -const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback"; - - -var log = Log.repository.getLogger("AddonManager.InstallTrigger"); -log.level = Log.Level[Preferences.get("extensions.logging.enabled", false) ? "Warn" : "Trace"]; - -function CallbackObject(id, callback, urls, mediator) { - this.id = id; - this.callback = callback; - this.urls = new Set(urls); - this.callCallback = function(url, status) { - try { - this.callback(url, status); - } - catch (e) { - log.warn("InstallTrigger callback threw an exception: " + e); - } - - this.urls.delete(url); - if (this.urls.size == 0) - mediator._callbacks.delete(id); - }; -} - -function RemoteMediator(window) { - window.QueryInterface(Ci.nsIInterfaceRequestor); - let utils = window.getInterface(Ci.nsIDOMWindowUtils); - this._windowID = utils.currentInnerWindowID; - - this.mm = window - .getInterface(Ci.nsIDocShell) - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIContentFrameMessageManager); - this.mm.addWeakMessageListener(MSG_INSTALL_CALLBACK, this); - - this._lastCallbackID = 0; - this._callbacks = new Map(); -} - -RemoteMediator.prototype = { - receiveMessage: function(message) { - if (message.name == MSG_INSTALL_CALLBACK) { - let payload = message.data; - let callbackHandler = this._callbacks.get(payload.callbackID); - if (callbackHandler) { - callbackHandler.callCallback(payload.url, payload.status); - } - } - }, - - enabled: function(url) { - let params = { - mimetype: XPINSTALL_MIMETYPE - }; - return this.mm.sendSyncMessage(MSG_INSTALL_ENABLED, params)[0]; - }, - - install: function(installs, principal, callback, window) { - let callbackID = this._addCallback(callback, installs.uris); - - installs.mimetype = XPINSTALL_MIMETYPE; - installs.triggeringPrincipal = principal; - installs.callbackID = callbackID; - - if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { - // When running in the main process this might be a frame inside an - // in-content UI page, walk up to find the first frame element in a chrome - // privileged document - let element = window.frameElement; - let ssm = Services.scriptSecurityManager; - while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal)) - element = element.ownerDocument.defaultView.frameElement; - - if (element) { - let listener = Cc["@mozilla.org/addons/integration;1"]. - getService(Ci.nsIMessageListener); - return listener.wrappedJSObject.receiveMessage({ - name: MSG_INSTALL_ADDONS, - target: element, - data: installs, - }); - } - } - - // Fall back to sending through the message manager - let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShell) - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIContentFrameMessageManager); - - return messageManager.sendSyncMessage(MSG_INSTALL_ADDONS, installs)[0]; - }, - - _addCallback: function(callback, urls) { - if (!callback || typeof callback != "function") - return -1; - - let callbackID = this._windowID + "-" + ++this._lastCallbackID; - let callbackObject = new CallbackObject(callbackID, callback, urls, this); - this._callbacks.set(callbackID, callbackObject); - return callbackID; - }, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]) -}; - - -function InstallTrigger() { -} - -InstallTrigger.prototype = { - // Here be magic. We've declared ourselves as providing the - // nsIDOMGlobalPropertyInitializer interface, and are registered in the - // "JavaScript-global-property" category in the XPCOM category manager. This - // means that for newly created windows, XPCOM will createinstance this - // object, and then call init, passing in the window for which we need to - // provide an instance. We then initialize ourselves and return the webidl - // version of this object using the webidl-provided _create method, which - // XPCOM will then duly expose as a property value on the window. All this - // indirection is necessary because webidl does not (yet) support statics - // (bug 863952). See bug 926712 for more details about this implementation. - init: function(window) { - this._window = window; - this._principal = window.document.nodePrincipal; - this._url = window.document.documentURIObject; - - try { - this._mediator = new RemoteMediator(window); - } catch (ex) { - // If we can't set up IPC (e.g., because this is a top-level window - // or something), then don't expose InstallTrigger. - return null; - } - - return window.InstallTriggerImpl._create(window, this); - }, - - enabled: function() { - return this._mediator.enabled(this._url.spec); - }, - - updateEnabled: function() { - return this.enabled(); - }, - - install: function(installs, callback) { - let installData = { - uris: [], - hashes: [], - names: [], - icons: [], - }; - - for (let name of Object.keys(installs)) { - let item = installs[name]; - if (typeof item === "string") { - item = { URL: item }; - } - if (!item.URL) { - throw new this._window.Error("Missing URL property for '" + name + "'"); - } - - let url = this._resolveURL(item.URL); - if (!this._checkLoadURIFromScript(url)) { - throw new this._window.Error("Insufficient permissions to install: " + url.spec); - } - - let iconUrl = null; - if (item.IconURL) { - iconUrl = this._resolveURL(item.IconURL); - if (!this._checkLoadURIFromScript(iconUrl)) { - iconUrl = null; // If page can't load the icon, just ignore it - } - } - - installData.uris.push(url.spec); - installData.hashes.push(item.Hash || null); - installData.names.push(name); - installData.icons.push(iconUrl ? iconUrl.spec : null); - } - - return this._mediator.install(installData, this._principal, callback, this._window); - }, - - startSoftwareUpdate: function(url, flags) { - let filename = Services.io.newURI(url, null, null) - .QueryInterface(Ci.nsIURL) - .filename; - let args = {}; - args[filename] = { "URL": url }; - return this.install(args); - }, - - installChrome: function(type, url, skin) { - return this.startSoftwareUpdate(url); - }, - - _resolveURL: function(url) { - return Services.io.newURI(url, null, this._url); - }, - - _checkLoadURIFromScript: function(uri) { - let secman = Services.scriptSecurityManager; - try { - secman.checkLoadURIWithPrincipal(this._principal, - uri, - secman.DISALLOW_INHERIT_PRINCIPAL); - return true; - } - catch (e) { - return false; - } - }, - - classID: Components.ID("{9df8ef2b-94da-45c9-ab9f-132eb55fddf1}"), - contractID: "@mozilla.org/addons/installtrigger;1", - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIDOMGlobalPropertyInitializer]) -}; - - - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InstallTrigger]); diff --git a/toolkit/mozapps/webextensions/amWebAPI.js b/toolkit/mozapps/webextensions/amWebAPI.js deleted file mode 100644 index 5ad0d23f1..000000000 --- a/toolkit/mozapps/webextensions/amWebAPI.js +++ /dev/null @@ -1,269 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); - -const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest"; -const MSG_PROMISE_RESULT = "WebAPIPromiseResult"; -const MSG_INSTALL_EVENT = "WebAPIInstallEvent"; -const MSG_INSTALL_CLEANUP = "WebAPICleanup"; -const MSG_ADDON_EVENT_REQ = "WebAPIAddonEventRequest"; -const MSG_ADDON_EVENT = "WebAPIAddonEvent"; - -class APIBroker { - constructor(mm) { - this.mm = mm; - - this._promises = new Map(); - - // _installMap maps integer ids to DOM AddonInstall instances - this._installMap = new Map(); - - this.mm.addMessageListener(MSG_PROMISE_RESULT, this); - this.mm.addMessageListener(MSG_INSTALL_EVENT, this); - - this._eventListener = null; - } - - receiveMessage(message) { - let payload = message.data; - - switch (message.name) { - case MSG_PROMISE_RESULT: { - if (!this._promises.has(payload.callbackID)) { - return; - } - - let resolve = this._promises.get(payload.callbackID); - this._promises.delete(payload.callbackID); - resolve(payload); - break; - } - - case MSG_INSTALL_EVENT: { - let install = this._installMap.get(payload.id); - if (!install) { - let err = new Error(`Got install event for unknown install ${payload.id}`); - Cu.reportError(err); - return; - } - install._dispatch(payload); - break; - } - - case MSG_ADDON_EVENT: { - if (this._eventListener) { - this._eventListener(payload); - } - } - } - } - - sendRequest(type, ...args) { - return new Promise(resolve => { - let callbackID = APIBroker._nextID++; - - this._promises.set(callbackID, resolve); - this.mm.sendAsyncMessage(MSG_PROMISE_REQUEST, { type, callbackID, args }); - }); - } - - setAddonListener(callback) { - this._eventListener = callback; - if (callback) { - this.mm.addMessageListener(MSG_ADDON_EVENT, this); - this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: true}); - } else { - this.mm.removeMessageListener(MSG_ADDON_EVENT, this); - this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: false}); - } - } - - sendCleanup(ids) { - this.setAddonListener(null); - this.mm.sendAsyncMessage(MSG_INSTALL_CLEANUP, { ids }); - } -} - -APIBroker._nextID = 0; - -// Base class for building classes to back content-exposed interfaces. -class APIObject { - init(window, broker, properties) { - this.window = window; - this.broker = broker; - - // Copy any provided properties onto this object, webidl bindings - // will only expose to content what should be exposed. - for (let key of Object.keys(properties)) { - this[key] = properties[key]; - } - } - - /** - * Helper to implement an asychronous method visible to content, where - * the method is implemented by sending a message to the parent process - * and then wrapping the returned object or error in an appropriate object. - * This helper method ensures that: - * - Returned Promise objects are from the content window - * - Rejected Promises have Error objects from the content window - * - Only non-internal errors are exposed to the caller - * - * @param {string} apiRequest The command to invoke in the parent process. - * @param {array} apiArgs The arguments to include with the - * request to the parent process. - * @param {function} resultConvert If provided, a function called with the - * result from the parent process as an - * argument. Used to convert the result - * into something appropriate for content. - * @returns {Promise} A Promise suitable for passing directly to content. - */ - _apiTask(apiRequest, apiArgs, resultConverter) { - let win = this.window; - let broker = this.broker; - return new win.Promise((resolve, reject) => { - Task.spawn(function*() { - let result = yield broker.sendRequest(apiRequest, ...apiArgs); - if ("reject" in result) { - let err = new win.Error(result.reject.message); - // We don't currently put any other properties onto Errors - // generated by mozAddonManager. If/when we do, they will - // need to get copied here. - reject(err); - return; - } - - let obj = result.resolve; - if (resultConverter) { - obj = resultConverter(obj); - } - resolve(obj); - }).catch(err => { - Cu.reportError(err); - reject(new win.Error("Unexpected internal error")); - }); - }); - } -} - -class Addon extends APIObject { - constructor(...args) { - super(); - this.init(...args); - } - - uninstall() { - return this._apiTask("addonUninstall", [this.id]); - } - - setEnabled(value) { - return this._apiTask("addonSetEnabled", [this.id, value]); - } -} - -class AddonInstall extends APIObject { - constructor(window, broker, properties) { - super(); - this.init(window, broker, properties); - - broker._installMap.set(properties.id, this); - } - - _dispatch(data) { - // The message for the event includes updated copies of all install - // properties. Use the usual "let webidl filter visible properties" trick. - for (let key of Object.keys(data)) { - this[key] = data[key]; - } - - let event = new this.window.Event(data.event); - this.__DOM_IMPL__.dispatchEvent(event); - } - - install() { - return this._apiTask("addonInstallDoInstall", [this.id]); - } - - cancel() { - return this._apiTask("addonInstallCancel", [this.id]); - } -} - -class WebAPI extends APIObject { - constructor() { - super(); - this.allInstalls = []; - this.listenerCount = 0; - } - - init(window) { - let mm = window - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell) - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIContentFrameMessageManager); - let broker = new APIBroker(mm); - - super.init(window, broker, {}); - - window.addEventListener("unload", event => { - this.broker.sendCleanup(this.allInstalls); - }); - } - - getAddonByID(id) { - return this._apiTask("getAddonByID", [id], addonInfo => { - if (!addonInfo) { - return null; - } - let addon = new Addon(this.window, this.broker, addonInfo); - return this.window.Addon._create(this.window, addon); - }); - } - - createInstall(options) { - return this._apiTask("createInstall", [options], installInfo => { - if (!installInfo) { - return null; - } - let install = new AddonInstall(this.window, this.broker, installInfo); - this.allInstalls.push(installInfo.id); - return this.window.AddonInstall._create(this.window, install); - }); - } - - eventListenerWasAdded(type) { - if (this.listenerCount == 0) { - this.broker.setAddonListener(data => { - let event = new this.window.AddonEvent(data.event, data); - this.__DOM_IMPL__.dispatchEvent(event); - }); - } - this.listenerCount++; - } - - eventListenerWasRemoved(type) { - this.listenerCount--; - if (this.listenerCount == 0) { - this.broker.setAddonListener(null); - } - } - - QueryInterface(iid) { - if (iid.equals(WebAPI.classID) || iid.equals(Ci.nsISupports) - || iid.equals(Ci.nsIDOMGlobalPropertyInitializer)) { - return this; - } - return Cr.NS_ERROR_NO_INTERFACE; - } -} - -WebAPI.prototype.classID = Components.ID("{8866d8e3-4ea5-48b7-a891-13ba0ac15235}"); -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebAPI]); diff --git a/toolkit/mozapps/webextensions/amWebInstallListener.js b/toolkit/mozapps/webextensions/amWebInstallListener.js deleted file mode 100644 index 0bcc345e8..000000000 --- a/toolkit/mozapps/webextensions/amWebInstallListener.js +++ /dev/null @@ -1,348 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * This is a default implementation of amIWebInstallListener that should work - * for most applications but can be overriden. It notifies the observer service - * about blocked installs. For normal installs it pops up an install - * confirmation when all the add-ons have been downloaded. - */ - -"use strict"; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/AddonManager.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Preferences.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "PromptUtils", "resource://gre/modules/SharedPromptUtils.jsm"); - -const URI_XPINSTALL_DIALOG = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; - -// Installation can begin from any of these states -const READY_STATES = [ - AddonManager.STATE_AVAILABLE, - AddonManager.STATE_DOWNLOAD_FAILED, - AddonManager.STATE_INSTALL_FAILED, - AddonManager.STATE_CANCELLED -]; - -Cu.import("resource://gre/modules/Log.jsm"); -const LOGGER_ID = "addons.weblistener"; - -// Create a new logger for use by the Addons Web Listener -// (Requires AddonManager.jsm) -var logger = Log.repository.getLogger(LOGGER_ID); - -function notifyObservers(aTopic, aBrowser, aUri, aInstalls) { - let info = { - browser: aBrowser, - originatingURI: aUri, - installs: aInstalls, - - QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) - }; - Services.obs.notifyObservers(info, aTopic, null); -} - -/** - * Creates a new installer to monitor downloads and prompt to install when - * ready - * - * @param aBrowser - * The browser that started the installations - * @param aUrl - * The URL that started the installations - * @param aInstalls - * An array of AddonInstalls - */ -function Installer(aBrowser, aUrl, aInstalls) { - this.browser = aBrowser; - this.url = aUrl; - this.downloads = aInstalls; - this.installed = []; - - notifyObservers("addon-install-started", aBrowser, aUrl, aInstalls); - - for (let install of aInstalls) { - install.addListener(this); - - // Start downloading if it hasn't already begun - if (READY_STATES.indexOf(install.state) != -1) - install.install(); - } - - this.checkAllDownloaded(); -} - -Installer.prototype = { - browser: null, - downloads: null, - installed: null, - isDownloading: true, - - /** - * Checks if all downloads are now complete and if so prompts to install. - */ - checkAllDownloaded: function() { - // Prevent re-entrancy caused by the confirmation dialog cancelling unwanted - // installs. - if (!this.isDownloading) - return; - - var failed = []; - var installs = []; - - for (let install of this.downloads) { - switch (install.state) { - case AddonManager.STATE_AVAILABLE: - case AddonManager.STATE_DOWNLOADING: - // Exit early if any add-ons haven't started downloading yet or are - // still downloading - return; - case AddonManager.STATE_DOWNLOAD_FAILED: - failed.push(install); - break; - case AddonManager.STATE_DOWNLOADED: - // App disabled items are not compatible and so fail to install - if (install.addon.appDisabled) - failed.push(install); - else - installs.push(install); - - if (install.linkedInstalls) { - for (let linkedInstall of install.linkedInstalls) { - linkedInstall.addListener(this); - // Corrupt or incompatible items fail to install - if (linkedInstall.state == AddonManager.STATE_DOWNLOAD_FAILED || linkedInstall.addon.appDisabled) - failed.push(linkedInstall); - else - installs.push(linkedInstall); - } - } - break; - case AddonManager.STATE_CANCELLED: - // Just ignore cancelled downloads - break; - default: - logger.warn("Download of " + install.sourceURI.spec + " in unexpected state " + - install.state); - } - } - - this.isDownloading = false; - this.downloads = installs; - - if (failed.length > 0) { - // Stop listening and cancel any installs that are failed because of - // compatibility reasons. - for (let install of failed) { - if (install.state == AddonManager.STATE_DOWNLOADED) { - install.removeListener(this); - install.cancel(); - } - } - notifyObservers("addon-install-failed", this.browser, this.url, failed); - } - - // If none of the downloads were successful then exit early - if (this.downloads.length == 0) - return; - - // Check for a custom installation prompt that may be provided by the - // applicaton - if ("@mozilla.org/addons/web-install-prompt;1" in Cc) { - try { - let prompt = Cc["@mozilla.org/addons/web-install-prompt;1"]. - getService(Ci.amIWebInstallPrompt); - prompt.confirm(this.browser, this.url, this.downloads, this.downloads.length); - return; - } - catch (e) {} - } - - if (Preferences.get("xpinstall.customConfirmationUI", false)) { - notifyObservers("addon-install-confirmation", this.browser, this.url, this.downloads); - return; - } - - let args = {}; - args.url = this.url; - args.installs = this.downloads; - args.wrappedJSObject = args; - - try { - Cc["@mozilla.org/base/telemetry;1"]. - getService(Ci.nsITelemetry). - getHistogramById("SECURITY_UI"). - add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL); - let parentWindow = null; - if (this.browser) { - parentWindow = this.browser.ownerDocument.defaultView; - PromptUtils.fireDialogEvent(parentWindow, "DOMWillOpenModalDialog", this.browser); - } - Services.ww.openWindow(parentWindow, URI_XPINSTALL_DIALOG, - null, "chrome,modal,centerscreen", args); - } catch (e) { - logger.warn("Exception showing install confirmation dialog", e); - for (let install of this.downloads) { - install.removeListener(this); - // Cancel the installs, as currently there is no way to make them fail - // from here. - install.cancel(); - } - notifyObservers("addon-install-cancelled", this.browser, this.url, - this.downloads); - } - }, - - /** - * Checks if all installs are now complete and if so notifies observers. - */ - checkAllInstalled: function() { - var failed = []; - - for (let install of this.downloads) { - switch (install.state) { - case AddonManager.STATE_DOWNLOADED: - case AddonManager.STATE_INSTALLING: - // Exit early if any add-ons haven't started installing yet or are - // still installing - return; - case AddonManager.STATE_INSTALL_FAILED: - failed.push(install); - break; - } - } - - this.downloads = null; - - if (failed.length > 0) - notifyObservers("addon-install-failed", this.browser, this.url, failed); - - if (this.installed.length > 0) - notifyObservers("addon-install-complete", this.browser, this.url, this.installed); - this.installed = null; - }, - - onDownloadCancelled: function(aInstall) { - aInstall.removeListener(this); - this.checkAllDownloaded(); - }, - - onDownloadFailed: function(aInstall) { - aInstall.removeListener(this); - this.checkAllDownloaded(); - }, - - onDownloadEnded: function(aInstall) { - this.checkAllDownloaded(); - return false; - }, - - onInstallCancelled: function(aInstall) { - aInstall.removeListener(this); - this.checkAllInstalled(); - }, - - onInstallFailed: function(aInstall) { - aInstall.removeListener(this); - this.checkAllInstalled(); - }, - - onInstallEnded: function(aInstall) { - aInstall.removeListener(this); - this.installed.push(aInstall); - - // If installing a theme that is disabled and can be enabled then enable it - if (aInstall.addon.type == "theme" && - aInstall.addon.userDisabled == true && - aInstall.addon.appDisabled == false) { - aInstall.addon.userDisabled = false; - } - - this.checkAllInstalled(); - } -}; - -function extWebInstallListener() { -} - -extWebInstallListener.prototype = { - /** - * @see amIWebInstallListener.idl - */ - onWebInstallDisabled: function(aBrowser, aUri, aInstalls) { - let info = { - browser: aBrowser, - originatingURI: aUri, - installs: aInstalls, - - QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) - }; - Services.obs.notifyObservers(info, "addon-install-disabled", null); - }, - - /** - * @see amIWebInstallListener.idl - */ - onWebInstallOriginBlocked: function(aBrowser, aUri, aInstalls) { - let info = { - browser: aBrowser, - originatingURI: aUri, - installs: aInstalls, - - install: function() { - }, - - QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) - }; - Services.obs.notifyObservers(info, "addon-install-origin-blocked", null); - - return false; - }, - - /** - * @see amIWebInstallListener.idl - */ - onWebInstallBlocked: function(aBrowser, aUri, aInstalls) { - let info = { - browser: aBrowser, - originatingURI: aUri, - installs: aInstalls, - - install: function() { - new Installer(this.browser, this.originatingURI, this.installs); - }, - - QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) - }; - Services.obs.notifyObservers(info, "addon-install-blocked", null); - - return false; - }, - - /** - * @see amIWebInstallListener.idl - */ - onWebInstallRequested: function(aBrowser, aUri, aInstalls) { - new Installer(aBrowser, aUri, aInstalls); - - // We start the installs ourself - return false; - }, - - classDescription: "XPI Install Handler", - contractID: "@mozilla.org/addons/web-install-listener;1", - classID: Components.ID("{0f38e086-89a3-40a5-8ffc-9b694de1d04a}"), - QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallListener, - Ci.amIWebInstallListener2]) -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([extWebInstallListener]); diff --git a/toolkit/mozapps/webextensions/content/about.js b/toolkit/mozapps/webextensions/content/about.js deleted file mode 100644 index 4f8fb353e..000000000 --- a/toolkit/mozapps/webextensions/content/about.js +++ /dev/null @@ -1,103 +0,0 @@ -// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -/* import-globals-from ../../../content/contentAreaUtils.js */ - -var Cu = Components.utils; -Cu.import("resource://gre/modules/AddonManager.jsm"); - -function init() { - var addon = window.arguments[0]; - var extensionsStrings = document.getElementById("extensionsStrings"); - - document.documentElement.setAttribute("addontype", addon.type); - - var iconURL = AddonManager.getPreferredIconURL(addon, 48, window); - if (iconURL) { - var extensionIcon = document.getElementById("extensionIcon"); - extensionIcon.src = iconURL; - } - - document.title = extensionsStrings.getFormattedString("aboutWindowTitle", [addon.name]); - var extensionName = document.getElementById("extensionName"); - extensionName.textContent = addon.name; - - var extensionVersion = document.getElementById("extensionVersion"); - if (addon.version) - extensionVersion.setAttribute("value", extensionsStrings.getFormattedString("aboutWindowVersionString", [addon.version])); - else - extensionVersion.hidden = true; - - var extensionDescription = document.getElementById("extensionDescription"); - if (addon.description) - extensionDescription.textContent = addon.description; - else - extensionDescription.hidden = true; - - var numDetails = 0; - - var extensionCreator = document.getElementById("extensionCreator"); - if (addon.creator) { - extensionCreator.setAttribute("value", addon.creator); - numDetails++; - } else { - extensionCreator.hidden = true; - var extensionCreatorLabel = document.getElementById("extensionCreatorLabel"); - extensionCreatorLabel.hidden = true; - } - - var extensionHomepage = document.getElementById("extensionHomepage"); - var homepageURL = addon.homepageURL; - if (homepageURL) { - extensionHomepage.setAttribute("homepageURL", homepageURL); - extensionHomepage.setAttribute("tooltiptext", homepageURL); - numDetails++; - } else { - extensionHomepage.hidden = true; - } - - numDetails += appendToList("extensionDevelopers", "developersBox", addon.developers); - numDetails += appendToList("extensionTranslators", "translatorsBox", addon.translators); - numDetails += appendToList("extensionContributors", "contributorsBox", addon.contributors); - - if (numDetails == 0) { - var groove = document.getElementById("groove"); - groove.hidden = true; - var extensionDetailsBox = document.getElementById("extensionDetailsBox"); - extensionDetailsBox.hidden = true; - } - - var acceptButton = document.documentElement.getButton("accept"); - acceptButton.label = extensionsStrings.getString("aboutWindowCloseButton"); - - setTimeout(sizeToContent, 0); -} - -function appendToList(aHeaderId, aNodeId, aItems) { - var header = document.getElementById(aHeaderId); - var node = document.getElementById(aNodeId); - - if (!aItems || aItems.length == 0) { - header.hidden = true; - return 0; - } - - for (let currentItem of aItems) { - var label = document.createElement("label"); - label.textContent = currentItem; - label.setAttribute("class", "contributor"); - node.appendChild(label); - } - - return aItems.length; -} - -function loadHomepage(aEvent) { - window.close(); - openURL(aEvent.target.getAttribute("homepageURL")); -} diff --git a/toolkit/mozapps/webextensions/content/eula.js b/toolkit/mozapps/webextensions/content/eula.js deleted file mode 100644 index 537ee7284..000000000 --- a/toolkit/mozapps/webextensions/content/eula.js +++ /dev/null @@ -1,25 +0,0 @@ -// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -var Cu = Components.utils; -Cu.import("resource://gre/modules/AddonManager.jsm"); - -function Startup() { - var bundle = document.getElementById("extensionsStrings"); - var addon = window.arguments[0].addon; - - document.documentElement.setAttribute("addontype", addon.type); - - var iconURL = AddonManager.getPreferredIconURL(addon, 48, window); - if (iconURL) - document.getElementById("icon").src = iconURL; - - var label = document.createTextNode(bundle.getFormattedString("eulaHeader", [addon.name])); - document.getElementById("heading").appendChild(label); - document.getElementById("eula").value = addon.eula; -} diff --git a/toolkit/mozapps/webextensions/content/extensions.css b/toolkit/mozapps/webextensions/content/extensions.css deleted file mode 100644 index cb5313365..000000000 --- a/toolkit/mozapps/webextensions/content/extensions.css +++ /dev/null @@ -1,270 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -@namespace xhtml "http://www.w3.org/1999/xhtml"; - -/* HTML link elements do weird things to the layout if they are not hidden */ -xhtml|link { - display: none; -} - -#categories { - -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#categories-list"); -} - -.category { - -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#category"); -} - -.sort-controls { - -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#sorters"); -} - -.addon[status="installed"] { - -moz-box-orient: vertical; - -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-generic"); -} - -.addon[status="installing"] { - -moz-box-orient: vertical; - -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-installing"); -} - -.addon[pending="uninstall"] { - -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-uninstalled"); -} - -.creator { - -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#creator-link"); -} - -.meta-rating { - -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#rating"); -} - -.download-progress, .download-progress[mode="undetermined"] { - -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#download-progress"); -} - -.install-status { - -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#install-status"); -} - -.detail-row { - -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#detail-row"); -} - -.text-list { - white-space: pre-line; - -moz-user-select: element; -} - -setting, row[unsupported="true"] { - display: none; -} - -setting[type="bool"] { - display: -moz-grid-line; - -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-bool"); -} - -setting[type="bool"][localized="true"] { - display: -moz-grid-line; - -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-localized-bool"); -} - -setting[type="bool"]:not([learnmore]) .preferences-learnmore, -setting[type="boolint"]:not([learnmore]) .preferences-learnmore { - visibility: collapse; -} - -setting[type="boolint"] { - display: -moz-grid-line; - -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-boolint"); -} - -setting[type="integer"] { - display: -moz-grid-line; - -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-integer"); -} - -setting[type="integer"]:not([size]) textbox { - -moz-box-flex: 1; -} - -setting[type="control"] { - display: -moz-grid-line; - -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-control"); -} - -setting[type="string"] { - display: -moz-grid-line; - -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-string"); -} - -setting[type="color"] { - display: -moz-grid-line; - -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-color"); -} - -setting[type="file"], -setting[type="directory"] { - display: -moz-grid-line; - -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-path"); -} - -setting[type="radio"], -setting[type="menulist"] { - display: -moz-grid-line; - -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-multi"); -} - -#addonitem-popup > menuitem[disabled="true"] { - display: none; -} - -#addonitem-popup[addontype="theme"] > #menuitem_enableItem, -#addonitem-popup[addontype="theme"] > #menuitem_disableItem, -#addonitem-popup:not([addontype="theme"]) > #menuitem_enableTheme, -#addonitem-popup:not([addontype="theme"]) > #menuitem_disableTheme { - display: none; -} - -#show-disabled-unsigned-extensions .button-text { - margin-inline-start: 3px !important; - margin-inline-end: 2px !important; -} - -#header-searching:not([active]) { - visibility: hidden; -} - -#search-list[local="false"] > .addon[remote="false"], -#search-list[remote="false"] > .addon[remote="true"] { - visibility: collapse; -} - -#detail-view { - overflow: auto; -} - -.addon:not([notification="warning"]) .warning, -.addon:not([notification="error"]) .error, -.addon:not([notification="info"]) .info, -.addon:not([pending]) .pending, -.addon:not([upgrade="true"]) .update-postfix, -.addon[active="true"] .disabled-postfix, -.addon[pending="install"] .update-postfix, -.addon[pending="install"] .disabled-postfix, -#detail-view:not([notification="warning"]) .warning, -#detail-view:not([notification="error"]) .error, -#detail-view:not([notification="info"]) .info, -#detail-view:not([pending]) .pending, -#detail-view:not([upgrade="true"]) .update-postfix, -#detail-view[active="true"] .disabled-postfix, -#detail-view[loading] .detail-view-container, -#detail-view:not([loading]) .alert-container, -.detail-row:not([value]), -#search-list[remote="false"] #search-allresults-link { - display: none; -} - -#addons-page:not([warning]) #list-view > .global-warning-container { - display: none; -} -#addon-list .date-updated { - display: none; -} - -.view-pane:not(#updates-view) .addon .relnotes-toggle, -.view-pane:not(#updates-view) .addon .include-update, -#updates-view:not([updatetype="available"]) .addon .include-update, -#updates-view[updatetype="available"] .addon .update-available-notice { - display: none; -} - -#addons-page:not([warning]) .global-warning, -#addons-page:not([warning="safemode"]) .global-warning-safemode, -#addons-page:not([warning="checkcompatibility"]) .global-warning-checkcompatibility, -#addons-page:not([warning="updatesecurity"]) .global-warning-updatesecurity { - display: none; -} - -/* Plugins aren't yet disabled by safemode (bug 342333), - so don't show that warning when viewing plugins. */ -#addons-page[warning="safemode"] .view-pane[type="plugin"] .global-warning-container, -#addons-page[warning="safemode"] #detail-view[loading="true"] .global-warning { - display: none; -} - -#addons-page .view-pane:not([type="plugin"]) #plugindeprecation-notice { - display: none; -} - -#addons-page .view-pane:not([type="experiment"]) .experiment-info-container { - display: none; -} - -.addon .relnotes { - -moz-user-select: text; -} -#detail-name, #detail-desc, #detail-fulldesc { - -moz-user-select: text; -} - -/* Make sure we're not animating hidden images. See bug 623739. */ -#view-port:not([selectedIndex="0"]) #discover-view .loading, -#discover-view:not([selectedIndex="0"]) .loading { - display: none; -} - -/* Elements in unselected richlistitems cannot be focused */ -richlistitem:not([selected]) * { - -moz-user-focus: ignore; -} - -#header-search { - width: 22em; -} - -#header-utils-btn { - -moz-user-focus: normal; -} - -.discover-button[disabled="true"] { - display: none; -} - -#experiments-learn-more[disabled="true"] { - display: none; -} - -#experiments-change-telemetry[disabled="true"] { - display: none; -} - -.view-pane[type="experiment"] .error, -.view-pane[type="experiment"] .warning, -.view-pane[type="experiment"] .addon:not([pending="uninstall"]) .pending, -.view-pane[type="experiment"] .disabled-postfix, -.view-pane[type="experiment"] .update-postfix, -.view-pane[type="experiment"] .addon-control.enable, -.view-pane[type="experiment"] .addon-control.disable, -#detail-view[type="experiment"] .alert-container, -#detail-view[type="experiment"] #detail-version, -#detail-view[type="experiment"] #detail-creator, -#detail-view[type="experiment"] #detail-enable-btn, -#detail-view[type="experiment"] #detail-disable-btn { - display: none; -} - -.view-pane:not([type="experiment"]) .experiment-container, -.view-pane:not([type="experiment"]) #detail-experiment-container { - display: none; -} - -.addon[type="experiment"][status="installing"] .experiment-time, -.addon[type="experiment"][status="installing"] .experiment-state { - display: none; -} diff --git a/toolkit/mozapps/webextensions/content/extensions.js b/toolkit/mozapps/webextensions/content/extensions.js deleted file mode 100644 index 3159eb1e1..000000000 --- a/toolkit/mozapps/webextensions/content/extensions.js +++ /dev/null @@ -1,3827 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -/* import-globals-from ../../../content/contentAreaUtils.js */ -/* globals XMLStylesheetProcessingInstruction*/ - -var Cc = Components.classes; -var Ci = Components.interfaces; -var Cu = Components.utils; -var Cr = Components.results; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/DownloadUtils.jsm"); -Cu.import("resource://gre/modules/AddonManager.jsm"); -Cu.import("resource://gre/modules/addons/AddonRepository.jsm"); - -const CONSTANTS = {}; -Cu.import("resource://gre/modules/addons/AddonConstants.jsm", CONSTANTS); -const SIGNING_REQUIRED = CONSTANTS.REQUIRE_SIGNING ? - true : - Services.prefs.getBoolPref("xpinstall.signatures.required"); - -XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", - "resource://gre/modules/PluralForm.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Preferences", - "resource://gre/modules/Preferences.jsm"); - -const PREF_DISCOVERURL = "extensions.webservice.discoverURL"; -const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane"; -const PREF_XPI_ENABLED = "xpinstall.enabled"; -const PREF_MAXRESULTS = "extensions.getAddons.maxResults"; -const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; -const PREF_GETADDONS_CACHE_ID_ENABLED = "extensions.%ID%.getAddons.cache.enabled"; -const PREF_UI_TYPE_HIDDEN = "extensions.ui.%TYPE%.hidden"; -const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory"; - -const LOADING_MSG_DELAY = 100; - -const SEARCH_SCORE_MULTIPLIER_NAME = 2; -const SEARCH_SCORE_MULTIPLIER_DESCRIPTION = 2; - -// Use integers so search scores are sortable by nsIXULSortService -const SEARCH_SCORE_MATCH_WHOLEWORD = 10; -const SEARCH_SCORE_MATCH_WORDBOUNDRY = 6; -const SEARCH_SCORE_MATCH_SUBSTRING = 3; - -const UPDATES_RECENT_TIMESPAN = 2 * 24 * 3600000; // 2 days (in milliseconds) -const UPDATES_RELEASENOTES_TRANSFORMFILE = "chrome://mozapps/content/extensions/updateinfo.xsl"; - -const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" - -var gViewDefault = "addons://discover/"; - -var gStrings = {}; -XPCOMUtils.defineLazyServiceGetter(gStrings, "bundleSvc", - "@mozilla.org/intl/stringbundle;1", - "nsIStringBundleService"); - -XPCOMUtils.defineLazyGetter(gStrings, "brand", function() { - return this.bundleSvc.createBundle("chrome://branding/locale/brand.properties"); -}); -XPCOMUtils.defineLazyGetter(gStrings, "ext", function() { - return this.bundleSvc.createBundle("chrome://mozapps/locale/extensions/extensions.properties"); -}); -XPCOMUtils.defineLazyGetter(gStrings, "dl", function() { - return this.bundleSvc.createBundle("chrome://mozapps/locale/downloads/downloads.properties"); -}); - -XPCOMUtils.defineLazyGetter(gStrings, "brandShortName", function() { - return this.brand.GetStringFromName("brandShortName"); -}); -XPCOMUtils.defineLazyGetter(gStrings, "appVersion", function() { - return Services.appinfo.version; -}); - -document.addEventListener("load", initialize, true); -window.addEventListener("unload", shutdown, false); - -class MessageDispatcher { - constructor(target) { - this.listeners = new Map(); - this.target = target; - } - - addMessageListener(name, handler) { - if (!this.listeners.has(name)) { - this.listeners.set(name, new Set()); - } - - this.listeners.get(name).add(handler); - } - - removeMessageListener(name, handler) { - if (this.listeners.has(name)) { - this.listeners.get(name).delete(handler); - } - } - - sendAsyncMessage(name, data) { - for (let handler of this.listeners.get(name) || new Set()) { - Promise.resolve().then(() => { - handler.receiveMessage({ - name, - data, - target: this.target, - }); - }); - } - } -} - -/** - * A mock FrameMessageManager global to allow frame scripts to run in - * non-top-level, non-remote s as if they were top-level or - * remote. - * - * @param {Element} browser - * A XUL element. - */ -class FakeFrameMessageManager { - constructor(browser) { - let dispatcher = new MessageDispatcher(browser); - let frameDispatcher = new MessageDispatcher(null); - - this.sendAsyncMessage = frameDispatcher.sendAsyncMessage.bind(frameDispatcher); - this.addMessageListener = dispatcher.addMessageListener.bind(dispatcher); - this.removeMessageListener = dispatcher.removeMessageListener.bind(dispatcher); - - this.frame = { - get content() { - return browser.contentWindow; - }, - - get docShell() { - return browser.docShell; - }, - - addEventListener: browser.addEventListener.bind(browser), - removeEventListener: browser.removeEventListener.bind(browser), - - sendAsyncMessage: dispatcher.sendAsyncMessage.bind(dispatcher), - addMessageListener: frameDispatcher.addMessageListener.bind(frameDispatcher), - removeMessageListener: frameDispatcher.removeMessageListener.bind(frameDispatcher), - } - } - - loadFrameScript(url) { - Services.scriptloader.loadSubScript(url, Object.create(this.frame)); - } -} - -var gPendingInitializations = 1; -Object.defineProperty(this, "gIsInitializing", { - get: () => gPendingInitializations > 0 -}); - -function initialize(event) { - // XXXbz this listener gets _all_ load events for all nodes in the - // document... but relies on not being called "too early". - if (event.target instanceof XMLStylesheetProcessingInstruction) { - return; - } - document.removeEventListener("load", initialize, true); - - let globalCommandSet = document.getElementById("globalCommandSet"); - globalCommandSet.addEventListener("command", function(event) { - gViewController.doCommand(event.target.id); - }); - - let viewCommandSet = document.getElementById("viewCommandSet"); - viewCommandSet.addEventListener("commandupdate", function(event) { - gViewController.updateCommands(); - }); - viewCommandSet.addEventListener("command", function(event) { - gViewController.doCommand(event.target.id); - }); - - let detailScreenshot = document.getElementById("detail-screenshot"); - detailScreenshot.addEventListener("load", function(event) { - this.removeAttribute("loading"); - }); - detailScreenshot.addEventListener("error", function(event) { - this.setAttribute("loading", "error"); - }); - - let addonPage = document.getElementById("addons-page"); - addonPage.addEventListener("dragenter", function(event) { - gDragDrop.onDragOver(event); - }); - addonPage.addEventListener("dragover", function(event) { - gDragDrop.onDragOver(event); - }); - addonPage.addEventListener("drop", function(event) { - gDragDrop.onDrop(event); - }); - addonPage.addEventListener("keypress", function(event) { - gHeader.onKeyPress(event); - }); - - if (!isDiscoverEnabled()) { - gViewDefault = "addons://list/extension"; - } - - gViewController.initialize(); - gCategories.initialize(); - gHeader.initialize(); - gEventManager.initialize(); - Services.obs.addObserver(sendEMPong, "EM-ping", false); - Services.obs.notifyObservers(window, "EM-loaded", ""); - - // If the initial view has already been selected (by a call to loadView from - // the above notifications) then bail out now - if (gViewController.initialViewSelected) - return; - - // If there is a history state to restore then use that - if (window.history.state) { - gViewController.updateState(window.history.state); - return; - } - - // Default to the last selected category - var view = gCategories.node.value; - - // Allow passing in a view through the window arguments - if ("arguments" in window && window.arguments.length > 0 && - window.arguments[0] !== null && "view" in window.arguments[0]) { - view = window.arguments[0].view; - } - - gViewController.loadInitialView(view); -} - -function notifyInitialized() { - if (!gIsInitializing) - return; - - gPendingInitializations--; - if (!gIsInitializing) { - var event = document.createEvent("Events"); - event.initEvent("Initialized", true, true); - document.dispatchEvent(event); - } -} - -function shutdown() { - gCategories.shutdown(); - gSearchView.shutdown(); - gEventManager.shutdown(); - gViewController.shutdown(); - Services.obs.removeObserver(sendEMPong, "EM-ping"); -} - -function sendEMPong(aSubject, aTopic, aData) { - Services.obs.notifyObservers(window, "EM-pong", ""); -} - -// Used by external callers to load a specific view into the manager -function loadView(aViewId) { - if (!gViewController.initialViewSelected) { - // The caller opened the window and immediately loaded the view so it - // should be the initial history entry - - gViewController.loadInitialView(aViewId); - } else { - gViewController.loadView(aViewId); - } -} - -function isCorrectlySigned(aAddon) { - // Add-ons without an "isCorrectlySigned" property are correctly signed as - // they aren't the correct type for signing. - return aAddon.isCorrectlySigned !== false; -} - -function isDiscoverEnabled() { - if (Services.prefs.getPrefType(PREF_DISCOVERURL) == Services.prefs.PREF_INVALID) - return false; - - try { - if (!Services.prefs.getBoolPref(PREF_DISCOVER_ENABLED)) - return false; - } catch (e) {} - - try { - if (!Services.prefs.getBoolPref(PREF_XPI_ENABLED)) - return false; - } catch (e) {} - - return true; -} - -/** - * Obtain the main DOMWindow for the current context. - */ -function getMainWindow() { - return window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem) - .rootTreeItem - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); -} - -function getBrowserElement() { - return window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell) - .chromeEventHandler; -} - -/** - * Obtain the DOMWindow that can open a preferences pane. - * - * This is essentially "get the browser chrome window" with the added check - * that the supposed browser chrome window is capable of opening a preferences - * pane. - * - * This may return null if we can't find the browser chrome window. - */ -function getMainWindowWithPreferencesPane() { - let mainWindow = getMainWindow(); - if (mainWindow && "openAdvancedPreferences" in mainWindow) { - return mainWindow; - } - return null; -} - -/** - * A wrapper around the HTML5 session history service that allows the browser - * back/forward controls to work within the manager - */ -var HTML5History = { - get index() { - return window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .sessionHistory.index; - }, - - get canGoBack() { - return window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .canGoBack; - }, - - get canGoForward() { - return window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .canGoForward; - }, - - back: function() { - window.history.back(); - gViewController.updateCommand("cmd_back"); - gViewController.updateCommand("cmd_forward"); - }, - - forward: function() { - window.history.forward(); - gViewController.updateCommand("cmd_back"); - gViewController.updateCommand("cmd_forward"); - }, - - pushState: function(aState) { - window.history.pushState(aState, document.title); - }, - - replaceState: function(aState) { - window.history.replaceState(aState, document.title); - }, - - popState: function() { - function onStatePopped(aEvent) { - window.removeEventListener("popstate", onStatePopped, true); - // TODO To ensure we can't go forward again we put an additional entry - // for the current state into the history. Ideally we would just strip - // the history but there doesn't seem to be a way to do that. Bug 590661 - window.history.pushState(aEvent.state, document.title); - } - window.addEventListener("popstate", onStatePopped, true); - window.history.back(); - gViewController.updateCommand("cmd_back"); - gViewController.updateCommand("cmd_forward"); - } -}; - -/** - * A wrapper around a fake history service - */ -var FakeHistory = { - pos: 0, - states: [null], - - get index() { - return this.pos; - }, - - get canGoBack() { - return this.pos > 0; - }, - - get canGoForward() { - return (this.pos + 1) < this.states.length; - }, - - back: function() { - if (this.pos == 0) - throw Components.Exception("Cannot go back from this point"); - - this.pos--; - gViewController.updateState(this.states[this.pos]); - gViewController.updateCommand("cmd_back"); - gViewController.updateCommand("cmd_forward"); - }, - - forward: function() { - if ((this.pos + 1) >= this.states.length) - throw Components.Exception("Cannot go forward from this point"); - - this.pos++; - gViewController.updateState(this.states[this.pos]); - gViewController.updateCommand("cmd_back"); - gViewController.updateCommand("cmd_forward"); - }, - - pushState: function(aState) { - this.pos++; - this.states.splice(this.pos, this.states.length); - this.states.push(aState); - }, - - replaceState: function(aState) { - this.states[this.pos] = aState; - }, - - popState: function() { - if (this.pos == 0) - throw Components.Exception("Cannot popState from this view"); - - this.states.splice(this.pos, this.states.length); - this.pos--; - - gViewController.updateState(this.states[this.pos]); - gViewController.updateCommand("cmd_back"); - gViewController.updateCommand("cmd_forward"); - } -}; - -// If the window has a session history then use the HTML5 History wrapper -// otherwise use our fake history implementation -if (window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .sessionHistory) { - var gHistory = HTML5History; -} -else { - gHistory = FakeHistory; -} - -var gEventManager = { - _listeners: {}, - _installListeners: [], - - initialize: function() { - const ADDON_EVENTS = ["onEnabling", "onEnabled", "onDisabling", - "onDisabled", "onUninstalling", "onUninstalled", - "onInstalled", "onOperationCancelled", - "onUpdateAvailable", "onUpdateFinished", - "onCompatibilityUpdateAvailable", - "onPropertyChanged"]; - for (let evt of ADDON_EVENTS) { - let event = evt; - this[event] = (...aArgs) => this.delegateAddonEvent(event, aArgs); - } - - const INSTALL_EVENTS = ["onNewInstall", "onDownloadStarted", - "onDownloadEnded", "onDownloadFailed", - "onDownloadProgress", "onDownloadCancelled", - "onInstallStarted", "onInstallEnded", - "onInstallFailed", "onInstallCancelled", - "onExternalInstall"]; - for (let evt of INSTALL_EVENTS) { - let event = evt; - this[event] = (...aArgs) => this.delegateInstallEvent(event, aArgs); - } - - AddonManager.addManagerListener(this); - AddonManager.addInstallListener(this); - AddonManager.addAddonListener(this); - - this.refreshGlobalWarning(); - this.refreshAutoUpdateDefault(); - - var contextMenu = document.getElementById("addonitem-popup"); - contextMenu.addEventListener("popupshowing", function() { - var addon = gViewController.currentViewObj.getSelectedAddon(); - contextMenu.setAttribute("addontype", addon.type); - - var menuSep = document.getElementById("addonitem-menuseparator"); - var countMenuItemsBeforeSep = 0; - for (let child of contextMenu.children) { - if (child == menuSep) { - break; - } - if (child.nodeName == "menuitem" && - gViewController.isCommandEnabled(child.command)) { - countMenuItemsBeforeSep++; - } - } - - // Hide the separator if there are no visible menu items before it - menuSep.hidden = (countMenuItemsBeforeSep == 0); - - }, false); - - let addonTooltip = document.getElementById("addonitem-tooltip"); - addonTooltip.addEventListener("popupshowing", function() { - let addonItem = addonTooltip.triggerNode; - // The way the test triggers the tooltip the richlistitem is the - // tooltipNode but in normal use it is the anonymous node. This allows - // any case - if (addonItem.localName != "richlistitem") - addonItem = document.getBindingParent(addonItem); - - let tiptext = addonItem.getAttribute("name"); - - if (addonItem.mAddon) { - if (shouldShowVersionNumber(addonItem.mAddon)) { - tiptext += " " + (addonItem.hasAttribute("upgrade") ? addonItem.mManualUpdate.version - : addonItem.mAddon.version); - } - } - else if (shouldShowVersionNumber(addonItem.mInstall)) { - tiptext += " " + addonItem.mInstall.version; - } - - addonTooltip.label = tiptext; - }, false); - }, - - shutdown: function() { - AddonManager.removeManagerListener(this); - AddonManager.removeInstallListener(this); - AddonManager.removeAddonListener(this); - }, - - registerAddonListener: function(aListener, aAddonId) { - if (!(aAddonId in this._listeners)) - this._listeners[aAddonId] = []; - else if (this._listeners[aAddonId].indexOf(aListener) != -1) - return; - this._listeners[aAddonId].push(aListener); - }, - - unregisterAddonListener: function(aListener, aAddonId) { - if (!(aAddonId in this._listeners)) - return; - var index = this._listeners[aAddonId].indexOf(aListener); - if (index == -1) - return; - this._listeners[aAddonId].splice(index, 1); - }, - - registerInstallListener: function(aListener) { - if (this._installListeners.indexOf(aListener) != -1) - return; - this._installListeners.push(aListener); - }, - - unregisterInstallListener: function(aListener) { - var i = this._installListeners.indexOf(aListener); - if (i == -1) - return; - this._installListeners.splice(i, 1); - }, - - delegateAddonEvent: function(aEvent, aParams) { - var addon = aParams.shift(); - if (!(addon.id in this._listeners)) - return; - - var listeners = this._listeners[addon.id]; - for (let listener of listeners) { - if (!(aEvent in listener)) - continue; - try { - listener[aEvent].apply(listener, aParams); - } catch (e) { - // this shouldn't be fatal - Cu.reportError(e); - } - } - }, - - delegateInstallEvent: function(aEvent, aParams) { - var existingAddon = aEvent == "onExternalInstall" ? aParams[1] : aParams[0].existingAddon; - // If the install is an update then send the event to all listeners - // registered for the existing add-on - if (existingAddon) - this.delegateAddonEvent(aEvent, [existingAddon].concat(aParams)); - - for (let listener of this._installListeners) { - if (!(aEvent in listener)) - continue; - try { - listener[aEvent].apply(listener, aParams); - } catch (e) { - // this shouldn't be fatal - Cu.reportError(e); - } - } - }, - - refreshGlobalWarning: function() { - var page = document.getElementById("addons-page"); - - if (Services.appinfo.inSafeMode) { - page.setAttribute("warning", "safemode"); - return; - } - - if (AddonManager.checkUpdateSecurityDefault && - !AddonManager.checkUpdateSecurity) { - page.setAttribute("warning", "updatesecurity"); - return; - } - - if (!AddonManager.checkCompatibility) { - page.setAttribute("warning", "checkcompatibility"); - return; - } - - page.removeAttribute("warning"); - }, - - refreshAutoUpdateDefault: function() { - var updateEnabled = AddonManager.updateEnabled; - var autoUpdateDefault = AddonManager.autoUpdateDefault; - - // The checkbox needs to reflect that both prefs need to be true - // for updates to be checked for and applied automatically - document.getElementById("utils-autoUpdateDefault") - .setAttribute("checked", updateEnabled && autoUpdateDefault); - - document.getElementById("utils-resetAddonUpdatesToAutomatic").hidden = !autoUpdateDefault; - document.getElementById("utils-resetAddonUpdatesToManual").hidden = autoUpdateDefault; - }, - - onCompatibilityModeChanged: function() { - this.refreshGlobalWarning(); - }, - - onCheckUpdateSecurityChanged: function() { - this.refreshGlobalWarning(); - }, - - onUpdateModeChanged: function() { - this.refreshAutoUpdateDefault(); - } -}; - - -var gViewController = { - viewPort: null, - currentViewId: "", - currentViewObj: null, - currentViewRequest: 0, - viewObjects: {}, - viewChangeCallback: null, - initialViewSelected: false, - lastHistoryIndex: -1, - - initialize: function() { - this.viewPort = document.getElementById("view-port"); - this.headeredViews = document.getElementById("headered-views"); - this.headeredViewsDeck = document.getElementById("headered-views-content"); - - this.viewObjects["search"] = gSearchView; - this.viewObjects["discover"] = gDiscoverView; - this.viewObjects["list"] = gListView; - this.viewObjects["detail"] = gDetailView; - this.viewObjects["updates"] = gUpdatesView; - - for (let type in this.viewObjects) { - let view = this.viewObjects[type]; - view.initialize(); - } - - window.controllers.appendController(this); - - window.addEventListener("popstate", function(e) { - gViewController.updateState(e.state); - }, - false); - }, - - shutdown: function() { - if (this.currentViewObj) - this.currentViewObj.hide(); - this.currentViewRequest = 0; - - for (let type in this.viewObjects) { - let view = this.viewObjects[type]; - if ("shutdown" in view) { - try { - view.shutdown(); - } catch (e) { - // this shouldn't be fatal - Cu.reportError(e); - } - } - } - - window.controllers.removeController(this); - }, - - updateState: function(state) { - try { - this.loadViewInternal(state.view, state.previousView, state); - this.lastHistoryIndex = gHistory.index; - } - catch (e) { - // The attempt to load the view failed, try moving further along history - if (this.lastHistoryIndex > gHistory.index) { - if (gHistory.canGoBack) - gHistory.back(); - else - gViewController.replaceView(gViewDefault); - } else if (gHistory.canGoForward) { - gHistory.forward(); - } else { - gViewController.replaceView(gViewDefault); - } - } - }, - - parseViewId: function(aViewId) { - var matchRegex = /^addons:\/\/([^\/]+)\/(.*)$/; - var [, viewType, viewParam] = aViewId.match(matchRegex) || []; - return {type: viewType, param: decodeURIComponent(viewParam)}; - }, - - get isLoading() { - return !this.currentViewObj || this.currentViewObj.node.hasAttribute("loading"); - }, - - loadView: function(aViewId) { - var isRefresh = false; - if (aViewId == this.currentViewId) { - if (this.isLoading) - return; - if (!("refresh" in this.currentViewObj)) - return; - if (!this.currentViewObj.canRefresh()) - return; - isRefresh = true; - } - - var state = { - view: aViewId, - previousView: this.currentViewId - }; - if (!isRefresh) { - gHistory.pushState(state); - this.lastHistoryIndex = gHistory.index; - } - this.loadViewInternal(aViewId, this.currentViewId, state); - }, - - // Replaces the existing view with a new one, rewriting the current history - // entry to match. - replaceView: function(aViewId) { - if (aViewId == this.currentViewId) - return; - - var state = { - view: aViewId, - previousView: null - }; - gHistory.replaceState(state); - this.loadViewInternal(aViewId, null, state); - }, - - loadInitialView: function(aViewId) { - var state = { - view: aViewId, - previousView: null - }; - gHistory.replaceState(state); - - this.loadViewInternal(aViewId, null, state); - this.initialViewSelected = true; - notifyInitialized(); - }, - - get displayedView() { - if (this.viewPort.selectedPanel == this.headeredViews) { - return this.headeredViewsDeck.selectedPanel; - } - return this.viewPort.selectedPanel; - }, - - set displayedView(view) { - let node = view.node; - if (node.parentNode == this.headeredViewsDeck) { - this.headeredViewsDeck.selectedPanel = node; - this.viewPort.selectedPanel = this.headeredViews; - } else { - this.viewPort.selectedPanel = node; - } - }, - - loadViewInternal: function(aViewId, aPreviousView, aState) { - var view = this.parseViewId(aViewId); - - if (!view.type || !(view.type in this.viewObjects)) - throw Components.Exception("Invalid view: " + view.type); - - var viewObj = this.viewObjects[view.type]; - if (!viewObj.node) - throw Components.Exception("Root node doesn't exist for '" + view.type + "' view"); - - if (this.currentViewObj && aViewId != aPreviousView) { - try { - let canHide = this.currentViewObj.hide(); - if (canHide === false) - return; - this.displayedView.removeAttribute("loading"); - } catch (e) { - // this shouldn't be fatal - Cu.reportError(e); - } - } - - gCategories.select(aViewId, aPreviousView); - - this.currentViewId = aViewId; - this.currentViewObj = viewObj; - - this.displayedView = this.currentViewObj; - this.currentViewObj.node.setAttribute("loading", "true"); - this.currentViewObj.node.focus(); - - if (aViewId == aPreviousView) - this.currentViewObj.refresh(view.param, ++this.currentViewRequest, aState); - else - this.currentViewObj.show(view.param, ++this.currentViewRequest, aState); - }, - - // Moves back in the document history and removes the current history entry - popState: function(aCallback) { - this.viewChangeCallback = aCallback; - gHistory.popState(); - }, - - notifyViewChanged: function() { - this.displayedView.removeAttribute("loading"); - - if (this.viewChangeCallback) { - this.viewChangeCallback(); - this.viewChangeCallback = null; - } - - var event = document.createEvent("Events"); - event.initEvent("ViewChanged", true, true); - this.currentViewObj.node.dispatchEvent(event); - }, - - commands: { - cmd_back: { - isEnabled: function() { - return gHistory.canGoBack; - }, - doCommand: function() { - gHistory.back(); - } - }, - - cmd_forward: { - isEnabled: function() { - return gHistory.canGoForward; - }, - doCommand: function() { - gHistory.forward(); - } - }, - - cmd_focusSearch: { - isEnabled: () => true, - doCommand: function() { - gHeader.focusSearchBox(); - } - }, - - cmd_restartApp: { - isEnabled: function() { - return true; - }, - doCommand: function() { - let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]. - createInstance(Ci.nsISupportsPRBool); - Services.obs.notifyObservers(cancelQuit, "quit-application-requested", - "restart"); - if (cancelQuit.data) - return; // somebody canceled our quit request - - let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]. - getService(Ci.nsIAppStartup); - appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart); - } - }, - - cmd_enableCheckCompatibility: { - isEnabled: function() { - return true; - }, - doCommand: function() { - AddonManager.checkCompatibility = true; - } - }, - - cmd_enableUpdateSecurity: { - isEnabled: function() { - return true; - }, - doCommand: function() { - AddonManager.checkUpdateSecurity = true; - } - }, - - cmd_toggleAutoUpdateDefault: { - isEnabled: function() { - return true; - }, - doCommand: function() { - if (!AddonManager.updateEnabled || !AddonManager.autoUpdateDefault) { - // One or both of the prefs is false, i.e. the checkbox is not checked. - // Now toggle both to true. If the user wants us to auto-update - // add-ons, we also need to auto-check for updates. - AddonManager.updateEnabled = true; - AddonManager.autoUpdateDefault = true; - } else { - // Both prefs are true, i.e. the checkbox is checked. - // Toggle the auto pref to false, but don't touch the enabled check. - AddonManager.autoUpdateDefault = false; - } - } - }, - - cmd_resetAddonAutoUpdate: { - isEnabled: function() { - return true; - }, - doCommand: function() { - AddonManager.getAllAddons(function(aAddonList) { - for (let addon of aAddonList) { - if ("applyBackgroundUpdates" in addon) - addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; - } - }); - } - }, - - cmd_goToDiscoverPane: { - isEnabled: function() { - return gDiscoverView.enabled; - }, - doCommand: function() { - gViewController.loadView("addons://discover/"); - } - }, - - cmd_goToRecentUpdates: { - isEnabled: function() { - return true; - }, - doCommand: function() { - gViewController.loadView("addons://updates/recent"); - } - }, - - cmd_goToAvailableUpdates: { - isEnabled: function() { - return true; - }, - doCommand: function() { - gViewController.loadView("addons://updates/available"); - } - }, - - cmd_showItemDetails: { - isEnabled: function(aAddon) { - return !!aAddon && (gViewController.currentViewObj != gDetailView); - }, - doCommand: function(aAddon, aScrollToPreferences) { - gViewController.loadView("addons://detail/" + - encodeURIComponent(aAddon.id) + - (aScrollToPreferences ? "/preferences" : "")); - } - }, - - cmd_findAllUpdates: { - inProgress: false, - isEnabled: function() { - return !this.inProgress; - }, - doCommand: function() { - this.inProgress = true; - gViewController.updateCommand("cmd_findAllUpdates"); - document.getElementById("updates-noneFound").hidden = true; - document.getElementById("updates-progress").hidden = false; - document.getElementById("updates-manualUpdatesFound-btn").hidden = true; - - var pendingChecks = 0; - var numUpdated = 0; - var numManualUpdates = 0; - var restartNeeded = false; - - let updateStatus = () => { - if (pendingChecks > 0) - return; - - this.inProgress = false; - gViewController.updateCommand("cmd_findAllUpdates"); - document.getElementById("updates-progress").hidden = true; - gUpdatesView.maybeRefresh(); - - if (numManualUpdates > 0 && numUpdated == 0) { - document.getElementById("updates-manualUpdatesFound-btn").hidden = false; - return; - } - - if (numUpdated == 0) { - document.getElementById("updates-noneFound").hidden = false; - return; - } - - if (restartNeeded) { - document.getElementById("updates-downloaded").hidden = false; - document.getElementById("updates-restart-btn").hidden = false; - } else { - document.getElementById("updates-installed").hidden = false; - } - } - - var updateInstallListener = { - onDownloadFailed: function() { - pendingChecks--; - updateStatus(); - }, - onInstallFailed: function() { - pendingChecks--; - updateStatus(); - }, - onInstallEnded: function(aInstall, aAddon) { - pendingChecks--; - numUpdated++; - if (isPending(aInstall.existingAddon, "upgrade")) - restartNeeded = true; - updateStatus(); - } - }; - - var updateCheckListener = { - onUpdateAvailable: function(aAddon, aInstall) { - gEventManager.delegateAddonEvent("onUpdateAvailable", - [aAddon, aInstall]); - if (AddonManager.shouldAutoUpdate(aAddon)) { - aInstall.addListener(updateInstallListener); - aInstall.install(); - } else { - pendingChecks--; - numManualUpdates++; - updateStatus(); - } - }, - onNoUpdateAvailable: function(aAddon) { - pendingChecks--; - updateStatus(); - }, - onUpdateFinished: function(aAddon, aError) { - gEventManager.delegateAddonEvent("onUpdateFinished", - [aAddon, aError]); - } - }; - - AddonManager.getAddonsByTypes(null, function(aAddonList) { - for (let addon of aAddonList) { - if (addon.permissions & AddonManager.PERM_CAN_UPGRADE) { - pendingChecks++; - addon.findUpdates(updateCheckListener, - AddonManager.UPDATE_WHEN_USER_REQUESTED); - } - } - - if (pendingChecks == 0) - updateStatus(); - }); - } - }, - - cmd_findItemUpdates: { - isEnabled: function(aAddon) { - if (!aAddon) - return false; - return hasPermission(aAddon, "upgrade"); - }, - doCommand: function(aAddon) { - var listener = { - onUpdateAvailable: function(aAddon, aInstall) { - gEventManager.delegateAddonEvent("onUpdateAvailable", - [aAddon, aInstall]); - if (AddonManager.shouldAutoUpdate(aAddon)) - aInstall.install(); - }, - onNoUpdateAvailable: function(aAddon) { - gEventManager.delegateAddonEvent("onNoUpdateAvailable", - [aAddon]); - } - }; - gEventManager.delegateAddonEvent("onCheckingUpdate", [aAddon]); - aAddon.findUpdates(listener, AddonManager.UPDATE_WHEN_USER_REQUESTED); - } - }, - - cmd_showItemPreferences: { - isEnabled: function(aAddon) { - if (!aAddon || - (!aAddon.isActive && !aAddon.isGMPlugin) || - !aAddon.optionsURL) { - return false; - } - if (gViewController.currentViewObj == gDetailView && - (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE || - aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_BROWSER)) { - return false; - } - if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO) - return false; - return true; - }, - doCommand: function(aAddon) { - if (hasInlineOptions(aAddon)) { - gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true); - return; - } - var optionsURL = aAddon.optionsURL; - if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_TAB && - openOptionsInTab(optionsURL)) { - return; - } - var windows = Services.wm.getEnumerator(null); - while (windows.hasMoreElements()) { - var win = windows.getNext(); - if (win.closed) { - continue; - } - if (win.document.documentURI == optionsURL) { - win.focus(); - return; - } - } - var features = "chrome,titlebar,toolbar,centerscreen"; - try { - var instantApply = Services.prefs.getBoolPref("browser.preferences.instantApply"); - features += instantApply ? ",dialog=no" : ",modal"; - } catch (e) { - features += ",modal"; - } - openDialog(optionsURL, "", features); - } - }, - - cmd_showItemAbout: { - isEnabled: function(aAddon) { - // XXXunf This may be applicable to install items too. See bug 561260 - return !!aAddon; - }, - doCommand: function(aAddon) { - var aboutURL = aAddon.aboutURL; - if (aboutURL) - openDialog(aboutURL, "", "chrome,centerscreen,modal", aAddon); - else - openDialog("chrome://mozapps/content/extensions/about.xul", - "", "chrome,centerscreen,modal", aAddon); - } - }, - - cmd_enableItem: { - isEnabled: function(aAddon) { - if (!aAddon) - return false; - let addonType = AddonManager.addonTypes[aAddon.type]; - return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && - hasPermission(aAddon, "enable")); - }, - doCommand: function(aAddon) { - aAddon.userDisabled = false; - }, - getTooltip: function(aAddon) { - if (!aAddon) - return ""; - if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE) - return gStrings.ext.GetStringFromName("enableAddonRestartRequiredTooltip"); - return gStrings.ext.GetStringFromName("enableAddonTooltip"); - } - }, - - cmd_disableItem: { - isEnabled: function(aAddon) { - if (!aAddon) - return false; - let addonType = AddonManager.addonTypes[aAddon.type]; - return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && - hasPermission(aAddon, "disable")); - }, - doCommand: function(aAddon) { - aAddon.userDisabled = true; - }, - getTooltip: function(aAddon) { - if (!aAddon) - return ""; - if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE) - return gStrings.ext.GetStringFromName("disableAddonRestartRequiredTooltip"); - return gStrings.ext.GetStringFromName("disableAddonTooltip"); - } - }, - - cmd_installItem: { - isEnabled: function(aAddon) { - if (!aAddon) - return false; - return aAddon.install && aAddon.install.state == AddonManager.STATE_AVAILABLE; - }, - doCommand: function(aAddon) { - function doInstall() { - gViewController.currentViewObj.getListItemForID(aAddon.id)._installStatus.installRemote(); - } - - if (gViewController.currentViewObj == gDetailView) - gViewController.popState(doInstall); - else - doInstall(); - } - }, - - cmd_purchaseItem: { - isEnabled: function(aAddon) { - if (!aAddon) - return false; - return !!aAddon.purchaseURL; - }, - doCommand: function(aAddon) { - openURL(aAddon.purchaseURL); - } - }, - - cmd_uninstallItem: { - isEnabled: function(aAddon) { - if (!aAddon) - return false; - return hasPermission(aAddon, "uninstall"); - }, - doCommand: function(aAddon) { - if (gViewController.currentViewObj != gDetailView) { - aAddon.uninstall(); - return; - } - - gViewController.popState(function() { - gViewController.currentViewObj.getListItemForID(aAddon.id).uninstall(); - }); - }, - getTooltip: function(aAddon) { - if (!aAddon) - return ""; - if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL) - return gStrings.ext.GetStringFromName("uninstallAddonRestartRequiredTooltip"); - return gStrings.ext.GetStringFromName("uninstallAddonTooltip"); - } - }, - - cmd_cancelUninstallItem: { - isEnabled: function(aAddon) { - if (!aAddon) - return false; - return isPending(aAddon, "uninstall"); - }, - doCommand: function(aAddon) { - aAddon.cancelUninstall(); - } - }, - - cmd_installFromFile: { - isEnabled: function() { - return true; - }, - doCommand: function() { - const nsIFilePicker = Ci.nsIFilePicker; - var fp = Cc["@mozilla.org/filepicker;1"] - .createInstance(nsIFilePicker); - fp.init(window, - gStrings.ext.GetStringFromName("installFromFile.dialogTitle"), - nsIFilePicker.modeOpenMultiple); - try { - fp.appendFilter(gStrings.ext.GetStringFromName("installFromFile.filterName"), - "*.xpi;*.jar"); - fp.appendFilters(nsIFilePicker.filterAll); - } catch (e) { } - - if (fp.show() != nsIFilePicker.returnOK) - return; - - var files = fp.files; - var installs = []; - - function buildNextInstall() { - if (!files.hasMoreElements()) { - if (installs.length > 0) { - // Display the normal install confirmation for the installs - let webInstaller = Cc["@mozilla.org/addons/web-install-listener;1"]. - getService(Ci.amIWebInstallListener); - webInstaller.onWebInstallRequested(getBrowserElement(), - document.documentURIObject, - installs); - } - return; - } - - var file = files.getNext(); - AddonManager.getInstallForFile(file, function(aInstall) { - installs.push(aInstall); - buildNextInstall(); - }); - } - - buildNextInstall(); - } - }, - - cmd_debugAddons: { - isEnabled: function() { - return true; - }, - doCommand: function() { - let mainWindow = getMainWindow(); - if ("switchToTabHavingURI" in mainWindow) { - mainWindow.switchToTabHavingURI("about:debugging#addons", true); - } - }, - }, - - cmd_cancelOperation: { - isEnabled: function(aAddon) { - if (!aAddon) - return false; - return aAddon.pendingOperations != AddonManager.PENDING_NONE; - }, - doCommand: function(aAddon) { - if (isPending(aAddon, "install")) { - aAddon.install.cancel(); - } else if (isPending(aAddon, "upgrade")) { - aAddon.pendingUpgrade.install.cancel(); - } else if (isPending(aAddon, "uninstall")) { - aAddon.cancelUninstall(); - } else if (isPending(aAddon, "enable")) { - aAddon.userDisabled = true; - } else if (isPending(aAddon, "disable")) { - aAddon.userDisabled = false; - } - } - }, - - cmd_contribute: { - isEnabled: function(aAddon) { - if (!aAddon) - return false; - return ("contributionURL" in aAddon && aAddon.contributionURL); - }, - doCommand: function(aAddon) { - openURL(aAddon.contributionURL); - } - }, - - cmd_askToActivateItem: { - isEnabled: function(aAddon) { - if (!aAddon) - return false; - let addonType = AddonManager.addonTypes[aAddon.type]; - return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && - hasPermission(aAddon, "ask_to_activate")); - }, - doCommand: function(aAddon) { - aAddon.userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE; - } - }, - - cmd_alwaysActivateItem: { - isEnabled: function(aAddon) { - if (!aAddon) - return false; - let addonType = AddonManager.addonTypes[aAddon.type]; - return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && - hasPermission(aAddon, "enable")); - }, - doCommand: function(aAddon) { - aAddon.userDisabled = false; - } - }, - - cmd_neverActivateItem: { - isEnabled: function(aAddon) { - if (!aAddon) - return false; - let addonType = AddonManager.addonTypes[aAddon.type]; - return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && - hasPermission(aAddon, "disable")); - }, - doCommand: function(aAddon) { - aAddon.userDisabled = true; - } - }, - - cmd_showUnsignedExtensions: { - isEnabled: function() { - return true; - }, - doCommand: function() { - gViewController.loadView("addons://list/extension?unsigned=true"); - }, - }, - - cmd_showAllExtensions: { - isEnabled: function() { - return true; - }, - doCommand: function() { - gViewController.loadView("addons://list/extension"); - }, - }, - }, - - supportsCommand: function(aCommand) { - return (aCommand in this.commands); - }, - - isCommandEnabled: function(aCommand) { - if (!this.supportsCommand(aCommand)) - return false; - var addon = this.currentViewObj.getSelectedAddon(); - return this.commands[aCommand].isEnabled(addon); - }, - - updateCommands: function() { - // wait until the view is initialized - if (!this.currentViewObj) - return; - var addon = this.currentViewObj.getSelectedAddon(); - for (let commandId in this.commands) - this.updateCommand(commandId, addon); - }, - - updateCommand: function(aCommandId, aAddon) { - if (typeof aAddon == "undefined") - aAddon = this.currentViewObj.getSelectedAddon(); - var cmd = this.commands[aCommandId]; - var cmdElt = document.getElementById(aCommandId); - cmdElt.setAttribute("disabled", !cmd.isEnabled(aAddon)); - if ("getTooltip" in cmd) { - let tooltip = cmd.getTooltip(aAddon); - if (tooltip) - cmdElt.setAttribute("tooltiptext", tooltip); - else - cmdElt.removeAttribute("tooltiptext"); - } - }, - - doCommand: function(aCommand, aAddon) { - if (!this.supportsCommand(aCommand)) - return; - var cmd = this.commands[aCommand]; - if (!aAddon) - aAddon = this.currentViewObj.getSelectedAddon(); - if (!cmd.isEnabled(aAddon)) - return; - cmd.doCommand(aAddon); - }, - - onEvent: function() {} -}; - -function hasInlineOptions(aAddon) { - return (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE || - aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_BROWSER || - aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO); -} - -function openOptionsInTab(optionsURL) { - let mainWindow = getMainWindow(); - if ("switchToTabHavingURI" in mainWindow) { - mainWindow.switchToTabHavingURI(optionsURL, true); - return true; - } - return false; -} - -function formatDate(aDate) { - const locale = Cc["@mozilla.org/chrome/chrome-registry;1"] - .getService(Ci.nsIXULChromeRegistry) - .getSelectedLocale("global", true); - const dtOptions = { year: 'numeric', month: 'long', day: 'numeric' }; - return aDate.toLocaleDateString(locale, dtOptions); -} - - -function hasPermission(aAddon, aPerm) { - var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()]; - return !!(aAddon.permissions & perm); -} - - -function isPending(aAddon, aAction) { - var action = AddonManager["PENDING_" + aAction.toUpperCase()]; - return !!(aAddon.pendingOperations & action); -} - -function isInState(aInstall, aState) { - var state = AddonManager["STATE_" + aState.toUpperCase()]; - return aInstall.state == state; -} - -function shouldShowVersionNumber(aAddon) { - if (!aAddon.version) - return false; - - // The version number is hidden for lightweight themes. - if (aAddon.type == "theme") - return !/@personas\.mozilla\.org$/.test(aAddon.id); - - return true; -} - -function createItem(aObj, aIsInstall, aIsRemote) { - let item = document.createElement("richlistitem"); - - item.setAttribute("class", "addon addon-view"); - item.setAttribute("name", aObj.name); - item.setAttribute("type", aObj.type); - item.setAttribute("remote", !!aIsRemote); - - if (aIsInstall) { - item.mInstall = aObj; - - if (aObj.state != AddonManager.STATE_INSTALLED) { - item.setAttribute("status", "installing"); - return item; - } - aObj = aObj.addon; - } - - item.mAddon = aObj; - - item.setAttribute("status", "installed"); - - // set only attributes needed for sorting and XBL binding, - // the binding handles the rest - item.setAttribute("value", aObj.id); - - return item; -} - -function sortElements(aElements, aSortBy, aAscending) { - // aSortBy is an Array of attributes to sort by, in decending - // order of priority. - - const DATE_FIELDS = ["updateDate"]; - const NUMERIC_FIELDS = ["size", "relevancescore", "purchaseAmount"]; - - // We're going to group add-ons into the following buckets: - // - // enabledInstalled - // * Enabled - // * Incompatible but enabled because compatibility checking is off - // * Waiting to be installed - // * Waiting to be enabled - // - // pendingDisable - // * Waiting to be disabled - // - // pendingUninstall - // * Waiting to be removed - // - // disabledIncompatibleBlocked - // * Disabled - // * Incompatible - // * Blocklisted - - const UISTATE_ORDER = ["enabled", "askToActivate", "pendingDisable", - "pendingUninstall", "disabled"]; - - function dateCompare(a, b) { - var aTime = a.getTime(); - var bTime = b.getTime(); - if (aTime < bTime) - return -1; - if (aTime > bTime) - return 1; - return 0; - } - - function numberCompare(a, b) { - return a - b; - } - - function stringCompare(a, b) { - return a.localeCompare(b); - } - - function uiStateCompare(a, b) { - // If we're in descending order, swap a and b, because - // we don't ever want to have descending uiStates - if (!aAscending) - [a, b] = [b, a]; - - return (UISTATE_ORDER.indexOf(a) - UISTATE_ORDER.indexOf(b)); - } - - function getValue(aObj, aKey) { - if (!aObj) - return null; - - if (aObj.hasAttribute(aKey)) - return aObj.getAttribute(aKey); - - var addon = aObj.mAddon || aObj.mInstall; - var addonType = aObj.mAddon && AddonManager.addonTypes[aObj.mAddon.type]; - - if (!addon) - return null; - - if (aKey == "uiState") { - if (addon.pendingOperations == AddonManager.PENDING_DISABLE) - return "pendingDisable"; - if (addon.pendingOperations == AddonManager.PENDING_UNINSTALL) - return "pendingUninstall"; - if (!addon.isActive && - (addon.pendingOperations != AddonManager.PENDING_ENABLE && - addon.pendingOperations != AddonManager.PENDING_INSTALL)) - return "disabled"; - if (addonType && (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && - addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) - return "askToActivate"; - return "enabled"; - } - - return addon[aKey]; - } - - // aSortFuncs will hold the sorting functions that we'll - // use per element, in the correct order. - var aSortFuncs = []; - - for (let i = 0; i < aSortBy.length; i++) { - var sortBy = aSortBy[i]; - - aSortFuncs[i] = stringCompare; - - if (sortBy == "uiState") - aSortFuncs[i] = uiStateCompare; - else if (DATE_FIELDS.indexOf(sortBy) != -1) - aSortFuncs[i] = dateCompare; - else if (NUMERIC_FIELDS.indexOf(sortBy) != -1) - aSortFuncs[i] = numberCompare; - } - - - aElements.sort(function(a, b) { - if (!aAscending) - [a, b] = [b, a]; - - for (let i = 0; i < aSortFuncs.length; i++) { - var sortBy = aSortBy[i]; - var aValue = getValue(a, sortBy); - var bValue = getValue(b, sortBy); - - if (!aValue && !bValue) - return 0; - if (!aValue) - return -1; - if (!bValue) - return 1; - if (aValue != bValue) { - var result = aSortFuncs[i](aValue, bValue); - - if (result != 0) - return result; - } - } - - // If we got here, then all values of a and b - // must have been equal. - return 0; - - }); -} - -function sortList(aList, aSortBy, aAscending) { - var elements = Array.slice(aList.childNodes, 0); - sortElements(elements, [aSortBy], aAscending); - - while (aList.listChild) - aList.removeChild(aList.lastChild); - - for (let element of elements) - aList.appendChild(element); -} - -function getAddonsAndInstalls(aType, aCallback) { - let addons = null, installs = null; - let types = (aType != null) ? [aType] : null; - - AddonManager.getAddonsByTypes(types, function(aAddonsList) { - addons = aAddonsList.filter(a => !a.hidden); - if (installs != null) - aCallback(addons, installs); - }); - - AddonManager.getInstallsByTypes(types, function(aInstallsList) { - // skip over upgrade installs and non-active installs - installs = aInstallsList.filter(function(aInstall) { - return !(aInstall.existingAddon || - aInstall.state == AddonManager.STATE_AVAILABLE); - }); - - if (addons != null) - aCallback(addons, installs) - }); -} - -function doPendingUninstalls(aListBox) { - // Uninstalling add-ons can mutate the list so find the add-ons first then - // uninstall them - var items = []; - var listitem = aListBox.firstChild; - while (listitem) { - if (listitem.getAttribute("pending") == "uninstall" && - !(listitem.opRequiresRestart("UNINSTALL"))) - items.push(listitem.mAddon); - listitem = listitem.nextSibling; - } - - for (let addon of items) - addon.uninstall(); -} - -var gCategories = { - node: null, - _search: null, - - initialize: function() { - this.node = document.getElementById("categories"); - this._search = this.get("addons://search/"); - - var types = AddonManager.addonTypes; - for (var type in types) - this.onTypeAdded(types[type]); - - AddonManager.addTypeListener(this); - - try { - this.node.value = Services.prefs.getCharPref(PREF_UI_LASTCATEGORY); - } catch (e) { } - - // If there was no last view or no existing category matched the last view - // then the list will default to selecting the search category and we never - // want to show that as the first view so switch to the default category - if (!this.node.selectedItem || this.node.selectedItem == this._search) - this.node.value = gViewDefault; - - this.node.addEventListener("select", () => { - this.maybeHideSearch(); - gViewController.loadView(this.node.selectedItem.value); - }, false); - - this.node.addEventListener("click", (aEvent) => { - var selectedItem = this.node.selectedItem; - if (aEvent.target.localName == "richlistitem" && - aEvent.target == selectedItem) { - var viewId = selectedItem.value; - - if (gViewController.parseViewId(viewId).type == "search") { - viewId += encodeURIComponent(gHeader.searchQuery); - } - - gViewController.loadView(viewId); - } - }, false); - }, - - shutdown: function() { - AddonManager.removeTypeListener(this); - }, - - _insertCategory: function(aId, aName, aView, aPriority, aStartHidden) { - // If this category already exists then don't re-add it - if (document.getElementById("category-" + aId)) - return; - - var category = document.createElement("richlistitem"); - category.setAttribute("id", "category-" + aId); - category.setAttribute("value", aView); - category.setAttribute("class", "category"); - category.setAttribute("name", aName); - category.setAttribute("tooltiptext", aName); - category.setAttribute("priority", aPriority); - category.setAttribute("hidden", aStartHidden); - - var node; - for (node of this.node.children) { - var nodePriority = parseInt(node.getAttribute("priority")); - // If the new type's priority is higher than this one then this is the - // insertion point - if (aPriority < nodePriority) - break; - // If the new type's priority is lower than this one then this is isn't - // the insertion point - if (aPriority > nodePriority) - continue; - // If the priorities are equal and the new type's name is earlier - // alphabetically then this is the insertion point - if (String.localeCompare(aName, node.getAttribute("name")) < 0) - break; - } - - this.node.insertBefore(category, node); - }, - - _removeCategory: function(aId) { - var category = document.getElementById("category-" + aId); - if (!category) - return; - - // If this category is currently selected then switch to the default view - if (this.node.selectedItem == category) - gViewController.replaceView(gViewDefault); - - this.node.removeChild(category); - }, - - onTypeAdded: function(aType) { - // Ignore types that we don't have a view object for - if (!(aType.viewType in gViewController.viewObjects)) - return; - - var aViewId = "addons://" + aType.viewType + "/" + aType.id; - - var startHidden = false; - if (aType.flags & AddonManager.TYPE_UI_HIDE_EMPTY) { - var prefName = PREF_UI_TYPE_HIDDEN.replace("%TYPE%", aType.id); - try { - startHidden = Services.prefs.getBoolPref(prefName); - } - catch (e) { - // Default to hidden - startHidden = true; - } - - gPendingInitializations++; - getAddonsAndInstalls(aType.id, (aAddonsList, aInstallsList) => { - var hidden = (aAddonsList.length == 0 && aInstallsList.length == 0); - var item = this.get(aViewId); - - // Don't load view that is becoming hidden - if (hidden && aViewId == gViewController.currentViewId) - gViewController.loadView(gViewDefault); - - item.hidden = hidden; - Services.prefs.setBoolPref(prefName, hidden); - - if (aAddonsList.length > 0 || aInstallsList.length > 0) { - notifyInitialized(); - return; - } - - gEventManager.registerInstallListener({ - onDownloadStarted: function(aInstall) { - this._maybeShowCategory(aInstall); - }, - - onInstallStarted: function(aInstall) { - this._maybeShowCategory(aInstall); - }, - - onInstallEnded: function(aInstall, aAddon) { - this._maybeShowCategory(aAddon); - }, - - onExternalInstall: function(aAddon, aExistingAddon, aRequiresRestart) { - this._maybeShowCategory(aAddon); - }, - - _maybeShowCategory: aAddon => { - if (aType.id == aAddon.type) { - this.get(aViewId).hidden = false; - Services.prefs.setBoolPref(prefName, false); - gEventManager.unregisterInstallListener(this); - } - } - }); - - notifyInitialized(); - }); - } - - this._insertCategory(aType.id, aType.name, aViewId, aType.uiPriority, - startHidden); - }, - - onTypeRemoved: function(aType) { - this._removeCategory(aType.id); - }, - - get selected() { - return this.node.selectedItem ? this.node.selectedItem.value : null; - }, - - select: function(aId, aPreviousView) { - var view = gViewController.parseViewId(aId); - if (view.type == "detail" && aPreviousView) { - aId = aPreviousView; - view = gViewController.parseViewId(aPreviousView); - } - aId = aId.replace(/\?.*/, ""); - - Services.prefs.setCharPref(PREF_UI_LASTCATEGORY, aId); - - if (this.node.selectedItem && - this.node.selectedItem.value == aId) { - this.node.selectedItem.hidden = false; - this.node.selectedItem.disabled = false; - return; - } - - var item; - if (view.type == "search") - item = this._search; - else - item = this.get(aId); - - if (item) { - item.hidden = false; - item.disabled = false; - this.node.suppressOnSelect = true; - this.node.selectedItem = item; - this.node.suppressOnSelect = false; - this.node.ensureElementIsVisible(item); - - this.maybeHideSearch(); - } - }, - - get: function(aId) { - var items = document.getElementsByAttribute("value", aId); - if (items.length) - return items[0]; - return null; - }, - - setBadge: function(aId, aCount) { - let item = this.get(aId); - if (item) - item.badgeCount = aCount; - }, - - maybeHideSearch: function() { - var view = gViewController.parseViewId(this.node.selectedItem.value); - this._search.disabled = view.type != "search"; - } -}; - - -var gHeader = { - _search: null, - _dest: "", - - initialize: function() { - this._search = document.getElementById("header-search"); - - this._search.addEventListener("command", function(aEvent) { - var query = aEvent.target.value; - if (query.length == 0) - return; - - gViewController.loadView("addons://search/" + encodeURIComponent(query)); - }, false); - - function updateNavButtonVisibility() { - var shouldShow = gHeader.shouldShowNavButtons; - document.getElementById("back-btn").hidden = !shouldShow; - document.getElementById("forward-btn").hidden = !shouldShow; - } - - window.addEventListener("focus", function(aEvent) { - if (aEvent.target == window) - updateNavButtonVisibility(); - }, false); - - updateNavButtonVisibility(); - }, - - focusSearchBox: function() { - this._search.focus(); - }, - - onKeyPress: function(aEvent) { - if (String.fromCharCode(aEvent.charCode) == "/") { - this.focusSearchBox(); - return; - } - }, - - get shouldShowNavButtons() { - var docshellItem = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem); - - // If there is no outer frame then make the buttons visible - if (docshellItem.rootTreeItem == docshellItem) - return true; - - var outerWin = docshellItem.rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - var outerDoc = outerWin.document; - var node = outerDoc.getElementById("back-button"); - // If the outer frame has no back-button then make the buttons visible - if (!node) - return true; - - // If the back-button or any of its parents are hidden then make the buttons - // visible - while (node != outerDoc) { - var style = outerWin.getComputedStyle(node, ""); - if (style.display == "none") - return true; - if (style.visibility != "visible") - return true; - node = node.parentNode; - } - - return false; - }, - - get searchQuery() { - return this._search.value; - }, - - set searchQuery(aQuery) { - this._search.value = aQuery; - }, -}; - - -var gDiscoverView = { - node: null, - enabled: true, - // Set to true after the view is first shown. If initialization completes - // after this then it must also load the discover homepage - loaded: false, - _browser: null, - _loading: null, - _error: null, - homepageURL: null, - _loadListeners: [], - hideHeader: true, - - initialize: function() { - this.enabled = isDiscoverEnabled(); - if (!this.enabled) { - gCategories.get("addons://discover/").hidden = true; - return; - } - - this.node = document.getElementById("discover-view"); - this._loading = document.getElementById("discover-loading"); - this._error = document.getElementById("discover-error"); - this._browser = document.getElementById("discover-browser"); - - let compatMode = "normal"; - if (!AddonManager.checkCompatibility) - compatMode = "ignore"; - else if (AddonManager.strictCompatibility) - compatMode = "strict"; - - var url = Services.prefs.getCharPref(PREF_DISCOVERURL); - url = url.replace("%COMPATIBILITY_MODE%", compatMode); - url = Services.urlFormatter.formatURL(url); - - let setURL = (aURL) => { - try { - this.homepageURL = Services.io.newURI(aURL, null, null); - } catch (e) { - this.showError(); - notifyInitialized(); - return; - } - - this._browser.homePage = this.homepageURL.spec; - this._browser.addProgressListener(this); - - if (this.loaded) - this._loadURL(this.homepageURL.spec, false, notifyInitialized); - else - notifyInitialized(); - } - - if (Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED) == false) { - setURL(url); - return; - } - - gPendingInitializations++; - AddonManager.getAllAddons(function(aAddons) { - var list = {}; - for (let addon of aAddons) { - var prefName = PREF_GETADDONS_CACHE_ID_ENABLED.replace("%ID%", - addon.id); - try { - if (!Services.prefs.getBoolPref(prefName)) - continue; - } catch (e) { } - list[addon.id] = { - name: addon.name, - version: addon.version, - type: addon.type, - userDisabled: addon.userDisabled, - isCompatible: addon.isCompatible, - isBlocklisted: addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED - } - } - - setURL(url + "#" + JSON.stringify(list)); - }); - }, - - destroy: function() { - try { - this._browser.removeProgressListener(this); - } - catch (e) { - // Ignore the case when the listener wasn't already registered - } - }, - - show: function(aParam, aRequest, aState, aIsRefresh) { - gViewController.updateCommands(); - - // If we're being told to load a specific URL then just do that - if (aState && "url" in aState) { - this.loaded = true; - this._loadURL(aState.url); - } - - // If the view has loaded before and still at the homepage (if refreshing), - // and the error page is not visible then there is nothing else to do - if (this.loaded && this.node.selectedPanel != this._error && - (!aIsRefresh || (this._browser.currentURI && - this._browser.currentURI.spec == this._browser.homePage))) { - gViewController.notifyViewChanged(); - return; - } - - this.loaded = true; - - // No homepage means initialization isn't complete, the browser will get - // loaded once initialization is complete - if (!this.homepageURL) { - this._loadListeners.push(gViewController.notifyViewChanged.bind(gViewController)); - return; - } - - this._loadURL(this.homepageURL.spec, aIsRefresh, - gViewController.notifyViewChanged.bind(gViewController)); - }, - - canRefresh: function() { - if (this._browser.currentURI && - this._browser.currentURI.spec == this._browser.homePage) - return false; - return true; - }, - - refresh: function(aParam, aRequest, aState) { - this.show(aParam, aRequest, aState, true); - }, - - hide: function() { }, - - showError: function() { - this.node.selectedPanel = this._error; - }, - - _loadURL: function(aURL, aKeepHistory, aCallback) { - if (this._browser.currentURI.spec == aURL) { - if (aCallback) - aCallback(); - return; - } - - if (aCallback) - this._loadListeners.push(aCallback); - - var flags = 0; - if (!aKeepHistory) - flags |= Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY; - - this._browser.loadURIWithFlags(aURL, flags); - }, - - onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) { - // Ignore the about:blank load - if (aLocation.spec == "about:blank") - return; - - // When using the real session history the inner-frame will update the - // session history automatically, if using the fake history though it must - // be manually updated - if (gHistory == FakeHistory) { - var docshell = aWebProgress.QueryInterface(Ci.nsIDocShell); - - var state = { - view: "addons://discover/", - url: aLocation.spec - }; - - var replaceHistory = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY << 16; - if (docshell.loadType & replaceHistory) - gHistory.replaceState(state); - else - gHistory.pushState(state); - gViewController.lastHistoryIndex = gHistory.index; - } - - gViewController.updateCommands(); - - // If the hostname is the same as the new location's host and either the - // default scheme is insecure or the new location is secure then continue - // with the load - if (aLocation.host == this.homepageURL.host && - (!this.homepageURL.schemeIs("https") || aLocation.schemeIs("https"))) - return; - - // Canceling the request will send an error to onStateChange which will show - // the error page - aRequest.cancel(Components.results.NS_BINDING_ABORTED); - }, - - onSecurityChange: function(aWebProgress, aRequest, aState) { - // Don't care about security if the page is not https - if (!this.homepageURL.schemeIs("https")) - return; - - // If the request was secure then it is ok - if (aState & Ci.nsIWebProgressListener.STATE_IS_SECURE) - return; - - // Canceling the request will send an error to onStateChange which will show - // the error page - aRequest.cancel(Components.results.NS_BINDING_ABORTED); - }, - - onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) { - let transferStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT | - Ci.nsIWebProgressListener.STATE_IS_REQUEST | - Ci.nsIWebProgressListener.STATE_TRANSFERRING; - // Once transferring begins show the content - if ((aStateFlags & transferStart) === transferStart) - this.node.selectedPanel = this._browser; - - // Only care about the network events - if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_IS_NETWORK))) - return; - - // If this is the start of network activity then show the loading page - if (aStateFlags & (Ci.nsIWebProgressListener.STATE_START)) - this.node.selectedPanel = this._loading; - - // Ignore anything except stop events - if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP))) - return; - - // Consider the successful load of about:blank as still loading - if (aRequest instanceof Ci.nsIChannel && aRequest.URI.spec == "about:blank") - return; - - // If there was an error loading the page or the new hostname is not the - // same as the default hostname or the default scheme is secure and the new - // scheme is insecure then show the error page - const NS_ERROR_PARSED_DATA_CACHED = 0x805D0021; - if (!(Components.isSuccessCode(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED) || - (aRequest && aRequest instanceof Ci.nsIHttpChannel && !aRequest.requestSucceeded)) { - this.showError(); - } else { - // Got a successful load, make sure the browser is visible - this.node.selectedPanel = this._browser; - gViewController.updateCommands(); - } - - var listeners = this._loadListeners; - this._loadListeners = []; - - for (let listener of listeners) - listener(); - }, - - onProgressChange: function() { }, - onStatusChange: function() { }, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, - Ci.nsISupportsWeakReference]), - - getSelectedAddon: function() { - return null; - } -}; - - -var gCachedAddons = {}; - -var gSearchView = { - node: null, - _filter: null, - _sorters: null, - _loading: null, - _listBox: null, - _emptyNotice: null, - _allResultsLink: null, - _lastQuery: null, - _lastRemoteTotal: 0, - _pendingSearches: 0, - - initialize: function() { - this.node = document.getElementById("search-view"); - this._filter = document.getElementById("search-filter-radiogroup"); - this._sorters = document.getElementById("search-sorters"); - this._sorters.handler = this; - this._loading = document.getElementById("search-loading"); - this._listBox = document.getElementById("search-list"); - this._emptyNotice = document.getElementById("search-list-empty"); - this._allResultsLink = document.getElementById("search-allresults-link"); - - if (!AddonManager.isInstallEnabled("application/x-xpinstall")) - this._filter.hidden = true; - - this._listBox.addEventListener("keydown", aEvent => { - if (aEvent.keyCode == aEvent.DOM_VK_RETURN) { - var item = this._listBox.selectedItem; - if (item) - item.showInDetailView(); - } - }, false); - - this._filter.addEventListener("command", () => this.updateView(), false); - }, - - shutdown: function() { - if (AddonRepository.isSearching) - AddonRepository.cancelSearch(); - }, - - get isSearching() { - return this._pendingSearches > 0; - }, - - show: function(aQuery, aRequest) { - gEventManager.registerInstallListener(this); - - this.showEmptyNotice(false); - this.showAllResultsLink(0); - this.showLoading(true); - this._sorters.showprice = false; - - gHeader.searchQuery = aQuery; - aQuery = aQuery.trim().toLocaleLowerCase(); - if (this._lastQuery == aQuery) { - this.updateView(); - gViewController.notifyViewChanged(); - return; - } - this._lastQuery = aQuery; - - if (AddonRepository.isSearching) - AddonRepository.cancelSearch(); - - while (this._listBox.firstChild.localName == "richlistitem") - this._listBox.removeChild(this._listBox.firstChild); - - gCachedAddons = {}; - this._pendingSearches = 2; - this._sorters.setSort("relevancescore", false); - - var elements = []; - - let createSearchResults = (aObjsList, aIsInstall, aIsRemote) => { - for (let index in aObjsList) { - let obj = aObjsList[index]; - let score = aObjsList.length - index; - if (!aIsRemote && aQuery.length > 0) { - score = this.getMatchScore(obj, aQuery); - if (score == 0) - continue; - } - - let item = createItem(obj, aIsInstall, aIsRemote); - item.setAttribute("relevancescore", score); - if (aIsRemote) { - gCachedAddons[obj.id] = obj; - if (obj.purchaseURL) - this._sorters.showprice = true; - } - - elements.push(item); - } - } - - let finishSearch = (createdCount) => { - if (elements.length > 0) { - sortElements(elements, [this._sorters.sortBy], this._sorters.ascending); - for (let element of elements) - this._listBox.insertBefore(element, this._listBox.lastChild); - this.updateListAttributes(); - } - - this._pendingSearches--; - this.updateView(); - - if (!this.isSearching) - gViewController.notifyViewChanged(); - } - - getAddonsAndInstalls(null, function(aAddons, aInstalls) { - if (gViewController && aRequest != gViewController.currentViewRequest) - return; - - createSearchResults(aAddons, false, false); - createSearchResults(aInstalls, true, false); - finishSearch(); - }); - - var maxRemoteResults = 0; - try { - maxRemoteResults = Services.prefs.getIntPref(PREF_MAXRESULTS); - } catch (e) {} - - if (maxRemoteResults <= 0) { - finishSearch(0); - return; - } - - AddonRepository.searchAddons(aQuery, maxRemoteResults, { - searchFailed: () => { - if (gViewController && aRequest != gViewController.currentViewRequest) - return; - - this._lastRemoteTotal = 0; - - // XXXunf Better handling of AMO search failure. See bug 579502 - finishSearch(0); // Silently fail - }, - - searchSucceeded: (aAddonsList, aAddonCount, aTotalResults) => { - if (gViewController && aRequest != gViewController.currentViewRequest) - return; - - if (aTotalResults > maxRemoteResults) - this._lastRemoteTotal = aTotalResults; - else - this._lastRemoteTotal = 0; - - var createdCount = createSearchResults(aAddonsList, false, true); - finishSearch(createdCount); - } - }); - }, - - showLoading: function(aLoading) { - this._loading.hidden = !aLoading; - this._listBox.hidden = aLoading; - }, - - updateView: function() { - var showLocal = this._filter.value == "local"; - - if (!showLocal && !AddonManager.isInstallEnabled("application/x-xpinstall")) - showLocal = true; - - this._listBox.setAttribute("local", showLocal); - this._listBox.setAttribute("remote", !showLocal); - - this.showLoading(this.isSearching && !showLocal); - if (!this.isSearching) { - var isEmpty = true; - var results = this._listBox.getElementsByTagName("richlistitem"); - for (let result of results) { - var isRemote = (result.getAttribute("remote") == "true"); - if ((isRemote && !showLocal) || (!isRemote && showLocal)) { - isEmpty = false; - break; - } - } - - this.showEmptyNotice(isEmpty); - this.showAllResultsLink(this._lastRemoteTotal); - } - - gViewController.updateCommands(); - }, - - hide: function() { - gEventManager.unregisterInstallListener(this); - doPendingUninstalls(this._listBox); - }, - - getMatchScore: function(aObj, aQuery) { - var score = 0; - score += this.calculateMatchScore(aObj.name, aQuery, - SEARCH_SCORE_MULTIPLIER_NAME); - score += this.calculateMatchScore(aObj.description, aQuery, - SEARCH_SCORE_MULTIPLIER_DESCRIPTION); - return score; - }, - - calculateMatchScore: function(aStr, aQuery, aMultiplier) { - var score = 0; - if (!aStr || aQuery.length == 0) - return score; - - aStr = aStr.trim().toLocaleLowerCase(); - var haystack = aStr.split(/\s+/); - var needles = aQuery.split(/\s+/); - - for (let needle of needles) { - for (let hay of haystack) { - if (hay == needle) { - // matching whole words is best - score += SEARCH_SCORE_MATCH_WHOLEWORD; - } else { - let i = hay.indexOf(needle); - if (i == 0) // matching on word boundries is also good - score += SEARCH_SCORE_MATCH_WORDBOUNDRY; - else if (i > 0) // substring matches not so good - score += SEARCH_SCORE_MATCH_SUBSTRING; - } - } - } - - // give progressively higher score for longer queries, since longer queries - // are more likely to be unique and therefore more relevant. - if (needles.length > 1 && aStr.indexOf(aQuery) != -1) - score += needles.length; - - return score * aMultiplier; - }, - - showEmptyNotice: function(aShow) { - this._emptyNotice.hidden = !aShow; - this._listBox.hidden = aShow; - }, - - showAllResultsLink: function(aTotalResults) { - if (aTotalResults == 0) { - this._allResultsLink.hidden = true; - return; - } - - var linkStr = gStrings.ext.GetStringFromName("showAllSearchResults"); - linkStr = PluralForm.get(aTotalResults, linkStr); - linkStr = linkStr.replace("#1", aTotalResults); - this._allResultsLink.setAttribute("value", linkStr); - - this._allResultsLink.setAttribute("href", - AddonRepository.getSearchURL(this._lastQuery)); - this._allResultsLink.hidden = false; - }, - - updateListAttributes: function() { - var item = this._listBox.querySelector("richlistitem[remote='true'][first]"); - if (item) - item.removeAttribute("first"); - item = this._listBox.querySelector("richlistitem[remote='true'][last]"); - if (item) - item.removeAttribute("last"); - var items = this._listBox.querySelectorAll("richlistitem[remote='true']"); - if (items.length > 0) { - items[0].setAttribute("first", true); - items[items.length - 1].setAttribute("last", true); - } - - item = this._listBox.querySelector("richlistitem:not([remote='true'])[first]"); - if (item) - item.removeAttribute("first"); - item = this._listBox.querySelector("richlistitem:not([remote='true'])[last]"); - if (item) - item.removeAttribute("last"); - items = this._listBox.querySelectorAll("richlistitem:not([remote='true'])"); - if (items.length > 0) { - items[0].setAttribute("first", true); - items[items.length - 1].setAttribute("last", true); - } - - }, - - onSortChanged: function(aSortBy, aAscending) { - var footer = this._listBox.lastChild; - this._listBox.removeChild(footer); - - sortList(this._listBox, aSortBy, aAscending); - this.updateListAttributes(); - - this._listBox.appendChild(footer); - }, - - onDownloadCancelled: function(aInstall) { - this.removeInstall(aInstall); - }, - - onInstallCancelled: function(aInstall) { - this.removeInstall(aInstall); - }, - - removeInstall: function(aInstall) { - for (let item of this._listBox.childNodes) { - if (item.mInstall == aInstall) { - this._listBox.removeChild(item); - return; - } - } - }, - - getSelectedAddon: function() { - var item = this._listBox.selectedItem; - if (item) - return item.mAddon; - return null; - }, - - getListItemForID: function(aId) { - var listitem = this._listBox.firstChild; - while (listitem) { - if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId) - return listitem; - listitem = listitem.nextSibling; - } - return null; - } -}; - - -var gListView = { - node: null, - _listBox: null, - _emptyNotice: null, - _type: null, - - initialize: function() { - this.node = document.getElementById("list-view"); - this._listBox = document.getElementById("addon-list"); - this._emptyNotice = document.getElementById("addon-list-empty"); - - this._listBox.addEventListener("keydown", (aEvent) => { - if (aEvent.keyCode == aEvent.DOM_VK_RETURN) { - var item = this._listBox.selectedItem; - if (item) - item.showInDetailView(); - } - }, false); - - document.getElementById("signing-learn-more").setAttribute("href", - Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons"); - - let findSignedAddonsLink = document.getElementById("find-alternative-addons"); - try { - findSignedAddonsLink.setAttribute("href", - Services.urlFormatter.formatURLPref("extensions.getAddons.link.url")); - } catch (e) { - findSignedAddonsLink.classList.remove("text-link"); - } - - try { - document.getElementById("signing-dev-manual-link").setAttribute("href", - Services.prefs.getCharPref("xpinstall.signatures.devInfoURL")); - } catch (e) { - document.getElementById("signing-dev-info").hidden = true; - } - - // To-Do: remove deprecation notice content. - document.getElementById("plugindeprecation-notice").hidden = true; - }, - - show: function(aType, aRequest) { - let showOnlyDisabledUnsigned = false; - if (aType.endsWith("?unsigned=true")) { - aType = aType.replace(/\?.*/, ""); - showOnlyDisabledUnsigned = true; - } - - if (!(aType in AddonManager.addonTypes)) - throw Components.Exception("Attempting to show unknown type " + aType, Cr.NS_ERROR_INVALID_ARG); - - this._type = aType; - this.node.setAttribute("type", aType); - this.showEmptyNotice(false); - - while (this._listBox.itemCount > 0) - this._listBox.removeItemAt(0); - - if (aType == "plugin") { - navigator.plugins.refresh(false); - } - - getAddonsAndInstalls(aType, (aAddonsList, aInstallsList) => { - if (gViewController && aRequest != gViewController.currentViewRequest) - return; - - var elements = []; - - for (let addonItem of aAddonsList) - elements.push(createItem(addonItem)); - - for (let installItem of aInstallsList) - elements.push(createItem(installItem, true)); - - this.showEmptyNotice(elements.length == 0); - if (elements.length > 0) { - sortElements(elements, ["uiState", "name"], true); - for (let element of elements) - this._listBox.appendChild(element); - } - - this.filterDisabledUnsigned(showOnlyDisabledUnsigned); - - gEventManager.registerInstallListener(this); - gViewController.updateCommands(); - gViewController.notifyViewChanged(); - }); - }, - - hide: function() { - gEventManager.unregisterInstallListener(this); - doPendingUninstalls(this._listBox); - }, - - filterDisabledUnsigned: function(aFilter = true) { - let foundDisabledUnsigned = false; - - if (SIGNING_REQUIRED) { - for (let item of this._listBox.childNodes) { - if (!isCorrectlySigned(item.mAddon)) - foundDisabledUnsigned = true; - else - item.hidden = aFilter; - } - } - - document.getElementById("show-disabled-unsigned-extensions").hidden = - aFilter || !foundDisabledUnsigned; - - document.getElementById("show-all-extensions").hidden = !aFilter; - document.getElementById("disabled-unsigned-addons-info").hidden = !aFilter; - }, - - showEmptyNotice: function(aShow) { - this._emptyNotice.hidden = !aShow; - this._listBox.hidden = aShow; - }, - - onSortChanged: function(aSortBy, aAscending) { - sortList(this._listBox, aSortBy, aAscending); - }, - - onExternalInstall: function(aAddon, aExistingAddon, aRequiresRestart) { - // The existing list item will take care of upgrade installs - if (aExistingAddon) - return; - - if (aAddon.hidden) - return; - - this.addItem(aAddon); - }, - - onDownloadStarted: function(aInstall) { - this.addItem(aInstall, true); - }, - - onInstallStarted: function(aInstall) { - this.addItem(aInstall, true); - }, - - onDownloadCancelled: function(aInstall) { - this.removeItem(aInstall, true); - }, - - onInstallCancelled: function(aInstall) { - this.removeItem(aInstall, true); - }, - - onInstallEnded: function(aInstall) { - // Remove any install entries for upgrades, their status will appear against - // the existing item - if (aInstall.existingAddon) - this.removeItem(aInstall, true); - }, - - addItem: function(aObj, aIsInstall) { - if (aObj.type != this._type) - return; - - if (aIsInstall && aObj.existingAddon) - return; - - let prop = aIsInstall ? "mInstall" : "mAddon"; - for (let item of this._listBox.childNodes) { - if (item[prop] == aObj) - return; - } - - let item = createItem(aObj, aIsInstall); - this._listBox.insertBefore(item, this._listBox.firstChild); - this.showEmptyNotice(false); - }, - - removeItem: function(aObj, aIsInstall) { - let prop = aIsInstall ? "mInstall" : "mAddon"; - - for (let item of this._listBox.childNodes) { - if (item[prop] == aObj) { - this._listBox.removeChild(item); - this.showEmptyNotice(this._listBox.itemCount == 0); - return; - } - } - }, - - getSelectedAddon: function() { - var item = this._listBox.selectedItem; - if (item) - return item.mAddon; - return null; - }, - - getListItemForID: function(aId) { - var listitem = this._listBox.firstChild; - while (listitem) { - if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId) - return listitem; - listitem = listitem.nextSibling; - } - return null; - } -}; - - -var gDetailView = { - node: null, - _addon: null, - _loadingTimer: null, - _autoUpdate: null, - - initialize: function() { - this.node = document.getElementById("detail-view"); - - this._autoUpdate = document.getElementById("detail-autoUpdate"); - - this._autoUpdate.addEventListener("command", () => { - this._addon.applyBackgroundUpdates = this._autoUpdate.value; - }, true); - }, - - shutdown: function() { - AddonManager.removeManagerListener(this); - }, - - onUpdateModeChanged: function() { - this.onPropertyChanged(["applyBackgroundUpdates"]); - }, - - _updateView: function(aAddon, aIsRemote, aScrollToPreferences) { - AddonManager.addManagerListener(this); - this.clearLoading(); - - this._addon = aAddon; - gEventManager.registerAddonListener(this, aAddon.id); - gEventManager.registerInstallListener(this); - - this.node.setAttribute("type", aAddon.type); - - // If the search category isn't selected then make sure to select the - // correct category - if (gCategories.selected != "addons://search/") - gCategories.select("addons://list/" + aAddon.type); - - document.getElementById("detail-name").textContent = aAddon.name; - var icon = AddonManager.getPreferredIconURL(aAddon, 64, window); - document.getElementById("detail-icon").src = icon ? icon : ""; - document.getElementById("detail-creator").setCreator(aAddon.creator, aAddon.homepageURL); - - var version = document.getElementById("detail-version"); - if (shouldShowVersionNumber(aAddon)) { - version.hidden = false; - version.value = aAddon.version; - } else { - version.hidden = true; - } - - var screenshotbox = document.getElementById("detail-screenshot-box"); - var screenshot = document.getElementById("detail-screenshot"); - if (aAddon.screenshots && aAddon.screenshots.length > 0) { - if (aAddon.screenshots[0].thumbnailURL) { - screenshot.src = aAddon.screenshots[0].thumbnailURL; - screenshot.width = aAddon.screenshots[0].thumbnailWidth; - screenshot.height = aAddon.screenshots[0].thumbnailHeight; - } else { - screenshot.src = aAddon.screenshots[0].url; - screenshot.width = aAddon.screenshots[0].width; - screenshot.height = aAddon.screenshots[0].height; - } - screenshot.setAttribute("loading", "true"); - screenshotbox.hidden = false; - } else { - screenshotbox.hidden = true; - } - - var desc = document.getElementById("detail-desc"); - desc.textContent = aAddon.description; - - var fullDesc = document.getElementById("detail-fulldesc"); - if (aAddon.fullDescription) { - // The following is part of an awful hack to include the licenses for GMP - // plugins without having bug 624602 fixed yet, and intentionally ignores - // localisation. - if (aAddon.isGMPlugin) { - fullDesc.innerHTML = aAddon.fullDescription; - } else { - fullDesc.textContent = aAddon.fullDescription; - } - - fullDesc.hidden = false; - } else { - fullDesc.hidden = true; - } - - var contributions = document.getElementById("detail-contributions"); - if ("contributionURL" in aAddon && aAddon.contributionURL) { - contributions.hidden = false; - var amount = document.getElementById("detail-contrib-suggested"); - if (aAddon.contributionAmount) { - amount.value = gStrings.ext.formatStringFromName("contributionAmount2", - [aAddon.contributionAmount], - 1); - amount.hidden = false; - } else { - amount.hidden = true; - } - } else { - contributions.hidden = true; - } - - if ("purchaseURL" in aAddon && aAddon.purchaseURL) { - var purchase = document.getElementById("detail-purchase-btn"); - purchase.label = gStrings.ext.formatStringFromName("cmd.purchaseAddon.label", - [aAddon.purchaseDisplayAmount], - 1); - purchase.accesskey = gStrings.ext.GetStringFromName("cmd.purchaseAddon.accesskey"); - } - - var updateDateRow = document.getElementById("detail-dateUpdated"); - if (aAddon.updateDate) { - var date = formatDate(aAddon.updateDate); - updateDateRow.value = date; - } else { - updateDateRow.value = null; - } - - // TODO if the add-on was downloaded from releases.mozilla.org link to the - // AMO profile (bug 590344) - if (false) { - document.getElementById("detail-repository-row").hidden = false; - document.getElementById("detail-homepage-row").hidden = true; - var repository = document.getElementById("detail-repository"); - repository.value = aAddon.homepageURL; - repository.href = aAddon.homepageURL; - } else if (aAddon.homepageURL) { - document.getElementById("detail-repository-row").hidden = true; - document.getElementById("detail-homepage-row").hidden = false; - var homepage = document.getElementById("detail-homepage"); - homepage.value = aAddon.homepageURL; - homepage.href = aAddon.homepageURL; - } else { - document.getElementById("detail-repository-row").hidden = true; - document.getElementById("detail-homepage-row").hidden = true; - } - - var rating = document.getElementById("detail-rating"); - if (aAddon.averageRating) { - rating.averageRating = aAddon.averageRating; - rating.hidden = false; - } else { - rating.hidden = true; - } - - var reviews = document.getElementById("detail-reviews"); - if (aAddon.reviewURL) { - var text = gStrings.ext.GetStringFromName("numReviews"); - text = PluralForm.get(aAddon.reviewCount, text) - text = text.replace("#1", aAddon.reviewCount); - reviews.value = text; - reviews.hidden = false; - reviews.href = aAddon.reviewURL; - } else { - reviews.hidden = true; - } - - document.getElementById("detail-rating-row").hidden = !aAddon.averageRating && !aAddon.reviewURL; - - var sizeRow = document.getElementById("detail-size"); - if (aAddon.size && aIsRemote) { - let [size, unit] = DownloadUtils.convertByteUnits(parseInt(aAddon.size)); - let formatted = gStrings.dl.GetStringFromName("doneSize"); - formatted = formatted.replace("#1", size).replace("#2", unit); - sizeRow.value = formatted; - } else { - sizeRow.value = null; - } - - var downloadsRow = document.getElementById("detail-downloads"); - if (aAddon.totalDownloads && aIsRemote) { - var downloads = aAddon.totalDownloads; - downloadsRow.value = downloads; - } else { - downloadsRow.value = null; - } - - var canUpdate = !aIsRemote && hasPermission(aAddon, "upgrade") && aAddon.id != AddonManager.hotfixID; - document.getElementById("detail-updates-row").hidden = !canUpdate; - - if ("applyBackgroundUpdates" in aAddon) { - this._autoUpdate.hidden = false; - this._autoUpdate.value = aAddon.applyBackgroundUpdates; - let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon); - document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates; - } else { - this._autoUpdate.hidden = true; - document.getElementById("detail-findUpdates-btn").hidden = false; - } - - document.getElementById("detail-prefs-btn").hidden = !aIsRemote && - !gViewController.commands.cmd_showItemPreferences.isEnabled(aAddon); - - var gridRows = document.querySelectorAll("#detail-grid rows row"); - let first = true; - for (let gridRow of gridRows) { - if (first && window.getComputedStyle(gridRow, null).getPropertyValue("display") != "none") { - gridRow.setAttribute("first-row", true); - first = false; - } else { - gridRow.removeAttribute("first-row"); - } - } - - this.fillSettingsRows(aScrollToPreferences, (function() { - this.updateState(); - gViewController.notifyViewChanged(); - }).bind(this)); - }, - - show: function(aAddonId, aRequest) { - let index = aAddonId.indexOf("/preferences"); - let scrollToPreferences = false; - if (index >= 0) { - aAddonId = aAddonId.substring(0, index); - scrollToPreferences = true; - } - - this._loadingTimer = setTimeout(() => { - this.node.setAttribute("loading-extended", true); - }, LOADING_MSG_DELAY); - - var view = gViewController.currentViewId; - - AddonManager.getAddonByID(aAddonId, (aAddon) => { - if (gViewController && aRequest != gViewController.currentViewRequest) - return; - - if (aAddon) { - this._updateView(aAddon, false, scrollToPreferences); - return; - } - - // Look for an add-on pending install - AddonManager.getAllInstalls(aInstalls => { - for (let install of aInstalls) { - if (install.state == AddonManager.STATE_INSTALLED && - install.addon.id == aAddonId) { - this._updateView(install.addon, false); - return; - } - } - - if (aAddonId in gCachedAddons) { - this._updateView(gCachedAddons[aAddonId], true); - return; - } - - // This might happen due to session restore restoring us back to an - // add-on that doesn't exist but otherwise shouldn't normally happen. - // Either way just revert to the default view. - gViewController.replaceView(gViewDefault); - }); - }); - }, - - hide: function() { - AddonManager.removeManagerListener(this); - this.clearLoading(); - if (this._addon) { - if (hasInlineOptions(this._addon)) { - Services.obs.notifyObservers(document, - AddonManager.OPTIONS_NOTIFICATION_HIDDEN, - this._addon.id); - } - - gEventManager.unregisterAddonListener(this, this._addon.id); - gEventManager.unregisterInstallListener(this); - this._addon = null; - - // Flush the preferences to disk so they survive any crash - if (this.node.getElementsByTagName("setting").length) - Services.prefs.savePrefFile(null); - } - }, - - updateState: function() { - gViewController.updateCommands(); - - var pending = this._addon.pendingOperations; - if (pending != AddonManager.PENDING_NONE) { - this.node.removeAttribute("notification"); - - pending = null; - const PENDING_OPERATIONS = ["enable", "disable", "install", "uninstall", - "upgrade"]; - for (let op of PENDING_OPERATIONS) { - if (isPending(this._addon, op)) - pending = op; - } - - this.node.setAttribute("pending", pending); - document.getElementById("detail-pending").textContent = gStrings.ext.formatStringFromName( - "details.notification." + pending, - [this._addon.name, gStrings.brandShortName], 2 - ); - } else { - this.node.removeAttribute("pending"); - - if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) { - this.node.setAttribute("notification", "error"); - document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName( - "details.notification.blocked", - [this._addon.name], 1 - ); - var errorLink = document.getElementById("detail-error-link"); - errorLink.value = gStrings.ext.GetStringFromName("details.notification.blocked.link"); - errorLink.href = this._addon.blocklistURL; - errorLink.hidden = false; - } else if (!isCorrectlySigned(this._addon) && SIGNING_REQUIRED) { - this.node.setAttribute("notification", "error"); - document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName( - "details.notification.unsignedAndDisabled", [this._addon.name, gStrings.brandShortName], 2 - ); - let errorLink = document.getElementById("detail-error-link"); - errorLink.value = gStrings.ext.GetStringFromName("details.notification.unsigned.link"); - errorLink.href = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons"; - errorLink.hidden = false; - } else if (!this._addon.isCompatible && (AddonManager.checkCompatibility || - (this._addon.blocklistState != Ci.nsIBlocklistService.STATE_SOFTBLOCKED))) { - this.node.setAttribute("notification", "warning"); - document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName( - "details.notification.incompatible", - [this._addon.name, gStrings.brandShortName, gStrings.appVersion], 3 - ); - document.getElementById("detail-warning-link").hidden = true; - } else if (!isCorrectlySigned(this._addon)) { - this.node.setAttribute("notification", "warning"); - document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName( - "details.notification.unsigned", [this._addon.name, gStrings.brandShortName], 2 - ); - var warningLink = document.getElementById("detail-warning-link"); - warningLink.value = gStrings.ext.GetStringFromName("details.notification.unsigned.link"); - warningLink.href = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons"; - warningLink.hidden = false; - } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) { - this.node.setAttribute("notification", "warning"); - document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName( - "details.notification.softblocked", - [this._addon.name], 1 - ); - let warningLink = document.getElementById("detail-warning-link"); - warningLink.value = gStrings.ext.GetStringFromName("details.notification.softblocked.link"); - warningLink.href = this._addon.blocklistURL; - warningLink.hidden = false; - } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) { - this.node.setAttribute("notification", "warning"); - document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName( - "details.notification.outdated", - [this._addon.name], 1 - ); - let warningLink = document.getElementById("detail-warning-link"); - warningLink.value = gStrings.ext.GetStringFromName("details.notification.outdated.link"); - warningLink.href = this._addon.blocklistURL; - warningLink.hidden = false; - } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) { - this.node.setAttribute("notification", "error"); - document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName( - "details.notification.vulnerableUpdatable", - [this._addon.name], 1 - ); - let errorLink = document.getElementById("detail-error-link"); - errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableUpdatable.link"); - errorLink.href = this._addon.blocklistURL; - errorLink.hidden = false; - } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) { - this.node.setAttribute("notification", "error"); - document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName( - "details.notification.vulnerableNoUpdate", - [this._addon.name], 1 - ); - let errorLink = document.getElementById("detail-error-link"); - errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableNoUpdate.link"); - errorLink.href = this._addon.blocklistURL; - errorLink.hidden = false; - } else if (this._addon.isGMPlugin && !this._addon.isInstalled && - this._addon.isActive) { - this.node.setAttribute("notification", "warning"); - let warning = document.getElementById("detail-warning"); - warning.textContent = - gStrings.ext.formatStringFromName("details.notification.gmpPending", - [this._addon.name], 1); - } else { - this.node.removeAttribute("notification"); - } - } - - let menulist = document.getElementById("detail-state-menulist"); - let addonType = AddonManager.addonTypes[this._addon.type]; - if (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) { - let askItem = document.getElementById("detail-ask-to-activate-menuitem"); - let alwaysItem = document.getElementById("detail-always-activate-menuitem"); - let neverItem = document.getElementById("detail-never-activate-menuitem"); - let hasActivatePermission = - ["ask_to_activate", "enable", "disable"].some(perm => hasPermission(this._addon, perm)); - - if (!this._addon.isActive) { - menulist.selectedItem = neverItem; - } else if (this._addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) { - menulist.selectedItem = askItem; - } else { - menulist.selectedItem = alwaysItem; - } - - menulist.disabled = !hasActivatePermission; - menulist.hidden = false; - menulist.classList.add('no-auto-hide'); - } else { - menulist.hidden = true; - } - - this.node.setAttribute("active", this._addon.isActive); - }, - - clearLoading: function() { - if (this._loadingTimer) { - clearTimeout(this._loadingTimer); - this._loadingTimer = null; - } - - this.node.removeAttribute("loading-extended"); - }, - - emptySettingsRows: function() { - var lastRow = document.getElementById("detail-downloads"); - var rows = lastRow.parentNode; - while (lastRow.nextSibling) - rows.removeChild(rows.lastChild); - }, - - fillSettingsRows: function(aScrollToPreferences, aCallback) { - this.emptySettingsRows(); - if (!hasInlineOptions(this._addon)) { - if (aCallback) - aCallback(); - return; - } - - // We can't use a promise for this, since some code (especially in tests) - // relies on us finishing before the ViewChanged event bubbles up to its - // listeners, and promises resolve asynchronously. - let whenViewLoaded = callback => { - if (gViewController.displayedView.hasAttribute("loading")) { - gDetailView.node.addEventListener("ViewChanged", function viewChangedEventListener() { - gDetailView.node.removeEventListener("ViewChanged", viewChangedEventListener); - callback(); - }); - } else { - callback(); - } - }; - - let finish = (firstSetting) => { - // Ensure the page has loaded and force the XBL bindings to be synchronously applied, - // then notify observers. - whenViewLoaded(() => { - if (firstSetting) - firstSetting.clientTop; - Services.obs.notifyObservers(document, - AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, - this._addon.id); - if (aScrollToPreferences) - gDetailView.scrollToPreferencesRows(); - }); - } - - // This function removes and returns the text content of aNode without - // removing any child elements. Removing the text nodes ensures any XBL - // bindings apply properly. - function stripTextNodes(aNode) { - var text = ''; - for (var i = 0; i < aNode.childNodes.length; i++) { - if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) { - text += aNode.childNodes[i].textContent; - aNode.removeChild(aNode.childNodes[i--]); - } else { - text += stripTextNodes(aNode.childNodes[i]); - } - } - return text; - } - - var rows = document.getElementById("detail-downloads").parentNode; - - try { - if (this._addon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_BROWSER) { - whenViewLoaded(() => { - this.createOptionsBrowser(rows).then(browser => { - // Make sure the browser is unloaded as soon as we change views, - // rather than waiting for the next detail view to load. - document.addEventListener("ViewChanged", function viewChangedEventListener() { - document.removeEventListener("ViewChanged", viewChangedEventListener); - browser.remove(); - }); - - finish(browser); - }); - }); - - if (aCallback) - aCallback(); - } else { - var xhr = new XMLHttpRequest(); - xhr.open("GET", this._addon.optionsURL, true); - xhr.responseType = "xml"; - xhr.onload = (function() { - var xml = xhr.responseXML; - var settings = xml.querySelectorAll(":root > setting"); - - var firstSetting = null; - for (var setting of settings) { - - var desc = stripTextNodes(setting).trim(); - if (!setting.hasAttribute("desc")) - setting.setAttribute("desc", desc); - - var type = setting.getAttribute("type"); - if (type == "file" || type == "directory") - setting.setAttribute("fullpath", "true"); - - setting = document.importNode(setting, true); - var style = setting.getAttribute("style"); - if (style) { - setting.removeAttribute("style"); - setting.setAttribute("style", style); - } - - rows.appendChild(setting); - var visible = window.getComputedStyle(setting, null).getPropertyValue("display") != "none"; - if (!firstSetting && visible) { - setting.setAttribute("first-row", true); - firstSetting = setting; - } - } - - finish(firstSetting); - - if (aCallback) - aCallback(); - }).bind(this); - xhr.onerror = function(aEvent) { - Cu.reportError("Error " + aEvent.target.status + - " occurred while receiving " + this._addon.optionsURL); - if (aCallback) - aCallback(); - }; - xhr.send(); - } - } catch (e) { - Cu.reportError(e); - if (aCallback) - aCallback(); - } - }, - - scrollToPreferencesRows: function() { - // We find this row, rather than remembering it from above, - // in case it has been changed by the observers. - let firstRow = gDetailView.node.querySelector('setting[first-row="true"]'); - if (firstRow) { - let top = firstRow.boxObject.y; - top -= parseInt(window.getComputedStyle(firstRow, null).getPropertyValue("margin-top")); - - let detailViewBoxObject = gDetailView.node.boxObject; - top -= detailViewBoxObject.y; - - detailViewBoxObject.scrollTo(0, top); - } - }, - - createOptionsBrowser: function(parentNode) { - let browser = document.createElement("browser"); - browser.setAttribute("type", "content"); - browser.setAttribute("disableglobalhistory", "true"); - browser.setAttribute("class", "inline-options-browser"); - - return new Promise((resolve, reject) => { - let messageListener = { - receiveMessage({name, data}) { - if (name === "Extension:BrowserResized") - browser.style.height = `${data.height}px`; - else if (name === "Extension:BrowserContentLoaded") - resolve(browser); - }, - }; - - let onload = () => { - browser.removeEventListener("load", onload, true); - - let mm = new FakeFrameMessageManager(browser); - mm.loadFrameScript("chrome://extensions/content/ext-browser-content.js", - false); - mm.addMessageListener("Extension:BrowserContentLoaded", messageListener); - mm.addMessageListener("Extension:BrowserResized", messageListener); - mm.sendAsyncMessage("Extension:InitBrowser", {fixedWidth: true}); - - browser.setAttribute("src", this._addon.optionsURL); - }; - browser.addEventListener("load", onload, true); - browser.addEventListener("error", reject); - - parentNode.appendChild(browser); - }); - }, - - getSelectedAddon: function() { - return this._addon; - }, - - onEnabling: function() { - this.updateState(); - }, - - onEnabled: function() { - this.updateState(); - this.fillSettingsRows(); - }, - - onDisabling: function(aNeedsRestart) { - this.updateState(); - if (!aNeedsRestart && hasInlineOptions(this._addon)) { - Services.obs.notifyObservers(document, - AddonManager.OPTIONS_NOTIFICATION_HIDDEN, - this._addon.id); - } - }, - - onDisabled: function() { - this.updateState(); - this.emptySettingsRows(); - }, - - onUninstalling: function() { - this.updateState(); - }, - - onUninstalled: function() { - gViewController.popState(); - }, - - onOperationCancelled: function() { - this.updateState(); - }, - - onPropertyChanged: function(aProperties) { - if (aProperties.indexOf("applyBackgroundUpdates") != -1) { - this._autoUpdate.value = this._addon.applyBackgroundUpdates; - let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon); - document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates; - } - - if (aProperties.indexOf("appDisabled") != -1 || - aProperties.indexOf("signedState") != -1 || - aProperties.indexOf("userDisabled") != -1) - this.updateState(); - }, - - onExternalInstall: function(aAddon, aExistingAddon, aNeedsRestart) { - // Only care about upgrades for the currently displayed add-on - if (!aExistingAddon || aExistingAddon.id != this._addon.id) - return; - - if (!aNeedsRestart) - this._updateView(aAddon, false); - else - this.updateState(); - }, - - onInstallCancelled: function(aInstall) { - if (aInstall.addon.id == this._addon.id) - gViewController.popState(); - } -}; - - -var gUpdatesView = { - node: null, - _listBox: null, - _emptyNotice: null, - _sorters: null, - _updateSelected: null, - _categoryItem: null, - - initialize: function() { - this.node = document.getElementById("updates-view"); - this._listBox = document.getElementById("updates-list"); - this._emptyNotice = document.getElementById("updates-list-empty"); - this._sorters = document.getElementById("updates-sorters"); - this._sorters.handler = this; - - this._categoryItem = gCategories.get("addons://updates/available"); - - this._updateSelected = document.getElementById("update-selected-btn"); - this._updateSelected.addEventListener("command", function() { - gUpdatesView.installSelected(); - }, false); - - this.updateAvailableCount(true); - - AddonManager.addAddonListener(this); - AddonManager.addInstallListener(this); - }, - - shutdown: function() { - AddonManager.removeAddonListener(this); - AddonManager.removeInstallListener(this); - }, - - show: function(aType, aRequest) { - document.getElementById("empty-availableUpdates-msg").hidden = aType != "available"; - document.getElementById("empty-recentUpdates-msg").hidden = aType != "recent"; - this.showEmptyNotice(false); - - while (this._listBox.itemCount > 0) - this._listBox.removeItemAt(0); - - this.node.setAttribute("updatetype", aType); - if (aType == "recent") - this._showRecentUpdates(aRequest); - else - this._showAvailableUpdates(false, aRequest); - }, - - hide: function() { - this._updateSelected.hidden = true; - this._categoryItem.disabled = this._categoryItem.badgeCount == 0; - doPendingUninstalls(this._listBox); - }, - - _showRecentUpdates: function(aRequest) { - AddonManager.getAllAddons((aAddonsList) => { - if (gViewController && aRequest != gViewController.currentViewRequest) - return; - - var elements = []; - let threshold = Date.now() - UPDATES_RECENT_TIMESPAN; - for (let addon of aAddonsList) { - if (addon.hidden || !addon.updateDate || addon.updateDate.getTime() < threshold) - continue; - - elements.push(createItem(addon)); - } - - this.showEmptyNotice(elements.length == 0); - if (elements.length > 0) { - sortElements(elements, [this._sorters.sortBy], this._sorters.ascending); - for (let element of elements) - this._listBox.appendChild(element); - } - - gViewController.notifyViewChanged(); - }); - }, - - _showAvailableUpdates: function(aIsRefresh, aRequest) { - /* Disable the Update Selected button so it can't get clicked - before everything is initialized asynchronously. - It will get re-enabled by maybeDisableUpdateSelected(). */ - this._updateSelected.disabled = true; - - AddonManager.getAllInstalls((aInstallsList) => { - if (!aIsRefresh && gViewController && aRequest && - aRequest != gViewController.currentViewRequest) - return; - - if (aIsRefresh) { - this.showEmptyNotice(false); - this._updateSelected.hidden = true; - - while (this._listBox.childNodes.length > 0) - this._listBox.removeChild(this._listBox.firstChild); - } - - var elements = []; - - for (let install of aInstallsList) { - if (!this.isManualUpdate(install)) - continue; - - let item = createItem(install.existingAddon); - item.setAttribute("upgrade", true); - item.addEventListener("IncludeUpdateChanged", () => { - this.maybeDisableUpdateSelected(); - }, false); - elements.push(item); - } - - this.showEmptyNotice(elements.length == 0); - if (elements.length > 0) { - this._updateSelected.hidden = false; - sortElements(elements, [this._sorters.sortBy], this._sorters.ascending); - for (let element of elements) - this._listBox.appendChild(element); - } - - // ensure badge count is in sync - this._categoryItem.badgeCount = this._listBox.itemCount; - - gViewController.notifyViewChanged(); - }); - }, - - showEmptyNotice: function(aShow) { - this._emptyNotice.hidden = !aShow; - this._listBox.hidden = aShow; - }, - - isManualUpdate: function(aInstall, aOnlyAvailable) { - var isManual = aInstall.existingAddon && - !AddonManager.shouldAutoUpdate(aInstall.existingAddon); - if (isManual && aOnlyAvailable) - return isInState(aInstall, "available"); - return isManual; - }, - - maybeRefresh: function() { - if (gViewController.currentViewId == "addons://updates/available") - this._showAvailableUpdates(true); - this.updateAvailableCount(); - }, - - updateAvailableCount: function(aInitializing) { - if (aInitializing) - gPendingInitializations++; - AddonManager.getAllInstalls((aInstallsList) => { - var count = aInstallsList.filter(aInstall => { - return this.isManualUpdate(aInstall, true); - }).length; - this._categoryItem.disabled = gViewController.currentViewId != "addons://updates/available" && - count == 0; - this._categoryItem.badgeCount = count; - if (aInitializing) - notifyInitialized(); - }); - }, - - maybeDisableUpdateSelected: function() { - for (let item of this._listBox.childNodes) { - if (item.includeUpdate) { - this._updateSelected.disabled = false; - return; - } - } - this._updateSelected.disabled = true; - }, - - installSelected: function() { - for (let item of this._listBox.childNodes) { - if (item.includeUpdate) - item.upgrade(); - } - - this._updateSelected.disabled = true; - }, - - getSelectedAddon: function() { - var item = this._listBox.selectedItem; - if (item) - return item.mAddon; - return null; - }, - - getListItemForID: function(aId) { - var listitem = this._listBox.firstChild; - while (listitem) { - if (listitem.mAddon.id == aId) - return listitem; - listitem = listitem.nextSibling; - } - return null; - }, - - onSortChanged: function(aSortBy, aAscending) { - sortList(this._listBox, aSortBy, aAscending); - }, - - onNewInstall: function(aInstall) { - if (!this.isManualUpdate(aInstall)) - return; - this.maybeRefresh(); - }, - - onInstallStarted: function(aInstall) { - this.updateAvailableCount(); - }, - - onInstallCancelled: function(aInstall) { - if (!this.isManualUpdate(aInstall)) - return; - this.maybeRefresh(); - }, - - onPropertyChanged: function(aAddon, aProperties) { - if (aProperties.indexOf("applyBackgroundUpdates") != -1) - this.updateAvailableCount(); - } -}; - -var gDragDrop = { - onDragOver: function(aEvent) { - var types = aEvent.dataTransfer.types; - if (types.includes("text/uri-list") || - types.includes("text/x-moz-url") || - types.includes("application/x-moz-file")) - aEvent.preventDefault(); - }, - - onDrop: function(aEvent) { - var dataTransfer = aEvent.dataTransfer; - var urls = []; - - // Convert every dropped item into a url - for (var i = 0; i < dataTransfer.mozItemCount; i++) { - var url = dataTransfer.mozGetDataAt("text/uri-list", i); - if (url) { - urls.push(url); - continue; - } - - url = dataTransfer.mozGetDataAt("text/x-moz-url", i); - if (url) { - urls.push(url.split("\n")[0]); - continue; - } - - var file = dataTransfer.mozGetDataAt("application/x-moz-file", i); - if (file) { - urls.push(Services.io.newFileURI(file).spec); - continue; - } - } - - var pos = 0; - var installs = []; - - function buildNextInstall() { - if (pos == urls.length) { - if (installs.length > 0) { - // Display the normal install confirmation for the installs - let webInstaller = Cc["@mozilla.org/addons/web-install-listener;1"]. - getService(Ci.amIWebInstallListener); - webInstaller.onWebInstallRequested(getBrowserElement(), - document.documentURIObject, - installs); - } - return; - } - - AddonManager.getInstallForURL(urls[pos++], function(aInstall) { - installs.push(aInstall); - buildNextInstall(); - }, "application/x-xpinstall"); - } - - buildNextInstall(); - - aEvent.preventDefault(); - } -}; diff --git a/toolkit/mozapps/webextensions/content/extensions.xml b/toolkit/mozapps/webextensions/content/extensions.xml deleted file mode 100644 index b49645cf0..000000000 --- a/toolkit/mozapps/webextensions/content/extensions.xml +++ /dev/null @@ -1,2008 +0,0 @@ - - - - - -%extensionsDTD; -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - i); - ]]> - - - - - - - - - - - - this._updateStars(); - - - - - - - - - - - - - - - - - - - - - - - - - - - document.getAnonymousElementByAttribute(this, "anonid", "progress"); - - - document.getAnonymousElementByAttribute(this, "anonid", "cancel-btn"); - - - document.getAnonymousElementByAttribute(this, "anonid", "status"); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - null - - document.getAnonymousElementByAttribute(this, "anonid", "name-btn"); - - - document.getAnonymousElementByAttribute(this, "anonid", "date-btn"); - - - document.getAnonymousElementByAttribute(this, "anonid", "price-btn"); - - - document.getAnonymousElementByAttribute(this, "anonid", "relevance-btn"); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - = 0); - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - document.getAnonymousElementByAttribute(this, "anonid", "label"); - - - document.getAnonymousElementByAttribute(this, "anonid", "creator-link"); - - - document.getAnonymousElementByAttribute(this, "anonid", "creator-name"); - - - - - - - - - - - - - - - - - - - - - - - - - document.getAnonymousElementByAttribute(this, "anonid", "message"); - - - document.getAnonymousElementByAttribute(this, "anonid", "progress"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "purchase-remote-btn"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "install-remote-btn"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "restart-needed"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "undo-btn"); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - document.getAnonymousElementByAttribute(this, "anonid", - "warning-container"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "warning"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "warning-link"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "warning-btn"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "error-container"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "error"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "error-link"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "pending-container"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "pending"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "info-container"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "info"); - - - document.getAnonymousElementByAttribute(this, "anonid", "experiment-state"); - - - document.getAnonymousElementByAttribute(this, "anonid", "experiment-time"); - - - document.getAnonymousElementByAttribute(this, "anonid", "icon"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "date-updated"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "description"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "state-menulist"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "ask-to-activate-menuitem"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "always-activate-menuitem"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "never-activate-menuitem"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "preferences-btn"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "enable-btn"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "disable-btn"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "remove-btn"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "update-btn"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "control-container"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "install-status"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "checking-update"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "update-available"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "include-update"); - - false - - document.getAnonymousElementByAttribute(this, "anonid", - "relnotes-toggle-btn"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "relnotes-loading"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "relnotes-error"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "relnotes-container"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "relnotes"); - - - - - - - - - - - - - - - { - // This can return after the binding has been destroyed, - // so try to detect that and return early - if (!("onNewInstall" in this)) - return; - for (let install of aInstallsList) { - if (install.existingAddon && - install.existingAddon.id == this.mAddon.id && - install.state == AddonManager.STATE_AVAILABLE) { - this.onNewInstall(install); - this.onIncludeUpdateChanged(); - } - } - }); - } - ]]> - - - - - - - - - - - - - this.hasPermission(perm)); - this._stateMenulist.disabled = !hasActivatePermission; - this._stateMenulist.hidden = false; - this._stateMenulist.classList.add('no-auto-hide'); - } else { - this._stateMenulist.hidden = true; - - let enableTooltip = gViewController.commands["cmd_enableItem"] - .getTooltip(this.mAddon); - this._enableBtn.setAttribute("tooltiptext", enableTooltip); - if (this.hasPermission("enable")) { - this._enableBtn.hidden = false; - } else { - this._enableBtn.hidden = true; - } - - let disableTooltip = gViewController.commands["cmd_disableItem"] - .getTooltip(this.mAddon); - this._disableBtn.setAttribute("tooltiptext", disableTooltip); - if (this.hasPermission("disable")) { - this._disableBtn.hidden = false; - } else { - this._disableBtn.hidden = true; - } - } - - let uninstallTooltip = gViewController.commands["cmd_uninstallItem"] - .getTooltip(this.mAddon); - this._removeBtn.setAttribute("tooltiptext", uninstallTooltip); - if (this.hasPermission("uninstall")) { - this._removeBtn.hidden = false; - } else { - this._removeBtn.hidden = true; - } - - this.setAttribute("active", this.mAddon.isActive); - - var showProgress = this.mAddon.purchaseURL || (this.mAddon.install && - this.mAddon.install.state != AddonManager.STATE_INSTALLED); - this._showStatus(showProgress ? "progress" : "none"); - - if (this.mAddon.type == "experiment") { - this.removeAttribute("notification"); - let prefix = "experiment."; - let active = this.mAddon.isActive; - - if (!showProgress) { - let stateKey = prefix + "state." + (active ? "active" : "complete"); - this._experimentState.value = gStrings.ext.GetStringFromName(stateKey); - - let now = Date.now(); - let end = this.endDate; - let days = Math.abs(end - now) / (24 * 60 * 60 * 1000); - - let timeKey = prefix + "time."; - let timeMessage; - - if (days < 1) { - timeKey += (active ? "endsToday" : "endedToday"); - timeMessage = gStrings.ext.GetStringFromName(timeKey); - } else { - timeKey += (active ? "daysRemaining" : "daysPassed"); - days = Math.round(days); - let timeString = gStrings.ext.GetStringFromName(timeKey); - timeMessage = PluralForm.get(days, timeString) - .replace("#1", days); - } - - this._experimentTime.value = timeMessage; - } - } - ]]> - - - - - { - var event = document.createEvent("Events"); - event.initEvent("RelNotesToggle", true, true); - this.dispatchEvent(event); - } - - let showRelNotes = () => { - if (!relNotesData || !transformData) - return; - - this._relNotesLoading.hidden = true; - - var processor = Components.classes["@mozilla.org/document-transformer;1?type=xslt"] - .createInstance(Components.interfaces.nsIXSLTProcessor); - processor.flags |= Components.interfaces.nsIXSLTProcessorPrivate.DISABLE_ALL_LOADS; - - processor.importStylesheet(transformData); - var fragment = processor.transformToFragment(relNotesData, document); - this._relNotes.appendChild(fragment); - if (this.hasAttribute("show-relnotes")) { - var container = this._relNotesContainer; - container.style.height = container.scrollHeight + "px"; - } - sendToggleEvent(); - } - - let handleError = () => { - dataReq.abort(); - styleReq.abort(); - this._relNotesLoading.hidden = true; - this._relNotesError.hidden = false; - this._relNotesLoaded = false; // allow loading to be re-tried - sendToggleEvent(); - } - - function handleResponse(aEvent) { - var req = aEvent.target; - var ct = req.getResponseHeader("content-type"); - if ((!ct || ct.indexOf("text/html") < 0) && - req.responseXML && - req.responseXML.documentElement.namespaceURI != XMLURI_PARSE_ERROR) { - if (req == dataReq) - relNotesData = req.responseXML; - else - transformData = req.responseXML; - showRelNotes(); - } else { - handleError(); - } - } - - var dataReq = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(Components.interfaces.nsIXMLHttpRequest); - dataReq.open("GET", aURI.spec, true); - dataReq.responseType = "document"; - dataReq.addEventListener("load", handleResponse, false); - dataReq.addEventListener("error", handleError, false); - dataReq.send(null); - - var styleReq = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(Components.interfaces.nsIXMLHttpRequest); - styleReq.open("GET", UPDATES_RELEASENOTES_TRANSFORMFILE, true); - styleReq.responseType = "document"; - styleReq.addEventListener("load", handleResponse, false); - styleReq.addEventListener("error", handleError, false); - styleReq.send(null); - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - document.getAnonymousElementByAttribute(this, "anonid", "notice"); - - - document.getAnonymousElementByAttribute(this, "anonid", "restart-btn"); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - document.getAnonymousElementByAttribute(this, "anonid", "icon"); - - - document.getAnonymousElementByAttribute(this, "anonid", "name"); - - - document.getAnonymousElementByAttribute(this, "anonid", "warning"); - - - document.getAnonymousElementByAttribute(this, "anonid", "warning-link"); - - - document.getAnonymousElementByAttribute(this, "anonid", - "install-status"); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/toolkit/mozapps/webextensions/content/extensions.xul b/toolkit/mozapps/webextensions/content/extensions.xul deleted file mode 100644 index 70939d024..000000000 --- a/toolkit/mozapps/webextensions/content/extensions.xul +++ /dev/null @@ -1,715 +0,0 @@ - - - - - - - - -%brandDTD; - -%extensionsDTD; -]> - - - - - -