diff --git a/addon-sdk/source/test/addons/jetpack-addon.ini b/addon-sdk/source/test/addons/jetpack-addon.ini index bb4b35e3459a..64648607a707 100644 --- a/addon-sdk/source/test/addons/jetpack-addon.ini +++ b/addon-sdk/source/test/addons/jetpack-addon.ini @@ -1,7 +1,6 @@ [addon-manager.xpi] [author-email.xpi] [child_process.xpi] -skip-if = true [chrome.xpi] [content-permissions.xpi] [content-script-messages-latency.xpi] diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 094c7b1adaf9..c2083d00e3e3 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -69,8 +69,6 @@ pref("extensions.screenshots.system-disabled", true); // Disable add-ons that are not installed by the user in all scopes by default. // See the SCOPE constants in AddonManager.jsm for values to use here. pref("extensions.autoDisableScopes", 15); -// Scopes to scan for changes at startup. -pref("extensions.startupScanScopes", 0); // This is where the profiler WebExtension API will look for breakpad symbols. // NOTE: deliberately http right now since https://symbols.mozilla.org is not supported. diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index a6f6ceaaafb7..ab5596e757fd 100755 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -1143,11 +1143,6 @@ addEventListener("DOMContentLoaded", function onDCL() { gBrowser.updateBrowserRemoteness(initBrowser, gMultiProcessBrowser); }); -let _resolveDelayedStartup; -var delayedStartupPromise = new Promise(resolve => { - _resolveDelayedStartup = resolve; -}); - var gBrowserInit = { delayedStartupFinished: false, @@ -1624,7 +1619,6 @@ var gBrowserInit = { this.delayedStartupFinished = true; - _resolveDelayedStartup(); Services.obs.notifyObservers(window, "browser-delayed-startup-finished"); TelemetryTimestamps.add("delayedStartupFinished"); }, diff --git a/browser/base/content/test/webextensions/browser_extension_sideloading.js b/browser/base/content/test/webextensions/browser_extension_sideloading.js index c7afb3b2b85b..f25840414cec 100644 --- a/browser/base/content/test/webextensions/browser_extension_sideloading.js +++ b/browser/base/content/test/webextensions/browser_extension_sideloading.js @@ -1,89 +1,155 @@ const {AddonManagerPrivate} = Cu.import("resource://gre/modules/AddonManager.jsm", {}); -const {AddonTestUtils} = Cu.import("resource://testing-common/AddonTestUtils.jsm", {}); +// MockAddon mimics the AddonInternal interface and MockProvider implements +// just enough of the AddonManager provider interface to make it look like +// we have sideloaded webextensions so the sideloading flow can be tested. -AddonTestUtils.initMochitest(this); +// MockAddon -> callback +let setCallbacks = new Map(); -async function createWebExtension(details) { - let options = { - manifest: { - applications: {gecko: {id: details.id}}, +class MockAddon { + constructor(props) { + this._userDisabled = false; + this.pendingOperations = 0; + this.type = "extension"; - name: details.name, - - permissions: details.permissions, - }, - }; - - if (details.iconURL) { - options.manifest.icons = {"64": details.iconURL}; + for (let name in props) { + if (name == "userDisabled") { + this._userDisabled = props[name]; + } + this[name] = props[name]; + } } - let xpi = AddonTestUtils.createTempWebExtensionFile(options); + markAsSeen() { + this.seen = true; + } - await AddonTestUtils.manuallyInstall(xpi); + get userDisabled() { + return this._userDisabled; + } + + set userDisabled(val) { + this._userDisabled = val; + AddonManagerPrivate.callAddonListeners(val ? "onDisabled" : "onEnabled", this); + let fn = setCallbacks.get(this); + if (fn) { + setCallbacks.delete(this); + fn(val); + } + return val; + } + + get permissions() { + return this._userDisabled ? AddonManager.PERM_CAN_ENABLE : AddonManager.PERM_CAN_DISABLE; + } } -async function createXULExtension(details) { - let xpi = AddonTestUtils.createTempXPIFile({ - "install.rdf": { - id: details.id, - name: details.name, - version: "0.1", - targetApplications: [{ - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "*", - }], - }, - }); +class MockProvider { + constructor(...addons) { + this.addons = new Set(addons); + } - await AddonTestUtils.manuallyInstall(xpi); + startup() { } + shutdown() { } + + getAddonByID(id, callback) { + for (let addon of this.addons) { + if (addon.id == id) { + callback(addon); + return; + } + } + callback(null); + } + + getAddonsByTypes(types, callback) { + let addons = []; + if (!types || types.includes("extension")) { + addons = [...this.addons]; + } + callback(addons); + } +} + +function promiseSetDisabled(addon) { + return new Promise(resolve => { + setCallbacks.set(addon, resolve); + }); } let cleanup; -add_task(function* test_sideloading() { +add_task(function* () { + // ICON_URL wouldn't ever appear as an actual webextension icon, but + // we're just mocking out the addon here, so all we care about is that + // that it propagates correctly to the popup. + const ICON_URL = "chrome://mozapps/skin/extensions/category-extensions.svg"; const DEFAULT_ICON_URL = "chrome://mozapps/skin/extensions/extensionGeneric.svg"; - yield SpecialPowers.pushPrefEnv({ - set: [ - ["xpinstall.signatures.required", false], - ["extensions.autoDisableScopes", 15], - ["extensions.ui.ignoreUnsigned", true], - ], - }); - const ID1 = "addon1@tests.mozilla.org"; - yield createWebExtension({ + let mock1 = new MockAddon({ id: ID1, name: "Test 1", userDisabled: true, - permissions: ["history", "https://*/*"], - iconURL: "foo-icon.png", + seen: false, + userPermissions: { + permissions: ["history"], + origins: ["https://*/*"], + }, + iconURL: ICON_URL, }); const ID2 = "addon2@tests.mozilla.org"; - yield createXULExtension({ + let mock2 = new MockAddon({ id: ID2, name: "Test 2", + userDisabled: true, + seen: false, + userPermissions: { + permissions: [], + origins: [], + }, }); const ID3 = "addon3@tests.mozilla.org"; - yield createWebExtension({ + let mock3 = new MockAddon({ id: ID3, name: "Test 3", - permissions: [""], + isWebExtension: true, + userDisabled: true, + seen: false, + userPermissions: { + permissions: [], + origins: [""], + } }); const ID4 = "addon4@tests.mozilla.org"; - yield createWebExtension({ + let mock4 = new MockAddon({ id: ID4, name: "Test 4", - permissions: [""], + isWebExtension: true, + userDisabled: true, + seen: false, + userPermissions: { + permissions: [], + origins: [""], + } }); + let provider = new MockProvider(mock1, mock2, mock3, mock4); + AddonManagerPrivate.registerProvider(provider, [{ + id: "extension", + name: "Extensions", + uiPriority: 4000, + flags: AddonManager.TYPE_UI_VIEW_LIST | + AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL, + }]); + testCleanup = async function() { + AddonManagerPrivate.unregisterProvider(provider); + // clear out ExtensionsUI state about sideloaded extensions so // subsequent tests don't get confused. ExtensionsUI.sideloaded.clear(); @@ -137,13 +203,17 @@ add_task(function* test_sideloading() { is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list"); // Check the contents of the notification, then choose "Cancel" - checkNotification(panel, /\/foo-icon\.png$/, [ + checkNotification(panel, ICON_URL, [ ["webextPerms.hostDescription.allUrls"], ["webextPerms.description.history"], ]); + let disablePromise = promiseSetDisabled(mock1); panel.secondaryButton.click(); + let value = yield disablePromise; + is(value, true, "Addon should remain disabled"); + let [addon1, addon2, addon3, addon4] = yield AddonManager.getAddonsByIDs([ID1, ID2, ID3, ID4]); ok(addon1.seen, "Addon should be marked as seen"); is(addon1.userDisabled, true, "Addon 1 should still be disabled"); @@ -175,8 +245,12 @@ add_task(function* test_sideloading() { checkNotification(panel, DEFAULT_ICON_URL, []); // This time accept the install. + disablePromise = promiseSetDisabled(mock2); panel.button.click(); + value = yield disablePromise; + is(value, false, "Addon should be set to enabled"); + [addon1, addon2, addon3, addon4] = yield AddonManager.getAddonsByIDs([ID1, ID2, ID3, ID4]); is(addon1.userDisabled, true, "Addon 1 should still be disabled"); is(addon2.userDisabled, false, "Addon 2 should now be enabled"); @@ -214,7 +288,10 @@ add_task(function* test_sideloading() { checkNotification(panel, DEFAULT_ICON_URL, [["webextPerms.hostDescription.allUrls"]]); // Accept the permissions + disablePromise = promiseSetDisabled(mock3); panel.button.click(); + value = yield disablePromise; + is(value, false, "userDisabled should be set on addon 3"); addon3 = yield AddonManager.getAddonByID(ID3); is(addon3.userDisabled, false, "Addon 3 should be enabled"); @@ -239,7 +316,10 @@ add_task(function* test_sideloading() { checkNotification(panel, DEFAULT_ICON_URL, [["webextPerms.hostDescription.allUrls"]]); // Accept the permissions + disablePromise = promiseSetDisabled(mock4); panel.button.click(); + value = yield disablePromise; + is(value, false, "userDisabled should be set on addon 4"); addon4 = yield AddonManager.getAddonByID(ID4); is(addon4.userDisabled, false, "Addon 4 should be enabled"); @@ -249,11 +329,5 @@ add_task(function* test_sideloading() { isnot(menuButton.getAttribute("badge-status"), "addon-alert", "Should no longer have addon alert badge"); - yield new Promise(resolve => setTimeout(resolve, 100)); - - for (let addon of [addon1, addon2, addon3, addon4]) { - addon.uninstall(); - } - yield BrowserTestUtils.removeTab(gBrowser.selectedTab); }); diff --git a/browser/base/content/test/webextensions/head.js b/browser/base/content/test/webextensions/head.js index c02f84e367ab..ee90f6516374 100644 --- a/browser/base/content/test/webextensions/head.js +++ b/browser/base/content/test/webextensions/head.js @@ -218,7 +218,7 @@ function checkNotification(panel, checkIcon, permissions) { let header = document.getElementById("addon-webext-perm-intro"); if (checkIcon instanceof RegExp) { - ok(checkIcon.test(icon), `Notification icon is correct ${JSON.stringify(icon)} ~= ${checkIcon}`); + ok(checkIcon.test(icon), "Notification icon is correct"); } else if (typeof checkIcon == "function") { ok(checkIcon(icon), "Notification icon is correct"); } else { diff --git a/browser/modules/ExtensionsUI.jsm b/browser/modules/ExtensionsUI.jsm index 1a8a659237a7..fb1e649aa2c2 100644 --- a/browser/modules/ExtensionsUI.jsm +++ b/browser/modules/ExtensionsUI.jsm @@ -12,8 +12,6 @@ Cu.import("resource://gre/modules/EventEmitter.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", - "resource://gre/modules/AddonManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", @@ -37,7 +35,7 @@ this.ExtensionsUI = { sideloadListener: null, histogram: null, - async init() { + init() { this.histogram = Services.telemetry.getHistogramById("EXTENSION_INSTALL_PROMPT_RESULT"); Services.obs.addObserver(this, "webextension-permission-prompt"); @@ -45,55 +43,53 @@ this.ExtensionsUI = { Services.obs.addObserver(this, "webextension-install-notify"); Services.obs.addObserver(this, "webextension-optional-permission-prompt"); - await RecentWindow.getMostRecentBrowserWindow().delayedStartupPromise; - this._checkForSideloaded(); }, - async _checkForSideloaded() { - let sideloaded = await AddonManagerPrivate.getNewSideloads(); + _checkForSideloaded() { + AddonManager.getAllAddons(addons => { + // Check for any side-loaded addons that the user is allowed + // to enable. + let sideloaded = addons.filter( + addon => addon.seen === false && (addon.permissions & AddonManager.PERM_CAN_ENABLE)); - if (!sideloaded.length) { - // No new side-loads. We're done. - return; - } - - // The ordering shouldn't matter, but tests depend on notifications - // happening in a specific order. - sideloaded.sort((a, b) => a.id.localeCompare(b.id)); - - if (WEBEXT_PERMISSION_PROMPTS) { - if (!this.sideloadListener) { - this.sideloadListener = { - onEnabled: addon => { - if (!this.sideloaded.has(addon)) { - return; - } - - this.sideloaded.delete(addon); - this.emit("change"); - - if (this.sideloaded.size == 0) { - AddonManager.removeAddonListener(this.sideloadListener); - this.sideloadListener = null; - } - }, - }; - AddonManager.addAddonListener(this.sideloadListener); + if (!sideloaded.length) { + return; } - for (let addon of sideloaded) { - this.sideloaded.add(addon); + if (WEBEXT_PERMISSION_PROMPTS) { + if (!this.sideloadListener) { + this.sideloadListener = { + onEnabled: addon => { + if (!this.sideloaded.has(addon)) { + return; + } + + this.sideloaded.delete(addon); + this.emit("change"); + + if (this.sideloaded.size == 0) { + AddonManager.removeAddonListener(this.sideloadListener); + this.sideloadListener = null; + } + }, + }; + AddonManager.addAddonListener(this.sideloadListener); + } + + for (let addon of sideloaded) { + this.sideloaded.add(addon); + } + this.emit("change"); + } else { + // This and all the accompanying about:newaddon code can eventually + // be removed. See bug 1331521. + let win = RecentWindow.getMostRecentBrowserWindow(); + for (let addon of sideloaded) { + win.openUILinkIn(`about:newaddon?id=${addon.id}`, "tab"); + } } - this.emit("change"); - } else { - // This and all the accompanying about:newaddon code can eventually - // be removed. See bug 1331521. - let win = RecentWindow.getMostRecentBrowserWindow(); - for (let addon of sideloaded) { - win.openUILinkIn(`about:newaddon?id=${addon.id}`, "tab"); - } - } + }); }, showAddonsManager(browser, strings, icon, histkey) { @@ -150,11 +146,6 @@ this.ExtensionsUI = { } info.unsigned = info.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING; - if (info.unsigned && Cu.isInAutomation && - Services.prefs.getBoolPref("extensions.ui.ignoreUnsigned", false)) { - info.unsigned = false; - } - let strings = this._buildStrings(info); // If this is an update with no promptable permissions, just apply it diff --git a/js/xpconnect/idl/xpccomponents.idl b/js/xpconnect/idl/xpccomponents.idl index 3a0e0c49440c..10c0537c801c 100644 --- a/js/xpconnect/idl/xpccomponents.idl +++ b/js/xpconnect/idl/xpccomponents.idl @@ -529,12 +529,6 @@ interface nsIXPCComponents_Utils : nsISupports [implicit_jscontext] attribute boolean ion; - // Returns true if we're running in automation and certain security - // restrictions can be eased. - readonly attribute boolean isInAutomation; - - void crashIfNotInAutomation(); - [implicit_jscontext] void setGCZeal(in long zeal); diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp index ff63133193d6..3aec2e7dff6d 100644 --- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -2916,7 +2916,7 @@ nsXPCComponents_Utils::SetWantXrays(HandleValue vscope, JSContext* cx) NS_IMETHODIMP nsXPCComponents_Utils::ForcePermissiveCOWs(JSContext* cx) { - xpc::CrashIfNotInAutomation(); + CrashIfNotInAutomation(); CompartmentPrivate::Get(CurrentGlobalOrNull(cx))->forcePermissiveCOWs = true; return NS_OK; } @@ -2927,7 +2927,7 @@ nsXPCComponents_Utils::ForcePrivilegedComponentsForScope(HandleValue vscope, { if (!vscope.isObject()) return NS_ERROR_INVALID_ARG; - xpc::CrashIfNotInAutomation(); + CrashIfNotInAutomation(); JSObject* scopeObj = js::UncheckedUnwrap(&vscope.toObject()); XPCWrappedNativeScope* scope = ObjectScope(scopeObj); scope->ForcePrivilegedComponents(); @@ -3012,22 +3012,6 @@ nsXPCComponents_Utils::SetGCZeal(int32_t aValue, JSContext* cx) return NS_OK; } -NS_IMETHODIMP -nsXPCComponents_Utils::GetIsInAutomation(bool* aResult) -{ - NS_ENSURE_ARG_POINTER(aResult); - - *aResult = xpc::IsInAutomation(); - return NS_OK; -} - -NS_IMETHODIMP -nsXPCComponents_Utils::CrashIfNotInAutomation() -{ - xpc::CrashIfNotInAutomation(); - return NS_OK; -} - NS_IMETHODIMP nsXPCComponents_Utils::NukeSandbox(HandleValue obj, JSContext* cx) { diff --git a/testing/specialpowers/content/SpecialPowersObserverAPI.js b/testing/specialpowers/content/SpecialPowersObserverAPI.js index 4208906c62f1..a2c8185315a5 100644 --- a/testing/specialpowers/content/SpecialPowersObserverAPI.js +++ b/testing/specialpowers/content/SpecialPowersObserverAPI.js @@ -612,8 +612,8 @@ SpecialPowersObserverAPI.prototype = { let id = aMessage.data.id; let extension = this._extensions.get(id); this._extensions.delete(id); - let done = () => this._sendReply(aMessage, "SPExtensionMessage", {id, type: "extensionUnloaded", args: []}); - extension.shutdown().then(done, done); + extension.shutdown(); + this._sendReply(aMessage, "SPExtensionMessage", {id, type: "extensionUnloaded", args: []}); return undefined; } diff --git a/toolkit/components/build/nsToolkitCompsModule.cpp b/toolkit/components/build/nsToolkitCompsModule.cpp index dd6eac9ef540..88007fecf4b3 100644 --- a/toolkit/components/build/nsToolkitCompsModule.cpp +++ b/toolkit/components/build/nsToolkitCompsModule.cpp @@ -36,7 +36,6 @@ #include "mozilla/FinalizationWitnessService.h" #include "mozilla/NativeOSFileInternals.h" #include "mozilla/AddonContentPolicy.h" -#include "mozilla/AddonManagerStartup.h" #include "mozilla/AddonPathService.h" #if defined(XP_WIN) @@ -127,7 +126,6 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NativeFileWatcherService, Init) NS_GENERIC_FACTORY_CONSTRUCTOR(AddonContentPolicy) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AddonPathService, AddonPathService::GetInstance) -NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AddonManagerStartup, AddonManagerStartup::GetInstance) NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebRequestListener) @@ -163,7 +161,6 @@ NS_DEFINE_NAMED_CID(FINALIZATIONWITNESSSERVICE_CID); NS_DEFINE_NAMED_CID(NATIVE_OSFILE_INTERNALS_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_ADDONCONTENTPOLICY_CID); NS_DEFINE_NAMED_CID(NS_ADDON_PATH_SERVICE_CID); -NS_DEFINE_NAMED_CID(NS_ADDON_MANAGER_STARTUP_CID); NS_DEFINE_NAMED_CID(NATIVE_FILEWATCHER_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_WEBREQUESTLISTENER_CID); @@ -199,7 +196,6 @@ static const Module::CIDEntry kToolkitCIDs[] = { { &kNATIVE_OSFILE_INTERNALS_SERVICE_CID, false, nullptr, NativeOSFileInternalsServiceConstructor }, { &kNS_ADDONCONTENTPOLICY_CID, false, nullptr, AddonContentPolicyConstructor }, { &kNS_ADDON_PATH_SERVICE_CID, false, nullptr, AddonPathServiceConstructor }, - { &kNS_ADDON_MANAGER_STARTUP_CID, false, nullptr, AddonManagerStartupConstructor }, { &kNATIVE_FILEWATCHER_SERVICE_CID, false, nullptr, NativeFileWatcherServiceConstructor }, { &kNS_WEBREQUESTLISTENER_CID, false, nullptr, nsWebRequestListenerConstructor }, { nullptr } @@ -237,7 +233,6 @@ static const Module::ContractIDEntry kToolkitContracts[] = { { NATIVE_OSFILE_INTERNALS_SERVICE_CONTRACTID, &kNATIVE_OSFILE_INTERNALS_SERVICE_CID }, { NS_ADDONCONTENTPOLICY_CONTRACTID, &kNS_ADDONCONTENTPOLICY_CID }, { NS_ADDONPATHSERVICE_CONTRACTID, &kNS_ADDON_PATH_SERVICE_CID }, - { NS_ADDONMANAGERSTARTUP_CONTRACTID, &kNS_ADDON_MANAGER_STARTUP_CID }, { NATIVE_FILEWATCHER_SERVICE_CONTRACTID, &kNATIVE_FILEWATCHER_SERVICE_CID }, { NS_WEBREQUESTLISTENER_CONTRACTID, &kNS_WEBREQUESTLISTENER_CID }, { nullptr } diff --git a/toolkit/components/extensions/Extension.jsm b/toolkit/components/extensions/Extension.jsm index 2634a883224e..dec542fb14b2 100644 --- a/toolkit/components/extensions/Extension.jsm +++ b/toolkit/components/extensions/Extension.jsm @@ -958,19 +958,14 @@ this.Extension = class extends ExtensionData { return super.initLocale(locale); } - startup() { - this.startupPromise = this._startup(); - return this.startupPromise; - } - - async _startup() { - this.started = false; + async startup() { + let started = false; try { let [, perms] = await Promise.all([this.loadManifest(), ExtensionPermissions.get(this)]); ExtensionManagement.startupExtension(this.uuid, this.addonData.resourceURI, this); - this.started = true; + started = true; if (!this.hasShutdown) { await this.initLocale(); @@ -1009,8 +1004,7 @@ this.Extension = class extends ExtensionData { dump(`Extension error: ${e.message} ${e.filename || e.fileName}:${e.lineNumber} :: ${e.stack || new Error().stack}\n`); Cu.reportError(e); - if (this.started) { - this.started = false; + if (started) { ExtensionManagement.shutdownExtension(this.uuid); } @@ -1018,8 +1012,6 @@ this.Extension = class extends ExtensionData { throw e; } - - this.startupPromise = null; } cleanupGeneratedFile() { @@ -1041,22 +1033,10 @@ this.Extension = class extends ExtensionData { }).catch(Cu.reportError); } - async shutdown(reason) { - try { - if (this.startupPromise) { - await this.startupPromise; - } - } catch (e) { - Cu.reportError(e); - } - + shutdown(reason) { this.shutdownReason = reason; this.hasShutdown = true; - if (!this.started) { - return; - } - if (this.cleanupFile || ["ADDON_INSTALL", "ADDON_UNINSTALL", "ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(reason)) { StartupCache.clearAddonData(this.id); diff --git a/toolkit/components/extensions/ExtensionManagement.jsm b/toolkit/components/extensions/ExtensionManagement.jsm index be62666614aa..fcf0b9117641 100644 --- a/toolkit/components/extensions/ExtensionManagement.jsm +++ b/toolkit/components/extensions/ExtensionManagement.jsm @@ -112,9 +112,6 @@ var Service = { // Called when an extension is unloaded. shutdownExtension(uuid) { let extension = this.uuidMap.get(uuid); - if (!extension) { - return; - } this.uuidMap.delete(uuid); this.aps.setAddonHasPermissionCallback(extension.id, null); this.aps.setAddonLoadURICallback(extension.id, null); diff --git a/toolkit/components/extensions/ext-backgroundPage.js b/toolkit/components/extensions/ext-backgroundPage.js index 3445f3115ce1..2f5c09aa3a8e 100644 --- a/toolkit/components/extensions/ext-backgroundPage.js +++ b/toolkit/components/extensions/ext-backgroundPage.js @@ -50,7 +50,7 @@ class BackgroundPage extends HiddenExtensionPage { // console. if (this.extension.addonData.instanceID) { AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID) - .then(addon => addon && addon.setDebugGlobal(window)); + .then(addon => addon.setDebugGlobal(window)); } } @@ -67,7 +67,7 @@ class BackgroundPage extends HiddenExtensionPage { shutdown() { if (this.extension.addonData.instanceID) { AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID) - .then(addon => addon && addon.setDebugGlobal(null)); + .then(addon => addon.setDebugGlobal(null)); } super.shutdown(); diff --git a/toolkit/components/extensions/ext-runtime.js b/toolkit/components/extensions/ext-runtime.js index 196764929c58..3dd17c0f33fd 100644 --- a/toolkit/components/extensions/ext-runtime.js +++ b/toolkit/components/extensions/ext-runtime.js @@ -64,9 +64,7 @@ this.runtime = class extends ExtensionAPI { fire.sync(details); }); return () => { - AddonManager.removeUpgradeListener(instanceID).catch(e => { - // This can happen if we try this after shutdown is complete. - }); + AddonManager.removeUpgradeListener(instanceID); }; }).api(), diff --git a/toolkit/components/extensions/extension-process-script.js b/toolkit/components/extensions/extension-process-script.js index 842009802261..fb26b4b3e030 100644 --- a/toolkit/components/extensions/extension-process-script.js +++ b/toolkit/components/extensions/extension-process-script.js @@ -638,11 +638,9 @@ ExtensionManager = { let extension = this.extensions.get(data.id); this.extensions.delete(data.id); - if (extension) { - extension.shutdown(); + extension.shutdown(); - DocumentManager.uninitExtension(extension); - } + DocumentManager.uninitExtension(extension); break; } diff --git a/toolkit/components/extensions/test/browser/browser_ext_management_themes.js b/toolkit/components/extensions/test/browser/browser_ext_management_themes.js index 49b2d1f279f9..13727ece8024 100644 --- a/toolkit/components/extensions/test/browser/browser_ext_management_themes.js +++ b/toolkit/components/extensions/test/browser/browser_ext_management_themes.js @@ -129,7 +129,5 @@ add_task(async function test_management_themes() { theme.unload(), extension.awaitMessage("onUninstalled"), ]); - - is(await extension.awaitMessage("onEnabled"), "Default", "default enabled"); await extension.unload(); }); diff --git a/toolkit/modules/JSONFile.jsm b/toolkit/modules/JSONFile.jsm index 7944134da7a9..03fff47391ec 100644 --- a/toolkit/modules/JSONFile.jsm +++ b/toolkit/modules/JSONFile.jsm @@ -90,8 +90,6 @@ const kSaveDelayMs = 1500; * automatically finalize the file when triggered. Defaults * to `profileBeforeChange`; exposed as an option for * testing. - * - compression: A compression algorithm to use when reading and - * writing the data. */ function JSONFile(config) { this.path = config.path; @@ -108,11 +106,6 @@ function JSONFile(config) { } this._saver = new DeferredTask(() => this._save(), config.saveDelayMs); - this._options = {}; - if (config.compression) { - this._options.compression = config.compression; - } - this._finalizeAt = config.finalizeAt || AsyncShutdown.profileBeforeChange; this._finalizeInternalBound = this._finalizeInternal.bind(this); this._finalizeAt.addBlocker("JSON store: writing data", @@ -183,7 +176,7 @@ JSONFile.prototype = { let data = {}; try { - let bytes = yield OS.File.read(this.path, this._options); + let bytes = yield OS.File.read(this.path); // If synchronous loading happened in the meantime, exit now. if (this.dataReady) { @@ -293,9 +286,7 @@ JSONFile.prototype = { yield Promise.resolve(this._beforeSave()); } yield OS.File.writeAtomic(this.path, bytes, - Object.assign( - { tmpPath: this.path + ".tmp" }, - this._options)); + { tmpPath: this.path + ".tmp" }); }), /** diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 1af0eb47b427..22355b4f90cc 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -2250,7 +2250,7 @@ var AddonManagerInternal = { throw Components.Exception("aInstanceID must be a symbol", Cr.NS_ERROR_INVALID_ARG); - return this.getAddonByInstanceID(aInstanceID).then(addon => { + this.getAddonByInstanceID(aInstanceID).then(addon => { if (!addon) { throw Error("No addon for instanceID:", aInstanceID.toString()); } @@ -3086,17 +3086,6 @@ this.AddonManagerPrivate = { .addonIsActive(addonId); }, - /** - * Gets an array of add-ons which were side-loaded prior to the last - * startup, and are currently disabled. - * - * @returns {Promise>} - */ - getNewSideloads() { - return AddonManagerInternal._getProviderByName("XPIProvider") - .getNewSideloads(); - }, - get browserUpdated() { return gBrowserUpdated; }, @@ -3665,7 +3654,7 @@ this.AddonManager = { }, removeUpgradeListener(aInstanceID) { - return AddonManagerInternal.removeUpgradeListener(aInstanceID); + AddonManagerInternal.removeUpgradeListener(aInstanceID); }, addAddonListener(aListener) { diff --git a/toolkit/mozapps/extensions/AddonManagerStartup-inlines.h b/toolkit/mozapps/extensions/AddonManagerStartup-inlines.h deleted file mode 100644 index 83e77358f0a9..000000000000 --- a/toolkit/mozapps/extensions/AddonManagerStartup-inlines.h +++ /dev/null @@ -1,281 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; 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 AddonManagerStartup_inlines_h -#define AddonManagerStartup_inlines_h - -#include "jsapi.h" -#include "nsJSUtils.h" - -#include "mozilla/Maybe.h" -#include "mozilla/Move.h" - -namespace mozilla { - -class ArrayIterElem; -class PropertyIterElem; - - -/***************************************************************************** - * Object iterator base classes - *****************************************************************************/ - -template -class MOZ_STACK_CLASS BaseIter { -public: - typedef T SelfType; - - PropertyType begin() const - { - PropertyType elem(Self()); - return Move(elem); - } - - PropertyType end() const - { - PropertyType elem(Self()); - return elem.End(); - } - - void* Context() const { return mContext; } - -protected: - BaseIter(JSContext* cx, JS::HandleObject object, void* context = nullptr) - : mCx(cx) - , mObject(object) - , mContext(context) - {} - - const SelfType& Self() const - { - return *static_cast(this); - } - SelfType& Self() - { - return *static_cast(this); - } - - JSContext* mCx; - - JS::HandleObject mObject; - - void* mContext; -}; - -template -class MOZ_STACK_CLASS BaseIterElem { -public: - typedef T SelfType; - - explicit BaseIterElem(const IterType& iter, uint32_t index = 0) - : mIter(iter) - , mIndex(index) - {} - - uint32_t Length() const - { - return mIter.Length(); - } - - JS::Value Value() - { - JS::RootedValue value(mIter.mCx, JS::UndefinedValue()); - - auto& self = Self(); - if (!self.GetValue(&value)) { - JS_ClearPendingException(mIter.mCx); - } - - return value; - } - - SelfType& operator*() { return Self(); } - - SelfType& operator++() - { - MOZ_ASSERT(mIndex < Length()); - mIndex++; - return Self(); - } - - bool operator!=(const SelfType& other) const - { - return &mIter != &other.mIter || mIndex != other.mIndex; - } - - - SelfType End() const - { - SelfType end(mIter); - end.mIndex = Length(); - return Move(end); - } - - void* Context() const { return mIter.Context(); } - -protected: - const SelfType& Self() const - { - return *static_cast(this); - } - SelfType& Self() { - return *static_cast(this); - } - - const IterType& mIter; - - uint32_t mIndex; -}; - - -/***************************************************************************** - * Property iteration - *****************************************************************************/ - -class MOZ_STACK_CLASS PropertyIter - : public BaseIter -{ - friend class PropertyIterElem; - friend class BaseIterElem; - -public: - PropertyIter(JSContext* cx, JS::HandleObject object, void* context = nullptr) - : BaseIter(cx, object, context) - , mIds(cx, JS::IdVector(cx)) - { - if (!JS_Enumerate(cx, object, &mIds)) { - JS_ClearPendingException(cx); - } - } - - PropertyIter(const PropertyIter& other) - : PropertyIter(other.mCx, other.mObject, other.mContext) - {} - - PropertyIter& operator=(const PropertyIter& other) - { - MOZ_ASSERT(other.mObject == mObject); - mCx = other.mCx; - mContext = other.mContext; - - mIds.clear(); - if (!JS_Enumerate(mCx, mObject, &mIds)) { - JS_ClearPendingException(mCx); - } - return *this; - } - - int32_t Length() const - { - return mIds.length(); - } - -protected: - JS::Rooted mIds; -}; - -class MOZ_STACK_CLASS PropertyIterElem - : public BaseIterElem -{ - friend class BaseIterElem; - -public: - using BaseIterElem::BaseIterElem; - - PropertyIterElem(const PropertyIterElem& other) - : BaseIterElem(other.mIter, other.mIndex) - {} - - jsid Id() - { - MOZ_ASSERT(mIndex < mIter.mIds.length()); - - return mIter.mIds[mIndex]; - } - - const nsAString& Name() - { - if(mName.isNothing()) { - mName.emplace(); - mName.ref().init(mIter.mCx, Id()); - } - return mName.ref(); - } - - JSContext* Cx() { return mIter.mCx; } - -protected: - bool GetValue(JS::MutableHandleValue value) - { - MOZ_ASSERT(mIndex < Length()); - JS::Rooted id(mIter.mCx, Id()); - - return JS_GetPropertyById(mIter.mCx, mIter.mObject, id, value); - } - -private: - Maybe mName; -}; - - -/***************************************************************************** - * Array iteration - *****************************************************************************/ - -class MOZ_STACK_CLASS ArrayIter - : public BaseIter -{ - friend class ArrayIterElem; - friend class BaseIterElem; - -public: - ArrayIter(JSContext* cx, JS::HandleObject object) - : BaseIter(cx, object) - , mLength(0) - { - bool isArray; - if (!JS_IsArrayObject(cx, object, &isArray) || !isArray) { - JS_ClearPendingException(cx); - return; - } - - if (!JS_GetArrayLength(cx, object, &mLength)) { - JS_ClearPendingException(cx); - } - } - - uint32_t Length() const - { - return mLength; - } - -private: - uint32_t mLength; -}; - -class MOZ_STACK_CLASS ArrayIterElem - : public BaseIterElem -{ - friend class BaseIterElem; - -public: - using BaseIterElem::BaseIterElem; - - ArrayIterElem(const ArrayIterElem& other) - : BaseIterElem(other.mIter, other.mIndex) - {} - -protected: - bool - GetValue(JS::MutableHandleValue value) - { - MOZ_ASSERT(mIndex < Length()); - return JS_GetElement(mIter.mCx, mIter.mObject, mIndex, value); - } -}; - -} - -#endif // AddonManagerStartup_inlines_h diff --git a/toolkit/mozapps/extensions/AddonManagerStartup.cpp b/toolkit/mozapps/extensions/AddonManagerStartup.cpp deleted file mode 100644 index 1afd4fc62689..000000000000 --- a/toolkit/mozapps/extensions/AddonManagerStartup.cpp +++ /dev/null @@ -1,595 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; 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 "AddonManagerStartup.h" -#include "AddonManagerStartup-inlines.h" - -#include "jsapi.h" -#include "js/TracingAPI.h" -#include "xpcpublic.h" - -#include "mozilla/ClearOnShutdown.h" -#include "mozilla/EndianUtils.h" -#include "mozilla/Compression.h" -#include "mozilla/Preferences.h" -#include "mozilla/ScopeExit.h" -#include "mozilla/Services.h" -#include "mozilla/Unused.h" - -#include "nsAppDirectoryServiceDefs.h" -#include "nsAppRunner.h" -#include "nsIAddonInterposition.h" -#include "nsXULAppAPI.h" - -#include - -namespace mozilla { - -template <> -class MOZ_MUST_USE_TYPE GenericErrorResult -{ - nsresult mErrorValue; - - template friend class Result; - -public: - explicit GenericErrorResult(nsresult aErrorValue) : mErrorValue(aErrorValue) {} - - operator nsresult() { return mErrorValue; } -}; - -static inline Result -WrapNSResult(PRStatus aRv) -{ - if (aRv != PR_SUCCESS) { - return Err(NS_ERROR_FAILURE); - } - return Ok(); -} - -static inline Result -WrapNSResult(nsresult aRv) -{ - if (NS_FAILED(aRv)) { - return Err(aRv); - } - return Ok(); -} - -#define NS_TRY(expr) MOZ_TRY(WrapNSResult(expr)) - - -using Compression::LZ4; - -#ifdef XP_WIN -# define READ_BINARYMODE "rb" -#else -# define READ_BINARYMODE "r" -#endif - -AddonManagerStartup& -AddonManagerStartup::GetSingleton() -{ - static RefPtr singleton; - if (!singleton) { - singleton = new AddonManagerStartup(); - ClearOnShutdown(&singleton); - } - return *singleton; -} - -AddonManagerStartup::AddonManagerStartup() - : mInitialized(false) -{} - - -nsIFile* -AddonManagerStartup::ProfileDir() -{ - if (!mProfileDir) { - nsresult rv; - - rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mProfileDir)); - MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); - } - - return mProfileDir; -} - -NS_IMPL_ISUPPORTS(AddonManagerStartup, amIAddonManagerStartup) - - -/***************************************************************************** - * File utils - *****************************************************************************/ - -static already_AddRefed -CloneAndAppend(nsIFile* aFile, const char* name) -{ - nsCOMPtr file; - aFile->Clone(getter_AddRefs(file)); - file->AppendNative(nsDependentCString(name)); - return file.forget(); -} - -static bool -IsNormalFile(nsIFile* file) -{ - bool result; - return NS_SUCCEEDED(file->IsFile(&result)) && result; -} - -static nsCString -ReadFile(const char* path) -{ - nsCString result; - - FILE* fd = fopen(path, READ_BINARYMODE); - if (!fd) { - return result; - } - auto cleanup = MakeScopeExit([&] () { - fclose(fd); - }); - - if (fseek(fd, 0, SEEK_END) != 0) { - return result; - } - size_t len = ftell(fd); - if (len <= 0 || fseek(fd, 0, SEEK_SET) != 0) { - return result; - } - - result.SetLength(len); - size_t rd = fread(result.BeginWriting(), sizeof(char), len, fd); - if (rd != len) { - result.Truncate(); - } - - return result; -} - -/** - * Reads the contents of a LZ4-compressed file, as stored by the OS.File - * module, and returns the decompressed contents on success. - * - * A nonexistent or empty file is treated as success. A corrupt or non-LZ4 - * file is treated as failure. - */ -static Result -ReadFileLZ4(const char* path) -{ - static const char MAGIC_NUMBER[] = "mozLz40"; - constexpr auto HEADER_SIZE = sizeof(MAGIC_NUMBER) + 4; - - nsCString result; - - nsCString lz4 = ReadFile(path); - if (lz4.IsEmpty()) { - return result; - } - - // Note: We want to include the null terminator here. - nsDependentCSubstring magic(MAGIC_NUMBER, sizeof(MAGIC_NUMBER)); - - if (lz4.Length() < HEADER_SIZE || StringHead(lz4, magic.Length()) != magic) { - return Err(NS_ERROR_UNEXPECTED); - } - - auto size = LittleEndian::readUint32(lz4.get() + magic.Length()); - - if (!result.SetLength(size, fallible) || - !LZ4::decompress(lz4.get() + HEADER_SIZE, result.BeginWriting(), size)) { - return Err(NS_ERROR_UNEXPECTED); - } - - return result; -} - - -static bool -ParseJSON(JSContext* cx, nsACString& jsonData, JS::MutableHandleValue result) -{ - NS_ConvertUTF8toUTF16 str(jsonData); - jsonData.Truncate(); - - return JS_ParseJSON(cx, str.Data(), str.Length(), result); -} - - -/***************************************************************************** - * JSON data handling - *****************************************************************************/ - -class MOZ_STACK_CLASS WrapperBase { -protected: - WrapperBase(JSContext* cx, JSObject* object) - : mCx(cx) - , mObject(cx, object) - {} - - WrapperBase(JSContext* cx, const JS::Value& value) - : mCx(cx) - , mObject(cx) - { - if (value.isObject()) { - mObject = &value.toObject(); - } else { - mObject = JS_NewPlainObject(cx); - } - } - -protected: - JSContext* mCx; - JS::RootedObject mObject; - - bool GetBool(const char* name, bool defVal = false); - - double GetNumber(const char* name, double defVal = 0); - - nsString GetString(const char* name, const char* defVal = ""); - - JSObject* GetObject(const char* name); -}; - -bool -WrapperBase::GetBool(const char* name, bool defVal) -{ - JS::RootedObject obj(mCx, mObject); - - JS::RootedValue val(mCx, JS::UndefinedValue()); - if (!JS_GetProperty(mCx, obj, name, &val)) { - JS_ClearPendingException(mCx); - } - - if (val.isBoolean()) { - return val.toBoolean(); - } - return defVal; -} - -double -WrapperBase::GetNumber(const char* name, double defVal) -{ - JS::RootedObject obj(mCx, mObject); - - JS::RootedValue val(mCx, JS::UndefinedValue()); - if (!JS_GetProperty(mCx, obj, name, &val)) { - JS_ClearPendingException(mCx); - } - - if (val.isNumber()) { - return val.toNumber(); - } - return defVal; -} - -nsString -WrapperBase::GetString(const char* name, const char* defVal) -{ - JS::RootedObject obj(mCx, mObject); - - JS::RootedValue val(mCx, JS::UndefinedValue()); - if (!JS_GetProperty(mCx, obj, name, &val)) { - JS_ClearPendingException(mCx); - } - - nsString res; - if (val.isString()) { - AssignJSString(mCx, res, val.toString()); - } else { - res.AppendASCII(defVal); - } - return res; -} - -JSObject* -WrapperBase::GetObject(const char* name) -{ - JS::RootedObject obj(mCx, mObject); - - JS::RootedValue val(mCx, JS::UndefinedValue()); - if (!JS_GetProperty(mCx, obj, name, &val)) { - JS_ClearPendingException(mCx); - } - - if (val.isObject()) { - return &val.toObject(); - } - return nullptr; -} - - -class MOZ_STACK_CLASS InstallLocation : public WrapperBase { -public: - InstallLocation(JSContext* cx, const JS::Value& value); - - MOZ_IMPLICIT InstallLocation(PropertyIterElem& iter) - : InstallLocation(iter.Cx(), iter.Value()) - {} - - InstallLocation(const InstallLocation& other) - : InstallLocation(other.mCx, JS::ObjectValue(*other.mObject)) - {} - - void SetChanged(bool changed) - { - JS::RootedObject obj(mCx, mObject); - - JS::RootedValue val(mCx, JS::BooleanValue(changed)); - if (!JS_SetProperty(mCx, obj, "changed", val)) { - JS_ClearPendingException(mCx); - } - } - - PropertyIter& Addons() { return mAddonsIter.ref(); } - - nsString Path() { return GetString("path"); } - - bool ShouldCheckStartupModifications() { return GetBool("checkStartupModifications"); } - - -private: - JS::RootedObject mAddonsObj; - Maybe mAddonsIter; -}; - - -class MOZ_STACK_CLASS Addon : public WrapperBase { -public: - Addon(JSContext* cx, InstallLocation& location, const nsAString& id, JSObject* object) - : WrapperBase(cx, object) - , mId(id) - , mLocation(location) - {} - - MOZ_IMPLICIT Addon(PropertyIterElem& iter) - : WrapperBase(iter.Cx(), iter.Value()) - , mId(iter.Name()) - , mLocation(*static_cast(iter.Context())) - {} - - Addon(const Addon& other) - : WrapperBase(other.mCx, other.mObject) - , mId(other.mId) - , mLocation(other.mLocation) - {} - - const nsString& Id() { return mId; } - - nsString Path() { return GetString("path"); } - - bool Bootstrapped() { return GetBool("bootstrapped"); } - - bool Enabled() { return GetBool("enabled"); } - - bool ShimsEnabled() { return GetBool("enableShims"); } - - double LastModifiedTime() { return GetNumber("lastModifiedTime"); } - - - already_AddRefed FullPath(); - - NSLocationType LocationType(); - - bool UpdateLastModifiedTime(); - - -private: - nsString mId; - InstallLocation& mLocation; -}; - -already_AddRefed -Addon::FullPath() -{ - nsString path = mLocation.Path(); - - nsCOMPtr file; - NS_NewLocalFile(path, false, getter_AddRefs(file)); - MOZ_RELEASE_ASSERT(file); - - path = Path(); - file->AppendRelativePath(path); - - return file.forget(); -} - -NSLocationType -Addon::LocationType() -{ - nsString type = GetString("type", "extension"); - if (type.LowerCaseEqualsLiteral("theme")) { - return NS_SKIN_LOCATION; - } - return NS_EXTENSION_LOCATION; -} - -bool -Addon::UpdateLastModifiedTime() -{ - nsCOMPtr file = FullPath(); - - bool result; - if (NS_FAILED(file->Exists(&result)) || !result) { - return true; - } - - PRTime time; - - nsCOMPtr manifest = file; - if (!IsNormalFile(manifest)) { - manifest = CloneAndAppend(file, "install.rdf"); - if (!IsNormalFile(manifest)) { - manifest = CloneAndAppend(file, "manifest.json"); - if (!IsNormalFile(manifest)) { - return true; - } - } - } - - if (NS_FAILED(manifest->GetLastModifiedTime(&time))) { - return true; - } - - JS::RootedObject obj(mCx, mObject); - - double lastModified = time; - JS::RootedValue value(mCx, JS::NumberValue(lastModified)); - if (!JS_SetProperty(mCx, obj, "currentModifiedTime", value)) { - JS_ClearPendingException(mCx); - } - - return lastModified != LastModifiedTime();; -} - - -InstallLocation::InstallLocation(JSContext* cx, const JS::Value& value) - : WrapperBase(cx, value) - , mAddonsObj(cx) - , mAddonsIter() -{ - mAddonsObj = GetObject("addons"); - if (!mAddonsObj) { - mAddonsObj = JS_NewPlainObject(cx); - } - mAddonsIter.emplace(cx, mAddonsObj, this); -} - - -/***************************************************************************** - * XPC interfacing - *****************************************************************************/ - -static void -EnableShims(const nsAString& addonId) -{ - NS_ConvertUTF16toUTF8 id(addonId); - - nsCOMPtr interposition = - do_GetService("@mozilla.org/addons/multiprocess-shims;1"); - - if (!interposition || !xpc::SetAddonInterposition(id, interposition)) { - return; - } - - Unused << xpc::AllowCPOWsInAddon(id, true); -} - -void -AddonManagerStartup::AddInstallLocation(Addon& addon) -{ - nsCOMPtr file = addon.FullPath(); - - nsString path; - if (NS_FAILED(file->GetPath(path))) { - return; - } - - auto type = addon.LocationType(); - - if (type == NS_SKIN_LOCATION) { - mThemePaths.AppendElement(file); - } else { - mExtensionPaths.AppendElement(file); - } - - if (StringTail(path, 4).LowerCaseEqualsLiteral(".xpi")) { - XRE_AddJarManifestLocation(type, file); - } else { - nsCOMPtr manifest = CloneAndAppend(file, "chrome.manifest"); - XRE_AddManifestLocation(type, manifest); - } -} - -nsresult -AddonManagerStartup::ReadStartupData(JSContext* cx, JS::MutableHandleValue locations) -{ - locations.set(JS::UndefinedValue()); - - nsCOMPtr file = CloneAndAppend(ProfileDir(), "addonStartup.json.lz4"); - - nsCString path; - NS_TRY(file->GetNativePath(path)); - - nsCString data; - MOZ_TRY_VAR(data, ReadFileLZ4(path.get())); - - if (data.IsEmpty() || !ParseJSON(cx, data, locations)) { - return NS_OK; - } - - if (!locations.isObject()) { - return NS_ERROR_UNEXPECTED; - } - - JS::RootedObject locs(cx, &locations.toObject()); - for (auto& e1 : PropertyIter(cx, locs)) { - InstallLocation loc(e1); - - if (!loc.ShouldCheckStartupModifications()) { - continue; - } - - for (auto& e2 : loc.Addons()) { - Addon addon(e2); - - if (addon.Enabled() && addon.UpdateLastModifiedTime()) { - loc.SetChanged(true); - } - } - } - - return NS_OK; -} - -nsresult -AddonManagerStartup::InitializeExtensions(JS::HandleValue locations, JSContext* cx) -{ - NS_ENSURE_FALSE(mInitialized, NS_ERROR_UNEXPECTED); - NS_ENSURE_TRUE(locations.isObject(), NS_ERROR_INVALID_ARG); - - mInitialized = true; - - if (!Preferences::GetBool("extensions.defaultProviders.enabled", true)) { - return NS_OK; - } - - bool enableInterpositions = Preferences::GetBool("extensions.interposition.enabled", false); - - JS::RootedObject locs(cx, &locations.toObject()); - for (auto& e1 : PropertyIter(cx, locs)) { - InstallLocation loc(e1); - - for (auto& e2 : loc.Addons()) { - Addon addon(e2); - - if (!addon.Bootstrapped()) { - AddInstallLocation(addon); - } - - if (enableInterpositions && addon.ShimsEnabled()) { - EnableShims(addon.Id()); - } - } - } - - return NS_OK; -} - -nsresult -AddonManagerStartup::Reset() -{ - MOZ_RELEASE_ASSERT(xpc::IsInAutomation()); - - mInitialized = false; - - mExtensionPaths.Clear(); - mThemePaths.Clear(); - - return NS_OK; -} - -} // namespace mozilla diff --git a/toolkit/mozapps/extensions/AddonManagerStartup.h b/toolkit/mozapps/extensions/AddonManagerStartup.h deleted file mode 100644 index 4907dfdd5cad..000000000000 --- a/toolkit/mozapps/extensions/AddonManagerStartup.h +++ /dev/null @@ -1,73 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; 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 AddonManagerStartup_h -#define AddonManagerStartup_h - -#include "amIAddonManagerStartup.h" -#include "nsCOMArray.h" -#include "nsCOMPtr.h" -#include "nsIFile.h" -#include "nsISupports.h" - -#include "jsapi.h" - -namespace mozilla { - -class Addon; - -class AddonManagerStartup final : public amIAddonManagerStartup -{ -public: - NS_DECL_ISUPPORTS - NS_DECL_AMIADDONMANAGERSTARTUP - - AddonManagerStartup(); - - static AddonManagerStartup& GetSingleton(); - - static already_AddRefed GetInstance() - { - RefPtr inst = &GetSingleton(); - return inst.forget(); - } - - const nsCOMArray& ExtensionPaths() - { - return mExtensionPaths; - } - - const nsCOMArray& ThemePaths() - { - return mExtensionPaths; - } - -private: - void AddInstallLocation(Addon& addon); - - nsIFile* ProfileDir(); - - nsCOMPtr mProfileDir; - - nsCOMArray mExtensionPaths; - nsCOMArray mThemePaths; - - bool mInitialized; - -protected: - virtual ~AddonManagerStartup() = default; -}; - -} // namespace mozilla - -#define NS_ADDONMANAGERSTARTUP_CONTRACTID \ - "@mozilla.org/addons/addon-manager-startup;1" - -// {17a59a6b-92b8-42e5-bce0-ab434c7a7135 -#define NS_ADDON_MANAGER_STARTUP_CID \ -{ 0x17a59a6b, 0x92b8, 0x42e5, \ - { 0xbc, 0xe0, 0xab, 0x43, 0x4c, 0x7a, 0x71, 0x35 } } - -#endif // AddonManagerStartup_h diff --git a/toolkit/mozapps/extensions/amIAddonManagerStartup.idl b/toolkit/mozapps/extensions/amIAddonManagerStartup.idl deleted file mode 100644 index a97a21eb8303..000000000000 --- a/toolkit/mozapps/extensions/amIAddonManagerStartup.idl +++ /dev/null @@ -1,36 +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/. */ - -#include "nsISupports.idl" - -[scriptable, builtinclass, uuid(01dfa47b-87e4-4135-877b-586d033e1b5d)] -interface amIAddonManagerStartup : nsISupports -{ - /** - * Reads and parses startup data from the addonState.json.lz4 file, checks - * for modifications, and returns the result. - * - * Returns null for an empty or nonexistent state file, but throws for an - * invalid one. - */ - [implicit_jscontext] - jsval readStartupData(); - - /** - * Initializes the chrome registry for the enabled, non-restartless add-on - * in the given state data. - */ - [implicit_jscontext] - void initializeExtensions(in jsval locations); - - /** - * Resets the internal state of the startup service, and allows - * initializeExtensions() to be called again. Does *not* fully unregister - * chrome registry locations for previously registered add-ons. - * - * NOT FOR USE OUTSIDE OF UNIT TESTS. - */ - void reset(); -}; - diff --git a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm index b004a007526c..8852baa93bdd 100644 --- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm +++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm @@ -30,9 +30,6 @@ const {OS} = Cu.import("resource://gre/modules/osfile.jsm", {}); XPCOMUtils.defineLazyModuleGetter(this, "Extension", "resource://gre/modules/Extension.jsm"); -XPCOMUtils.defineLazyServiceGetter(this, "aomStartup", - "@mozilla.org/addons/addon-manager-startup;1", - "amIAddonManagerStartup"); XPCOMUtils.defineLazyServiceGetter(this, "rdfService", "@mozilla.org/rdf/rdf-service;1", "nsIRDFService"); XPCOMUtils.defineLazyServiceGetter(this, "uuidGen", @@ -45,8 +42,6 @@ XPCOMUtils.defineLazyGetter(this, "AppInfo", () => { return AppInfo; }); -const PREF_DISABLE_SECURITY = ("security.turn_off_all_security_so_that_" + - "viruses_can_take_over_this_computer"); const ArrayBufferInputStream = Components.Constructor( "@mozilla.org/io/arraybuffer-input-stream;1", @@ -134,46 +129,46 @@ function escaped(strings, ...values) { class AddonsList { - constructor(file) { + constructor(extensionsINI) { this.multiprocessIncompatibleIDs = new Set(); - this.extensions = []; - this.themes = []; - if (!file.exists()) { + if (!extensionsINI.exists()) { + this.extensions = []; + this.themes = []; return; } - let data = aomStartup.readStartupData(); + let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"] + .getService(Ci.nsIINIParserFactory); - for (let loc of Object.values(data)) { - let dir = loc.path && new nsFile(loc.path); + let parser = factory.createINIParser(extensionsINI); - for (let [id, addon] of Object.entries(loc.addons)) { - if (addon.enabled && !addon.bootstrapped) { - let file; - if (dir) { - file = dir.clone(); - try { - file.appendRelativePath(addon.path); - } catch (e) { - file = new nsFile(addon.path); - } - } else { - file = new nsFile(addon.path); - } + function readDirectories(section) { + var dirs = []; + var keys = parser.getKeys(section); + for (let key of XPCOMUtils.IterStringEnumerator(keys)) { + let descriptor = parser.getString(section, key); - addon.type = addon.type || "extension"; - - if (addon.type == "theme") { - this.themes.push(file); - } else { - this.extensions.push(file); - if (addon.enableShims) { - this.multiprocessIncompatibleIDs.add(id); - } - } + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + try { + file.persistentDescriptor = descriptor; + } catch (e) { + // Throws if the directory doesn't exist, we can ignore this since the + // platform will too. + continue; } + dirs.push(file); } + return dirs; + } + + this.extensions = readDirectories("ExtensionDirs"); + this.themes = readDirectories("ThemeDirs"); + + var keys = parser.getKeys("MultiprocessIncompatibleExtensions"); + for (let key of XPCOMUtils.IterStringEnumerator(keys)) { + let id = parser.getString("MultiprocessIncompatibleExtensions", key); + this.multiprocessIncompatibleIDs.add(id); } } @@ -186,7 +181,7 @@ class AddonsList { return this[type].some(file => { if (!file.exists()) - throw new Error(`Non-existent path found in addonStartup.json: ${file.path}`); + throw new Error(`Non-existent path found in extensions.ini: ${file.path}`); if (file.isDirectory()) return file.equals(path); @@ -213,7 +208,7 @@ var AddonTestUtils = { addonIntegrationService: null, addonsList: null, appInfo: null, - addonStartup: null, + extensionsINI: null, testUnpacked: false, useRealCertChecks: false, @@ -223,11 +218,8 @@ var AddonTestUtils = { // Get the profile directory for tests to use. this.profileDir = testScope.do_get_profile(); - this.profileExtensions = this.profileDir.clone(); - this.profileExtensions.append("extensions"); - - this.addonStartup = this.profileDir.clone(); - this.addonStartup.append("addonStartup.json.lz4"); + this.extensionsINI = this.profileDir.clone(); + this.extensionsINI.append("extensions.ini"); // Register a temporary directory for the tests. this.tempDir = this.profileDir.clone(); @@ -251,9 +243,6 @@ var AddonTestUtils = { // By default don't disable add-ons from any scope Services.prefs.setIntPref("extensions.autoDisableScopes", 0); - // And scan for changes at startup - Services.prefs.setIntPref("extensions.startupScanScopes", 15); - // By default, don't cache add-ons in AddonRepository.jsm Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", false); @@ -295,7 +284,10 @@ var AddonTestUtils = { } testScope.do_register_cleanup(() => { - this.cleanupTempXPIs(); + for (let file of this.tempXPIs) { + if (file.exists()) + file.remove(false); + } // Check that the temporary directory is empty var dirEntries = this.tempDir.directoryEntries @@ -347,37 +339,6 @@ var AddonTestUtils = { }); }, - initMochitest(testScope) { - this.profileDir = FileUtils.getDir("ProfD", []); - - this.profileExtensions = FileUtils.getDir("ProfD", ["extensions"]); - - this.tempDir = FileUtils.getDir("TmpD", []); - this.tempDir.append("addons-mochitest"); - this.tempDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - - testScope.registerCleanupFunction(() => { - this.cleanupTempXPIs(); - try { - this.tempDir.remove(true); - } catch (e) { - Cu.reportError(e); - } - }); - }, - - cleanupTempXPIs() { - for (let file of this.tempXPIs.splice(0)) { - if (file.exists()) { - try { - file.remove(false); - } catch (e) { - Cu.reportError(e); - } - } - } - }, - /** * Helper to spin the event loop until a promise resolves or rejects * @@ -448,10 +409,6 @@ var AddonTestUtils = { } }, - getIDFromExtension(file) { - return this.getIDFromManifest(this.getManifestURI(file)); - }, - getIDFromManifest: Task.async(function*(manifestURI) { let body = yield fetch(manifestURI.spec); @@ -560,13 +517,15 @@ var AddonTestUtils = { * An optional boolean parameter to simulate the case where the * application has changed version since the last run. If not passed it * defaults to true + * @returns {Promise} + * Resolves when the add-on manager's startup has completed. */ - async promiseStartupManager(appChanged = true) { + promiseStartupManager(appChanged = true) { if (this.addonIntegrationService) throw new Error("Attempting to startup manager that was already started."); - if (appChanged && this.addonStartup.exists()) - this.addonStartup.remove(true); + if (appChanged && this.extensionsINI.exists()) + this.extensionsINI.remove(true); this.addonIntegrationService = Cc["@mozilla.org/addons/integration;1"] .getService(Ci.nsIObserver); @@ -576,7 +535,9 @@ var AddonTestUtils = { this.emit("addon-manager-started"); // Load the add-ons list as it was after extension registration - await this.loadAddonsList(true); + this.loadAddonsList(); + + return Promise.resolve(); }, promiseShutdownManager() { @@ -606,12 +567,6 @@ var AddonTestUtils = { AddonManagerPrivate.unregisterProvider(XPIscope.XPIProvider); Cu.unload("resource://gre/modules/addons/XPIProvider.jsm"); - // We need to set this in order reset the startup service, which - // is only possible when running in automation. - Services.prefs.setBoolPref(PREF_DISABLE_SECURITY, true); - - aomStartup.reset(); - if (shutdownError) throw shutdownError; @@ -629,14 +584,8 @@ var AddonTestUtils = { }); }, - async loadAddonsList(flush = false) { - if (flush) { - let XPIScope = Cu.import("resource://gre/modules/addons/XPIProvider.jsm", {}); - XPIScope.XPIStates.save(); - await XPIScope.XPIStates._jsonFile._save(); - } - - this.addonsList = new AddonsList(this.addonStartup); + loadAddonsList() { + this.addonsList = new AddonsList(this.extensionsINI); }, /** @@ -900,9 +849,9 @@ var AddonTestUtils = { * * @param {nsIFile} xpiFile * The XPI file to install. - * @param {nsIFile} [installLocation = this.profileExtensions] + * @param {nsIFile} installLocation * The install location (an nsIFile) to install into. - * @param {string} [id] + * @param {string} id * The ID to install as. * @param {boolean} [unpacked = this.testUnpacked] * If true, install as an unpacked directory, rather than a @@ -911,11 +860,7 @@ var AddonTestUtils = { * A file pointing to the installed location of the XPI file or * unpacked directory. */ - async manuallyInstall(xpiFile, installLocation = this.profileExtensions, id = null, unpacked = this.testUnpacked) { - if (id == null) { - id = await this.getIDFromExtension(xpiFile); - } - + manuallyInstall(xpiFile, installLocation, id, unpacked = this.testUnpacked) { if (unpacked) { let dir = installLocation.clone(); dir.append(id); diff --git a/toolkit/mozapps/extensions/internal/WebExtensionBootstrap.js b/toolkit/mozapps/extensions/internal/WebExtensionBootstrap.js index 555c2b8c2c83..80d68ccdf33a 100644 --- a/toolkit/mozapps/extensions/internal/WebExtensionBootstrap.js +++ b/toolkit/mozapps/extensions/internal/WebExtensionBootstrap.js @@ -31,7 +31,6 @@ function startup(data, reason) { function shutdown(data, reason) { extension.shutdown(BOOTSTRAP_REASON_TO_STRING_MAP[reason]); - extension = null; } function uninstall(data, reason) { diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 868e9ba44bf7..c864958684b0 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -58,8 +58,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "isAddonPartOfE10SRollout", "resource://gre/modules/addons/E10SAddonsRollout.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "JSONFile", - "resource://gre/modules/JSONFile.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "LegacyExtensionsUtils", "resource://gre/modules/LegacyExtensionsUtils.jsm"); @@ -83,9 +81,6 @@ XPCOMUtils.defineLazyServiceGetter(this, "AddonPathService", "@mozilla.org/addon-path-service;1", "amIAddonPathService"); -XPCOMUtils.defineLazyServiceGetter(this, "aomStartup", - "@mozilla.org/addons/addon-manager-startup;1", - "amIAddonManagerStartup"); XPCOMUtils.defineLazyGetter(this, "CertUtils", function() { let certUtils = {}; @@ -103,90 +98,14 @@ Cu.importGlobalProperties(["URL"]); const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile", "initWithPath"); -/** - * Returns a nsIFile instance for the given path, relative to the given - * base file, if provided. - * - * @param {string} path - * The (possibly relative) path of the file. - * @param {nsIFile} [base] - * An optional file to use as a base path if `path` is relative. - * @returns {nsIFile} - */ -function getFile(path, base = null) { - if (base) { - let file = base.clone(); - try { - file.appendRelativePath(path); - return file; - } catch (e) {} - } - return new nsIFile(path); -} - -/** - * Returns the modification time of the given file, or 0 if the file - * does not exist, or cannot be accessed. - * - * @param {nsIFile} file - * The file to retrieve the modification time for. - * @returns {double} - * The file's modification time, in seconds since the Unix epoch. - */ -function tryGetMtime(file) { - try { - return file.lastModifiedTime; - } catch (e) { - return 0; - } -} - -/** - * Returns the path to `file` relative to the directory `dir`, or an - * absolute path to the file if no directory is passed, or the file is - * not a descendant of it. - * - * @param {nsIFile} file - * The file to return a relative path to. - * @param {nsIFile} [dir] - * If passed, return a path relative to this directory. - * @returns {string} - */ -function getRelativePath(file, dir) { - if (dir && dir.contains(file)) { - let path = file.getRelativePath(dir); - if (AppConstants.platform == "win") { - path = path.replace(/\//g, "\\"); - } - return path; - } - return file.path; -} - -/** - * Converts the given opaque descriptor string into an ordinary path - * string. In practice, the path string is always exactly equal to the - * descriptor string, but theoretically may not have been on some legacy - * systems. - * - * @param {string} descriptor - * The opaque descriptor string to convert. - * @param {nsIFile} [dir] - * If passed, return a path relative to this directory. - * @returns {string} - * The file's path. - */ -function descriptorToPath(descriptor, dir) { - try { - let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); - file.persistentDescriptor = descriptor; - return getRelativePath(file, dir); - } catch (e) { - return null; - } +function getFile(descriptor) { + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.persistentDescriptor = descriptor; + return file; } const PREF_DB_SCHEMA = "extensions.databaseSchema"; +const PREF_INSTALL_CACHE = "extensions.installCache"; const PREF_XPI_STATE = "extensions.xpiState"; const PREF_BOOTSTRAP_ADDONS = "extensions.bootstrappedAddons"; const PREF_PENDING_OPERATIONS = "extensions.pendingOperations"; @@ -195,9 +114,9 @@ const PREF_SKIN_TO_SELECT = "extensions.lastSelectedSkin"; const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; const PREF_EM_UPDATE_URL = "extensions.update.url"; const PREF_EM_UPDATE_BACKGROUND_URL = "extensions.update.background.url"; +const PREF_EM_ENABLED_ADDONS = "extensions.enabledAddons"; const PREF_EM_EXTENSION_FORMAT = "extensions."; const PREF_EM_ENABLED_SCOPES = "extensions.enabledScopes"; -const PREF_EM_STARTUP_SCAN_SCOPES = "extensions.startupScanScopes"; const PREF_EM_SHOW_MISMATCH_UI = "extensions.showMismatchUI"; const PREF_XPI_ENABLED = "xpinstall.enabled"; const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required"; @@ -230,13 +149,6 @@ const PREF_EM_HOTFIX_ID = "extensions.hotfix.id"; const PREF_EM_CERT_CHECKATTRIBUTES = "extensions.hotfix.cert.checkAttributes"; const PREF_EM_HOTFIX_CERTS = "extensions.hotfix.certs."; -const OBSOLETE_PREFERENCES = [ - "extensions.bootstrappedAddons", - "extensions.enabledAddons", - "extensions.xpiState", - "extensions.installCache", -]; - const URI_EXTENSION_UPDATE_DIALOG = "chrome://mozapps/content/extensions/update.xul"; const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties"; @@ -247,7 +159,6 @@ const DIR_SYSTEM_ADDONS = "features"; const DIR_STAGE = "staged"; const DIR_TRASH = "trash"; -const FILE_XPI_STATES = "addonStartup.json.lz4"; const FILE_DATABASE = "extensions.json"; const FILE_OLD_CACHE = "extensions.cache"; const FILE_RDF_MANIFEST = "install.rdf"; @@ -269,11 +180,6 @@ const KEY_APP_SYSTEM_SHARE = "app-system-share"; const KEY_APP_SYSTEM_USER = "app-system-user"; const KEY_APP_TEMPORARY = "app-temporary"; -const STARTUP_MTIME_SCOPES = [KEY_APP_GLOBAL, - KEY_APP_SYSTEM_LOCAL, - KEY_APP_SYSTEM_SHARE, - KEY_APP_SYSTEM_USER]; - const NOTIFICATION_FLUSH_PERMISSIONS = "flush-pending-permissions"; const XPI_PERMISSION = "install"; @@ -315,17 +221,6 @@ const STATIC_BLOCKLIST_PATTERNS = [ blockID: "i162" } ]; -function encoded(strings, ...values) { - let result = []; - - for (let [i, string] of strings.entries()) { - result.push(string); - if (i < values.length) - result.push(encodeURIComponent(values[i])); - } - - return result.join(""); -} const BOOTSTRAP_REASONS = { APP_STARTUP: 1, @@ -394,9 +289,7 @@ const TEMP_INSTALL_ID_GEN_SESSION = function mustSign(aType) { if (!SIGNED_TYPES.has(aType)) return false; - - return ((REQUIRE_SIGNING && !Cu.isInAutomation) || - Preferences.get(PREF_XPI_SIGNATURES_REQUIRED, false)); + return REQUIRE_SIGNING || Preferences.get(PREF_XPI_SIGNATURES_REQUIRED, false); } // Keep track of where we are in startup for telemetry @@ -434,9 +327,6 @@ const LAZY_OBJECTS = ["XPIDatabase", "XPIDatabaseReconcile"]; var gLazyObjectsLoaded = false; -XPCOMUtils.defineLazyPreferenceGetter(this, "gStartupScanScopes", - PREF_EM_STARTUP_SCAN_SCOPES, 0); - function loadLazyObjects() { let uri = "resource://gre/modules/addons/XPIProviderUtils.js"; let scope = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), { @@ -444,7 +334,7 @@ function loadLazyObjects() { wantGlobalProperties: ["TextDecoder"], }); - Object.assign(scope, { + let shared = { ADDON_SIGNING, SIGNED_TYPES, BOOTSTRAP_REASONS, @@ -457,8 +347,11 @@ function loadLazyObjects() { recordAddonTelemetry, applyBlocklistChanges, flushChromeCaches, - descriptorToPath, - }); + canRunInSafeMode, + } + + for (let key of Object.keys(shared)) + scope[key] = shared[key]; Services.scriptloader.loadSubScript(uri, scope); @@ -612,7 +505,8 @@ SafeInstallOperation.prototype = { if (aCopy) { newFile.copyTo(aTargetDirectory, null); // copyTo does not update the nsIFile with the new. - newFile = getFile(aFile.leafName, aTargetDirectory); + newFile = aTargetDirectory.clone(); + newFile.append(aFile.leafName); // Windows roaming profiles won't properly sync directories if a new file // has an older lastModifiedTime than a previous file, so update. newFile.lastModifiedTime = Date.now(); @@ -634,7 +528,8 @@ SafeInstallOperation.prototype = { throw err; } - let newDir = getFile(aDirectory.leafName, aTargetDirectory); + let newDir = aTargetDirectory.clone(); + newDir.append(aDirectory.leafName); try { newDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); } catch (e) { @@ -775,7 +670,8 @@ SafeInstallOperation.prototype = { if (move.isMoveTo) { move.newFile.moveTo(move.oldDir.parent, move.oldDir.leafName); } else if (move.newFile.isDirectory() && !move.newFile.isSymlink()) { - let oldDir = getFile(move.oldFile.leafName, move.oldFile.parent); + let oldDir = move.oldFile.parent.clone(); + oldDir.append(move.oldFile.leafName); oldDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); } else if (!move.oldFile) { // No old file means this was a copied file @@ -858,7 +754,8 @@ function canRunInSafeMode(aAddon) { if (aAddon._installLocation.name == KEY_APP_TEMPORARY) return true; - return aAddon._installLocation.isSystem; + return aAddon._installLocation.name == KEY_APP_SYSTEM_DEFAULTS || + aAddon._installLocation.name == KEY_APP_SYSTEM_ADDONS; } /** @@ -987,7 +884,8 @@ function getExternalType(aType) { } function getManifestFileForDir(aDir) { - let file = getFile(FILE_RDF_MANIFEST, aDir); + let file = aDir.clone(); + file.append(FILE_RDF_MANIFEST); if (file.exists() && file.isFile()) return file; file.leafName = FILE_WEB_MANIFEST; @@ -1561,20 +1459,23 @@ var loadManifestFromDir = Task.async(function*(aDir, aInstallLocation) { fis.close(); } - let iconFile = getFile("icon.png", aDir); + let iconFile = aDir.clone(); + iconFile.append("icon.png"); if (iconFile.exists()) { addon.icons[32] = "icon.png"; addon.icons[48] = "icon.png"; } - let icon64File = getFile("icon64.png", aDir); + let icon64File = aDir.clone(); + icon64File.append("icon64.png"); if (icon64File.exists()) { addon.icons[64] = "icon64.png"; } - let file = getFile("chrome.manifest", aDir); + let file = aDir.clone(); + file.append("chrome.manifest"); let chromeManifest = ChromeManifestParser.parseSync(Services.io.newFileURI(file)); addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest, "binary-component"); @@ -1770,7 +1671,7 @@ function syncLoadManifestFromFile(aFile, aInstallLocation) { * @return an nsIURI pointing at the resource */ function getURIForResourceInFile(aFile, aPath) { - if (aFile.exists() && aFile.isDirectory()) { + if (aFile.isDirectory()) { let resource = aFile.clone(); if (aPath) aPath.split("/").forEach(part => resource.append(part)); @@ -2157,6 +2058,46 @@ function recursiveRemove(aFile) { } } +/** + * Returns the timestamp and leaf file name of the most recently modified + * entry in a directory, + * or simply the file's own timestamp if it is not a directory. + * Also returns the total number of items (directories and files) visited in the scan + * + * @param aFile + * A non-null nsIFile object + * @return [File Name, Epoch time, items visited], as described above. + */ +function recursiveLastModifiedTime(aFile) { + try { + let modTime = aFile.lastModifiedTime; + let fileName = aFile.leafName; + if (aFile.isFile()) + return [fileName, modTime, 1]; + + if (aFile.isDirectory()) { + let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); + let entry; + let totalItems = 1; + while ((entry = entries.nextFile)) { + let [subName, subTime, items] = recursiveLastModifiedTime(entry); + totalItems += items; + if (subTime > modTime) { + modTime = subTime; + fileName = subName; + } + } + entries.close(); + return [fileName, modTime, totalItems]; + } + } catch (e) { + logger.warn("Problem getting last modified time for " + aFile.path, e); + } + + // If the file is something else, just ignore it. + return ["", 0, 0]; +} + /** * Gets a snapshot of directory entries. * @@ -2182,9 +2123,7 @@ function getDirectoryEntries(aDir, aSortEntries) { return entries } catch (e) { - if (aDir.exists()) { - logger.warn("Can't iterate directory " + aDir.path, e); - } + logger.warn("Can't iterate directory " + aDir.path, e); return []; } finally { if (dirEnum) { @@ -2209,156 +2148,41 @@ function recordAddonTelemetry(aAddon) { /** * The on-disk state of an individual XPI, created from an Object - * as stored in the addonStartup.json file. + * as stored in the 'extensions.xpiState' pref. */ -const JSON_FIELDS = Object.freeze([ - "bootstrapped", - "changed", - "dependencies", - "enabled", - "enableShims", - "file", - "hasEmbeddedWebExtension", - "lastModifiedTime", - "path", - "runInSafeMode", - "type", - "version", -]); - -const BOOTSTRAPPED_FIELDS = Object.freeze([ - "dependencies", - "hasEmbeddedWebExtension", - "runInSafeMode", - "type", - "version", -]); - -class XPIState { - constructor(location, id, saved = {}) { - this.location = location; - this.id = id; - - // Set default values. - this.type = "extension"; - this.bootstrapped = false; - this.enableShims = false; - - for (let prop of JSON_FIELDS) { - if (prop in saved) { - this[prop] = saved[prop]; - } - } - - if (saved.currentModifiedTime && saved.currentModifiedTime != this.lastModifiedTime) { - this.lastModifiedTime = saved.currentModifiedTime; - this.changed = true; +function XPIState(saved) { + for (let [short, long] of XPIState.prototype.fields) { + if (short in saved) { + this[long] = saved[short]; } } +} +XPIState.prototype = { + fields: [["d", "descriptor"], + ["e", "enabled"], + ["v", "version"], + ["st", "scanTime"], + ["mt", "manifestTime"]], /** - * Migrates an add-on's data from xpiState and bootstrappedAddons - * preferences, and returns an XPIState object for it. - * - * @param {XPIStateLocation} location - * The location of the add-on. - * @param {string} id - * The ID of the add-on to migrate. - * @param {object} state - * The add-on's data from the xpiState preference. - * @param {object} [bootstrapped] - * The add-on's data from the bootstrappedAddons preference, if - * applicable. + * Return the last modified time, based on enabled/disabled */ - static migrate(location, id, saved, bootstrapped) { - let data = { - enabled: saved.e, - path: descriptorToPath(saved.d, location.dir), - lastModifiedTime: saved.mt || saved.st, - version: saved.v, - enableShims: false, - }; - - if (bootstrapped) { - data.bootstrapped = true; - data.enabled = true; - data.enableShims = !bootstrapped.multiprocessCompatible; - data.path = descriptorToPath(bootstrapped.descriptor, location.dir); - - for (let field of BOOTSTRAPPED_FIELDS) { - if (field in bootstrapped) { - data[field] = bootstrapped[field]; - } - } - } - - return new XPIState(location, id, data); - } - - // Compatibility shim getters for legacy callers in XPIProviderUtils: get mtime() { - return this.lastModifiedTime; - } - get active() { - return this.enabled; - } - get multiprocessCompatible() { - return !this.enableShims; - } - - - /** - * @property {string} path - * The full on-disk path of the add-on. - */ - get path() { - return this.file && this.file.path; - } - set path(path) { - this.file = getFile(path, this.location.dir) - } - - /** - * @property {string} relativePath - * The path to the add-on relative to its parent location, or - * the full path if its parent location has no on-disk path. - */ - get relativePath() { - if (this.location.dir && this.location.dir.contains(this.file)) { - let path = this.file.getRelativePath(this.location.dir); - if (AppConstants.platform == "win") { - path = path.replace(/\//g, "\\"); - } - return path; + if (!this.enabled && ("manifestTime" in this) && this.manifestTime > this.scanTime) { + return this.manifestTime; } - return this.path; - } + return this.scanTime; + }, - /** - * Returns a JSON-compatible representation of this add-on's state - * data, to be saved to addonStartup.json. - */ toJSON() { - let json = { - enabled: this.enabled, - lastModifiedTime: this.lastModifiedTime, - path: this.relativePath, - version: this.version, - }; - if (this.type != "extension") { - json.type = this.type; - } - if (this.enableShims) { - json.enableShims = true; - } - if (this.bootstrapped) { - json.bootstrapped = true; - json.dependencies = this.dependencies; - json.runInSafeMode = this.runInSafeMode; - json.hasEmbeddedWebExtension = this.hasEmbeddedWebExtension; + let json = {}; + for (let [short, long] of XPIState.prototype.fields) { + if (long in this) { + json[short] = this[long]; + } } return json; - } + }, /** * Update the last modified time for an add-on on disk. @@ -2367,19 +2191,53 @@ class XPIState { * @return True if the time stamp has changed. */ getModTime(aFile, aId) { - // Modified time is the install manifest time, if any. If no manifest - // exists, we assume this is a packed .xpi and use the time stamp of - // {path} - let mtime = (tryGetMtime(getManifestFileForDir(aFile)) || - tryGetMtime(aFile)); - if (!mtime) { - logger.warn("Can't get modified time of ${file}", {file: aFile.path}); + let changed = false; + let scanStarted = Cu.now(); + // For an unknown or enabled add-on, we do a full recursive scan. + if (!("scanTime" in this) || this.enabled) { + logger.debug("getModTime: Recursive scan of " + aId); + let [modFile, modTime, items] = recursiveLastModifiedTime(aFile); + XPIProvider._mostRecentlyModifiedFile[aId] = modFile; + XPIProvider.setTelemetry(aId, "scan_items", items); + if (modTime != this.scanTime) { + this.scanTime = modTime; + changed = true; + } } + // if the add-on is disabled, modified time is the install manifest time, if + // any. If no manifest exists, we assume this is a packed .xpi and use + // the time stamp of {path} + try { + // Get the install manifest update time, if any. + let maniFile = getManifestFileForDir(aFile); + if (!(aId in XPIProvider._mostRecentlyModifiedFile)) { + XPIProvider._mostRecentlyModifiedFile[aId] = maniFile.leafName; + } + let maniTime = maniFile.lastModifiedTime; + if (maniTime != this.manifestTime) { + this.manifestTime = maniTime; + changed = true; + } + } catch (e) { + // No manifest + delete this.manifestTime; + try { + let dtime = aFile.lastModifiedTime; + if (dtime != this.scanTime) { + changed = true; + this.scanTime = dtime; + } + } catch (e) { + logger.warn("Can't get modified time of ${file}: ${e}", {file: aFile.path, e}); + changed = true; + this.scanTime = 0; + } + } + // Record duration of file-modified check + XPIProvider.setTelemetry(aId, "scan_MS", Math.round(Cu.now() - scanStarted)); - this.changed = mtime != this.lastModifiedTime; - this.lastModifiedTime = mtime; - return this.changed; - } + return changed; + }, /** * Update the XPIState to match an XPIDatabase entry; if 'enabled' is changed to true, @@ -2396,132 +2254,31 @@ class XPIState { // did a full recursive scan in that case, so we don't need to do it again. // We don't use aDBAddon.active here because it's not updated until after restart. let mustGetMod = (aDBAddon.visible && !aDBAddon.disabled && !this.enabled); - - this.enabled = aDBAddon.visible && !aDBAddon.disabled; + this.enabled = (aDBAddon.visible && !aDBAddon.disabled); this.version = aDBAddon.version; - this.type = aDBAddon.type; - this.enableShims = this.type == "extension" && !aDBAddon.multiprocessCompatible; - - this.bootstrapped = !!aDBAddon.bootstrap; - if (this.bootstrapped) { - this.hasEmbeddedWebExtension = aDBAddon.hasEmbeddedWebExtension; - this.dependencies = aDBAddon.dependencies; - this.runInSafeMode = canRunInSafeMode(aDBAddon); - } - + // XXX Eventually also copy bootstrap, etc. if (aUpdated || mustGetMod) { - this.getModTime(this.file, aDBAddon.id); - if (this.lastModifiedTime != aDBAddon.updateDate) { - aDBAddon.updateDate = this.lastModifiedTime; - if (XPIDatabase.initialized) { - XPIDatabase.saveChanges(); - } + this.getModTime(new nsIFile(this.descriptor), aDBAddon.id); + if (this.scanTime != aDBAddon.updateDate) { + aDBAddon.updateDate = this.scanTime; + XPIDatabase.saveChanges(); } } - } -} + }, +}; -/** - * Manages the state data for add-ons in a given install location. - * - * @param {string} name - * The name of the install location (e.g., "app-profile"). - * @param {string?} path - * The on-disk path of the install location. May be null for some - * locations which do not map to a specific on-disk path. - * @param {object} [saved = {}] - * The persisted JSON state data to restore. - */ -class XPIStateLocation extends Map { - constructor(name, path, saved = {}) { - super(); - - this.name = name; - this.path = path || saved.path || null; - this.dir = this.path && new nsIFile(this.path); - - for (let [id, data] of Object.entries(saved.addons || {})) { - let xpiState = this._addState(id, data); - // Make a note that this state was restored from saved data. - xpiState.wasRestored = true; +// Constructor for an ES6 Map that knows how to convert itself into a +// regular object for toJSON(). +function SerializableMap(arg) { + let m = new Map(arg); + m.toJSON = function() { + let out = {} + for (let [key, val] of m) { + out[key] = val; } - } - - /** - * Returns a JSON-compatible representation of this location's state - * data, to be saved to addonStartup.json. - */ - toJSON() { - let json = { addons: {} }; - - if (this.path) { - json.path = this.path; - } - - if (STARTUP_MTIME_SCOPES.includes(this.name)) { - json.checkStartupModifications = true; - } - - for (let [id, addon] of this.entries()) { - if (addon.type != "experiment") { - json.addons[id] = addon; - } - } - return json; - } - - _addState(addonId, saved) { - let xpiState = new XPIState(this, addonId, saved); - this.set(addonId, xpiState); - return xpiState; - } - - /** - * Adds state data for the given DB add-on to the DB. - * - * @param {DBAddon} addon - * The DBAddon to add. - */ - addAddon(addon) { - logger.debug("XPIStates adding add-on ${id} in ${location}: ${path}", addon); - - let xpiState = this._addState(addon.id, {file: addon._sourceBundle}); - xpiState.syncWithDB(addon, true); - - XPIProvider.setTelemetry(addon.id, "location", this.name); - } - - /** - * Adds stub state data for the local file to the DB. - * - * @param {string} addonId - * The ID of the add-on represented by the given file. - * @param {nsIFile} file - * The local file or directory containing the add-on. - * @returns {XPIState} - */ - addFile(addonId, file) { - let xpiState = this._addState(addonId, {enabled: true, file: file.clone()}); - xpiState.getModTime(xpiState.file, addonId); - return xpiState; - } - - /** - * Migrates saved state data for the given add-on from the values - * stored in xpiState and bootstrappedAddons preferences, and adds it to - * the DB. - * - * @param {string} id - * The ID of the add-on to migrate. - * @param {object} state - * The add-on's data from the xpiState preference. - * @param {object} [bootstrapped] - * The add-on's data from the bootstrappedAddons preference, if - * applicable. - */ - migrateAddon(id, state, bootstrapped) { - this.set(id, XPIState.migrate(this, id, state, bootstrapped)); - } + return out; + }; + return m; } /** @@ -2531,176 +2288,103 @@ this.XPIStates = { // Map(location name -> Map(add-on ID -> XPIState)) db: null, - _jsonFile: null, - - /** - * @property {Map} sideLoadedAddons - * A map of new add-ons detected during install location - * directory scans. Keys are add-on IDs, values are XPIState - * objects corresponding to those add-ons. - */ - sideLoadedAddons: new Map(), - get size() { + if (!this.db) { + return 0; + } let count = 0; - if (this.db) { - for (let location of this.db.values()) { - count += location.size; - } + for (let location of this.db.values()) { + count += location.size; } return count; }, /** - * Migrates state data from the xpiState and bootstrappedAddons - * preferences and adds it to the DB. Returns a JSON-compatible - * representation of the current state of the DB. - * - * @returns {object} - */ - migrateStateFromPrefs() { - logger.info("No addonStartup.json found. Attempting to migrate data from preferences"); - - let state; - // Try to migrate state data from old storage locations. - let bootstrappedAddons; - try { - state = JSON.parse(Preferences.get(PREF_XPI_STATE)); - bootstrappedAddons = JSON.parse(Preferences.get(PREF_BOOTSTRAP_ADDONS, "{}")); - } catch (e) { - logger.warn("Error parsing extensions.xpiState and " + - "extensions.bootstrappedAddons: ${error}", - {error: e}); - - } - - for (let [locName, addons] of Object.entries(state)) { - for (let [id, addon] of Object.entries(addons)) { - let loc = this.getLocation(locName); - if (loc) { - loc.migrateAddon(id, addon, bootstrappedAddons[id] || null); - } - } - } - - // Clear out old state data. - for (let pref of OBSOLETE_PREFERENCES) { - Preferences.reset(pref); - } - OS.File.remove(OS.Path.join(OS.Constants.Path.profileDir, - FILE_XPI_ADDONS_LIST)); - - // Serialize and deserialize so we get the expected JSON data. - let data = JSON.parse(JSON.stringify(this)); - - logger.debug("Migrated data: ${}", data); - - return data; - }, - - /** - * Load extension state data from addonStartup.json, or migrates it - * from legacy state preferences, if they exist. + * Load extension state data from preferences. */ loadExtensionState() { - let state; + let state = {}; + + // Clear out old directory state cache. + Preferences.reset(PREF_INSTALL_CACHE); + + let cache = Preferences.get(PREF_XPI_STATE, "{}"); try { - state = aomStartup.readStartupData(); + state = JSON.parse(cache); } catch (e) { - logger.warn("Error parsing extensions state: ${error}", - {error: e}); + logger.warn("Error parsing extensions.xpiState ${state}: ${error}", + {state: cache, error: e}); } - - if (!state && Preferences.has(PREF_XPI_STATE)) { - try { - state = this.migrateStateFromPrefs(); - } catch (e) { - logger.warn("Error migrating extensions.xpiState and " + - "extensions.bootstrappedAddons: ${error}", - {error: e}); - } - } - - logger.debug("Loaded add-on state: ${}", state); - return state || {}; + logger.debug("Loaded add-on state from prefs: ${}", state); + return state; }, /** * Walk through all install locations, highest priority first, * comparing the on-disk state of extensions to what is stored in prefs. - * - * @param {bool} [ignoreSideloads = true] - * If true, ignore changes in scopes where we don't accept - * side-loads. - * * @return true if anything has changed. */ - getInstallState(ignoreSideloads = true) { - if (!this.db) { - this.db = new Map(); - } - - let oldState = this.initialStateData || this.loadExtensionState(); - this.initialStateData = oldState; - + getInstallState() { + let oldState = this.loadExtensionState(); let changed = false; - let oldLocations = new Set(Object.keys(oldState)); + this.db = new SerializableMap(); for (let location of XPIProvider.installLocations) { - oldLocations.delete(location.name); - + // The list of add-on like file/directory names in the install location. + let addons = location.getAddonLocations(); // The results of scanning this location. - let loc = this.getLocation(location.name, location.path || null, - oldState[location.name]); - changed = changed || loc.changed; + let foundAddons = new SerializableMap(); - // Don't bother checking scopes where we don't accept side-loads. - if (ignoreSideloads && !(location.scope & gStartupScanScopes)) { - continue; + // What our old state thinks should be in this location. + let locState = {}; + if (location.name in oldState) { + locState = oldState[location.name]; + // We've seen this location. + delete oldState[location.name]; } - if (location.name == KEY_APP_TEMPORARY) { - continue; - } - - let knownIds = new Set(loc.keys()); - for (let [id, file] of location.getAddonLocations(true)) { - knownIds.delete(id); - - let xpiState = loc.get(id); - if (!xpiState) { + for (let [id, file] of addons) { + if (!(id in locState)) { logger.debug("New add-on ${id} in ${location}", {id, location: location.name}); - - changed = true; - xpiState = loc.addFile(id, file); - if (!location.isSystem) { - this.sideLoadedAddons.set(id, xpiState); - } + let xpiState = new XPIState({d: file.persistentDescriptor}); + changed = xpiState.getModTime(file, id) || changed; + foundAddons.set(id, xpiState); } else { - let addonChanged = (xpiState.getModTime(file, id) || - file.path != xpiState.path); - xpiState.file = file.clone(); + let xpiState = new XPIState(locState[id]); + // We found this add-on in the file system + delete locState[id]; - if (addonChanged) { + changed = xpiState.getModTime(file, id) || changed; + + if (file.persistentDescriptor != xpiState.descriptor) { + xpiState.descriptor = file.persistentDescriptor; changed = true; + } + if (changed) { logger.debug("Changed add-on ${id} in ${location}", {id, location: location.name}); } else { logger.debug("Existing add-on ${id} in ${location}", {id, location: location.name}); } + foundAddons.set(id, xpiState); } XPIProvider.setTelemetry(id, "location", location.name); } // Anything left behind in oldState was removed from the file system. - for (let id of knownIds) { - loc.delete(id); + if (Object.keys(locState).length) { changed = true; } + // If we found anything, add this location to our database. + if (foundAddons.size != 0) { + this.db.set(location.name, foundAddons); + } } // If there's anything left in oldState, an install location that held add-ons // was removed from the browser configuration. - changed = changed || oldLocations.size > 0; + if (Object.keys(oldState).length) { + changed = true; + } logger.debug("getInstallState changed: ${rv}, state: ${state}", {rv: changed, state: this.db}); @@ -2709,25 +2393,11 @@ this.XPIStates = { /** * Get the Map of XPI states for a particular location. - * @param name The name of the install location. - * @return XPIStateLocation (id -> XPIState) or null if there are no add-ons in the location. + * @param aLocation The name of the install location. + * @return Map (id -> XPIState) or null if there are no add-ons in the location. */ - getLocation(name, path, saved) { - let location = this.db.get(name); - - if (path && location && location.path != path) { - location = null; - saved = null; - } - - if (!location || (path && location.path != path)) { - let loc = XPIProvider.installLocationsByName[name]; - if (loc) { - location = new XPIStateLocation(name, path || loc.path || null, saved); - this.db.set(name, location); - } - } - return location; + getLocation(aLocation) { + return this.db.get(aLocation); }, /** @@ -2739,60 +2409,28 @@ this.XPIStates = { */ getAddon(aLocation, aId) { let location = this.db.get(aLocation); - return location && location.get(aId); + if (!location) { + return null; + } + return location.get(aId); }, /** * Find the highest priority location of an add-on by ID and return the * location and the XPIState. * @param aId The add-on ID - * @return {XPIState?} + * @return [locationName, XPIState] if the add-on is found, [undefined, undefined] + * if the add-on is not found. */ findAddon(aId) { // Fortunately the Map iterator returns in order of insertion, which is // also our highest -> lowest priority order. - for (let location of this.db.values()) { + for (let [name, location] of this.db) { if (location.has(aId)) { - return location.get(aId); - } - } - return undefined; - }, - - /** - * Iterates over the list of all enabled add-ons in any location. - */ - * enabledAddons() { - for (let location of this.db.values()) { - for (let entry of location.values()) { - if (entry.enabled) { - yield entry; - } - } - } - }, - - /** - * Iterates over the list of all add-ons which were initially restored - * from the startup state cache. - */ - * initialEnabledAddons() { - for (let addon of this.enabledAddons()) { - if (addon.wasRestored) { - yield addon; - } - } - }, - - /** - * Iterates over all enabled bootstrapped add-ons, in any location. - */ - * bootstrappedAddons() { - for (let addon of this.enabledAddons()) { - if (addon.bootstrapped) { - yield addon; + return [name, location.get(aId)]; } } + return [undefined, undefined]; }, /** @@ -2800,34 +2438,29 @@ this.XPIStates = { * @param aAddon DBAddonInternal for the new add-on. */ addAddon(aAddon) { - let location = this.getLocation(aAddon._installLocation.name); - location.addAddon(aAddon); + let location = this.db.get(aAddon.location); + if (!location) { + // First add-on in this location. + location = new SerializableMap(); + this.db.set(aAddon.location, location); + } + logger.debug("XPIStates adding add-on ${id} in ${location}: ${descriptor}", aAddon); + let xpiState = new XPIState({d: aAddon.descriptor}); + location.set(aAddon.id, xpiState); + xpiState.syncWithDB(aAddon, true); + XPIProvider.setTelemetry(aAddon.id, "location", aAddon.location); }, /** * Save the current state of installed add-ons. + * XXX this *totally* should be a .json file using DeferredSave... */ save() { - if (!this._jsonFile) { - this._jsonFile = new JSONFile({ - path: OS.Path.join(OS.Constants.Path.profileDir, FILE_XPI_STATES), - finalizeAt: AddonManager.shutdown, - compression: "lz4", - }) - this._jsonFile.data = this; - } + let db = new SerializableMap(this.db); + db.delete(TemporaryInstallLocation.name); - this._jsonFile.saveSoon(); - }, - - toJSON() { - let data = {}; - for (let [key, loc] of this.db.entries()) { - if (key != TemporaryInstallLocation.name && loc.size) { - data[key] = loc; - } - } - return data; + let cache = JSON.stringify(db); + Services.prefs.setCharPref(PREF_XPI_STATE, cache); }, /** @@ -2838,27 +2471,19 @@ this.XPIStates = { removeAddon(aLocation, aId) { logger.debug("Removing XPIState for " + aLocation + ":" + aId); let location = this.db.get(aLocation); - if (location) { - location.delete(aId); - if (location.size == 0) { - this.db.delete(aLocation); - } - this.save(); + if (!location) { + return; } - }, - - /** - * Disable the XPIState for an add-on. - */ - disableAddon(aId) { - logger.debug(`Disabling XPIState for ${aId}`); - let state = this.findAddon(aId); - if (state) { - state.enabled = false; + location.delete(aId); + if (location.size == 0) { + this.db.delete(aLocation); } + this.save(); }, }; +const hasOwnProperty = Function.call.bind({}.hasOwnProperty); + this.XPIProvider = { get name() { return "XPIProvider"; @@ -2882,6 +2507,8 @@ this.XPIProvider = { minCompatibleAppVersion: null, // The value of the minCompatiblePlatformVersion preference minCompatiblePlatformVersion: null, + // A dictionary of the file descriptors for bootstrappable add-ons by ID + bootstrappedAddons: {}, // A Map of active addons to their bootstrapScope by ID activeAddons: new Map(), // True if the platform could have activated extensions @@ -2889,8 +2516,13 @@ this.XPIProvider = { // True if all of the add-ons found during startup were installed in the // application install location allAppGlobal: true, + // A string listing the enabled add-ons for annotating crash reports + enabledAddons: null, // Keep track of startup phases for telemetry runPhase: XPI_STARTING, + // Keep track of the newest file in each add-on, in case we want to + // report it to telemetry. + _mostRecentlyModifiedFile: {}, // Per-addon telemetry information _telemetryDetails: {}, // A Map from an add-on install to its ID @@ -2909,7 +2541,11 @@ this.XPIProvider = { * @returns {boolean} */ addonIsActive(addonId) { - let state = XPIStates.findAddon(addonId); + if (hasOwnProperty(this.bootstrappedAddons, addonId)) { + return true; + } + + let [, state] = XPIStates.findAddon(addonId); return state && state.enabled; }, @@ -2925,13 +2561,11 @@ this.XPIProvider = { * object, which is the same as the add-ons ID. */ sortBootstrappedAddons() { - // Sort the list so that ordering is deterministic. - let list = Array.from(XPIStates.bootstrappedAddons()); - list.sort((a, b) => String.localeCompare(a.id, b.id)); - let addons = {}; - for (let entry of list) { - addons[entry.id] = entry; + + // Sort the list of IDs so that the ordering is deterministic. + for (let id of Object.keys(this.bootstrappedAddons).sort()) { + addons[id] = Object.assign({id}, this.bootstrappedAddons[id]); } let res = new Set(); @@ -3215,12 +2849,13 @@ this.XPIProvider = { null); this.minCompatiblePlatformVersion = Preferences.get(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, null); + this.enabledAddons = ""; Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this); Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this); Services.prefs.addObserver(PREF_E10S_ADDON_BLOCKLIST, this); Services.prefs.addObserver(PREF_E10S_ADDON_POLICY, this); - if (!REQUIRE_SIGNING || Cu.isInAutomation) + if (!REQUIRE_SIGNING) Services.prefs.addObserver(PREF_XPI_SIGNATURES_REQUIRED, this); Services.prefs.addObserver(PREF_ALLOW_NON_MPC, this); Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS); @@ -3240,7 +2875,6 @@ this.XPIProvider = { } } - let flushCaches = this.checkForChanges(aAppChanged, aOldAppVersion, aOldPlatformVersion); @@ -3268,6 +2902,8 @@ this.XPIProvider = { Services.obs.notifyObservers(null, "chrome-flush-caches"); } + this.enabledAddons = Preferences.get(PREF_EM_ENABLED_ADDONS, ""); + if ("nsICrashReporter" in Ci && Services.appinfo instanceof Ci.nsICrashReporter) { // Annotate the crash report with relevant add-on information. @@ -3286,6 +2922,7 @@ this.XPIProvider = { for (let addon of this.sortBootstrappedAddons()) { try { + let file = getFile(addon.descriptor); let reason = BOOTSTRAP_REASONS.APP_STARTUP; // Eventually set INSTALLED reason when a bootstrap addon // is dropped in profile folder and automatically installed @@ -3293,7 +2930,7 @@ this.XPIProvider = { .indexOf(addon.id) !== -1) reason = BOOTSTRAP_REASONS.ADDON_INSTALL; this.callBootstrapMethod(createAddonDetails(addon.id, addon), - addon.file, "startup", reason); + file, "startup", reason); } catch (e) { logger.error("Failed to load bootstrap addon " + addon.id + " from " + addon.descriptor, e); @@ -3317,15 +2954,17 @@ this.XPIProvider = { if (!XPIProvider.activeAddons.has(addon.id)) continue; + let file = getFile(addon.descriptor); let addonDetails = createAddonDetails(addon.id, addon); // If the add-on was pending disable then shut it down and remove it // from the persisted data. if (addon.disable) { - XPIProvider.callBootstrapMethod(addonDetails, addon.file, "shutdown", + XPIProvider.callBootstrapMethod(addonDetails, file, "shutdown", BOOTSTRAP_REASONS.ADDON_DISABLE); + delete XPIProvider.bootstrappedAddons[addon.id]; } else { - XPIProvider.callBootstrapMethod(addonDetails, addon.file, "shutdown", + XPIProvider.callBootstrapMethod(addonDetails, file, "shutdown", BOOTSTRAP_REASONS.APP_SHUTDOWN); } } @@ -3376,17 +3015,19 @@ this.XPIProvider = { for (let [id, addon] of tempLocation.entries()) { tempLocation.delete(id); - this.callBootstrapMethod(createAddonDetails(id, addon), - addon.file, "uninstall", + let file = getFile(addon.descriptor); + + this.callBootstrapMethod(createAddonDetails(id, this.bootstrappedAddons[id]), + file, "uninstall", BOOTSTRAP_REASONS.ADDON_UNINSTALL); this.unloadBootstrapScope(id); TemporaryInstallLocation.uninstallAddon(id); - let state = XPIStates.findAddon(id); - if (state) { - let newAddon = XPIDatabase.makeAddonLocationVisible(id, state.location.name); + let [locationName, ] = XPIStates.findAddon(id); + if (locationName) { + let newAddon = XPIDatabase.makeAddonLocationVisible(id, locationName); - let file = new nsIFile(newAddon.path); + let file = getFile(newAddon.descriptor); this.callBootstrapMethod(createAddonDetails(id, newAddon), file, "install", @@ -3395,7 +3036,9 @@ this.XPIProvider = { } } + this.bootstrappedAddons = {}; this.activeAddons.clear(); + this.enabledAddons = null; this.allAppGlobal = true; // If there are pending operations then we must update the list of active @@ -3403,7 +3046,8 @@ this.XPIProvider = { if (Preferences.get(PREF_PENDING_OPERATIONS, false)) { AddonManagerPrivate.recordSimpleMeasure("XPIDB_pending_ops", 1); XPIDatabase.updateActiveAddons(); - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false); + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, + !XPIDatabase.writeAddonsList()); } this.installs = null; @@ -3519,7 +3163,9 @@ this.XPIProvider = { getService(Ci.nsIWindowWatcher); ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, variant); - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false); + // Ensure any changes to the add-ons list are flushed to disk + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, + !XPIDatabase.writeAddonsList()); }, async updateSystemAddons() { @@ -3694,6 +3340,25 @@ this.XPIProvider = { }); }, + /** + * Persists changes to XPIProvider.bootstrappedAddons to its store (a pref). + */ + persistBootstrappedAddons() { + // Experiments are disabled upon app load, so don't persist references. + let filtered = {}; + for (let id in this.bootstrappedAddons) { + let entry = this.bootstrappedAddons[id]; + if (entry.type == "experiment") { + continue; + } + + filtered[id] = entry; + } + + Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS, + JSON.stringify(filtered)); + }, + /** * Adds a list of currently active add-ons to the next crash report. */ @@ -3707,8 +3372,11 @@ this.XPIProvider = { if (Services.appinfo.inSafeMode) return; - let data = Array.from(XPIStates.enabledAddons(), - a => encoded`${a.id}:${a.version}`).join(","); + let data = this.enabledAddons; + for (let id in this.bootstrappedAddons) { + data += (data ? "," : "") + encodeURIComponent(id) + ":" + + encodeURIComponent(this.bootstrappedAddons[id].version); + } try { Services.appinfo.annotateCrashReport("Add-ons", data); @@ -3812,7 +3480,6 @@ this.XPIProvider = { try { location.uninstallAddon(id); - XPIStates.removeAddon(location.name, id); seenFiles.push(stageDirEntry.leafName); } catch (e) { logger.error("Failed to uninstall add-on " + id + " in " + location.name, e); @@ -3825,7 +3492,8 @@ this.XPIProvider = { aManifests[location.name][id] = null; let existingAddonID = id; - let jsonfile = getFile(`${id}.json`, stagingDir); + let jsonfile = stagingDir.clone(); + jsonfile.append(id + ".json"); // Assume this was a foreign install if there is no cached metadata file let foreignInstall = !jsonfile.exists(); let addon; @@ -3880,23 +3548,22 @@ this.XPIProvider = { var oldBootstrap = null; logger.debug("Processing install of " + id + " in " + location.name); - let existingAddon = XPIStates.findAddon(existingAddonID); - if (existingAddon && existingAddon.bootstrapped) { + if (existingAddonID in this.bootstrappedAddons) { try { - var file = existingAddon.file; - if (file.exists()) { - oldBootstrap = existingAddon; + var existingAddon = getFile(this.bootstrappedAddons[existingAddonID].descriptor); + if (existingAddon.exists()) { + oldBootstrap = this.bootstrappedAddons[existingAddonID]; // We'll be replacing a currently active bootstrapped add-on so // call its uninstall method let newVersion = addon.version; - let oldVersion = existingAddon; + let oldVersion = oldBootstrap.version; let uninstallReason = Services.vc.compare(oldVersion, newVersion) < 0 ? BOOTSTRAP_REASONS.ADDON_UPGRADE : BOOTSTRAP_REASONS.ADDON_DOWNGRADE; - this.callBootstrapMethod(createAddonDetails(existingAddonID, existingAddon), - file, "uninstall", uninstallReason, + this.callBootstrapMethod(createAddonDetails(existingAddonID, oldBootstrap), + existingAddon, "uninstall", uninstallReason, { newVersion }); this.unloadBootstrapScope(existingAddonID); flushChromeCaches(); @@ -3911,7 +3578,6 @@ this.XPIProvider = { source: stageDirEntry, existingAddonID }); - XPIStates.addAddon(addon); } catch (e) { logger.error("Failed to install staged add-on " + id + " in " + location.name, e); @@ -4043,7 +3709,6 @@ this.XPIProvider = { // Install the add-on try { addon._sourceBundle = profileLocation.installAddon({ id, source: entry, action: "copy" }); - XPIStates.addAddon(addon); logger.debug("Installed distribution add-on " + id); Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true) @@ -4097,7 +3762,8 @@ this.XPIProvider = { * if it is a new profile or the version is unknown * @return true if a change requiring a restart was detected */ - checkForChanges(aAppChanged, aOldAppVersion, aOldPlatformVersion) { + checkForChanges(aAppChanged, aOldAppVersion, + aOldPlatformVersion) { logger.debug("checkForChanges"); // Keep track of whether and why we need to open and update the database at @@ -4107,9 +3773,14 @@ this.XPIProvider = { updateReasons.push("appChanged"); } - let installChanged = XPIStates.getInstallState(aAppChanged === false); - if (installChanged) { - updateReasons.push("directoryState"); + // Load the list of bootstrapped add-ons first so processFileChanges can + // modify it + // XXX eventually get rid of bootstrappedAddons + try { + this.bootstrappedAddons = JSON.parse(Preferences.get(PREF_BOOTSTRAP_ADDONS, + "{}")); + } catch (e) { + logger.warn("Error parsing enabled bootstrapped extensions cache", e); } // First install any new add-ons into the locations, if there are any @@ -4137,6 +3808,15 @@ this.XPIProvider = { } } + // Telemetry probe added around getInstallState() to check perf + let telemetryCaptureTime = Cu.now(); + let installChanged = XPIStates.getInstallState(); + let telemetry = Services.telemetry; + telemetry.getHistogramById("CHECK_ADDONS_MODIFIED_MS").add(Math.round(Cu.now() - telemetryCaptureTime)); + if (installChanged) { + updateReasons.push("directoryState"); + } + let haveAnyAddons = (XPIStates.size > 0); // If the schema appears to have changed then we should update the database @@ -4159,6 +3839,24 @@ this.XPIProvider = { updateReasons.push("needNewDatabase"); } + // XXX This will go away when we fold bootstrappedAddons into XPIStates. + if (updateReasons.length == 0) { + let bootstrapDescriptors = new Set(Object.keys(this.bootstrappedAddons) + .map(b => this.bootstrappedAddons[b].descriptor)); + + for (let location of XPIStates.db.values()) { + for (let state of location.values()) { + bootstrapDescriptors.delete(state.descriptor); + } + } + + if (bootstrapDescriptors.size > 0) { + logger.warn("Bootstrap state is invalid (missing add-ons: " + + Array.from(bootstrapDescriptors).join(", ") + ")"); + updateReasons.push("missingBootstrapAddon"); + } + } + // Catch and log any errors during the main startup try { let extensionListChanged = false; @@ -4201,51 +3899,30 @@ this.XPIProvider = { // If the application crashed before completing any pending operations then // we should perform them now. if (extensionListChanged || hasPendingChanges) { - this._updateActiveAddons(); - - // Serialize and deserialize so we get the expected JSON data. - let state = JSON.parse(JSON.stringify(XPIStates)); - aomStartup.initializeExtensions(state); + logger.debug("Updating database with changes to installed add-ons"); + XPIDatabase.updateActiveAddons(); + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, + !XPIDatabase.writeAddonsList()); + Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS, + JSON.stringify(this.bootstrappedAddons)); return true; } - aomStartup.initializeExtensions(XPIStates.initialStateData); - logger.debug("No changes found"); } catch (e) { logger.error("Error during startup file checks", e); } - return false; - }, - - _updateActiveAddons() { - logger.debug("Updating database with changes to installed add-ons"); - XPIDatabase.updateActiveAddons(); - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false); - }, - - /** - * Gets an array of add-ons which were placed in a known install location - * prior to startup of the current session, were detected by a directory scan - * of those locations, and are currently disabled. - * - * @returns {Promise>} - */ - async getNewSideloads() { - if (XPIStates.getInstallState(false)) { - // We detected changes. Update the database to account for them. - await XPIDatabase.asyncLoadDB(false); - XPIDatabaseReconcile.processFileChanges({}, false); - this._updateActiveAddons(); + // Check that the add-ons list still exists + let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST], + true); + // the addons list file should exist if and only if we have add-ons installed + if (addonsList.exists() != haveAnyAddons) { + logger.debug("Add-ons list is invalid, rebuilding"); + XPIDatabase.writeAddonsList(); } - let addons = await Promise.all( - Array.from(XPIStates.sideLoadedAddons.keys(), - id => AddonManager.getAddonByID(id))); - - return addons.filter(addon => (addon.seen === false && - addon.permissions & AddonManager.PERM_CAN_ENABLE)); + return false; }, /** @@ -4905,8 +4582,10 @@ this.XPIProvider = { return false; // System add-ons are exempt - let loc = aAddon._installLocation; - if (loc && loc.isSystem) + let locName = aAddon._installLocation ? aAddon._installLocation.name + : undefined; + if (locName == KEY_APP_SYSTEM_DEFAULTS || + locName == KEY_APP_SYSTEM_ADDONS) return false; if (isAddonPartOfE10SRollout(aAddon)) { @@ -4937,8 +4616,10 @@ this.XPIProvider = { return false; // System add-ons are exempt - let loc = aAddon._installLocation; - if (loc && loc.isSystem) + let locName = aAddon._installLocation ? aAddon._installLocation.name + : undefined; + if (locName == KEY_APP_SYSTEM_DEFAULTS || + locName == KEY_APP_SYSTEM_ADDONS) return false; return true; @@ -5148,6 +4829,19 @@ this.XPIProvider = { loadBootstrapScope(aId, aFile, aVersion, aType, aMultiprocessCompatible, aRunInSafeMode, aDependencies, hasEmbeddedWebExtension) { + // Mark the add-on as active for the crash reporter before loading + this.bootstrappedAddons[aId] = { + version: aVersion, + type: aType, + descriptor: aFile.persistentDescriptor, + multiprocessCompatible: aMultiprocessCompatible, + runInSafeMode: aRunInSafeMode, + dependencies: aDependencies, + hasEmbeddedWebExtension, + }; + this.persistBootstrappedAddons(); + this.addAddonsToCrashReporter(); + this.activeAddons.set(aId, { debugGlobal: null, safeWrapper: null, @@ -5155,10 +4849,6 @@ this.XPIProvider = { // a Symbol passed to this add-on, which it can use to identify itself instanceID: Symbol(aId), }); - - // Mark the add-on as active for the crash reporter before loading - this.addAddonsToCrashReporter(); - let activeAddon = this.activeAddons.get(aId); // Locales only contain chrome and can't have bootstrap scripts @@ -5207,7 +4897,10 @@ this.XPIProvider = { activeAddon.bootstrapScope[name] = BOOTSTRAP_REASONS[name]; // Add other stuff that extensions want. - Object.assign(activeAddon.bootstrapScope, {Worker, ChromeWorker}); + const features = [ "Worker", "ChromeWorker" ]; + + for (let feature of features) + activeAddon.bootstrapScope[feature] = gGlobalScope[feature]; // Define a console for the add-on XPCOMUtils.defineLazyGetter( @@ -5250,6 +4943,8 @@ this.XPIProvider = { Cu.allowCPOWsInAddon(aId, false); this.activeAddons.delete(aId); + delete this.bootstrappedAddons[aId]; + this.persistBootstrappedAddons(); this.addAddonsToCrashReporter(); // Only access BrowserToolboxProcess if ToolboxProcess.jsm has been @@ -5466,16 +5161,6 @@ this.XPIProvider = { // Flag that active states in the database need to be updated on shutdown Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); - // Sync with XPIStates. - let xpiState = XPIStates.getAddon(aAddon.location, aAddon.id); - if (xpiState) { - xpiState.syncWithDB(aAddon); - XPIStates.save(); - } else { - // There should always be an xpiState - logger.warn("No XPIState for ${id} in ${location}", aAddon); - } - // Have we just gone back to the current state? if (isDisabled != aAddon.active) { AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper); @@ -5507,9 +5192,36 @@ this.XPIProvider = { } AddonManagerPrivate.callAddonListeners("onEnabled", wrapper); } + } else if (aAddon.bootstrap) { + // Something blocked the restartless add-on from enabling or disabling + // make sure it happens on the next startup + if (isDisabled) { + this.bootstrappedAddons[aAddon.id].disable = true; + } else { + this.bootstrappedAddons[aAddon.id] = { + version: aAddon.version, + type: aAddon.type, + descriptor: aAddon._sourceBundle.persistentDescriptor, + multiprocessCompatible: aAddon.multiprocessCompatible, + runInSafeMode: canRunInSafeMode(aAddon), + dependencies: aAddon.dependencies, + hasEmbeddedWebExtension: aAddon.hasEmbeddedWebExtension, + }; + this.persistBootstrappedAddons(); + } } } + // Sync with XPIStates. + let xpiState = XPIStates.getAddon(aAddon.location, aAddon.id); + if (xpiState) { + xpiState.syncWithDB(aAddon); + XPIStates.save(); + } else { + // There should always be an xpiState + logger.warn("No XPIState for ${id} in ${location}", aAddon); + } + // Notify any other providers that a new theme has been enabled if (isTheme(aAddon.type) && !isDisabled) AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, needsRestart); @@ -5563,7 +5275,8 @@ this.XPIProvider = { // automatically uninstalled on shutdown anyway so there is no need to // do this for them. if (aAddon._installLocation.name != KEY_APP_TEMPORARY) { - let stage = getFile(aAddon.id, aAddon._installLocation.getStagingDir()); + let stage = aAddon._installLocation.getStagingDir(); + stage.append(aAddon.id); if (!stage.exists()) stage.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); } @@ -5626,9 +5339,9 @@ this.XPIProvider = { } function findAddonAndReveal(aId) { - let state = XPIStates.findAddon(aId); - if (state) { - XPIDatabase.getAddonInLocation(aId, state.location.name, revealAddon); + let [locationName, ] = XPIStates.findAddon(aId); + if (locationName) { + XPIDatabase.getAddonInLocation(aId, locationName, revealAddon); } } @@ -5641,7 +5354,6 @@ this.XPIProvider = { this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "uninstall", BOOTSTRAP_REASONS.ADDON_UNINSTALL); - XPIStates.disableAddon(aAddon.id); this.unloadBootstrapScope(aAddon.id); flushChromeCaches(); } @@ -5654,7 +5366,6 @@ this.XPIProvider = { } else if (aAddon.bootstrap && aAddon.active && !this.disableRequiresRestart(aAddon)) { this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown", BOOTSTRAP_REASONS.ADDON_UNINSTALL); - XPIStates.disableAddon(aAddon.id); this.unloadBootstrapScope(aAddon.id); XPIDatabase.updateAddonActive(aAddon, false); } @@ -5686,9 +5397,6 @@ this.XPIProvider = { if (!aAddon.visible) return; - XPIStates.getAddon(aAddon.location, aAddon.id).syncWithDB(aAddon); - XPIStates.save(); - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); // TODO hide hidden add-ons (bug 557710) @@ -5843,7 +5551,8 @@ class AddonInstall { break; case AddonManager.STATE_INSTALLED: logger.debug("Cancelling install of " + this.addon.id); - let xpi = getFile(`${this.addon.id}.xpi`, this.installLocation.getStagingDir()); + let xpi = this.installLocation.getStagingDir(); + xpi.append(this.addon.id + ".xpi"); flushJarCache(xpi); this.installLocation.cleanStagingDir([this.addon.id, this.addon.id + ".xpi", this.addon.id + ".json"]); @@ -6164,7 +5873,8 @@ class AddonInstall { // remove any previously staged files yield this.unstageInstall(stagedAddon); - stagedAddon.append(`${this.addon.id}.xpi`); + stagedAddon.append(this.addon.id); + stagedAddon.leafName = this.addon.id + ".xpi"; installedUnpacked = yield this.stageInstall(requiresRestart, stagedAddon, isUpgrade); @@ -6336,15 +6046,22 @@ class AddonInstall { /** * Removes any previously staged upgrade. */ - async unstageInstall(stagedAddon) { - let stagedJSON = getFile(`${this.addon.id}.json`, stagedAddon); - if (stagedJSON.exists()) { - stagedJSON.remove(true); - } + unstageInstall(stagedAddon) { + return Task.spawn((function*() { + let stagedJSON = stagedAddon.clone(); + let removedAddon = stagedAddon.clone(); - await removeAsync(getFile(this.addon.id, stagedAddon)); + stagedJSON.append(this.addon.id + ".json"); - await removeAsync(getFile(`${this.addon.id}.xpi`, stagedAddon)); + if (stagedJSON.exists()) { + stagedJSON.remove(true); + } + + removedAddon.append(this.addon.id); + yield removeAsync(removedAddon); + removedAddon.leafName = this.addon.id + ".xpi"; + yield removeAsync(removedAddon); + }).bind(this)); } /** @@ -6353,45 +6070,49 @@ class AddonInstall { * @param {Function} resumeFn - a function for the add-on to run * when resuming. */ - async postpone(resumeFn) { - this.state = AddonManager.STATE_POSTPONED; + postpone(resumeFn) { + return Task.spawn((function*() { + this.state = AddonManager.STATE_POSTPONED; - let stagingDir = this.installLocation.getStagingDir(); + let stagingDir = this.installLocation.getStagingDir(); + let stagedAddon = stagingDir.clone(); - await this.installLocation.requestStagingDir(); - await this.unstageInstall(stagingDir); + yield this.installLocation.requestStagingDir(); + yield this.unstageInstall(stagedAddon); - let stagedAddon = getFile(`${this.addon.id}.xpi`, stagingDir); + stagedAddon.append(this.addon.id); + stagedAddon.leafName = this.addon.id + ".xpi"; - await this.stageInstall(true, stagedAddon, true); + yield this.stageInstall(true, stagedAddon, true); - AddonManagerPrivate.callInstallListeners("onInstallPostponed", - this.listeners, this.wrapper) + AddonManagerPrivate.callInstallListeners("onInstallPostponed", + this.listeners, this.wrapper) - // upgrade has been staged for restart, provide a way for it to call the - // resume function. - let callback = AddonManagerPrivate.getUpgradeListener(this.addon.id); - if (callback) { - callback({ - version: this.version, - install: () => { - switch (this.state) { - case AddonManager.STATE_POSTPONED: - if (resumeFn) { - resumeFn(); + // upgrade has been staged for restart, provide a way for it to call the + // resume function. + let callback = AddonManagerPrivate.getUpgradeListener(this.addon.id); + if (callback) { + callback({ + version: this.version, + install: () => { + switch (this.state) { + case AddonManager.STATE_POSTPONED: + if (resumeFn) { + resumeFn(); + } + break; + default: + logger.warn(`${this.addon.id} cannot resume postponed upgrade from state (${this.state})`); + break; } - break; - default: - logger.warn(`${this.addon.id} cannot resume postponed upgrade from state (${this.state})`); - break; - } - }, - }); - } - // Release the staging directory lock, but since the staging dir is populated - // it will not be removed until resumed or installed by restart. - // See also cleanStagingDir() - this.installLocation.releaseStagingDir(); + }, + }); + } + // Release the staging directory lock, but since the staging dir is populated + // it will not be removed until resumed or installed by restart. + // See also cleanStagingDir() + this.installLocation.releaseStagingDir(); + }).bind(this)); } } @@ -7604,7 +7325,8 @@ AddonInternal.prototype = { if (!this._installLocation.locked && !this.pendingUninstall) { // Experiments cannot be upgraded. // System add-on upgrades are triggered through a different mechanism (see updateSystemAddons()) - let isSystem = this._installLocation.isSystem; + let isSystem = (this._installLocation.name == KEY_APP_SYSTEM_DEFAULTS || + this._installLocation.name == KEY_APP_SYSTEM_ADDONS); // Add-ons that are installed by a file link cannot be upgraded. if (this.type != "experiment" && !this._installLocation.isLinkedAddon(this.id) && !isSystem) { @@ -7971,12 +7693,14 @@ AddonWrapper.prototype = { if (addon._installLocation.name == KEY_APP_TEMPORARY) return false; - return addon._installLocation.isSystem; + return (addon._installLocation.name == KEY_APP_SYSTEM_DEFAULTS || + addon._installLocation.name == KEY_APP_SYSTEM_ADDONS); }, get isSystem() { let addon = addonFor(this); - return addon._installLocation.isSystem; + return (addon._installLocation.name == KEY_APP_SYSTEM_DEFAULTS || + addon._installLocation.name == KEY_APP_SYSTEM_ADDONS); }, // Returns true if Firefox Sync should sync this addon. Only non-hotfixes @@ -8344,19 +8068,12 @@ class DirectoryInstallLocation { this._IDToFileMap = {}; this._linkedAddons = []; - this.isSystem = (aName == KEY_APP_SYSTEM_ADDONS || - aName == KEY_APP_SYSTEM_DEFAULTS); - if (!aDirectory || !aDirectory.exists()) return; if (!aDirectory.isDirectory()) throw new Error("Location must be a directory."); - this.initialized = false; - } - - get path() { - return this._directory && this._directory.path; + this._readAddons(); } /** @@ -8420,12 +8137,7 @@ class DirectoryInstallLocation { /** * Finds all the add-ons installed in this location. */ - _readAddons(rescan = false) { - if ((this.initialized && !rescan) || !this._directory) { - return; - } - this.initialized = true; - + _readAddons() { // Use a snapshot of the directory contents to avoid possible issues with // iterating over a directory while removing files from it (the YAFFS2 // embedded filesystem has this issue, see bug 772238). @@ -8488,9 +8200,7 @@ class DirectoryInstallLocation { /** * Gets an array of nsIFiles for add-ons installed in this location. */ - getAddonLocations(rescan = false) { - this._readAddons(rescan); - + getAddonLocations() { let locations = new Map(); for (let id in this._IDToFileMap) { locations.set(id, this._IDToFileMap[id].clone()); @@ -8507,9 +8217,6 @@ class DirectoryInstallLocation { * @throws if the ID does not match any of the add-ons installed */ getLocationForID(aId) { - if (!(aId in this._IDToFileMap)) - this._readAddons(); - if (aId in this._IDToFileMap) return this._IDToFileMap[aId].clone(); throw new Error("Unknown add-on ID " + aId); @@ -8554,7 +8261,9 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { * @return an nsIFile */ getStagingDir() { - return getFile(DIR_STAGE, this._directory); + let dir = this._directory.clone(); + dir.append(DIR_STAGE); + return dir; } requestStagingDir() { @@ -8596,7 +8305,8 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { let dir = this.getStagingDir(); for (let name of aLeafNames) { - let file = getFile(name, dir); + let file = dir.clone(); + file.append(name); recursiveRemove(file); } @@ -8629,7 +8339,8 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { * @return an nsIFile */ getTrashDir() { - let trashDir = getFile(DIR_TRASH, this._directory); + let trashDir = this._directory.clone(); + trashDir.append(DIR_TRASH); let trashDirExists = trashDir.exists(); try { if (trashDirExists) @@ -8670,11 +8381,14 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { let transaction = new SafeInstallOperation(); let moveOldAddon = aId => { - let file = getFile(aId, this._directory); + let file = this._directory.clone(); + file.append(aId); + if (file.exists()) transaction.moveUnder(file, trashDir); - file = getFile(`${aId}.xpi`, this._directory); + file = this._directory.clone(); + file.append(aId + ".xpi"); if (file.exists()) { flushJarCache(file); transaction.moveUnder(file, trashDir); @@ -8703,7 +8417,8 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { KEY_PROFILEDIR, ["extension-data", id], false, true ); if (newDataDir.exists()) { - let trashData = getFile("data-directory", trashDir); + let trashData = trashDir.clone(); + trashData.append("data-directory"); transaction.moveUnder(newDataDir, trashData); } @@ -8774,7 +8489,8 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { return; } - file = getFile(aId, this._directory); + file = this._directory.clone(); + file.append(aId); if (!file.exists()) file.leafName += ".xpi"; @@ -8807,8 +8523,6 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { } } - XPIStates.removeAddon(this.name, aId); - delete this._IDToFileMap[aId]; } } @@ -8838,7 +8552,8 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { // Therefore, this is looked up before calling the // constructor on the superclass. if (addonSet.directory) { - directory = getFile(addonSet.directory, aDirectory); + directory = aDirectory.clone(); + directory.append(addonSet.directory); logger.info("SystemAddonInstallLocation scanning directory " + directory.path); } else { logger.info("SystemAddonInstallLocation directory is missing"); @@ -8870,8 +8585,10 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { this._addonSet = SystemAddonInstallLocation._loadAddonSet(); let dir = null; if (this._addonSet.directory) { - this._directory = getFile(this._addonSet.directory, this._baseDir); - dir = getFile(DIR_STAGE, this._directory); + this._directory = this._baseDir.clone(); + this._directory.append(this._addonSet.directory); + dir = this._directory.clone(); + dir.append(DIR_STAGE); } else { logger.info("SystemAddonInstallLocation directory is missing"); } @@ -8882,7 +8599,8 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { requestStagingDir() { this._addonSet = SystemAddonInstallLocation._loadAddonSet(); if (this._addonSet.directory) { - this._directory = getFile(this._addonSet.directory, this._baseDir); + this._directory = this._baseDir.clone(); + this._directory.append(this._addonSet.directory); } return super.requestStagingDir(); } @@ -8997,8 +8715,7 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { // remove everything from the pref first, if uninstall // fails then at least they will not be re-activated on // next restart. - this._addonSet = { schema: 1, addons: {} }; - SystemAddonInstallLocation._saveAddonSet(this._addonSet); + SystemAddonInstallLocation._saveAddonSet({ schema: 1, addons: {} }); // If this is running at app startup, the pref being cleared // will cause later stages of startup to notice that the @@ -9208,7 +8925,8 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { * @return an nsIFile */ getTrashDir() { - let trashDir = getFile(DIR_TRASH, this._directory); + let trashDir = this._directory.clone(); + trashDir.append(DIR_TRASH); let trashDirExists = trashDir.exists(); try { if (trashDirExists) @@ -9254,7 +8972,8 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { } } - let newFile = getFile(source.leafName, this._directory); + let newFile = this._directory.clone(); + newFile.append(source.leafName); try { newFile.lastModifiedTime = Date.now(); diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js index 4790e9b053e1..df19d9b58c7f 100644 --- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js @@ -8,7 +8,7 @@ /* globals ADDON_SIGNING, SIGNED_TYPES, BOOTSTRAP_REASONS, DB_SCHEMA, AddonInternal, XPIProvider, XPIStates, syncLoadManifestFromFile, isUsableAddon, recordAddonTelemetry, applyBlocklistChanges, - flushChromeCaches, descriptorToPath */ + flushChromeCaches, canRunInSafeMode*/ var Cc = Components.classes; var Ci = Components.interfaces; @@ -41,20 +41,23 @@ XPCOMUtils.defineLazyPreferenceGetter(this, "ALLOW_NON_MPC", Cu.import("resource://gre/modules/Log.jsm"); const LOGGER_ID = "addons.xpi-utils"; -const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile", - "initWithPath"); +const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile"); // Create a new logger for use by the Addons XPI Provider Utils // (Requires AddonManager.jsm) var logger = Log.repository.getLogger(LOGGER_ID); const KEY_PROFILEDIR = "ProfD"; +const FILE_DATABASE = "extensions.sqlite"; const FILE_JSON_DB = "extensions.json"; +const FILE_OLD_DATABASE = "extensions.rdf"; +const FILE_XPI_ADDONS_LIST = "extensions.ini"; // The last version of DB_SCHEMA implemented in SQLITE const LAST_SQLITE_DB_SCHEMA = 14; const PREF_DB_SCHEMA = "extensions.databaseSchema"; const PREF_PENDING_OPERATIONS = "extensions.pendingOperations"; +const PREF_EM_ENABLED_ADDONS = "extensions.enabledAddons"; const PREF_EM_AUTO_DISABLED_SCOPES = "extensions.autoDisableScopes"; const PREF_E10S_BLOCKED_BY_ADDONS = "extensions.e10sBlockedByAddons"; const PREF_E10S_MULTI_BLOCKED_BY_ADDONS = "extensions.e10sMultiBlockedByAddons"; @@ -66,13 +69,26 @@ const KEY_APP_SYSTEM_DEFAULTS = "app-system-defaults"; const KEY_APP_GLOBAL = "app-global"; const KEY_APP_TEMPORARY = "app-temporary"; +// Properties that only exist in the database +const DB_METADATA = ["syncGUID", + "installDate", + "updateDate", + "size", + "sourceURI", + "releaseNotesURI", + "applyBackgroundUpdates"]; +const DB_BOOL_METADATA = ["visible", "active", "userDisabled", "appDisabled", + "pendingUninstall", "bootstrap", "skinnable", + "softDisabled", "isForeignInstall", + "hasBinaryComponents", "strictCompatibility"]; + // Properties to save in JSON file const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type", "internalName", "updateURL", "updateKey", "optionsURL", "optionsType", "optionsBrowserStyle", "aboutURL", "defaultLocale", "visible", "active", "userDisabled", - "appDisabled", "pendingUninstall", "installDate", - "updateDate", "applyBackgroundUpdates", "bootstrap", "path", + "appDisabled", "pendingUninstall", "descriptor", "installDate", + "updateDate", "applyBackgroundUpdates", "bootstrap", "skinnable", "size", "sourceURI", "releaseNotesURI", "softDisabled", "foreignInstall", "hasBinaryComponents", "strictCompatibility", "locales", "targetApplications", @@ -80,9 +96,59 @@ const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type", "seen", "dependencies", "hasEmbeddedWebExtension", "mpcOptedOut", "userPermissions", "icons", "iconURL", "icon64URL"]; +// Properties that should be migrated where possible from an old database. These +// shouldn't include properties that can be read directly from install.rdf files +// or calculated +const DB_MIGRATE_METADATA = ["installDate", "userDisabled", "softDisabled", + "sourceURI", "applyBackgroundUpdates", + "releaseNotesURI", "foreignInstall", "syncGUID"]; + // Time to wait before async save of XPI JSON database, in milliseconds const ASYNC_SAVE_DELAY_MS = 20; +const PREFIX_ITEM_URI = "urn:mozilla:item:"; +const RDFURI_ITEM_ROOT = "urn:mozilla:item:root" +const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#"; + +XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1", + Ci.nsIRDFService); + +function EM_R(aProperty) { + return gRDF.GetResource(PREFIX_NS_EM + aProperty); +} + +/** + * Converts an RDF literal, resource or integer into a string. + * + * @param aLiteral + * The RDF object to convert + * @return a string if the object could be converted or null + */ +function getRDFValue(aLiteral) { + if (aLiteral instanceof Ci.nsIRDFLiteral) + return aLiteral.Value; + if (aLiteral instanceof Ci.nsIRDFResource) + return aLiteral.Value; + if (aLiteral instanceof Ci.nsIRDFInt) + return aLiteral.Value; + return null; +} + +/** + * Gets an RDF property as a string + * + * @param aDs + * The RDF datasource to read the property from + * @param aResource + * The RDF resource to read the property from + * @param aProperty + * The property to read + * @return a string if the property existed or null + */ +function getRDFProperty(aDs, aResource, aProperty) { + return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true)); +} + /** * Asynchronously fill in the _repositoryAddon field for one addon */ @@ -157,6 +223,50 @@ function asyncMap(aObjects, aMethod, aCallback) { }); } +/** + * A generator to synchronously return result rows from an mozIStorageStatement. + * + * @param aStatement + * The statement to execute + */ +function* resultRows(aStatement) { + try { + while (stepStatement(aStatement)) + yield aStatement.row; + } finally { + aStatement.reset(); + } +} + +/** + * A helper function to log an SQL error. + * + * @param aError + * The storage error code associated with the error + * @param aErrorString + * An error message + */ +function logSQLError(aError, aErrorString) { + logger.error("SQL error " + aError + ": " + aErrorString); +} + +/** + * A helper function to step a statement synchronously and log any error that + * occurs. + * + * @param aStatement + * A mozIStorageStatement to execute + */ +function stepStatement(aStatement) { + try { + return aStatement.executeStep(); + } catch (e) { + logSQLError(XPIDatabase.connection.lastError, + XPIDatabase.connection.lastErrorString); + throw e; + } +} + /** * Copies properties from one object to another. If no target object is passed * a new object will be created and returned. @@ -191,13 +301,6 @@ function copyProperties(aObject, aProperties, aTarget) { function DBAddonInternal(aLoaded) { AddonInternal.call(this); - if (aLoaded.descriptor) { - if (!aLoaded.path) { - aLoaded.path = descriptorToPath(aLoaded.descriptor); - } - delete aLoaded.descriptor; - } - copyProperties(aLoaded, PROP_JSON_FIELDS, this); if (!this.dependencies) @@ -214,7 +317,7 @@ function DBAddonInternal(aLoaded) { this._key = this.location + ":" + this.id; if (!aLoaded._sourceBundle) { - throw new Error("Expected passed argument to contain a path"); + throw new Error("Expected passed argument to contain a descriptor"); } this._sourceBundle = aLoaded._sourceBundle; @@ -395,6 +498,32 @@ this.XPIDatabase = { return toSave; }, + /** + * Pull upgrade information from an existing SQLITE database + * + * @return false if there is no SQLITE database + * true and sets this.migrateData to null if the SQLITE DB exists + * but does not contain useful information + * true and sets this.migrateData to + * {location: {id1:{addon1}, id2:{addon2}}, location2:{...}, ...} + * if there is useful information + */ + getMigrateDataFromSQLITE() { + let connection = null; + let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true); + // Attempt to open the database + try { + connection = Services.storage.openUnsharedDatabase(dbfile); + } catch (e) { + logger.warn("Failed to open sqlite database " + dbfile.path + " for upgrade", e); + return null; + } + logger.debug("Migrating data from sqlite"); + let migrateData = this.getMigrateDataFromDatabase(connection); + connection.close(); + return migrateData; + }, + /** * Synchronously opens and reads the database file, upgrading from old * databases or making a new DB if needed. @@ -501,10 +630,11 @@ this.XPIDatabase = { // Make AddonInternal instances from the loaded data and save them let addonDB = new Map(); for (let loadedAddon of inputAddons.addons) { + loadedAddon._sourceBundle = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); try { - loadedAddon._sourceBundle = new nsIFile(loadedAddon.path); + loadedAddon._sourceBundle.persistentDescriptor = loadedAddon.descriptor; } catch (e) { - // We can fail here when the path is invalid, usually from the + // We can fail here when the descriptor is invalid, usually from the // wrong OS logger.warn("Could not find source bundle for add-on " + loadedAddon.id, e); } @@ -538,12 +668,21 @@ this.XPIDatabase = { */ upgradeDB(aRebuildOnError) { let upgradeTimer = AddonManagerPrivate.simpleTimer("XPIDB_upgradeDB_MS"); - - let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA, 0); - if (schemaVersion > LAST_SQLITE_DB_SCHEMA) { - // we've upgraded before but the JSON file is gone, fall through - // and rebuild from scratch - AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError", "dbMissing"); + try { + let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA); + if (schemaVersion <= LAST_SQLITE_DB_SCHEMA) { + // we should have an older SQLITE database + logger.debug("Attempting to upgrade from SQLITE database"); + this.migrateData = this.getMigrateDataFromSQLITE(); + } else { + // we've upgraded before but the JSON file is gone, fall through + // and rebuild from scratch + AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError", "dbMissing"); + } + } catch (e) { + // No schema version pref means either a really old upgrade (RDF) or + // a new profile + this.migrateData = this.getMigrateDataFromRDF(); } this.rebuildDatabase(aRebuildOnError); @@ -640,13 +779,8 @@ this.XPIDatabase = { // If there is no migration data then load the list of add-on directories // that were active during the last run - if (!this.migrateData) { - this.activeBundles = Array.from(XPIStates.initialEnabledAddons(), - addon => addon.path); - if (!this.activeBundles.length) - this.activeBundles = null; - } - + if (!this.migrateData) + this.activeBundles = this.getActiveBundles(); if (aRebuildOnError) { logger.warn("Rebuilding add-ons database from installed extensions."); @@ -660,6 +794,203 @@ this.XPIDatabase = { } }, + /** + * Gets the list of file descriptors of active extension directories or XPI + * files from the add-ons list. This must be loaded from disk since the + * directory service gives no easy way to get both directly. This list doesn't + * include themes as preferences already say which theme is currently active + * + * @return an array of persistent descriptors for the directories + */ + getActiveBundles() { + let bundles = []; + + // non-bootstrapped extensions + let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST], + true); + + if (!addonsList.exists()) + // XXX Irving believes this is broken in the case where there is no + // extensions.ini but there are bootstrap extensions (e.g. Android) + return null; + + try { + let iniFactory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"] + .getService(Ci.nsIINIParserFactory); + let parser = iniFactory.createINIParser(addonsList); + let keys = parser.getKeys("ExtensionDirs"); + + while (keys.hasMore()) + bundles.push(parser.getString("ExtensionDirs", keys.getNext())); + } catch (e) { + logger.warn("Failed to parse extensions.ini", e); + return null; + } + + // Also include the list of active bootstrapped extensions + for (let id in XPIProvider.bootstrappedAddons) + bundles.push(XPIProvider.bootstrappedAddons[id].descriptor); + + return bundles; + }, + + /** + * Retrieves migration data from the old extensions.rdf database. + * + * @return an object holding information about what add-ons were previously + * userDisabled and any updated compatibility information + */ + getMigrateDataFromRDF(aDbWasMissing) { + + // Migrate data from extensions.rdf + let rdffile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_DATABASE], true); + if (!rdffile.exists()) + return null; + + logger.debug("Migrating data from " + FILE_OLD_DATABASE); + let migrateData = {}; + + try { + let ds = gRDF.GetDataSourceBlocking(Services.io.newFileURI(rdffile).spec); + let root = Cc["@mozilla.org/rdf/container;1"]. + createInstance(Ci.nsIRDFContainer); + root.Init(ds, gRDF.GetResource(RDFURI_ITEM_ROOT)); + let elements = root.GetElements(); + + while (elements.hasMoreElements()) { + let source = elements.getNext().QueryInterface(Ci.nsIRDFResource); + + let location = getRDFProperty(ds, source, "installLocation"); + if (location) { + if (!(location in migrateData)) + migrateData[location] = {}; + let id = source.ValueUTF8.substring(PREFIX_ITEM_URI.length); + migrateData[location][id] = { + version: getRDFProperty(ds, source, "version"), + userDisabled: false, + targetApplications: [] + } + + let disabled = getRDFProperty(ds, source, "userDisabled"); + if (disabled == "true" || disabled == "needs-disable") + migrateData[location][id].userDisabled = true; + + let targetApps = ds.GetTargets(source, EM_R("targetApplication"), + true); + while (targetApps.hasMoreElements()) { + let targetApp = targetApps.getNext() + .QueryInterface(Ci.nsIRDFResource); + let appInfo = { + id: getRDFProperty(ds, targetApp, "id") + }; + + let minVersion = getRDFProperty(ds, targetApp, "updatedMinVersion"); + if (minVersion) { + appInfo.minVersion = minVersion; + appInfo.maxVersion = getRDFProperty(ds, targetApp, "updatedMaxVersion"); + } else { + appInfo.minVersion = getRDFProperty(ds, targetApp, "minVersion"); + appInfo.maxVersion = getRDFProperty(ds, targetApp, "maxVersion"); + } + migrateData[location][id].targetApplications.push(appInfo); + } + } + } + } catch (e) { + logger.warn("Error reading " + FILE_OLD_DATABASE, e); + migrateData = null; + } + + return migrateData; + }, + + /** + * Retrieves migration data from a database that has an older or newer schema. + * + * @return an object holding information about what add-ons were previously + * userDisabled and any updated compatibility information + */ + getMigrateDataFromDatabase(aConnection) { + let migrateData = {}; + + // Attempt to migrate data from a different (even future!) version of the + // database + try { + var stmt = aConnection.createStatement("PRAGMA table_info(addon)"); + + const REQUIRED = ["internal_id", "id", "location", "userDisabled", + "installDate", "version"]; + + let reqCount = 0; + let props = []; + for (let row of resultRows(stmt)) { + if (REQUIRED.indexOf(row.name) != -1) { + reqCount++; + props.push(row.name); + } else if (DB_METADATA.indexOf(row.name) != -1) { + props.push(row.name); + } else if (DB_BOOL_METADATA.indexOf(row.name) != -1) { + props.push(row.name); + } + } + + if (reqCount < REQUIRED.length) { + logger.error("Unable to read anything useful from the database"); + return null; + } + stmt.finalize(); + + stmt = aConnection.createStatement("SELECT " + props.join(",") + " FROM addon"); + for (let row of resultRows(stmt)) { + if (!(row.location in migrateData)) + migrateData[row.location] = {}; + let addonData = { + targetApplications: [] + } + migrateData[row.location][row.id] = addonData; + + props.forEach(function(aProp) { + if (aProp == "isForeignInstall") + addonData.foreignInstall = (row[aProp] == 1); + if (DB_BOOL_METADATA.indexOf(aProp) != -1) + addonData[aProp] = row[aProp] == 1; + else + addonData[aProp] = row[aProp]; + }) + } + + var taStmt = aConnection.createStatement("SELECT id, minVersion, " + + "maxVersion FROM " + + "targetApplication WHERE " + + "addon_internal_id=:internal_id"); + + for (let location in migrateData) { + for (let id in migrateData[location]) { + taStmt.params.internal_id = migrateData[location][id].internal_id; + delete migrateData[location][id].internal_id; + for (let row of resultRows(taStmt)) { + migrateData[location][id].targetApplications.push({ + id: row.id, + minVersion: row.minVersion, + maxVersion: row.maxVersion + }); + } + } + } + } catch (e) { + // An error here means the schema is too different to read + logger.error("Error migrating data", e); + return null; + } finally { + if (taStmt) + taStmt.finalize(); + if (stmt) + stmt.finalize(); + } + + return migrateData; + }, + /** * Shuts down the database connection and releases all cached objects. * Return: Promise{integer} resolves / rejects with the result of the DB @@ -903,11 +1234,11 @@ this.XPIDatabase = { * * @param aAddon * AddonInternal to add - * @param aPath - * The file path of the add-on + * @param aDescriptor + * The file descriptor of the add-on * @return The DBAddonInternal that was added to the database */ - addAddonMetadata(aAddon, aPath) { + addAddonMetadata(aAddon, aDescriptor) { if (!this.addonDB) { AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_addMetadata", XPIProvider.runPhase); @@ -915,7 +1246,7 @@ this.XPIDatabase = { } let newAddon = new DBAddonInternal(aAddon); - newAddon.path = aPath; + newAddon.descriptor = aDescriptor; this.addonDB.set(newAddon._key, newAddon); if (newAddon.visible) { this.makeAddonVisible(newAddon); @@ -933,11 +1264,11 @@ this.XPIDatabase = { * The DBAddonInternal to be replaced * @param aNewAddon * The new AddonInternal to add - * @param aPath - * The file path of the add-on + * @param aDescriptor + * The file descriptor of the add-on * @return The DBAddonInternal that was added to the database */ - updateAddonMetadata(aOldAddon, aNewAddon, aPath) { + updateAddonMetadata(aOldAddon, aNewAddon, aDescriptor) { this.removeAddonMetadata(aOldAddon); aNewAddon.syncGUID = aOldAddon.syncGUID; aNewAddon.installDate = aOldAddon.installDate; @@ -947,7 +1278,7 @@ this.XPIDatabase = { aNewAddon.active = (aNewAddon.visible && !aNewAddon.disabled && !aNewAddon.pendingUninstall); // addAddonMetadata does a saveChanges() - return this.addAddonMetadata(aNewAddon, aPath); + return this.addAddonMetadata(aNewAddon, aDescriptor); }, /** @@ -961,14 +1292,6 @@ this.XPIDatabase = { this.saveChanges(); }, - updateXPIStates(addon) { - let xpiState = XPIStates.getAddon(addon.location, addon.id); - if (xpiState) { - xpiState.syncWithDB(addon); - XPIStates.save(); - } - }, - /** * Synchronously marks a DBAddonInternal as visible marking all other * instances with the same ID as not visible. @@ -983,12 +1306,9 @@ this.XPIDatabase = { logger.debug("Hide addon " + otherAddon._key); otherAddon.visible = false; otherAddon.active = false; - - this.updateXPIStates(otherAddon); } } aAddon.visible = true; - this.updateXPIStates(aAddon); this.saveChanges(); }, @@ -1010,13 +1330,11 @@ this.XPIDatabase = { logger.debug("Reveal addon " + addon._key); addon.visible = true; addon.active = true; - this.updateXPIStates(addon); result = addon; } else { logger.debug("Hide addon " + addon._key); addon.visible = false; addon.active = false; - this.updateXPIStates(addon); } } this.saveChanges(); @@ -1076,15 +1394,6 @@ this.XPIDatabase = { }, updateAddonsBlockingE10s() { - if (!this.addonDB) { - // jank-tastic! Must synchronously load DB if the theme switches from - // an XPI theme to a lightweight theme before the DB has loaded, - // because we're called from sync XPIProvider.addonChanged - logger.warn("Synchronous load of XPI database due to updateAddonsBlockingE10s()"); - AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_byType", XPIProvider.runPhase); - this.syncLoadDB(true); - } - let blockE10s = false; Preferences.set(PREF_E10S_HAS_NONEXEMPT_ADDON, false); @@ -1133,6 +1442,94 @@ this.XPIDatabase = { } } }, + + /** + * Writes out the XPI add-ons list for the platform to read. + * @return true if the file was successfully updated, false otherwise + */ + writeAddonsList() { + if (!this.addonDB) { + // force the DB to load + AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_writeList", + XPIProvider.runPhase); + this.syncLoadDB(true); + } + Services.appinfo.invalidateCachesOnRestart(); + + let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST], + true); + let enabledAddons = []; + let text = "[ExtensionDirs]\r\n"; + let count = 0; + let fullCount = 0; + + let activeAddons = _filterDB( + this.addonDB, + aAddon => aAddon.active && !aAddon.bootstrap && (aAddon.type != "theme")); + + for (let row of activeAddons) { + text += "Extension" + (count++) + "=" + row.descriptor + "\r\n"; + enabledAddons.push(encodeURIComponent(row.id) + ":" + + encodeURIComponent(row.version)); + } + fullCount += count; + + // The selected skin may come from an inactive theme (the default theme + // when a lightweight theme is applied for example) + text += "\r\n[ThemeDirs]\r\n"; + + let activeTheme = _findAddon( + this.addonDB, + aAddon => (aAddon.type == "theme") && + (aAddon.internalName == XPIProvider.selectedSkin)); + count = 0; + if (activeTheme) { + text += "Extension" + (count++) + "=" + activeTheme.descriptor + "\r\n"; + enabledAddons.push(encodeURIComponent(activeTheme.id) + ":" + + encodeURIComponent(activeTheme.version)); + } + fullCount += count; + + text += "\r\n[MultiprocessIncompatibleExtensions]\r\n"; + + count = 0; + for (let row of activeAddons) { + if (!row.multiprocessCompatible) { + text += "Extension" + (count++) + "=" + row.id + "\r\n"; + } + } + + if (fullCount > 0) { + logger.debug("Writing add-ons list"); + + try { + let addonsListTmp = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST + ".tmp"], + true); + var fos = FileUtils.openFileOutputStream(addonsListTmp); + fos.write(text, text.length); + fos.close(); + addonsListTmp.moveTo(addonsListTmp.parent, FILE_XPI_ADDONS_LIST); + + Services.prefs.setCharPref(PREF_EM_ENABLED_ADDONS, enabledAddons.join(",")); + } catch (e) { + logger.error("Failed to write add-ons list to profile directory", e); + return false; + } + } else { + if (addonsList.exists()) { + logger.debug("Deleting add-ons list"); + try { + addonsList.remove(false); + } catch (e) { + logger.error("Failed to remove " + addonsList.path, e); + return false; + } + } + + Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS); + } + return true; + } }; this.XPIDatabaseReconcile = { @@ -1205,17 +1602,20 @@ this.XPIDatabaseReconcile = { * @param aOldPlatformVersion * The version of the platform last run with this profile or null * if it is a new profile or the version is unknown + * @param aMigrateData + * If during startup the database had to be upgraded this will + * contain data that used to be held about this add-on * @return a boolean indicating if flushing caches is required to complete * changing this add-on */ addMetadata(aInstallLocation, aId, aAddonState, aNewAddon, aOldAppVersion, - aOldPlatformVersion) { + aOldPlatformVersion, aMigrateData) { logger.debug("New add-on " + aId + " installed in " + aInstallLocation.name); // If we had staged data for this add-on or we aren't recovering from a // corrupt database and we don't have migration data for this add-on then // this must be a new install. - let isNewInstall = !!aNewAddon || !XPIDatabase.activeBundles; + let isNewInstall = (!!aNewAddon) || (!XPIDatabase.activeBundles && !aMigrateData); // If it's a new install and we haven't yet loaded the manifest then it // must be something dropped directly into the install location @@ -1225,7 +1625,8 @@ this.XPIDatabaseReconcile = { try { if (!aNewAddon) { // Load the manifest from the add-on. - let file = new nsIFile(aAddonState.path); + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.persistentDescriptor = aAddonState.descriptor; aNewAddon = syncLoadManifestFromFile(file, aInstallLocation); } // The add-on in the manifest should match the add-on ID. @@ -1260,6 +1661,38 @@ this.XPIDatabaseReconcile = { // appDisabled depends on whether the add-on is a foreignInstall so update aNewAddon.appDisabled = !isUsableAddon(aNewAddon); + if (aMigrateData) { + // If there is migration data then apply it. + logger.debug("Migrating data from old database"); + + DB_MIGRATE_METADATA.forEach(function(aProp) { + // A theme's disabled state is determined by the selected theme + // preference which is read in loadManifestFromRDF + if (aProp == "userDisabled" && aNewAddon.type == "theme") + return; + + if (aProp in aMigrateData) + aNewAddon[aProp] = aMigrateData[aProp]; + }); + + // Force all non-profile add-ons to be foreignInstalls since they can't + // have been installed through the API + aNewAddon.foreignInstall |= aInstallLocation.name != KEY_APP_PROFILE; + + // Some properties should only be migrated if the add-on hasn't changed. + // The version property isn't a perfect check for this but covers the + // vast majority of cases. + if (aMigrateData.version == aNewAddon.version) { + logger.debug("Migrating compatibility info"); + if ("targetApplications" in aMigrateData) + aNewAddon.applyCompatibilityUpdate(aMigrateData, true); + } + + // Since the DB schema has changed make sure softDisabled is correct + applyBlocklistChanges(aNewAddon, aNewAddon, aOldAppVersion, + aOldPlatformVersion); + } + // The default theme is never a foreign install if (aNewAddon.type == "theme" && aNewAddon.internalName == XPIProvider.defaultSkin) aNewAddon.foreignInstall = false; @@ -1275,12 +1708,11 @@ this.XPIDatabaseReconcile = { // If we don't have an old app version then this is a new profile in // which case just mark any sideloaded add-ons as already seen. - aNewAddon.seen = (aInstallLocation.name != KEY_APP_PROFILE && - !aOldAppVersion); + aNewAddon.seen = !aOldAppVersion; } } - return XPIDatabase.addAddonMetadata(aNewAddon, aAddonState.path); + return XPIDatabase.addAddonMetadata(aNewAddon, aAddonState.descriptor); }, /** @@ -1321,7 +1753,8 @@ this.XPIDatabaseReconcile = { try { // If there isn't an updated install manifest for this add-on then load it. if (!aNewAddon) { - let file = new nsIFile(aAddonState.path); + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.persistentDescriptor = aAddonState.descriptor; aNewAddon = syncLoadManifestFromFile(file, aInstallLocation); applyBlocklistChanges(aOldAddon, aNewAddon); @@ -1352,11 +1785,11 @@ this.XPIDatabaseReconcile = { aNewAddon.updateDate = aAddonState.mtime; // Update the database - return XPIDatabase.updateAddonMetadata(aOldAddon, aNewAddon, aAddonState.path); + return XPIDatabase.updateAddonMetadata(aOldAddon, aNewAddon, aAddonState.descriptor); }, /** - * Updates an add-on's path for when the add-on has moved in the + * Updates an add-on's descriptor for when the add-on has moved in the * filesystem but hasn't changed in any other way. * * @param aInstallLocation @@ -1369,10 +1802,10 @@ this.XPIDatabaseReconcile = { * @return a boolean indicating if flushing caches is required to complete * changing this add-on */ - updatePath(aInstallLocation, aOldAddon, aAddonState) { - logger.debug("Add-on " + aOldAddon.id + " moved to " + aAddonState.path); - aOldAddon.path = aAddonState.path; - aOldAddon._sourceBundle = new nsIFile(aAddonState.path); + updateDescriptor(aInstallLocation, aOldAddon, aAddonState) { + logger.debug("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor); + aOldAddon.descriptor = aAddonState.descriptor; + aOldAddon._sourceBundle.persistentDescriptor = aAddonState.descriptor; return aOldAddon; }, @@ -1407,7 +1840,8 @@ this.XPIDatabaseReconcile = { // then fetch that property now if (aOldAddon.signedState === undefined && ADDON_SIGNING && SIGNED_TYPES.has(aOldAddon.type)) { - let file = new nsIFile(aAddonState.path); + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.persistentDescriptor = aAddonState.descriptor; let manifest = syncLoadManifestFromFile(file, aInstallLocation); aOldAddon.signedState = manifest.signedState; } @@ -1415,7 +1849,8 @@ this.XPIDatabaseReconcile = { // May be updating from a version of the app that didn't support all the // properties of the currently-installed add-ons. if (aReloadMetadata) { - let file = new nsIFile(aAddonState.path); + let file = new nsIFile() + file.persistentDescriptor = aAddonState.descriptor; let manifest = syncLoadManifestFromFile(file, aInstallLocation); // Avoid re-reading these properties from manifest, @@ -1472,7 +1907,7 @@ this.XPIDatabaseReconcile = { }; // Add-ons loaded from the database can have an uninitialized _sourceBundle - // if the path was invalid. Swallow that error and say they don't exist. + // if the descriptor was invalid. Swallow that error and say they don't exist. let exists = (aAddon) => { try { return aAddon._sourceBundle.exists(); @@ -1522,14 +1957,17 @@ this.XPIDatabaseReconcile = { // Did time change in the wrong direction? if (xpiState.mtime < oldAddon.updateDate) { XPIProvider.setTelemetry(oldAddon.id, "olderFile", { + name: XPIProvider._mostRecentlyModifiedFile[id], mtime: xpiState.mtime, oldtime: oldAddon.updateDate }); + } else { + XPIProvider.setTelemetry(oldAddon.id, "modifiedFile", + XPIProvider._mostRecentlyModifiedFile[id]); } } let wasDisabled = oldAddon.appDisabled; - let oldPath = oldAddon.path || descriptorToPath(oldAddon.descriptor); // The add-on has changed if the modification time has changed, if // we have an updated manifest for it, or if the schema version has @@ -1542,8 +1980,8 @@ this.XPIDatabaseReconcile = { (aUpdateCompatibility && (installLocation.name == KEY_APP_GLOBAL || installLocation.name == KEY_APP_SYSTEM_DEFAULTS))) { newAddon = this.updateMetadata(installLocation, oldAddon, xpiState, newAddon); - } else if (oldPath != xpiState.path) { - newAddon = this.updatePath(installLocation, oldAddon, xpiState); + } else if (oldAddon.descriptor != xpiState.descriptor) { + newAddon = this.updateDescriptor(installLocation, oldAddon, xpiState); } else if (aUpdateCompatibility || aSchemaChange) { // Check compatility when the application version and/or schema // version has changed. A schema change also reloads metadata from @@ -1624,6 +2062,7 @@ this.XPIDatabaseReconcile = { let previousVisible = this.getVisibleAddons(previousAddons); let currentVisible = this.flattenByID(currentAddons, hideLocation); let sawActiveTheme = false; + XPIProvider.bootstrappedAddons = {}; // Pass over the new set of visible add-ons, record any changes that occured // during startup and call bootstrap install/uninstall scripts as necessary @@ -1634,7 +2073,7 @@ this.XPIDatabaseReconcile = { if (currentAddon._installLocation.name != KEY_APP_GLOBAL) XPIProvider.allAppGlobal = false; - let isActive = !currentAddon.disabled && !currentAddon.pendingUninstall; + let isActive = !currentAddon.disabled; let wasActive = previousAddon ? previousAddon.active : currentAddon.active if (!previousAddon) { @@ -1649,7 +2088,7 @@ this.XPIDatabaseReconcile = { if (currentAddon.type == "theme") isActive = currentAddon.internalName == XPIProvider.currentSkin; else - isActive = XPIDatabase.activeBundles.includes(currentAddon.path); + isActive = XPIDatabase.activeBundles.indexOf(currentAddon.descriptor) != -1; // If the add-on wasn't active and it isn't already disabled in some way // then it was probably either softDisabled or userDisabled @@ -1701,7 +2140,8 @@ this.XPIDatabaseReconcile = { if (currentAddon.bootstrap) { // Visible bootstrapped add-ons need to have their install method called - let file = currentAddon._sourceBundle.clone(); + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.persistentDescriptor = currentAddon._sourceBundle.persistentDescriptor; XPIProvider.callBootstrapMethod(currentAddon, file, "install", installReason, { oldVersion: previousAddon.version }); @@ -1720,6 +2160,19 @@ this.XPIDatabaseReconcile = { XPIDatabase.makeAddonVisible(currentAddon); currentAddon.active = isActive; + // Make sure the bootstrap information is up to date for this ID + if (currentAddon.bootstrap && currentAddon.active) { + XPIProvider.bootstrappedAddons[id] = { + version: currentAddon.version, + type: currentAddon.type, + descriptor: currentAddon._sourceBundle.persistentDescriptor, + multiprocessCompatible: currentAddon.multiprocessCompatible, + runInSafeMode: canRunInSafeMode(currentAddon), + dependencies: currentAddon.dependencies, + hasEmbeddedWebExtension: currentAddon.hasEmbeddedWebExtension, + }; + } + if (currentAddon.active && currentAddon.internalName == XPIProvider.selectedSkin) sawActiveTheme = true; } @@ -1740,7 +2193,6 @@ this.XPIDatabaseReconcile = { XPIProvider.unloadBootstrapScope(previousAddon.id); } AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED, id); - XPIStates.removeAddon(previousAddon.location, id); // Make sure to flush the cache when an old add-on has gone away flushChromeCaches(); @@ -1771,6 +2223,8 @@ this.XPIDatabaseReconcile = { } XPIStates.save(); + XPIProvider.persistBootstrappedAddons(); + // Clear out any cached migration data. XPIDatabase.migrateData = null; XPIDatabase.saveChanges(); diff --git a/toolkit/mozapps/extensions/moz.build b/toolkit/mozapps/extensions/moz.build index fdc8731a0b63..4f013db75c8a 100644 --- a/toolkit/mozapps/extensions/moz.build +++ b/toolkit/mozapps/extensions/moz.build @@ -14,7 +14,6 @@ TEST_DIRS += ['test'] XPIDL_SOURCES += [ 'amIAddonManager.idl', - 'amIAddonManagerStartup.idl', 'amIAddonPathService.idl', 'amIWebInstallPrompt.idl', ] @@ -45,14 +44,12 @@ JAR_MANIFESTS += ['jar.mn'] EXPORTS.mozilla += [ 'AddonContentPolicy.h', - 'AddonManagerStartup.h', 'AddonManagerWebAPI.h', 'AddonPathService.h', ] UNIFIED_SOURCES += [ 'AddonContentPolicy.cpp', - 'AddonManagerStartup.cpp', 'AddonManagerWebAPI.cpp', 'AddonPathService.cpp', ] diff --git a/toolkit/mozapps/extensions/test/addons/test_bug594058/directory/file1 b/toolkit/mozapps/extensions/test/addons/test_bug594058/directory/file1 new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/toolkit/mozapps/extensions/test/addons/test_bug594058/install.rdf b/toolkit/mozapps/extensions/test/addons/test_bug594058/install.rdf new file mode 100644 index 000000000000..682831949b4a --- /dev/null +++ b/toolkit/mozapps/extensions/test/addons/test_bug594058/install.rdf @@ -0,0 +1,21 @@ + + + + + + bug594058@tests.mozilla.org + 1.0 + + + + xpcshell@tests.mozilla.org + 1 + 2 + + + bug 594058 + stat-based invalidation + true + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_migrate.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/test_migrate.rdf new file mode 100644 index 000000000000..d1dc992d580e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_migrate.rdf @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + 2 + + + + 2 + + + + 2 + + + + 2 + + + + 2 + + + + 2 + + + + 4 + + + + 4 + + + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_migrate4.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/test_migrate4.rdf new file mode 100644 index 000000000000..a3bf4f8aea13 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_migrate4.rdf @@ -0,0 +1,46 @@ + + + + + + + +
  • + + 2.0 + + + xpcshell@tests.mozilla.org + 0 + 2 + + + +
  • +
    +
    +
    + + + + +
  • + + 2.0 + + + xpcshell@tests.mozilla.org + 0 + 2 + http://localhost:%PORT%/addons/test_migrate4_6.xpi + http://example.com/updateInfo.xhtml + + + +
  • +
    +
    +
    + +
    diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index 3037913a90f1..7e5a2f9e6a08 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -64,10 +64,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "MockRegistrar", XPCOMUtils.defineLazyModuleGetter(this, "MockRegistry", "resource://testing-common/MockRegistry.jsm"); -XPCOMUtils.defineLazyServiceGetter(this, "aomStartup", - "@mozilla.org/addons/addon-manager-startup;1", - "amIAddonManagerStartup"); - const { awaitPromise, createAppInfo, @@ -75,6 +71,7 @@ const { createTempWebExtensionFile, createUpdateRDF, getFileForAddon, + manuallyInstall, manuallyUninstall, promiseAddonEvent, promiseCompleteAllInstalls, @@ -93,11 +90,6 @@ const { writeFilesToZip } = AddonTestUtils; -function manuallyInstall(...args) { - return AddonTestUtils.awaitPromise( - AddonTestUtils.manuallyInstall(...args)); -} - // WebExtension wrapper for ease of testing ExtensionTestUtils.init(this); @@ -110,9 +102,9 @@ Object.defineProperty(this, "gAppInfo", { }, }); -Object.defineProperty(this, "gAddonStartup", { +Object.defineProperty(this, "gExtensionsINI", { get() { - return AddonTestUtils.addonStartup.clone(); + return AddonTestUtils.extensionsINI.clone(); }, }); @@ -208,8 +200,6 @@ this.BootstrapMonitor = { startupPromises: [], installPromises: [], - restartfulIds: new Set(), - init() { this.inited = true; Services.obs.addObserver(this, "bootstrapmonitor-event"); @@ -326,13 +316,9 @@ this.BootstrapMonitor = { } if (info.event == "uninstall") { - // We currently support registering, but not unregistering, - // restartful add-on manifests during xpcshell AOM "restarts". - if (!this.restartfulIds.has(id)) { - // Chrome should be unregistered at this point - let isRegistered = isManifestRegistered(installPath); - do_check_false(isRegistered); - } + // Chrome should be unregistered at this point + let isRegistered = isManifestRegistered(installPath); + do_check_false(isRegistered); this.installed.delete(id); this.uninstalled.set(id, info) @@ -377,7 +363,8 @@ function do_check_in_crash_annotation(aId, aVersion) { } let addons = gAppInfo.annotations["Add-ons"].split(","); - do_check_true(addons.includes(`${encodeURIComponent(aId)}:${encodeURIComponent(aVersion)}`)); + do_check_false(addons.indexOf(encodeURIComponent(aId) + ":" + + encodeURIComponent(aVersion)) < 0); } /** @@ -399,7 +386,8 @@ function do_check_not_in_crash_annotation(aId, aVersion) { } let addons = gAppInfo.annotations["Add-ons"].split(","); - do_check_false(addons.includes(`${encodeURIComponent(aId)}:${encodeURIComponent(aVersion)}`)); + do_check_true(addons.indexOf(encodeURIComponent(aId) + ":" + + encodeURIComponent(aVersion)) < 0); } /** diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js b/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js index d7a1aa33690f..5bfd4b22b736 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js @@ -104,12 +104,6 @@ function getXS() { return XPI.XPIStates; } -async function getXSJSON() { - await AddonTestUtils.loadAddonsList(true); - - return aomStartup.readStartupData(); -} - add_task(function* detect_touches() { startupManager(); let [/* pe */, pd, /* ue */, ud] = yield promiseAddonsByIDs([ @@ -154,6 +148,10 @@ add_task(function* detect_touches() { let manifest = ueDir.clone(); manifest.append("install.rdf"); checkChange(XS, manifest, true); + // We also notice changing another file for enabled unpacked add-on. + let otherFile = ueDir.clone(); + otherFile.append("extraFile.js"); + checkChange(XS, otherFile, true); // We notice changing install.rdf for a *disabled* unpacked add-on. let udDir = profileDir.clone(); @@ -163,7 +161,7 @@ add_task(function* detect_touches() { checkChange(XS, manifest, true); // Finally, the case we actually care about... // We *don't* notice changing another file for disabled unpacked add-on. - let otherFile = udDir.clone(); + otherFile = udDir.clone(); otherFile.append("extraFile.js"); checkChange(XS, otherFile, false); @@ -175,7 +173,7 @@ add_task(function* detect_touches() { ud.userDisabled = false; let xState = XS.getAddon("app-profile", ud.id); do_check_true(xState.enabled); - do_check_eq(xState.mtime, ud.updateDate.getTime()); + do_check_eq(xState.scanTime, ud.updateDate.getTime()); }); /* @@ -190,9 +188,8 @@ add_task(function* uninstall_bootstrap() { "unpacked-disabled@tests.mozilla.org" ]); pe.uninstall(); - - let xpiState = yield getXSJSON(); - do_check_false("packed-enabled@tests.mozilla.org" in xpiState["app-profile"].addons); + let xpiState = Services.prefs.getCharPref("extensions.xpiState"); + do_check_false(xpiState.includes("\"packed-enabled@tests.mozilla.org\"")); }); /* @@ -208,7 +205,7 @@ add_task(function* install_bootstrap() { let xState = XS.getAddon("app-profile", newAddon.id); do_check_true(!!xState); do_check_true(xState.enabled); - do_check_eq(xState.mtime, newAddon.updateDate.getTime()); + do_check_eq(xState.scanTime, newAddon.updateDate.getTime()); newAddon.uninstall(); }); @@ -241,7 +238,7 @@ add_task(function* install_restart() { xState = XS.getAddon("app-profile", newID); do_check_true(xState); do_check_true(xState.enabled); - do_check_eq(xState.mtime, newAddon.updateDate.getTime()); + do_check_eq(xState.scanTime, newAddon.updateDate.getTime()); // Check that XPIState enabled flag is updated immediately, // and doesn't change over restart. diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js index 8703e79a5b9e..ff58599bcb7f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js @@ -81,12 +81,8 @@ function getUninstallNewVersion() { } function do_check_bootstrappedPref(aCallback) { - let XPIScope = AM_Cu.import("resource://gre/modules/addons/XPIProvider.jsm", {}); - - let data = {}; - for (let entry of XPIScope.XPIStates.bootstrappedAddons()) { - data[entry.id] = entry; - } + let data = Services.prefs.getCharPref("extensions.bootstrappedAddons"); + data = JSON.parse(data); AddonManager.getAddonsByTypes(["extension"], function(aAddons) { for (let addon of aAddons) { @@ -104,7 +100,7 @@ function do_check_bootstrappedPref(aCallback) { do_check_eq(addonData.version, addon.version); do_check_eq(addonData.type, addon.type); let file = addon.getResourceURI().QueryInterface(Components.interfaces.nsIFileURL).file; - do_check_eq(addonData.path, file.path); + do_check_eq(addonData.descriptor, file.persistentDescriptor); } do_check_eq(Object.keys(data).length, 0); @@ -120,7 +116,7 @@ function run_test() { do_check_false(gExtensionsJSON.exists()); - do_check_false(gAddonStartup.exists()); + do_check_false(gExtensionsINI.exists()); run_test_1(); } @@ -174,6 +170,8 @@ function run_test_1() { } function check_test_1(installSyncGUID) { + do_check_false(gExtensionsINI.exists()); + AddonManager.getAllInstalls(function(installs) { // There should be no active installs now since the install completed and // doesn't require a restart. @@ -261,7 +259,7 @@ function run_test_3() { do_check_eq(getShutdownNewVersion(), undefined); do_check_not_in_crash_annotation(ID1, "1.0"); - do_check_true(gAddonStartup.exists()); + do_check_false(gExtensionsINI.exists()); AddonManager.getAddonByID(ID1, function(b1) { do_check_neq(b1, null); @@ -1055,8 +1053,6 @@ function run_test_22() { let file = manuallyInstall(do_get_addon("test_bootstrap1_1"), profileDir, ID1); - if (file.isDirectory()) - file.append("install.rdf"); // Make it look old so changes are detected setExtensionModifiedTime(file, file.lastModifiedTime - 5000); @@ -1206,7 +1202,7 @@ function run_test_24() { Promise.all([BootstrapMonitor.promiseAddonStartup(ID2), promiseInstallAllFiles([do_get_addon("test_bootstrap1_1"), do_get_addon("test_bootstrap2_1")])]) - .then(async function test_24_pref() { + .then(function test_24_pref() { do_print("test 24 got prefs"); BootstrapMonitor.checkAddonInstalled(ID1, "1.0"); BootstrapMonitor.checkAddonStarted(ID1, "1.0"); @@ -1227,13 +1223,10 @@ function run_test_24() { BootstrapMonitor.checkAddonInstalled(ID2, "1.0"); BootstrapMonitor.checkAddonNotStarted(ID2); - // Break the JSON. - let data = aomStartup.readStartupData(); - data["app-profile"].addons[ID1].path += "foo"; - - await OS.File.writeAtomic(gAddonStartup.path, - new TextEncoder().encode(JSON.stringify(data)), - {compression: "lz4"}); + // Break the preference + let bootstrappedAddons = JSON.parse(Services.prefs.getCharPref("extensions.bootstrappedAddons")); + bootstrappedAddons[ID1].descriptor += "foo"; + Services.prefs.setCharPref("extensions.bootstrappedAddons", JSON.stringify(bootstrappedAddons)); startupManager(false); @@ -1337,8 +1330,6 @@ function run_test_27() { BootstrapMonitor.checkAddonInstalled(ID1, "1.0"); BootstrapMonitor.checkAddonNotStarted(ID1); - BootstrapMonitor.restartfulIds.add(ID1); - installAllFiles([do_get_addon("test_bootstrap1_4")], function() { // Updating disabled things happens immediately BootstrapMonitor.checkAddonNotInstalled(ID1); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js index b0dca06b923c..497e665269e6 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js @@ -9,7 +9,7 @@ const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; const profileDir = gProfD.clone(); profileDir.append("extensions"); -async function run_test() { +function run_test() { do_test_pending(); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); @@ -38,7 +38,7 @@ async function run_test() { }] }, profileDir); - await promiseStartupManager(); + startupManager(); do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js index b32f94c00e69..92ba3d68fb76 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js @@ -8,7 +8,7 @@ const profileDir = gProfD.clone(); profileDir.append("extensions"); -async function run_test() { +function run_test() { do_test_pending(); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "1.9.2"); @@ -26,9 +26,9 @@ async function run_test() { // the update makes the last modified time change. setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000); - await promiseStartupManager(); + startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(a) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a) { do_check_neq(a, null); do_check_eq(a.version, "1.0"); do_check_false(a.userDisabled); @@ -47,7 +47,7 @@ async function run_test() { }] }, profileDir); - await promiseRestartManager(); + restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a2) { do_check_neq(a2, null); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js index e6e1d1dab981..ed09e03aee1c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js @@ -123,10 +123,10 @@ function run_test_1() { function run_test_2() { restartManager(); - installAllFiles([do_get_addon("test_bug587088_1")], async function() { - await promiseRestartManager(); + installAllFiles([do_get_addon("test_bug587088_1")], function() { + restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { check_addon(a1, "1.0"); // Lock either install.rdf for unpacked add-ons or the xpi for packed add-ons. @@ -142,19 +142,19 @@ function run_test_2() { check_addon_uninstalling(a1); - await promiseRestartManager(); + restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(a1_2) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1_2) { check_addon_uninstalling(a1_2, true); - await promiseRestartManager(); + restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(a1_3) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1_3) { check_addon_uninstalling(a1_3, true); fstream.close(); - await promiseRestartManager(); + restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1_4) { do_check_eq(a1_4, null); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug594058.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug594058.js new file mode 100644 index 000000000000..0d21d0633c54 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug594058.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This tests is modifying a file in an unpacked extension +// causes cache invalidation. + +// Disables security checking our updates which haven't been signed +Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); +// Allow the mismatch UI to show +Services.prefs.setBoolPref("extensions.showMismatchUI", true); + +Components.utils.import("resource://testing-common/MockRegistrar.jsm"); + +var Ci = Components.interfaces; +const extDir = gProfD.clone(); +extDir.append("extensions"); + +var gCachePurged = false; + +// Override the window watcher +var WindowWatcher = { + openWindow(parent, url, name, features, args) { + do_check_false(gCachePurged); + }, + + QueryInterface(iid) { + if (iid.equals(Ci.nsIWindowWatcher) + || iid.equals(Ci.nsISupports)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + } +} + +MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1", WindowWatcher); + +/** + * Start the test by installing extensions. + */ +function run_test() { + do_test_pending(); + gCachePurged = false; + + let obs = AM_Cc["@mozilla.org/observer-service;1"]. + getService(AM_Ci.nsIObserverService); + obs.addObserver({ + observe(aSubject, aTopic, aData) { + gCachePurged = true; + } + }, "startupcache-invalidate"); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + + startupManager(); + // nsAppRunner takes care of clearing this when a new app is installed + do_check_false(gCachePurged); + + installAllFiles([do_get_addon("test_bug594058")], function() { + restartManager(); + do_check_true(gCachePurged); + gCachePurged = false; + + // Now, make it look like we've updated the file. First, start the EM + // so it records the bogus old time, then update the file and restart. + let extFile = extDir.clone(); + let pastTime = extFile.lastModifiedTime - 5000; + extFile.append("bug594058@tests.mozilla.org"); + setExtensionModifiedTime(extFile, pastTime); + let otherFile = extFile.clone(); + otherFile.append("directory"); + otherFile.lastModifiedTime = pastTime; + otherFile.append("file1"); + otherFile.lastModifiedTime = pastTime; + + restartManager(); + gCachePurged = false; + + otherFile.lastModifiedTime = pastTime + 5000; + restartManager(); + do_check_true(gCachePurged); + gCachePurged = false; + + restartManager(); + do_check_false(gCachePurged); + + do_test_finished(); + }); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js index 32043fbcc6a6..7e2bf7d778fa 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js @@ -16,8 +16,8 @@ function run_test() { } function run_test_1() { - installAllFiles([do_get_addon("test_bug595573")], async function() { - await promiseRestartManager(); + installAllFiles([do_get_addon("test_bug595573")], function() { + restartManager(); AddonManager.getAddonByID("{2f69dacd-03df-4150-a9f1-e8a7b2748829}", function(a1) { do_check_neq(a1, null); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js index 13c66185bffb..2cf1f1e95483 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js @@ -58,14 +58,14 @@ function end_test() { testserver.stop(do_test_finished); } -async function run_test_1() { +function run_test_1() { var time = Date.now(); var dir = writeInstallRDFForExtension(addon1, userDir); setExtensionModifiedTime(dir, time); manuallyInstall(do_get_addon("test_bug655254_2"), userDir, "addon2@tests.mozilla.org"); - await promiseStartupManager(); + startupManager(); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org"], function([a1, a2]) { @@ -81,10 +81,10 @@ async function run_test_1() { do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 1); a1.findUpdates({ - async onUpdateFinished() { - await promiseRestartManager(); + onUpdateFinished() { + restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(a1_2) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1_2) { do_check_neq(a1_2, null); do_check_false(a1_2.appDisabled); do_check_true(a1_2.isActive); @@ -100,7 +100,7 @@ async function run_test_1() { userDir.append(gAppInfo.ID); do_check_true(userDir.exists()); - await promiseStartupManager(false); + startupManager(false); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org"], function([a1_3, a2_3]) { @@ -125,7 +125,7 @@ async function run_test_1() { // Set up the profile function run_test_2() { - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(async function(a2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { do_check_neq(a2, null); do_check_false(a2.appDisabled); do_check_true(a2.isActive); @@ -143,7 +143,7 @@ function run_test_2() { userDir.append(gAppInfo.ID); do_check_true(userDir.exists()); - await promiseStartupManager(false); + startupManager(false); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org"], function([a1_2, a2_2]) { diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js index 88fedd7d17de..6e98a69a4bc7 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js @@ -59,13 +59,13 @@ function run_test() { } // Tests whether a schema migration without app version change works -async function run_test_1() { +function run_test_1() { writeInstallRDFForExtension(addon1, profileDir); writeInstallRDFForExtension(addon2, profileDir); writeInstallRDFForExtension(addon3, profileDir); writeInstallRDFForExtension(addon4, profileDir); - await promiseStartupManager(); + startupManager(); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", @@ -104,7 +104,7 @@ async function run_test_1() { installAllFiles([ do_get_addon("test_bug659772"), do_get_addon("test_bootstrap1_1") - ], async function() { + ], function() { shutdownManager(); // Make it look like the next time the app is started it has a new DB schema @@ -142,7 +142,7 @@ async function run_test_1() { Services.prefs.clearUserPref("bootstraptest.install_reason"); Services.prefs.clearUserPref("bootstraptest.uninstall_reason"); - await promiseStartupManager(false); + startupManager(false); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", @@ -194,7 +194,7 @@ async function run_test_1() { } // Tests whether a schema migration with app version change works -async function run_test_2() { +function run_test_2() { restartManager(); shutdownManager(); @@ -204,7 +204,7 @@ async function run_test_2() { writeInstallRDFForExtension(addon3, profileDir); writeInstallRDFForExtension(addon4, profileDir); - await promiseStartupManager(); + startupManager(); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", @@ -245,7 +245,7 @@ async function run_test_2() { do_get_addon("test_bootstrap1_1") ], function() { do_execute_soon(prepare_schema_migrate); }); - async function prepare_schema_migrate() { + function prepare_schema_migrate() { shutdownManager(); // Make it look like the next time the app is started it has a new DB schema @@ -284,7 +284,7 @@ async function run_test_2() { Services.prefs.clearUserPref("bootstraptest.uninstall_reason"); gAppInfo.version = "2"; - await promiseStartupManager(true); + startupManager(true); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_default_providers_pref.js b/toolkit/mozapps/extensions/test/xpcshell/test_default_providers_pref.js index 33c105d5a1b6..1b61e033a860 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_default_providers_pref.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_default_providers_pref.js @@ -4,10 +4,10 @@ // Tests the extensions.defaultProviders.enabled pref which turns // off the default XPIProvider and LightweightThemeManager. -async function run_test() { +function run_test() { Services.prefs.setBoolPref("extensions.defaultProviders.enabled", false); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); - await promiseStartupManager(); + startupManager(); do_check_false(AddonManager.isInstallEnabled("application/x-xpinstall")); Services.prefs.clearUserPref("extensions.defaultProviders.enabled"); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_disable.js b/toolkit/mozapps/extensions/test/xpcshell/test_disable.js index f9883ae6a2ff..867715863509 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_disable.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_disable.js @@ -32,14 +32,14 @@ function run_test() { startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_eq(a1, null); do_check_not_in_crash_annotation(addon1.id, addon1.version); writeInstallRDFForExtension(addon1, profileDir, addon1.id, "icon.png"); gIconURL = do_get_addon_root_uri(profileDir.clone(), addon1.id) + "icon.png"; - await promiseRestartManager(); + restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(newa1) { do_check_neq(newa1, null); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_experiment.js b/toolkit/mozapps/extensions/test/xpcshell/test_experiment.js index c4a603ecb9df..1c967a04e2e8 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_experiment.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_experiment.js @@ -1,23 +1,12 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ +var scope = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm", {}); +const XPIProvider = scope.XPIProvider; const ID = "experiment1@tests.mozilla.org"; var gIsNightly = false; -function getXS() { - let XPI = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm", {}); - return XPI.XPIStates; -} - -function getBootstrappedAddons() { - let obj = {} - for (let addon of getXS().bootstrappedAddons()) { - obj[addon.id] = addon; - } - return obj; -} - function run_test() { BootstrapMonitor.init(); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); @@ -91,7 +80,7 @@ add_task(function* test_userDisabledNotPersisted() { Assert.equal(addon2.userDisabled, false, "Add-on is no longer user disabled."); Assert.ok(addon2.isActive, "Add-on is active."); - Assert.ok(ID in getBootstrappedAddons(), + Assert.ok(ID in XPIProvider.bootstrappedAddons, "Experiment add-on listed in XPIProvider bootstrapped list."); addon = yield promiseAddonByID(ID); @@ -105,8 +94,8 @@ add_task(function* test_userDisabledNotPersisted() { // Now when we restart the manager the add-on should revert state. yield promiseRestartManager(); - - Assert.ok(!(ID in getBootstrappedAddons()), + let persisted = JSON.parse(Services.prefs.getCharPref("extensions.bootstrappedAddons")); + Assert.ok(!(ID in persisted), "Experiment add-on not persisted to bootstrappedAddons."); BootstrapMonitor.checkAddonInstalled(ID, "1.0"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js b/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js index 71222191a862..925e63626fdf 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js @@ -18,16 +18,16 @@ function run_test() { do_get_addon("test_chromemanifest_3"), do_get_addon("test_chromemanifest_4"), do_get_addon("test_chromemanifest_5")], - async function() { + function() { - await promiseRestartManager(); + restartManager(); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", "addon4@tests.mozilla.org", "addon5@tests.mozilla.org"], - async function([a1, a2, a3, a4, a5]) { + function([a1, a2, a3, a4, a5]) { // addon1 has no binary components do_check_neq(a1, null); do_check_false(a1.userDisabled); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_install.js b/toolkit/mozapps/extensions/test/xpcshell/test_install.js index b473be410ef3..55cb2cf0bd98 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_install.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install.js @@ -117,7 +117,7 @@ function check_test_1(installSyncGUID) { AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) { do_check_eq(olda1, null); - AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(async function(pendingAddons) { + AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(pendingAddons) { do_check_eq(pendingAddons.length, 1); do_check_eq(pendingAddons[0].id, "addon1@tests.mozilla.org"); let uri = NetUtil.newURI(pendingAddons[0].iconURL); @@ -150,7 +150,7 @@ function check_test_1(installSyncGUID) { do_check_false(hasFlag(pendingAddons[0].permissions, AddonManager.PERM_CAN_ENABLE)); do_check_false(hasFlag(pendingAddons[0].permissions, AddonManager.PERM_CAN_DISABLE)); - await promiseRestartManager(); + restartManager(); AddonManager.getAllInstalls(function(activeInstalls) { do_check_eq(activeInstalls, 0); @@ -276,9 +276,9 @@ function check_test_3(aInstall) { setExtensionModifiedTime(ext, updateDate); ensure_test_completed(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(async function(olda2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(olda2) { do_check_eq(olda2, null); - await promiseRestartManager(); + restartManager(); AddonManager.getAllInstalls(function(installs) { do_check_eq(installs, 0); @@ -465,9 +465,9 @@ function run_test_7() { function check_test_7() { ensure_test_completed(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", callback_soon(async function(olda3) { + AddonManager.getAddonByID("addon3@tests.mozilla.org", callback_soon(function(olda3) { do_check_eq(olda3, null); - await promiseRestartManager(); + restartManager(); AddonManager.getAllInstalls(function(installs) { do_check_eq(installs, 0); @@ -514,8 +514,8 @@ function run_test_8() { }); } -async function check_test_8() { - await promiseRestartManager(); +function check_test_8() { + restartManager(); AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { do_check_neq(a3, null); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js index 012ae7db229d..41af9031e0ee 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js @@ -114,7 +114,7 @@ function check_test_1() { AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) { do_check_eq(olda1, null); - AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(async function(pendingAddons) { + AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(pendingAddons) { do_check_eq(pendingAddons.length, 1); do_check_eq(pendingAddons[0].id, "addon1@tests.mozilla.org"); let uri = NetUtil.newURI(pendingAddons[0].iconURL); @@ -145,7 +145,7 @@ function check_test_1() { do_check_false(hasFlag(pendingAddons[0].permissions, AddonManager.PERM_CAN_ENABLE)); do_check_false(hasFlag(pendingAddons[0].permissions, AddonManager.PERM_CAN_DISABLE)); - await promiseRestartManager(); + restartManager(); AddonManager.getAllInstalls(function(activeInstalls) { do_check_eq(activeInstalls, 0); @@ -259,9 +259,9 @@ function check_test_3(aInstall) { setExtensionModifiedTime(ext, updateDate); ensure_test_completed(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(async function(olda2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(olda2) { do_check_eq(olda2, null); - await promiseRestartManager(); + restartManager(); AddonManager.getAllInstalls(function(installs) { do_check_eq(installs, 0); @@ -359,10 +359,10 @@ function check_test_5(install) { do_check_neq(olda2, null); do_check_true(hasFlag(olda2.pendingOperations, AddonManager.PENDING_UPGRADE)); - AddonManager.getInstallsByTypes(null, callback_soon(async function(installs) { + AddonManager.getInstallsByTypes(null, callback_soon(function(installs) { do_check_eq(installs.length, 1); do_check_eq(installs[0].addon, olda2.pendingUpgrade); - await promiseRestartManager(); + restartManager(); AddonManager.getInstallsByTypes(null, function(installs2) { do_check_eq(installs2.length, 0); @@ -446,9 +446,9 @@ function run_test_7() { function check_test_7() { ensure_test_completed(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", callback_soon(async function(olda3) { + AddonManager.getAddonByID("addon3@tests.mozilla.org", callback_soon(function(olda3) { do_check_eq(olda3, null); - await promiseRestartManager(); + restartManager(); AddonManager.getAllInstalls(function(installs) { do_check_eq(installs, 0); @@ -494,8 +494,8 @@ function run_test_8() { }); } -async function check_test_8() { - await promiseRestartManager(); +function check_test_8() { + restartManager(); AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { do_check_neq(a3, null); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js index 767f675e95f1..e269326ebe95 100755 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js @@ -182,7 +182,6 @@ add_task(function* init() { add_task(function* run_test_1() { restartManager(); - let [a1, a2, a3, a4, a5, a6, a7, t1, t2] = yield promiseAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", @@ -454,7 +453,8 @@ add_task(function* run_test_1() { do_print("Unlocking " + gExtensionsJSON.path); yield file.close(); gExtensionsJSON.permissions = filePermissions; - yield promiseStartupManager(); + startupManager(); + // Shouldn't have seen any startup changes check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js index 9c999ffdf142..2df91a91093d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js @@ -164,7 +164,7 @@ add_task(function*() { if (!OS.Constants.Win) { gExtensionsJSON.permissions = 0; } - yield promiseStartupManager(false); + startupManager(false); check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []); @@ -225,7 +225,7 @@ add_task(function*() { } yield file.close(); gExtensionsJSON.permissions = filePermissions; - yield promiseStartupManager(); + startupManager(); // On Unix, we can save the DB even when the original file wasn't // readable, so our changes were saved. On Windows, diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js index 199e851cdb32..344086cf7934 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js @@ -148,7 +148,7 @@ add_task(function* init() { writeInstallRDFForExtension(theme2, profileDir); // Startup the profile and setup the initial state - yield promiseStartupManager(); + startupManager(); // New profile so new add-ons are ignored check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); @@ -271,7 +271,7 @@ add_task(function* run_test_1() { if (!OS.Constants.Win) { gExtensionsJSON.permissions = 0; } - yield promiseStartupManager(false); + startupManager(false); // Shouldn't have seen any startup changes check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); @@ -365,7 +365,7 @@ add_task(function* run_test_1() { } catch (e) { // We're expecting an error here. } - yield promiseStartupManager(false); + startupManager(false); // Shouldn't have seen any startup changes check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); @@ -453,7 +453,7 @@ add_task(function* run_test_1() { do_print("Unlocking " + gExtensionsJSON.path); yield file.close(); gExtensionsJSON.permissions = filePermissions; - yield promiseStartupManager(false); + startupManager(false); // Shouldn't have seen any startup changes check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js new file mode 100644 index 000000000000..8c13593b915e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js @@ -0,0 +1,231 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Checks that we migrate data from the old rdf style database + +var addon1 = { + id: "addon1@tests.mozilla.org", + version: "1.0", + name: "Test 1", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon2 = { + id: "addon2@tests.mozilla.org", + version: "2.0", + name: "Test 2", + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon3 = { + id: "addon3@tests.mozilla.org", + version: "2.0", + name: "Test 3", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon4 = { + id: "addon4@tests.mozilla.org", + version: "2.0", + name: "Test 4", + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon5 = { + id: "addon5@tests.mozilla.org", + version: "2.0", + name: "Test 5", + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var theme1 = { + id: "theme1@tests.mozilla.org", + version: "1.0", + name: "Theme 1", + type: 4, + internalName: "theme1/1.0", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "2" + }] +}; + +var theme2 = { + id: "theme2@tests.mozilla.org", + version: "1.0", + name: "Theme 2", + type: 4, + internalName: "theme2/1.0", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "2" + }] +}; + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +function run_test() { + do_test_pending(); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2"); + + writeInstallRDFForExtension(addon1, profileDir); + writeInstallRDFForExtension(addon2, profileDir); + writeInstallRDFForExtension(addon3, profileDir); + writeInstallRDFForExtension(addon4, profileDir); + writeInstallRDFForExtension(addon5, profileDir); + writeInstallRDFForExtension(theme1, profileDir); + writeInstallRDFForExtension(theme2, profileDir); + + let stagedXPIs = profileDir.clone(); + stagedXPIs.append("staged-xpis"); + stagedXPIs.append("addon6@tests.mozilla.org"); + stagedXPIs.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + let addon6 = do_get_addon("test_migrate6"); + addon6.copyTo(stagedXPIs, "tmp.xpi"); + stagedXPIs = stagedXPIs.parent; + + stagedXPIs.append("addon7@tests.mozilla.org"); + stagedXPIs.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + let addon7 = do_get_addon("test_migrate7"); + addon7.copyTo(stagedXPIs, "tmp.xpi"); + stagedXPIs = stagedXPIs.parent; + + stagedXPIs.append("addon8@tests.mozilla.org"); + stagedXPIs.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + let addon8 = do_get_addon("test_migrate8"); + addon8.copyTo(stagedXPIs, "tmp.xpi"); + stagedXPIs = stagedXPIs.parent; + + let old = do_get_file("data/test_migrate.rdf"); + old.copyTo(gProfD, "extensions.rdf"); + + let oldCache = gProfD.clone(); + oldCache.append("extensions.cache"); + oldCache.create(AM_Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); + + // Theme state is determined by the selected theme pref + Services.prefs.setCharPref("general.skins.selectedSkin", "theme1/1.0"); + + Services.prefs.setCharPref("extensions.lastAppVersion", "1"); + + startupManager(); + check_startup_changes("installed", []); + check_startup_changes("updated", []); + check_startup_changes("uninstalled", []); + check_startup_changes("disabled", []); + check_startup_changes("enabled", []); + + do_check_false(oldCache.exists()); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org", + "addon7@tests.mozilla.org", + "addon8@tests.mozilla.org", + "theme1@tests.mozilla.org", + "theme2@tests.mozilla.org"], function([a1, a2, a3, + a4, a5, a6, + a7, a8, t1, + t2]) { + // addon1 was user and app enabled in the old extensions.rdf + do_check_neq(a1, null); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_true(a1.isActive); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + do_check_false(a1.hasBinaryComponents); + do_check_true(a1.seen); + + // addon2 was user disabled and app enabled in the old extensions.rdf + do_check_neq(a2, null); + do_check_true(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_false(a2.isActive); + do_check_false(isExtensionInAddonsList(profileDir, a2.id)); + do_check_false(a2.hasBinaryComponents); + do_check_true(a2.seen); + + // addon3 was pending user disable and app disabled in the old extensions.rdf + do_check_neq(a3, null); + do_check_true(a3.userDisabled); + do_check_true(a3.appDisabled); + do_check_false(a3.isActive); + do_check_false(isExtensionInAddonsList(profileDir, a3.id)); + do_check_false(a3.hasBinaryComponents); + do_check_true(a3.seen); + + // addon4 was pending user enable and app disabled in the old extensions.rdf + do_check_neq(a4, null); + do_check_false(a4.userDisabled); + do_check_true(a4.appDisabled); + do_check_false(a4.isActive); + do_check_false(isExtensionInAddonsList(profileDir, a4.id)); + do_check_false(a4.hasBinaryComponents); + do_check_true(a4.seen); + + // addon5 was disabled and compatible but a new version has been installed + // since, it should still be disabled but should be incompatible + do_check_neq(a5, null); + do_check_true(a5.userDisabled); + do_check_true(a5.appDisabled); + do_check_false(a5.isActive); + do_check_false(isExtensionInAddonsList(profileDir, a5.id)); + do_check_false(a5.hasBinaryComponents); + do_check_true(a5.seen); + + // addon6, addon7 and addon8 will have been lost as they were staged in the + // pre-Firefox 4.0 directory + do_check_eq(a6, null); + do_check_eq(a7, null); + do_check_eq(a8, null); + + // Theme 1 was previously enabled + do_check_neq(t1, null); + do_check_false(t1.userDisabled); + do_check_false(t1.appDisabled); + do_check_true(t1.isActive); + do_check_true(isThemeInAddonsList(profileDir, t1.id)); + do_check_false(hasFlag(t1.permissions, AddonManager.PERM_CAN_ENABLE)); + do_check_true(t1.seen); + + // Theme 2 was previously disabled + do_check_neq(t2, null); + do_check_true(t2.userDisabled); + do_check_false(t2.appDisabled); + do_check_false(t2.isActive); + do_check_false(isThemeInAddonsList(profileDir, t2.id)); + do_check_true(hasFlag(t2.permissions, AddonManager.PERM_CAN_ENABLE)); + do_check_true(t2.seen); + + do_execute_soon(do_test_finished); + }); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js new file mode 100644 index 000000000000..cc7336713f72 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js @@ -0,0 +1,267 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Checks that we migrate data from SQLITE databases +// Note that since the database doesn't contain the foreignInstall field we +// should just assume that no add-ons in the user profile were foreignInstalls + +// Enable loading extensions from the user and system scopes +Services.prefs.setIntPref("extensions.enabledScopes", + AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_USER + + AddonManager.SCOPE_SYSTEM); + +var addon1 = { + id: "addon1@tests.mozilla.org", + version: "1.0", + name: "Test 1", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon2 = { + id: "addon2@tests.mozilla.org", + version: "2.0", + name: "Test 2", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon3 = { + id: "addon3@tests.mozilla.org", + version: "2.0", + name: "Test 3", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon4 = { + id: "addon4@tests.mozilla.org", + version: "2.0", + name: "Test 4", + strictCompatibility: true, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon5 = { + id: "addon5@tests.mozilla.org", + version: "2.0", + name: "Test 5", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0", + maxVersion: "0" + }] +}; + +var addon6 = { + id: "addon6@tests.mozilla.org", + version: "2.0", + name: "Test 6", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0", + maxVersion: "0" + }] +}; + +var addon7 = { + id: "addon7@tests.mozilla.org", + version: "2.0", + name: "Test 7", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon8 = { + id: "addon8@tests.mozilla.org", + version: "2.0", + name: "Test 8", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); +const profileDir = gProfD.clone(); +profileDir.append("extensions"); +const globalDir = gProfD.clone(); +globalDir.append("extensions2"); +globalDir.append(gAppInfo.ID); +registerDirectory("XRESysSExtPD", globalDir.parent); +const userDir = gProfD.clone(); +userDir.append("extensions3"); +userDir.append(gAppInfo.ID); +registerDirectory("XREUSysExt", userDir.parent); + +function run_test() { + do_test_pending(); + + writeInstallRDFForExtension(addon1, profileDir); + writeInstallRDFForExtension(addon2, profileDir); + writeInstallRDFForExtension(addon3, profileDir); + writeInstallRDFForExtension(addon4, profileDir); + writeInstallRDFForExtension(addon5, profileDir); + writeInstallRDFForExtension(addon6, profileDir); + writeInstallRDFForExtension(addon7, globalDir); + writeInstallRDFForExtension(addon8, userDir); + + // Write out a minimal database + let dbfile = gProfD.clone(); + dbfile.append("extensions.sqlite"); + let db = AM_Cc["@mozilla.org/storage/service;1"]. + getService(AM_Ci.mozIStorageService). + openDatabase(dbfile); + db.createTable("addon", "internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "id TEXT, location TEXT, version TEXT, active INTEGER, " + + "userDisabled INTEGER, installDate INTEGER"); + db.createTable("targetApplication", "addon_internal_id INTEGER, " + + "id TEXT, minVersion TEXT, maxVersion TEXT"); + let stmt = db.createStatement("INSERT INTO addon VALUES (NULL, :id, :location, " + + ":version, :active, :userDisabled, :installDate)"); + + let internal_ids = {}; + + [["addon1@tests.mozilla.org", "app-profile", "1.0", "1", "0", "0"], + ["addon2@tests.mozilla.org", "app-profile", "2.0", "0", "1", "0"], + ["addon3@tests.mozilla.org", "app-profile", "2.0", "1", "1", "0"], + ["addon4@tests.mozilla.org", "app-profile", "2.0", "0", "0", "0"], + ["addon5@tests.mozilla.org", "app-profile", "2.0", "1", "0", "0"], + ["addon6@tests.mozilla.org", "app-profile", "1.0", "0", "1", "0"], + ["addon7@tests.mozilla.org", "app-system-share", "1.0", "1", "0", "0"], + ["addon8@tests.mozilla.org", "app-system-user", "1.0", "1", "0", "0"]].forEach(function(a) { + stmt.params.id = a[0]; + stmt.params.location = a[1]; + stmt.params.version = a[2]; + stmt.params.active = a[3]; + stmt.params.userDisabled = a[4]; + stmt.params.installDate = a[5]; + stmt.execute(); + internal_ids[a[0]] = db.lastInsertRowID; + }); + stmt.finalize(); + + // Add updated target application into for addon5 + stmt = db.createStatement("INSERT INTO targetApplication VALUES " + + "(:internal_id, :id, :minVersion, :maxVersion)"); + stmt.params.internal_id = internal_ids["addon5@tests.mozilla.org"]; + stmt.params.id = "xpcshell@tests.mozilla.org"; + stmt.params.minVersion = "0"; + stmt.params.maxVersion = "1"; + stmt.execute(); + + // Add updated target application into for addon6 + stmt.params.internal_id = internal_ids["addon6@tests.mozilla.org"]; + stmt.params.id = "xpcshell@tests.mozilla.org"; + stmt.params.minVersion = "0"; + stmt.params.maxVersion = "1"; + stmt.execute(); + stmt.finalize(); + + db.schemaVersion = 10000; + Services.prefs.setIntPref("extensions.databaseSchema", 14); + db.close(); + + startupManager(); + check_startup_changes("installed", []); + check_startup_changes("updated", []); + check_startup_changes("uninstalled", []); + check_startup_changes("disabled", []); + check_startup_changes("enabled", []); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org", + "addon7@tests.mozilla.org", + "addon8@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6, a7, a8]) { + // addon1 was enabled in the database + do_check_neq(a1, null); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_true(a1.isActive); + do_check_false(a1.strictCompatibility); + do_check_false(a1.foreignInstall); + do_check_true(a1.seen); + // addon2 was disabled in the database + do_check_neq(a2, null); + do_check_true(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_false(a2.isActive); + do_check_false(a2.strictCompatibility); + do_check_false(a2.foreignInstall); + do_check_true(a2.seen); + // addon3 was pending-disable in the database + do_check_neq(a3, null); + do_check_true(a3.userDisabled); + do_check_false(a3.appDisabled); + do_check_false(a3.isActive); + do_check_false(a3.strictCompatibility); + do_check_false(a3.foreignInstall); + do_check_true(a3.seen); + // addon4 was pending-enable in the database + do_check_neq(a4, null); + do_check_false(a4.userDisabled); + do_check_false(a4.appDisabled); + do_check_true(a4.isActive); + do_check_true(a4.strictCompatibility); + do_check_false(a4.foreignInstall); + do_check_true(a4.seen); + // addon5 was enabled in the database but needed a compatibility update + do_check_neq(a5, null); + do_check_false(a5.userDisabled); + do_check_false(a5.appDisabled); + do_check_true(a5.isActive); + do_check_false(a5.strictCompatibility); + do_check_false(a5.foreignInstall); + do_check_true(a5.seen); + // addon6 was disabled and compatible but a new version has been installed + // since, it should still be disabled but should be incompatible + do_check_neq(a6, null); + do_check_true(a6.userDisabled); + do_check_true(a6.appDisabled); + do_check_false(a6.isActive); + do_check_false(a6.strictCompatibility); + do_check_false(a6.foreignInstall); + do_check_true(a6.seen); + // addon7 is in the global install location so should be a foreignInstall + do_check_neq(a7, null); + do_check_false(a7.userDisabled); + do_check_false(a7.appDisabled); + do_check_true(a7.isActive); + do_check_false(a7.strictCompatibility); + do_check_true(a7.foreignInstall); + do_check_true(a7.seen); + // addon8 is in the user install location so should be a foreignInstall + do_check_neq(a8, null); + do_check_false(a8.userDisabled); + do_check_false(a8.appDisabled); + do_check_true(a8.isActive); + do_check_false(a8.strictCompatibility); + do_check_true(a8.foreignInstall); + do_check_true(a8.seen); + + do_execute_soon(do_test_finished); + }); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js new file mode 100644 index 000000000000..71bd666034f2 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js @@ -0,0 +1,229 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Checks that we migrate data from the old extensions.rdf database. This +// matches test_migrate1.js however it runs with a lightweight theme selected +// so the themes should appear disabled. + +Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm"); + +var addon1 = { + id: "addon1@tests.mozilla.org", + version: "1.0", + name: "Test 1", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon2 = { + id: "addon2@tests.mozilla.org", + version: "2.0", + name: "Test 2", + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon3 = { + id: "addon3@tests.mozilla.org", + version: "2.0", + name: "Test 3", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon4 = { + id: "addon4@tests.mozilla.org", + version: "2.0", + name: "Test 4", + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon5 = { + id: "addon5@tests.mozilla.org", + version: "2.0", + name: "Test 5", + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var theme1 = { + id: "theme1@tests.mozilla.org", + version: "1.0", + name: "Theme 1", + type: 4, + internalName: "theme1/1.0", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "2" + }] +}; + +var theme2 = { + id: "theme2@tests.mozilla.org", + version: "1.0", + name: "Theme 2", + type: 4, + internalName: "theme2/1.0", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "2" + }] +}; + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +function run_test() { + do_test_pending(); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2"); + + writeInstallRDFForExtension(addon1, profileDir); + writeInstallRDFForExtension(addon2, profileDir); + writeInstallRDFForExtension(addon3, profileDir); + writeInstallRDFForExtension(addon4, profileDir); + writeInstallRDFForExtension(addon5, profileDir); + writeInstallRDFForExtension(theme1, profileDir); + writeInstallRDFForExtension(theme2, profileDir); + + // Cannot use the LightweightThemeManager before AddonManager has been started + // so inject the correct prefs + Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify([{ + id: "1", + version: "1", + name: "Test LW Theme", + description: "A test theme", + author: "Mozilla", + homepageURL: "http://localhost/data/index.html", + headerURL: "http://localhost/data/header.png", + footerURL: "http://localhost/data/footer.png", + previewURL: "http://localhost/data/preview.png", + iconURL: "http://localhost/data/icon.png" + }])); + Services.prefs.setCharPref("lightweightThemes.selectedThemeID", "1"); + + let stagedXPIs = profileDir.clone(); + stagedXPIs.append("staged-xpis"); + stagedXPIs.append("addon6@tests.mozilla.org"); + stagedXPIs.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0o755); + + let addon6 = do_get_addon("test_migrate6"); + addon6.copyTo(stagedXPIs, "tmp.xpi"); + stagedXPIs = stagedXPIs.parent; + + stagedXPIs.append("addon7@tests.mozilla.org"); + stagedXPIs.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0o755); + + let addon7 = do_get_addon("test_migrate7"); + addon7.copyTo(stagedXPIs, "tmp.xpi"); + stagedXPIs = stagedXPIs.parent; + + let old = do_get_file("data/test_migrate.rdf"); + old.copyTo(gProfD, "extensions.rdf"); + + // Theme state is determined by the selected theme pref + Services.prefs.setCharPref("general.skins.selectedSkin", "theme1/1.0"); + + startupManager(); + check_startup_changes("installed", []); + check_startup_changes("updated", []); + check_startup_changes("uninstalled", []); + check_startup_changes("disabled", []); + check_startup_changes("enabled", []); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org", + "addon7@tests.mozilla.org", + "theme1@tests.mozilla.org", + "theme2@tests.mozilla.org"], function([a1, a2, a3, + a4, a5, a6, + a7, t1, t2]) { + // addon1 was user and app enabled in the old extensions.rdf + do_check_neq(a1, null); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_true(a1.isActive); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + do_check_true(a1.seen); + + // addon2 was user disabled and app enabled in the old extensions.rdf + do_check_neq(a2, null); + do_check_true(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_false(a2.isActive); + do_check_false(isExtensionInAddonsList(profileDir, a2.id)); + do_check_true(a2.seen); + + // addon3 was pending user disable and app disabled in the old extensions.rdf + do_check_neq(a3, null); + do_check_true(a3.userDisabled); + do_check_true(a3.appDisabled); + do_check_false(a3.isActive); + do_check_false(isExtensionInAddonsList(profileDir, a3.id)); + do_check_true(a3.seen); + + // addon4 was pending user enable and app disabled in the old extensions.rdf + do_check_neq(a4, null); + do_check_false(a4.userDisabled); + do_check_true(a4.appDisabled); + do_check_false(a4.isActive); + do_check_false(isExtensionInAddonsList(profileDir, a4.id)); + do_check_true(a4.seen); + + // addon5 was disabled and compatible but a new version has been installed + // since, it should still be disabled but should be incompatible + do_check_neq(a5, null); + do_check_true(a5.userDisabled); + do_check_true(a5.appDisabled); + do_check_false(a5.isActive); + do_check_false(isExtensionInAddonsList(profileDir, a5.id)); + do_check_true(a5.seen); + + // addon6 and addon7 will have been lost as they were staged in the + // pre-Firefox 4.0 directory + do_check_eq(a6, null); + do_check_eq(a7, null); + + // Theme 1 was previously disabled + do_check_neq(t1, null); + do_check_true(t1.userDisabled); + do_check_false(t1.appDisabled); + do_check_false(t1.isActive); + do_check_true(isThemeInAddonsList(profileDir, t1.id)); + do_check_true(hasFlag(t1.permissions, AddonManager.PERM_CAN_ENABLE)); + do_check_true(t1.seen); + + // Theme 2 was previously disabled + do_check_neq(t2, null); + do_check_true(t2.userDisabled); + do_check_false(t2.appDisabled); + do_check_false(t2.isActive); + do_check_false(isThemeInAddonsList(profileDir, t2.id)); + do_check_true(hasFlag(t2.permissions, AddonManager.PERM_CAN_ENABLE)); + do_check_true(t2.seen); + + do_execute_soon(do_test_finished); + }); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js new file mode 100644 index 000000000000..c466fd1d4351 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js @@ -0,0 +1,321 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Checks that we migrate data from a previous version of the JSON database + +// The test extension uses an insecure update url. +Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); + +Components.utils.import("resource://testing-common/httpd.js"); +var testserver = new HttpServer(); +testserver.start(-1); +gPort = testserver.identity.primaryPort; +mapFile("/data/test_migrate4.rdf", testserver); +testserver.registerDirectory("/addons/", do_get_file("addons")); + +var addon1 = { + id: "addon1@tests.mozilla.org", + version: "1.0", + name: "Test 1", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "2" + }] +}; + +var addon2 = { + id: "addon2@tests.mozilla.org", + version: "2.0", + name: "Test 2", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "2" + }] +}; + +var addon3 = { + id: "addon3@tests.mozilla.org", + version: "2.0", + name: "Test 3", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "2" + }] +}; + +var addon4 = { + id: "addon4@tests.mozilla.org", + version: "2.0", + name: "Test 4", + strictCompatibility: true, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "2" + }] +}; + +var addon5 = { + id: "addon5@tests.mozilla.org", + version: "2.0", + name: "Test 5", + updateURL: "http://localhost:" + gPort + "/data/test_migrate4.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0", + maxVersion: "1" + }] +}; + +var addon6 = { + id: "addon6@tests.mozilla.org", + version: "1.0", + name: "Test 6", + updateURL: "http://localhost:" + gPort + "/data/test_migrate4.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0", + maxVersion: "1" + }] +}; + +var defaultTheme = { + id: "default@tests.mozilla.org", + version: "1.0", + name: "Default", + internalName: "classic/1.0", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "2" + }] +}; + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +var oldSyncGUIDs = {}; + +function prepare_profile() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + writeInstallRDFForExtension(addon1, profileDir); + writeInstallRDFForExtension(addon2, profileDir); + writeInstallRDFForExtension(addon3, profileDir); + writeInstallRDFForExtension(addon4, profileDir); + writeInstallRDFForExtension(addon5, profileDir); + writeInstallRDFForExtension(addon6, profileDir); + writeInstallRDFForExtension(defaultTheme, profileDir); + + startupManager(); + installAllFiles([do_get_addon("test_migrate8"), do_get_addon("test_migrate9")], + function() { + restartManager(); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org", + "addon9@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6, a9]) { + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + a2.userDisabled = true; + a2.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + a3.applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE; + a4.userDisabled = true; + a6.userDisabled = true; + a9.userDisabled = false; + + for (let addon of [a1, a2, a3, a4, a5, a6]) { + oldSyncGUIDs[addon.id] = addon.syncGUID; + } + + a6.findUpdates({ + onUpdateAvailable(aAddon, aInstall6) { + AddonManager.getInstallForURL("http://localhost:" + gPort + "/addons/test_migrate4_7.xpi", function(aInstall7) { + completeAllInstalls([aInstall6, aInstall7], function() { + restartManager(); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + function([a1_2, a2_2, a3_2, a4_2, a5_2, a6_2]) { + a3_2.userDisabled = true; + a4_2.userDisabled = false; + + a5_2.findUpdates({ + onUpdateFinished() { + do_execute_soon(perform_migration); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + }, "application/x-xpinstall"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); +} + +function perform_migration() { + shutdownManager(); + + // Turn on disabling for all scopes + Services.prefs.setIntPref("extensions.autoDisableScopes", 15); + + changeXPIDBVersion(1, data => { + // Delete the seen property from all add-ons to make sure it defaults to true + for (let addon of data.addons) { + delete addon.seen; + } + }); + Services.prefs.setIntPref("extensions.databaseSchema", 1); + + gAppInfo.version = "2" + startupManager(true); + test_results(); +} + +function test_results() { + check_startup_changes("installed", []); + check_startup_changes("updated", []); + check_startup_changes("uninstalled", []); + check_startup_changes("disabled", []); + check_startup_changes("enabled", []); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org", + "addon7@tests.mozilla.org", + "addon8@tests.mozilla.org", + "addon9@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6, a7, a8, a9]) { + // addon1 was enabled + do_check_neq(a1, null); + do_check_eq(a1.syncGUID, oldSyncGUIDs[a1.id]); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_true(a1.isActive); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_true(a1.foreignInstall); + do_check_true(a1.seen); + do_check_false(a1.hasBinaryComponents); + do_check_false(a1.strictCompatibility); + + // addon2 was disabled + do_check_neq(a2, null); + do_check_eq(a2.syncGUID, oldSyncGUIDs[a2.id]); + do_check_true(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_false(a2.isActive); + do_check_eq(a2.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); + do_check_true(a2.foreignInstall); + do_check_true(a2.seen); + do_check_false(a2.hasBinaryComponents); + do_check_false(a2.strictCompatibility); + + // addon3 was pending-disable in the database + do_check_neq(a3, null); + do_check_eq(a3.syncGUID, oldSyncGUIDs[a3.id]); + do_check_true(a3.userDisabled); + do_check_false(a3.appDisabled); + do_check_false(a3.isActive); + do_check_eq(a3.applyBackgroundUpdates, AddonManager.AUTOUPDATE_ENABLE); + do_check_true(a3.foreignInstall); + do_check_true(a3.seen); + do_check_false(a3.hasBinaryComponents); + do_check_false(a3.strictCompatibility); + + // addon4 was pending-enable in the database + do_check_neq(a4, null); + do_check_eq(a4.syncGUID, oldSyncGUIDs[a4.id]); + do_check_false(a4.userDisabled); + do_check_false(a4.appDisabled); + do_check_true(a4.isActive); + do_check_eq(a4.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_true(a4.foreignInstall); + do_check_true(a4.seen); + do_check_false(a4.hasBinaryComponents); + do_check_true(a4.strictCompatibility); + + // addon5 was enabled in the database but needed a compatibility update + do_check_neq(a5, null); + do_check_false(a5.userDisabled); + do_check_false(a5.appDisabled); + do_check_true(a5.isActive); + do_check_eq(a4.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_true(a5.foreignInstall); + do_check_true(a5.seen); + do_check_false(a5.hasBinaryComponents); + do_check_false(a5.strictCompatibility); + + // addon6 was disabled and compatible but a new version has been installed + do_check_neq(a6, null); + do_check_eq(a6.syncGUID, oldSyncGUIDs[a6.id]); + do_check_eq(a6.version, "2.0"); + do_check_true(a6.userDisabled); + do_check_false(a6.appDisabled); + do_check_false(a6.isActive); + do_check_eq(a6.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_true(a6.foreignInstall); + do_check_true(a6.seen); + do_check_eq(a6.sourceURI.spec, "http://localhost:" + gPort + "/addons/test_migrate4_6.xpi"); + do_check_eq(a6.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + do_check_false(a6.hasBinaryComponents); + do_check_false(a6.strictCompatibility); + + // addon7 was installed manually + do_check_neq(a7, null); + do_check_eq(a7.version, "1.0"); + do_check_false(a7.userDisabled); + do_check_false(a7.appDisabled); + do_check_true(a7.isActive); + do_check_eq(a7.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_false(a7.foreignInstall); + do_check_true(a7.seen); + do_check_eq(a7.sourceURI.spec, "http://localhost:" + gPort + "/addons/test_migrate4_7.xpi"); + do_check_eq(a7.releaseNotesURI, null); + do_check_false(a7.hasBinaryComponents); + do_check_false(a7.strictCompatibility); + + // addon8 was enabled and has binary components + do_check_neq(a8, null); + do_check_false(a8.userDisabled); + do_check_false(a8.appDisabled); + do_check_true(a8.isActive); + do_check_false(a8.foreignInstall); + do_check_true(a8.seen); + do_check_true(a8.hasBinaryComponents); + do_check_false(a8.strictCompatibility); + + // addon9 is the active theme + do_check_neq(a9, null); + do_check_false(a9.userDisabled); + do_check_false(a9.appDisabled); + do_check_true(a9.isActive); + do_check_false(a9.foreignInstall); + do_check_true(a9.seen); + do_check_false(a9.hasBinaryComponents); + do_check_true(a9.strictCompatibility); + + testserver.stop(do_test_finished); + }); +} + +function run_test() { + do_test_pending(); + + prepare_profile(); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js new file mode 100644 index 000000000000..885cd5a7c12f --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js @@ -0,0 +1,139 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Checks that we fail to migrate but still start up ok when there is a SQLITE database +// with no useful data in it. + +const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; + +var addon1 = { + id: "addon1@tests.mozilla.org", + version: "1.0", + name: "Test 1", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon2 = { + id: "addon2@tests.mozilla.org", + version: "2.0", + name: "Test 5", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0", + maxVersion: "0" + }] +}; + +var defaultTheme = { + id: "default@tests.mozilla.org", + version: "2.0", + name: "Default theme", + internalName: "classic/1.0", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var theme1 = { + id: "theme1@tests.mozilla.org", + version: "2.0", + name: "Test theme", + internalName: "theme1/1.0", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +function run_test() { + do_test_pending(); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + writeInstallRDFForExtension(addon1, profileDir); + writeInstallRDFForExtension(addon2, profileDir); + writeInstallRDFForExtension(defaultTheme, profileDir); + writeInstallRDFForExtension(theme1, profileDir); + + Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, "theme1/1.0"); + + // Write out a broken database (no userDisabled field) + let dbfile = gProfD.clone(); + dbfile.append("extensions.sqlite"); + let db = AM_Cc["@mozilla.org/storage/service;1"]. + getService(AM_Ci.mozIStorageService). + openDatabase(dbfile); + db.createTable("addon", "internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "id TEXT, location TEXT, version TEXT, active INTEGER, " + + "installDate INTEGER"); + db.createTable("targetApplication", "addon_internal_id INTEGER, " + + "id TEXT, minVersion TEXT, maxVersion TEXT"); + let stmt = db.createStatement("INSERT INTO addon VALUES (NULL, :id, :location, " + + ":version, :active, :installDate)"); + + let internal_ids = {}; + + [["addon1@tests.mozilla.org", "app-profile", "1.0", "1", "0"], + ["addon2@tests.mozilla.org", "app-profile", "2.0", "0", "0"], + ["default@tests.mozilla.org", "app-profile", "2.0", "1", "0"], + ["theme1@tests.mozilla.org", "app-profile", "2.0", "0", "0"]].forEach(function(a) { + stmt.params.id = a[0]; + stmt.params.location = a[1]; + stmt.params.version = a[2]; + stmt.params.active = a[3]; + stmt.params.installDate = a[4]; + stmt.execute(); + internal_ids[a[0]] = db.lastInsertRowID; + }); + stmt.finalize(); + + db.schemaVersion = 100; + Services.prefs.setIntPref("extensions.databaseSchema", 100); + db.close(); + + startupManager(); + check_startup_changes("installed", []); + check_startup_changes("updated", []); + check_startup_changes("uninstalled", []); + check_startup_changes("disabled", []); + check_startup_changes("enabled", []); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "default@tests.mozilla.org", + "theme1@tests.mozilla.org"], + function([a1, a2, d, t1]) { + do_check_neq(a1, null); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_true(a1.isActive); + + do_check_neq(a2, null); + do_check_false(a2.userDisabled); + do_check_true(a2.appDisabled); + do_check_false(a2.isActive); + + // Should have enabled the selected theme + do_check_neq(t1, null); + do_check_false(t1.userDisabled); + do_check_false(t1.appDisabled); + do_check_true(t1.isActive); + + do_check_neq(d, null); + do_check_true(d.userDisabled); + do_check_false(d.appDisabled); + do_check_false(d.isActive); + + do_execute_soon(do_test_finished); + }); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrateAddonRepository.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrateAddonRepository.js new file mode 100644 index 000000000000..242a362adeae --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrateAddonRepository.js @@ -0,0 +1,127 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const EXPECTED_SCHEMA_VERSION = 4; +var dbfile; + +function run_test() { + do_test_pending(); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + // Write out a minimal database. + dbfile = gProfD.clone(); + dbfile.append("addons.sqlite"); + let db = AM_Cc["@mozilla.org/storage/service;1"]. + getService(AM_Ci.mozIStorageService). + openDatabase(dbfile); + + db.createTable("addon", + "internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "id TEXT UNIQUE, " + + "type TEXT, " + + "name TEXT, " + + "version TEXT, " + + "creator TEXT, " + + "creatorURL TEXT, " + + "description TEXT, " + + "fullDescription TEXT, " + + "developerComments TEXT, " + + "eula TEXT, " + + "iconURL TEXT, " + + "homepageURL TEXT, " + + "supportURL TEXT, " + + "contributionURL TEXT, " + + "contributionAmount TEXT, " + + "averageRating INTEGER, " + + "reviewCount INTEGER, " + + "reviewURL TEXT, " + + "totalDownloads INTEGER, " + + "weeklyDownloads INTEGER, " + + "dailyUsers INTEGER, " + + "sourceURI TEXT, " + + "repositoryStatus INTEGER, " + + "size INTEGER, " + + "updateDate INTEGER"); + + db.createTable("developer", + "addon_internal_id INTEGER, " + + "num INTEGER, " + + "name TEXT, " + + "url TEXT, " + + "PRIMARY KEY (addon_internal_id, num)"); + + db.createTable("screenshot", + "addon_internal_id INTEGER, " + + "num INTEGER, " + + "url TEXT, " + + "thumbnailURL TEXT, " + + "caption TEXT, " + + "PRIMARY KEY (addon_internal_id, num)"); + + let insertStmt = db.createStatement("INSERT INTO addon (id) VALUES (:id)"); + insertStmt.params.id = "test1@tests.mozilla.org"; + insertStmt.execute(); + insertStmt.finalize(); + + insertStmt = db.createStatement("INSERT INTO screenshot VALUES " + + "(:addon_internal_id, :num, :url, :thumbnailURL, :caption)"); + + insertStmt.params.addon_internal_id = 1; + insertStmt.params.num = 0; + insertStmt.params.url = "http://localhost/full1-1.png"; + insertStmt.params.thumbnailURL = "http://localhost/thumbnail1-1.png"; + insertStmt.params.caption = "Caption 1 - 1"; + insertStmt.execute(); + insertStmt.finalize(); + + db.schemaVersion = 1; + db.close(); + + + Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", true); + AddonRepository.getCachedAddonByID("test1@tests.mozilla.org", function(aAddon) { + do_check_neq(aAddon, null); + do_check_eq(aAddon.screenshots.length, 1); + do_check_true(aAddon.screenshots[0].width === null); + do_check_true(aAddon.screenshots[0].height === null); + do_check_true(aAddon.screenshots[0].thumbnailWidth === null); + do_check_true(aAddon.screenshots[0].thumbnailHeight === null); + do_check_eq(aAddon.iconURL, undefined); + do_check_eq(JSON.stringify(aAddon.icons), "{}"); + AddonRepository.shutdown().then( + function checkAfterRepoShutdown() { + // Check the DB schema has changed once AddonRepository has freed it. + db = AM_Cc["@mozilla.org/storage/service;1"]. + getService(AM_Ci.mozIStorageService). + openDatabase(dbfile); + do_check_eq(db.schemaVersion, EXPECTED_SCHEMA_VERSION); + do_check_true(db.indexExists("developer_idx")); + do_check_true(db.indexExists("screenshot_idx")); + do_check_true(db.indexExists("compatibility_override_idx")); + do_check_true(db.tableExists("compatibility_override")); + do_check_true(db.indexExists("icon_idx")); + do_check_true(db.tableExists("icon")); + + // Check the trigger is working + db.executeSimpleSQL("INSERT INTO addon (id, type, name) VALUES('test_addon', 'extension', 'Test Addon')"); + let internalID = db.lastInsertRowID; + db.executeSimpleSQL("INSERT INTO compatibility_override (addon_internal_id, num, type) VALUES('" + internalID + "', '1', 'incompatible')"); + + let selectStmt = db.createStatement("SELECT COUNT(*) AS count FROM compatibility_override"); + selectStmt.executeStep(); + do_check_eq(selectStmt.row.count, 1); + selectStmt.reset(); + + db.executeSimpleSQL("DELETE FROM addon"); + selectStmt.executeStep(); + do_check_eq(selectStmt.row.count, 0); + selectStmt.finalize(); + + db.close(); + do_test_finished(); + }, + do_report_unexpected_exception + ); + }); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js new file mode 100644 index 000000000000..bd3a6df07fe8 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js @@ -0,0 +1,103 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Checks that we don't migrate data from SQLITE if +// the "extensions.databaseSchema" preference shows we've +// already upgraded to JSON + +// Enable loading extensions from the user and system scopes +Services.prefs.setIntPref("extensions.enabledScopes", + AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_USER + + AddonManager.SCOPE_SYSTEM); + +var addon1 = { + id: "addon1@tests.mozilla.org", + version: "1.0", + name: "Test 1", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +function run_test() { + writeInstallRDFForExtension(addon1, profileDir); + + // Write out a minimal database + let dbfile = gProfD.clone(); + dbfile.append("extensions.sqlite"); + let db = AM_Cc["@mozilla.org/storage/service;1"]. + getService(AM_Ci.mozIStorageService). + openDatabase(dbfile); + db.createTable("addon", "internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "id TEXT, location TEXT, version TEXT, active INTEGER, " + + "userDisabled INTEGER, installDate INTEGER"); + db.createTable("targetApplication", "addon_internal_id INTEGER, " + + "id TEXT, minVersion TEXT, maxVersion TEXT"); + let stmt = db.createStatement("INSERT INTO addon VALUES (NULL, :id, :location, " + + ":version, :active, :userDisabled, :installDate)"); + + let internal_ids = {}; + + let a = ["addon1@tests.mozilla.org", "app-profile", "1.0", "0", "1", "0"]; + stmt.params.id = a[0]; + stmt.params.location = a[1]; + stmt.params.version = a[2]; + stmt.params.active = a[3]; + stmt.params.userDisabled = a[4]; + stmt.params.installDate = a[5]; + stmt.execute(); + internal_ids[a[0]] = db.lastInsertRowID; + stmt.finalize(); + + db.schemaVersion = 14; + Services.prefs.setIntPref("extensions.databaseSchema", 14); + db.close(); + + startupManager(); + run_next_test(); +} + +add_test(function before_rebuild() { + AddonManager.getAddonByID("addon1@tests.mozilla.org", + function check_before_rebuild(a1) { + // First check that it migrated OK once + // addon1 was disabled in the database + do_check_neq(a1, null); + do_check_true(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_false(a1.isActive); + do_check_false(a1.strictCompatibility); + do_check_false(a1.foreignInstall); + + run_next_test(); + }); +}); + +// now shut down, remove the JSON database, +// start up again, and make sure the data didn't migrate this time +add_test(function rebuild_again() { + shutdownManager(); + gExtensionsJSON.remove(true); + startupManager(); + + AddonManager.getAddonByID("addon1@tests.mozilla.org", + function check_after_rebuild(a1) { + // addon1 was rebuilt from extensions directory, + // so it appears enabled as a foreign install + do_check_neq(a1, null); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_true(a1.isActive); + do_check_false(a1.strictCompatibility); + do_check_true(a1.foreignInstall); + + run_next_test(); + }); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate_state_prefs.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate_state_prefs.js deleted file mode 100644 index fb184b6d9b1a..000000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate_state_prefs.js +++ /dev/null @@ -1,120 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -"use strict"; - -/* globals Preferences */ -AM_Cu.import("resource://gre/modules/Preferences.jsm"); - -function getXS() { - let XPI = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm", {}); - return XPI.XPIStates; -} - -function installExtension(id, data) { - return AddonTestUtils.promiseWriteFilesToExtension( - AddonTestUtils.profileExtensions.path, id, data); -} - -add_task(async function test_migrate_prefs() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "54"); - - ok(!AddonTestUtils.addonStartup.exists(), - "addonStartup.json.lz4 should not exist"); - - const ID1 = "bootstrapped-enabled@xpcshell.mozilla.org"; - const ID2 = "bootstrapped-disabled@xpcshell.mozilla.org"; - const ID3 = "restartful-enabled@xpcshell.mozilla.org"; - const ID4 = "restartful-disabled@xpcshell.mozilla.org"; - - let targetApplications = [{ id: "toolkit@mozilla.org", "minVersion": "0", "maxVersion": "*" }]; - - let file1 = await installExtension(ID1, { "install.rdf": { id: ID1, name: ID1, bootstrapped: true, version: "0.1", targetApplications } }); - let file2 = await installExtension(ID2, { "install.rdf": { id: ID2, name: ID2, bootstrapped: true, version: "0.2", targetApplications } }); - - let file3 = await installExtension(ID3, { "install.rdf": { id: ID3, name: ID1, bootstrapped: false, version: "0.3", targetApplications } }); - let file4 = await installExtension(ID4, { "install.rdf": { id: ID4, name: ID2, bootstrapped: false, version: "0.4", targetApplications } }); - - function mt(file) { - let f = file.clone(); - if (TEST_UNPACKED) { - f.append("install.rdf"); - } - return f.lastModifiedTime; - } - - // Startup and shut down the add-on manager so the add-ons are added - // to the DB. - await promiseStartupManager(); - await promiseShutdownManager(); - - // Remove the startup state file and add legacy prefs to replace it. - AddonTestUtils.addonStartup.remove(false); - - Preferences.set("extensions.xpiState", JSON.stringify({ - "app-profile": { - [ID1]: {e: true, d: file1.persistentDescriptor, v: "0.1", mt: mt(file1)}, - [ID2]: {e: false, d: file2.persistentDescriptor, v: "0.2", mt: mt(file2)}, - [ID3]: {e: true, d: file3.persistentDescriptor, v: "0.3", mt: mt(file3)}, - [ID4]: {e: false, d: file4.persistentDescriptor, v: "0.4", mt: mt(file4)}, - } - })); - - Preferences.set("extensions.bootstrappedAddons", JSON.stringify({ - [ID1]: { - version: "0.1", - type: "extension", - multiprocessCompatible: false, - descriptor: file1.persistentDescriptor, - hasEmbeddedWebExtension: true, - } - })); - - await promiseStartupManager(); - - // Check the the state data is updated correctly. - let states = getXS(); - - let addon1 = states.findAddon(ID1); - ok(addon1.enabled, "Addon 1 should be enabled"); - ok(addon1.bootstrapped, "Addon 1 should be bootstrapped"); - equal(addon1.version, "0.1", "Addon 1 has the correct version"); - equal(addon1.mtime, mt(file1), "Addon 1 has the correct timestamp"); - ok(addon1.enableShims, "Addon 1 has shims enabled"); - ok(addon1.hasEmbeddedWebExtension, "Addon 1 has an embedded WebExtension"); - - let addon2 = states.findAddon(ID2); - ok(!addon2.enabled, "Addon 2 should not be enabled"); - ok(!addon2.bootstrapped, "Addon 2 should be bootstrapped, because that information is not stored in xpiStates"); - equal(addon2.version, "0.2", "Addon 2 has the correct version"); - equal(addon2.mtime, mt(file2), "Addon 2 has the correct timestamp"); - ok(!addon2.enableShims, "Addon 2 does not have shims enabled"); - ok(!addon2.hasEmbeddedWebExtension, "Addon 2 no embedded WebExtension"); - - let addon3 = states.findAddon(ID3); - ok(addon3.enabled, "Addon 3 should be enabled"); - ok(!addon3.bootstrapped, "Addon 3 should not be bootstrapped"); - equal(addon3.version, "0.3", "Addon 3 has the correct version"); - equal(addon3.mtime, mt(file3), "Addon 3 has the correct timestamp"); - ok(!addon3.enableShims, "Addon 3 does not have shims enabled"); - ok(!addon3.hasEmbeddedWebExtension, "Addon 3 no embedded WebExtension"); - - let addon4 = states.findAddon(ID4); - ok(!addon4.enabled, "Addon 4 should not be enabled"); - ok(!addon4.bootstrapped, "Addon 4 should not be bootstrapped"); - equal(addon4.version, "0.4", "Addon 4 has the correct version"); - equal(addon4.mtime, mt(file4), "Addon 4 has the correct timestamp"); - ok(!addon4.enableShims, "Addon 4 does not have shims enabled"); - ok(!addon4.hasEmbeddedWebExtension, "Addon 4 no embedded WebExtension"); - - // Check that legacy prefs and files have been removed. - ok(!Preferences.has("extensions.xpiState"), "No xpiState pref left behind"); - ok(!Preferences.has("extensions.bootstrappedAddons"), "No bootstrappedAddons pref left behind"); - ok(!Preferences.has("extensions.enabledAddons"), "No enabledAddons pref left behind"); - - let file = AddonTestUtils.profileDir.clone(); - file.append("extensions.ini"); - ok(!file.exists(), "No extensions.ini file left behind"); - - await promiseShutdownManager(); -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_no_addons.js b/toolkit/mozapps/extensions/test/xpcshell/test_no_addons.js index c8a1930462c7..62ff5523f4eb 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_no_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_no_addons.js @@ -27,9 +27,23 @@ function checkPending() { } } +function checkString(aPref, aValue) { + try { + do_check_eq(Services.prefs.getCharPref(aPref), aValue) + } catch (e) { + // OK + } +} + // Make sure all our extension state is empty/nonexistent function check_empty_state() { + do_check_false(gExtensionsJSON.exists()); + do_check_false(gExtensionsINI.exists()); + do_check_eq(Services.prefs.getIntPref("extensions.databaseSchema"), DB_SCHEMA); + + checkString("extensions.bootstrappedAddons", "{}"); + checkString("extensions.installCache", "[]"); checkPending(); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_safemode.js b/toolkit/mozapps/extensions/test/xpcshell/test_safemode.js index 100c9a4396ee..05647f807f3b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_safemode.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_safemode.js @@ -33,14 +33,14 @@ function run_test() { startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_eq(a1, null); do_check_not_in_crash_annotation(addon1.id, addon1.version); writeInstallRDFForExtension(addon1, profileDir, addon1.id, "icon.png"); gIconURL = do_get_addon_root_uri(profileDir.clone(), addon1.id) + "icon.png"; - await promiseRestartManager(); + restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(newa1) { do_check_neq(newa1, null); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js b/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js index 71c8151f1d24..c4502750bef6 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js @@ -18,7 +18,6 @@ const IGNORE_PRIVATE = ["AddonAuthor", "AddonCompatibilityOverride", "AddonScreenshot", "AddonType", "startup", "shutdown", "addonIsActive", "registerProvider", "unregisterProvider", "addStartupChange", "removeStartupChange", - "getNewSideloads", "recordTimestamp", "recordSimpleMeasure", "recordException", "getSimpleMeasures", "simpleTimer", "setTelemetryDetails", "getTelemetryDetails", diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_sideloads.js b/toolkit/mozapps/extensions/test/xpcshell/test_sideloads.js deleted file mode 100644 index a607c8373b8d..000000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/test_sideloads.js +++ /dev/null @@ -1,90 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "49"); - -async function createWebExtension(details) { - let options = { - manifest: { - applications: {gecko: {id: details.id}}, - - name: details.name, - - permissions: details.permissions, - }, - }; - - if (details.iconURL) { - options.manifest.icons = {"64": details.iconURL}; - } - - let xpi = AddonTestUtils.createTempWebExtensionFile(options); - - await AddonTestUtils.manuallyInstall(xpi); -} - -async function createXULExtension(details) { - let xpi = AddonTestUtils.createTempXPIFile({ - "install.rdf": { - id: details.id, - name: details.name, - version: "0.1", - targetApplications: [{ - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "*", - }], - }, - }); - - await AddonTestUtils.manuallyInstall(xpi); -} - -add_task(async function test_sideloading() { - Services.prefs.setIntPref("extensions.autoDisableScopes", 15); - Services.prefs.setIntPref("extensions.startupScanScopes", 0); - - const ID1 = "addon1@tests.mozilla.org"; - await createWebExtension({ - id: ID1, - name: "Test 1", - userDisabled: true, - permissions: ["history", "https://*/*"], - iconURL: "foo-icon.png", - }); - - const ID2 = "addon2@tests.mozilla.org"; - await createXULExtension({ - id: ID2, - name: "Test 2", - }); - - const ID3 = "addon3@tests.mozilla.org"; - await createWebExtension({ - id: ID3, - name: "Test 3", - permissions: [""], - }); - - const ID4 = "addon4@tests.mozilla.org"; - await createWebExtension({ - id: ID4, - name: "Test 4", - permissions: [""], - }); - - await promiseStartupManager(); - - let sideloaded = await AddonManagerPrivate.getNewSideloads(); - - sideloaded.sort((a, b) => a.id.localeCompare(b.id)); - - deepEqual(sideloaded.map(a => a.id), - [ID1, ID2, ID3, ID4], - "Got the correct sideload add-ons"); - - deepEqual(sideloaded.map(a => a.userDisabled), - [true, true, true, true], - "All sideloaded add-ons are disabled"); -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js b/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js index e5ecc7e1c0a6..e896cd68f561 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js @@ -25,13 +25,8 @@ profileDir.append("extensions"); // Deletes a file from the test add-on in the profile function breakAddon(file) { if (TEST_UNPACKED) { - let f = file.clone(); - f.append("test.txt"); - f.remove(true); - - f = file.clone(); - f.append("install.rdf"); - f.lastModifiedTime = Date.now(); + file.append("test.txt"); + file.remove(true); } else { var zipW = AM_Cc["@mozilla.org/zipwriter;1"]. createInstance(AM_Ci.nsIZipWriter); @@ -261,7 +256,7 @@ add_task(function*() { // detection but the periodic scan will catch that yield promiseSetExtensionModifiedTime(file.path, Date.now() - 60000); - yield promiseStartupManager(); + startupManager(); let addon = yield promiseAddonByID(ID); do_check_neq(addon, null); do_check_false(addon.appDisabled); @@ -274,7 +269,7 @@ add_task(function*() { clearCache(file); breakAddon(file); - yield promiseStartupManager(); + startupManager(); addon = yield promiseAddonByID(ID); do_check_neq(addon, null); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_signed_migrate.js b/toolkit/mozapps/extensions/test/xpcshell/test_signed_migrate.js index 9a72e067112c..10f6cb5d5e33 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_migrate.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_migrate.js @@ -104,7 +104,7 @@ function* test_breaking_migrate(addons, test, expectedSignedState) { // Update the application gAppInfo.version = "5"; - yield promiseStartupManager(true); + startupManager(true); let addon = yield promiseAddonByID(ID); do_check_neq(addon, null); @@ -154,7 +154,7 @@ function* test_working_migrate(addons, test, expectedSignedState) { // Update the application gAppInfo.version = "5"; - yield promiseStartupManager(true); + startupManager(true); let addon = yield promiseAddonByID(ID); do_check_neq(addon, null); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js index b88367a8cb22..348c1eb625fd 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js @@ -134,7 +134,7 @@ function run_test() { do_check_false(gExtensionsJSON.exists()); - do_check_false(gAddonStartup.exists()); + do_check_false(gExtensionsINI.exists()); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", @@ -163,7 +163,7 @@ function end_test() { } // Try to install all the items into the profile -async function run_test_1() { +function run_test_1() { writeInstallRDFForExtension(addon1, profileDir); var dest = writeInstallRDFForExtension(addon2, profileDir); // Attempt to make this look like it was added some time in the past so @@ -177,7 +177,7 @@ async function run_test_1() { writeInstallRDFForExtension(addon7, profileDir); gCachePurged = false; - await promiseRestartManager(); + restartManager(); check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, ["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org"]); @@ -187,8 +187,8 @@ async function run_test_1() { check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []); do_check_true(gCachePurged); - do_print("Checking for " + gAddonStartup.path); - do_check_true(gAddonStartup.exists()); + do_print("Checking for " + gExtensionsINI.path); + do_check_true(gExtensionsINI.exists()); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", @@ -281,7 +281,7 @@ async function run_test_1() { // Test that modified items are detected and items in other install locations // are ignored -async function run_test_2() { +function run_test_2() { addon1.version = "1.1"; writeInstallRDFForExtension(addon1, userDir); addon2.version = "2.1"; @@ -295,8 +295,7 @@ async function run_test_2() { dest.remove(true); gCachePurged = false; - await promiseRestartManager(); - + restartManager(); check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, ["addon2@tests.mozilla.org"]); check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, ["addon3@tests.mozilla.org"]); @@ -304,7 +303,7 @@ async function run_test_2() { check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []); do_check_true(gCachePurged); - do_check_true(gAddonStartup.exists()); + do_check_true(gExtensionsINI.exists()); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", @@ -351,7 +350,7 @@ async function run_test_2() { } // Check that removing items from the profile reveals their hidden versions. -async function run_test_3() { +function run_test_3() { var dest = profileDir.clone(); dest.append(do_get_expected_addon_name("addon1@tests.mozilla.org")); dest.remove(true); @@ -361,8 +360,7 @@ async function run_test_3() { writeInstallRDFForExtension(addon3, profileDir, "addon4@tests.mozilla.org"); gCachePurged = false; - await promiseRestartManager(); - + restartManager(); check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, ["addon1@tests.mozilla.org", "addon2@tests.mozilla.org"]); @@ -417,12 +415,11 @@ async function run_test_3() { } // Test that disabling an install location works -async function run_test_4() { +function run_test_4() { Services.prefs.setIntPref("extensions.enabledScopes", AddonManager.SCOPE_SYSTEM); gCachePurged = false; - await promiseRestartManager(); - + restartManager(); check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, ["addon2@tests.mozilla.org"]); check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, ["addon1@tests.mozilla.org"]); @@ -457,12 +454,11 @@ async function run_test_4() { } // Switching disabled locations works -async function run_test_5() { +function run_test_5() { Services.prefs.setIntPref("extensions.enabledScopes", AddonManager.SCOPE_USER); gCachePurged = false; - await promiseRestartManager(); - + restartManager(); check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, ["addon1@tests.mozilla.org"]); check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, ["addon2@tests.mozilla.org"]); check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []); @@ -503,12 +499,11 @@ async function run_test_5() { } // Resetting the pref makes everything visible again -async function run_test_6() { +function run_test_6() { Services.prefs.clearUserPref("extensions.enabledScopes"); gCachePurged = false; - await promiseRestartManager(); - + restartManager(); check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []); @@ -549,7 +544,7 @@ async function run_test_6() { } // Check that items in the profile hide the others again. -async function run_test_7() { +function run_test_7() { addon1.version = "1.2"; writeInstallRDFForExtension(addon1, profileDir); var dest = userDir.clone(); @@ -557,8 +552,7 @@ async function run_test_7() { dest.remove(true); gCachePurged = false; - await promiseRestartManager(); - + restartManager(); check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, ["addon1@tests.mozilla.org", "addon2@tests.mozilla.org"]); @@ -609,12 +603,11 @@ async function run_test_7() { } // Disabling all locations still leaves the profile working -async function run_test_8() { +function run_test_8() { Services.prefs.setIntPref("extensions.enabledScopes", 0); gCachePurged = false; - await promiseRestartManager(); - + restartManager(); check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, ["addon2@tests.mozilla.org"]); @@ -649,7 +642,7 @@ async function run_test_8() { } // More hiding and revealing -async function run_test_9() { +function run_test_9() { Services.prefs.clearUserPref("extensions.enabledScopes"); var dest = userDir.clone(); @@ -662,8 +655,7 @@ async function run_test_9() { writeInstallRDFForExtension(addon2, profileDir); gCachePurged = false; - await promiseRestartManager(); - + restartManager(); check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, ["addon2@tests.mozilla.org"]); check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []); @@ -712,7 +704,7 @@ async function run_test_9() { // Checks that a removal from one location and an addition in another location // for the same item is handled -async function run_test_10() { +function run_test_10() { var dest = profileDir.clone(); dest.append(do_get_expected_addon_name("addon1@tests.mozilla.org")); dest.remove(true); @@ -720,8 +712,7 @@ async function run_test_10() { writeInstallRDFForExtension(addon1, userDir); gCachePurged = false; - await promiseRestartManager(); - + restartManager(); check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, ["addon1@tests.mozilla.org"]); check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []); @@ -769,7 +760,7 @@ async function run_test_10() { } // This should remove any remaining items -async function run_test_11() { +function run_test_11() { var dest = userDir.clone(); dest.append(do_get_expected_addon_name("addon1@tests.mozilla.org")); dest.remove(true); @@ -778,8 +769,7 @@ async function run_test_11() { dest.remove(true); gCachePurged = false; - await promiseRestartManager(); - + restartManager(); check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, ["addon1@tests.mozilla.org", diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js b/toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js index aa562c4a0191..ef4f2aee562e 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js @@ -89,7 +89,7 @@ const profileDir = gProfD.clone(); profileDir.append("extensions"); // Set up the profile -async function run_test() { +function run_test() { do_test_pending(); writeInstallRDFForExtension(addon1, profileDir); @@ -98,8 +98,7 @@ async function run_test() { writeInstallRDFForExtension(addon4, profileDir); writeInstallRDFForExtension(addon5, profileDir); - await promiseRestartManager(); - + restartManager(); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_theme.js b/toolkit/mozapps/extensions/test/xpcshell/test_theme.js index 0e3b58833b9a..61893e5778d0 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_theme.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_theme.js @@ -34,7 +34,7 @@ AM_Cc["@mozilla.org/observer-service;1"] .addObserver(LightweightThemeObserver, "lightweight-theme-styling-update"); -async function run_test() { +function run_test() { do_test_pending(); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); @@ -80,7 +80,7 @@ async function run_test() { }] }, profileDir); - await promiseStartupManager(); + startupManager(); // Make sure we only register once despite multiple calls AddonManager.addInstallListener(InstallListener); AddonManager.addAddonListener(AddonListener); @@ -158,9 +158,8 @@ function run_test_1() { }); } -async function check_test_1() { - await promiseRestartManager(); - +function check_test_1() { + restartManager(); do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme2/1.0"); AddonManager.getAddonsByIDs(["theme1@tests.mozilla.org", @@ -191,13 +190,12 @@ async function check_test_1() { // Removing the active theme should fall back to the default (not ideal in this // case since we don't have the default theme installed) -async function run_test_2() { +function run_test_2() { var dest = profileDir.clone(); dest.append(do_get_expected_addon_name("theme2@tests.mozilla.org")); dest.remove(true); - await promiseRestartManager(); - + restartManager(); do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); AddonManager.getAddonsByIDs(["theme1@tests.mozilla.org", diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js b/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js index 39f41e77822b..6b12489f2b76 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js @@ -25,12 +25,12 @@ function run_test() { do_test_pending(); startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(olda1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { do_check_eq(olda1, null); writeInstallRDFForExtension(addon1, profileDir); - await promiseRestartManager(); + restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); @@ -92,8 +92,8 @@ function check_test_1() { } // Cancelling the uninstall should send onOperationCancelled -async function run_test_2() { - await promiseRestartManager(); +function run_test_2() { + restartManager(); prepare_test({ "addon1@tests.mozilla.org": [ @@ -126,8 +126,8 @@ async function run_test_2() { }); } -async function check_test_2() { - await promiseRestartManager(); +function check_test_2() { + restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_update.js index d83b7a1a66f3..cc2082f2167d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js @@ -221,16 +221,14 @@ for (let test of testParams) { check_test_2 = () => { ensure_test_completed(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(olda1) { - await AddonTestUtils.loadAddonsList(true); - + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { do_check_neq(olda1, null); do_check_eq(olda1.version, "1.0"); do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); shutdownManager(); - await promiseStartupManager(); + startupManager(); do_check_true(isExtensionInAddonsList(profileDir, "addon1@tests.mozilla.org")); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js index e4069c558bf3..cdb879ac6e57 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js @@ -216,16 +216,14 @@ for (let test of testParams) { check_test_2 = () => { ensure_test_completed(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(olda1) { - await AddonTestUtils.loadAddonsList(true); - + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { do_check_neq(olda1, null); do_check_eq(olda1.version, "1.0"); do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); shutdownManager(); - await promiseStartupManager(); + startupManager(); do_check_true(isExtensionInAddonsList(profileDir, "addon1@tests.mozilla.org")); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js index a65b68a2ef64..fd2871c1e349 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js @@ -96,8 +96,8 @@ function end_test() { } // Test that the test extensions are all installed -async function run_test_1() { - await promiseStartupManager(); +function run_test_1() { + startupManager(); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", @@ -123,7 +123,7 @@ async function run_test_1() { } // Test that upgrading the application doesn't disable now incompatible add-ons -async function run_test_2() { +function run_test_2() { // Upgrade the extension var dest = writeInstallRDFForExtension({ id: "addon4@tests.mozilla.org", @@ -137,7 +137,7 @@ async function run_test_2() { }, globalDir); setExtensionModifiedTime(dest, gInstallTime); - await promiseRestartManager("2"); + restartManager("2"); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", @@ -178,7 +178,7 @@ function run_test_3() { // Simulates a simple Build ID change, the platform deletes extensions.ini // whenever the application is changed. - gAddonStartup.remove(true); + gExtensionsINI.remove(true); restartManager(); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js index d7017c8426bb..a660a8b6d12b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js @@ -99,8 +99,8 @@ function end_test() { } // Test that the test extensions are all installed -async function run_test_1() { - await promiseStartupManager(); +function run_test_1() { + startupManager(); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", @@ -126,7 +126,7 @@ async function run_test_1() { } // Test that upgrading the application disables now incompatible add-ons -async function run_test_2() { +function run_test_2() { // Upgrade the extension var dest = writeInstallRDFForExtension({ id: "addon4@tests.mozilla.org", @@ -140,8 +140,7 @@ async function run_test_2() { }, globalDir); setExtensionModifiedTime(dest, gInstallTime); - await promiseRestartManager("2"); - + restartManager("2"); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", @@ -182,7 +181,7 @@ function run_test_3() { // Simulates a simple Build ID change, the platform deletes extensions.ini // whenever the application is changed. - gAddonStartup.remove(true); + gExtensionsINI.remove(true); restartManager(); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js index d13d62b43149..d162268c6e11 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js @@ -105,8 +105,7 @@ add_task(function* has_embedded_webextension_persisted() { // hasEmbeddedWebExtension property as expected. yield promiseRestartManager(); - let persisted = aomStartup.readStartupData()["app-profile"].addons; - + let persisted = JSON.parse(Services.prefs.getCharPref("extensions.bootstrappedAddons")); ok(ID in persisted, "Hybrid add-on persisted to bootstrappedAddons."); equal(persisted[ID].hasEmbeddedWebExtension, true, "hasEmbeddedWebExtension flag persisted to bootstrappedAddons."); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_icons.js b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_icons.js index 3a2411555f46..3c44eb703264 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_icons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_icons.js @@ -27,10 +27,9 @@ function promiseAddonStartup() { function* testSimpleIconsetParsing(manifest) { yield promiseWriteWebManifestForExtension(manifest, profileDir); - yield Promise.all([ - promiseRestartManager(), - manifest.theme || promiseAddonStartup(), - ]); + yield promiseRestartManager(); + if (!manifest.theme) + yield promiseAddonStartup(); let uri = do_get_addon_root_uri(profileDir, ID); @@ -61,10 +60,9 @@ function* testSimpleIconsetParsing(manifest) { check_icons(addon); // check if icons are persisted through a restart - yield Promise.all([ - promiseRestartManager(), - manifest.theme || promiseAddonStartup(), - ]); + yield promiseRestartManager(); + if (!manifest.theme) + yield promiseAddonStartup(); addon = yield promiseAddonByID(ID); do_check_neq(addon, null); @@ -72,15 +70,16 @@ function* testSimpleIconsetParsing(manifest) { check_icons(addon); addon.uninstall(); + + yield promiseRestartManager(); } function* testRetinaIconsetParsing(manifest) { yield promiseWriteWebManifestForExtension(manifest, profileDir); - yield Promise.all([ - promiseRestartManager(), - manifest.theme || promiseAddonStartup(), - ]); + yield promiseRestartManager(); + if (!manifest.theme) + yield promiseAddonStartup(); let addon = yield promiseAddonByID(ID); do_check_neq(addon, null); @@ -101,15 +100,16 @@ function* testRetinaIconsetParsing(manifest) { }), uri + "icon128.png"); addon.uninstall(); + + yield promiseRestartManager(); } function* testNoIconsParsing(manifest) { yield promiseWriteWebManifestForExtension(manifest, profileDir); - yield Promise.all([ - promiseRestartManager(), - manifest.theme || promiseAddonStartup(), - ]); + yield promiseRestartManager(); + if (!manifest.theme) + yield promiseAddonStartup(); let addon = yield promiseAddonByID(ID); do_check_neq(addon, null); @@ -122,6 +122,8 @@ function* testNoIconsParsing(manifest) { equal(AddonManager.getPreferredIconURL(addon, 128), null); addon.uninstall(); + + yield promiseRestartManager(); } // Test simple icon set parsing diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini index 898f1e8207b5..b40035b60138 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini @@ -36,7 +36,7 @@ skip-if = os == "android" tags = blocklist [test_bootstrap.js] # Bug 676992: test consistently hangs on Android -skip-if = os == "android" || os == "win" # Bug 1358846 +skip-if = os == "android" [test_bootstrap_const.js] [test_bootstrap_resource.js] [test_bug299716.js] @@ -151,7 +151,7 @@ fail-if = os == "android" [test_bug570173.js] [test_bug576735.js] [test_bug587088.js] -skip-if = os == "win" # Bug 1358846 +[test_bug594058.js] [test_bug595081.js] [test_bug595573.js] [test_bug596607.js] @@ -247,6 +247,15 @@ run-sequentially = Uses hardcoded ports in xpi files. [test_mapURIToAddonID.js] # Same as test_bootstrap.js skip-if = os == "android" +[test_migrate1.js] +[test_migrate2.js] +[test_migrate3.js] +[test_migrate4.js] +# Times out during parallel runs on desktop +requesttimeoutfactor = 2 +[test_migrate5.js] +[test_migrateAddonRepository.js] +[test_migrate_max_version.js] [test_multiprocessCompatible.js] [test_no_addons.js] [test_onPropertyChanged_appDisabled.js] @@ -290,7 +299,6 @@ fail-if = os == "android" [test_undothemeuninstall.js] skip-if = appname == "thunderbird" [test_undouninstall.js] -skip-if = os == "win" # Bug 1358846 [test_uninstall.js] [test_update.js] # Bug 676992: test consistently hangs on Android @@ -309,7 +317,6 @@ skip-if = os == "android" skip-if = os == "android" run-sequentially = Uses hardcoded ports in xpi files. [test_json_updatecheck.js] -[test_migrate_state_prefs.js] [test_seen.js] [test_seen_newprofile.js] [test_updateid.js] @@ -328,7 +335,6 @@ run-sequentially = Uses global XCurProcD dir. [test_overrideblocklist.js] run-sequentially = Uses global XCurProcD dir. tags = blocklist -[test_sideloads.js] [test_sourceURI.js] [test_webextension_icons.js] skip-if = appname == "thunderbird" diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp index 1819e06cd009..93f68e4e3e9d 100644 --- a/toolkit/xre/nsXREDirProvider.cpp +++ b/toolkit/xre/nsXREDirProvider.cpp @@ -6,7 +6,6 @@ #include "nsAppRunner.h" #include "nsToolkitCompsCID.h" #include "nsXREDirProvider.h" -#include "mozilla/AddonManagerStartup.h" #include "jsapi.h" #include "xpcpublic.h" @@ -28,6 +27,7 @@ #include "nsXULAppAPI.h" #include "nsCategoryManagerUtils.h" +#include "nsINIParser.h" #include "nsDependentString.h" #include "nsCOMArray.h" #include "nsArrayEnumerator.h" @@ -86,6 +86,15 @@ static const char* GetContentProcessTempBaseDirKey(); static already_AddRefed CreateContentProcessSandboxTempDir(); #endif +static already_AddRefed +CloneAndAppend(nsIFile* aFile, const char* name) +{ + nsCOMPtr file; + aFile->Clone(getter_AddRefs(file)); + file->AppendNative(nsDependentCString(name)); + return file.forget(); +} + nsXREDirProvider* gDirServiceProvider = nullptr; nsXREDirProvider::nsXREDirProvider() : @@ -589,7 +598,7 @@ LoadDirIntoArray(nsIFile* dir, } static void -LoadDirsIntoArray(const nsCOMArray& aSourceDirs, +LoadDirsIntoArray(nsCOMArray& aSourceDirs, const char *const* aAppendList, nsCOMArray& aDirectories) { @@ -650,6 +659,73 @@ nsXREDirProvider::GetFiles(const char* aProperty, nsISimpleEnumerator** aResult) return NS_SUCCESS_AGGREGATE_RESULT; } +static void +RegisterExtensionInterpositions(nsINIParser &parser) +{ + if (!mozilla::Preferences::GetBool("extensions.interposition.enabled", false)) + return; + + nsCOMPtr interposition = + do_GetService("@mozilla.org/addons/multiprocess-shims;1"); + + nsresult rv; + int32_t i = 0; + do { + nsAutoCString buf("Extension"); + buf.AppendInt(i++); + + nsAutoCString addonId; + rv = parser.GetString("MultiprocessIncompatibleExtensions", buf.get(), addonId); + if (NS_FAILED(rv)) + return; + + if (!xpc::SetAddonInterposition(addonId, interposition)) + continue; + + if (!xpc::AllowCPOWsInAddon(addonId, true)) + continue; + } + while (true); +} + +static void +LoadExtensionDirectories(nsINIParser &parser, + const char *aSection, + nsCOMArray &aDirectories, + NSLocationType aType) +{ + nsresult rv; + int32_t i = 0; + do { + nsAutoCString buf("Extension"); + buf.AppendInt(i++); + + nsAutoCString path; + rv = parser.GetString(aSection, buf.get(), path); + if (NS_FAILED(rv)) + return; + + nsCOMPtr dir = do_CreateInstance("@mozilla.org/file/local;1", &rv); + if (NS_FAILED(rv)) + continue; + + rv = dir->SetPersistentDescriptor(path); + if (NS_FAILED(rv)) + continue; + + aDirectories.AppendObject(dir); + if (Substring(path, path.Length() - 4).EqualsLiteral(".xpi")) { + XRE_AddJarManifestLocation(aType, dir); + } + else { + nsCOMPtr manifest = + CloneAndAppend(dir, "chrome.manifest"); + XRE_AddManifestLocation(aType, manifest); + } + } + while (true); +} + #if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) static const char* @@ -827,6 +903,59 @@ DeleteDirIfExists(nsIFile* dir) #endif // (defined(XP_WIN) || defined(XP_MACOSX)) && // defined(MOZ_CONTENT_SANDBOX) +void +nsXREDirProvider::LoadExtensionBundleDirectories() +{ + if (!mozilla::Preferences::GetBool("extensions.defaultProviders.enabled", true)) + return; + + if (mProfileDir) { + if (!gSafeMode) { + nsCOMPtr extensionsINI; + mProfileDir->Clone(getter_AddRefs(extensionsINI)); + if (!extensionsINI) + return; + + extensionsINI->AppendNative(NS_LITERAL_CSTRING("extensions.ini")); + + nsCOMPtr extensionsINILF = + do_QueryInterface(extensionsINI); + if (!extensionsINILF) + return; + + nsINIParser parser; + nsresult rv = parser.Init(extensionsINILF); + if (NS_FAILED(rv)) + return; + + RegisterExtensionInterpositions(parser); + LoadExtensionDirectories(parser, "ExtensionDirs", mExtensionDirectories, + NS_EXTENSION_LOCATION); + LoadExtensionDirectories(parser, "ThemeDirs", mThemeDirectories, + NS_SKIN_LOCATION); +/* non-Firefox applications that use overrides in their default theme should + * define AC_DEFINE(MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES) in their + * configure.in */ +#if defined(MOZ_BUILD_APP_IS_BROWSER) || defined(MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES) + } else { + // In safe mode, still load the default theme directory: + nsCOMPtr themeManifest; + mXULAppDir->Clone(getter_AddRefs(themeManifest)); + themeManifest->AppendNative(NS_LITERAL_CSTRING("extensions")); + themeManifest->AppendNative(NS_LITERAL_CSTRING("{972ce4c6-7e08-4474-a285-3208198ce6fd}.xpi")); + bool exists = false; + if (NS_SUCCEEDED(themeManifest->Exists(&exists)) && exists) { + XRE_AddJarManifestLocation(NS_SKIN_LOCATION, themeManifest); + } else { + themeManifest->SetNativeLeafName(NS_LITERAL_CSTRING("{972ce4c6-7e08-4474-a285-3208198ce6fd}")); + themeManifest->AppendNative(NS_LITERAL_CSTRING("chrome.manifest")); + XRE_AddManifestLocation(NS_SKIN_LOCATION, themeManifest); + } +#endif + } + } +} + #ifdef MOZ_B2G void nsXREDirProvider::LoadAppBundleDirs() @@ -890,7 +1019,7 @@ nsXREDirProvider::GetFilesInternal(const char* aProperty, LoadDirsIntoArray(mAppBundleDirectories, kAppendNothing, directories); - LoadDirsIntoArray(AddonManagerStartup::GetSingleton().ExtensionPaths(), + LoadDirsIntoArray(mExtensionDirectories, kAppendNothing, directories); rv = NS_NewArrayEnumerator(aResult, directories); @@ -907,7 +1036,7 @@ nsXREDirProvider::GetFilesInternal(const char* aProperty, else if (!strcmp(aProperty, NS_EXT_PREFS_DEFAULTS_DIR_LIST)) { nsCOMArray directories; - LoadDirsIntoArray(AddonManagerStartup::GetSingleton().ExtensionPaths(), + LoadDirsIntoArray(mExtensionDirectories, kAppendPrefDir, directories); if (mProfileDir) { @@ -934,7 +1063,7 @@ nsXREDirProvider::GetFilesInternal(const char* aProperty, LoadDirsIntoArray(mAppBundleDirectories, kAppendChromeDir, directories); - LoadDirsIntoArray(AddonManagerStartup::GetSingleton().ExtensionPaths(), + LoadDirsIntoArray(mExtensionDirectories, kAppendChromeDir, directories); @@ -959,7 +1088,7 @@ nsXREDirProvider::GetFilesInternal(const char* aProperty, LoadDirsIntoArray(mAppBundleDirectories, kAppendPlugins, directories); - LoadDirsIntoArray(AddonManagerStartup::GetSingleton().ExtensionPaths(), + LoadDirsIntoArray(mExtensionDirectories, kAppendPlugins, directories); @@ -1032,6 +1161,8 @@ nsXREDirProvider::DoStartup() NS_WARNING("Failed to create Addons Manager."); } + LoadExtensionBundleDirectories(); + obsSvc->NotifyObservers(nullptr, "load-extension-defaults", nullptr); obsSvc->NotifyObservers(nullptr, "profile-after-change", kStartup); diff --git a/toolkit/xre/nsXREDirProvider.h b/toolkit/xre/nsXREDirProvider.h index 80fc0408e41d..7ec64da7877f 100644 --- a/toolkit/xre/nsXREDirProvider.h +++ b/toolkit/xre/nsXREDirProvider.h @@ -126,6 +126,9 @@ protected: nsresult LoadContentProcessTempDir(); #endif + // Calculate and register extension and theme bundle directories. + void LoadExtensionBundleDirectories(); + #ifdef MOZ_B2G // Calculate and register app-bundled extension directories. void LoadAppBundleDirs(); @@ -148,6 +151,8 @@ protected: nsCOMPtr mContentProcessSandboxTempDir; #endif nsCOMArray mAppBundleDirectories; + nsCOMArray mExtensionDirectories; + nsCOMArray mThemeDirectories; }; #endif