--- a/toolkit/components/build/nsToolkitCompsModule.cpp
+++ b/toolkit/components/build/nsToolkitCompsModule.cpp
@@ -31,16 +31,17 @@
#include "nsUrlClassifierStreamUpdater.h"
#include "nsUrlClassifierUtils.h"
#include "nsUrlClassifierPrefixSet.h"
#include "nsBrowserStatusFilter.h"
#include "mozilla/FinalizationWitnessService.h"
#include "mozilla/NativeOSFileInternals.h"
#include "mozilla/AddonContentPolicy.h"
+#include "mozilla/AddonManagerStartup.h"
#include "mozilla/AddonPathService.h"
#if defined(XP_WIN)
#include "NativeFileWatcherWin.h"
#else
#include "NativeFileWatcherNotSupported.h"
#endif // (XP_WIN)
@@ -119,16 +120,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsBrowser
NS_GENERIC_FACTORY_CONSTRUCTOR(nsUpdateProcessor)
#endif
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(FinalizationWitnessService, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR(NativeOSFileInternalsService)
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_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID);
#if defined(MOZ_HAS_PERFSTATS)
NS_DEFINE_NAMED_CID(NS_TOOLKIT_PERFORMANCESTATSSERVICE_CID);
#endif // defined (MOZ_HAS_PERFSTATS)
#if defined(MOZ_HAS_TERMINATOR)
NS_DEFINE_NAMED_CID(NS_TOOLKIT_TERMINATOR_CID);
@@ -152,16 +154,17 @@ NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERUTIL
NS_DEFINE_NAMED_CID(NS_BROWSERSTATUSFILTER_CID);
#if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID)
NS_DEFINE_NAMED_CID(NS_UPDATEPROCESSOR_CID);
#endif
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);
static const Module::CIDEntry kToolkitCIDs[] = {
{ &kNS_TOOLKIT_APPSTARTUP_CID, false, nullptr, nsAppStartupConstructor },
#if defined(MOZ_HAS_TERMINATOR)
{ &kNS_TOOLKIT_TERMINATOR_CID, false, nullptr, nsTerminatorConstructor },
#endif
#if defined(MOZ_HAS_PERFSTATS)
@@ -186,16 +189,17 @@ static const Module::CIDEntry kToolkitCI
{ &kNS_BROWSERSTATUSFILTER_CID, false, nullptr, nsBrowserStatusFilterConstructor },
#if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID)
{ &kNS_UPDATEPROCESSOR_CID, false, nullptr, nsUpdateProcessorConstructor },
#endif
{ &kFINALIZATIONWITNESSSERVICE_CID, false, nullptr, FinalizationWitnessServiceConstructor },
{ &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 },
{ nullptr }
};
static const Module::ContractIDEntry kToolkitContracts[] = {
{ NS_APPSTARTUP_CONTRACTID, &kNS_TOOLKIT_APPSTARTUP_CID },
#if defined(MOZ_HAS_TERMINATOR)
{ NS_TOOLKIT_TERMINATOR_CONTRACTID, &kNS_TOOLKIT_TERMINATOR_CID },
@@ -222,16 +226,17 @@ static const Module::ContractIDEntry kTo
{ NS_BROWSERSTATUSFILTER_CONTRACTID, &kNS_BROWSERSTATUSFILTER_CID },
#if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID)
{ NS_UPDATEPROCESSOR_CONTRACTID, &kNS_UPDATEPROCESSOR_CID },
#endif
{ FINALIZATIONWITNESSSERVICE_CONTRACTID, &kFINALIZATIONWITNESSSERVICE_CID },
{ 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 },
{ nullptr }
};
static const mozilla::Module::CategoryEntry kToolkitCategories[] = {
{ "content-policy", NS_ADDONCONTENTPOLICY_CONTRACTID, NS_ADDONCONTENTPOLICY_CONTRACTID },
{ nullptr }
};
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/AddonManagerStartup-inlines.h
@@ -0,0 +1,281 @@
+/* -*- 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 T, class PropertyType>
+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<const SelfType*>(this);
+ }
+ SelfType& Self()
+ {
+ return *static_cast<SelfType*>(this);
+ }
+
+ JSContext* mCx;
+
+ JS::HandleObject mObject;
+
+ void* mContext;
+};
+
+template<class T, class IterType>
+class MOZ_STACK_CLASS BaseIterElem {
+public:
+ typedef T SelfType;
+
+ 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<const SelfType*>(this);
+ }
+ SelfType& Self() {
+ return *static_cast<SelfType*>(this);
+ }
+
+ const IterType& mIter;
+
+ uint32_t mIndex;
+};
+
+
+/*****************************************************************************
+ * Property iteration
+ *****************************************************************************/
+
+class MOZ_STACK_CLASS PropertyIter
+ : public BaseIter<PropertyIter, PropertyIterElem>
+{
+ friend class PropertyIterElem;
+ friend class BaseIterElem<PropertyIterElem, PropertyIter>;
+
+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<JS::IdVector> mIds;
+};
+
+class MOZ_STACK_CLASS PropertyIterElem
+ : public BaseIterElem<PropertyIterElem, PropertyIter>
+{
+ friend class BaseIterElem<PropertyIterElem, PropertyIter>;
+
+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<jsid> id(mIter.mCx, Id());
+
+ return JS_GetPropertyById(mIter.mCx, mIter.mObject, id, value);
+ }
+
+private:
+ Maybe<nsAutoJSString> mName;
+};
+
+
+/*****************************************************************************
+ * Array iteration
+ *****************************************************************************/
+
+class MOZ_STACK_CLASS ArrayIter
+ : public BaseIter<ArrayIter, ArrayIterElem>
+{
+ friend class ArrayIterElem;
+ friend class BaseIterElem<ArrayIterElem, ArrayIter>;
+
+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<ArrayIterElem, ArrayIter>
+{
+ friend class BaseIterElem<ArrayIterElem, ArrayIter>;
+
+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
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/AddonManagerStartup.cpp
@@ -0,0 +1,561 @@
+/* -*- 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 <stdlib.h>
+
+namespace mozilla {
+
+using Compression::LZ4;
+
+#ifdef XP_WIN
+# define READ_BINARYMODE "rb"
+#else
+# define READ_BINARYMODE "r"
+#endif
+
+AddonManagerStartup*
+AddonManagerStartup::GetSingleton()
+{
+ static RefPtr<AddonManagerStartup> 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<nsIFile>
+CloneAndAppend(nsIFile* aFile, const char* name)
+{
+ nsCOMPtr<nsIFile> 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 stores the decompressed contents in result.
+ *
+ * Returns true on success, or false on failure. A nonexistent or empty file
+ * is treated as success. A corrupt or non-LZ4 file is treated as failure.
+ */
+static bool
+ReadFileLZ4(const char* path, nsCString& result)
+{
+ static const char MAGIC_NUMBER[] = "mozLz40";
+ constexpr auto HEADER_SIZE = sizeof(MAGIC_NUMBER) + 4;
+
+ nsCString lz4 = ReadFile(path);
+ if (lz4.IsEmpty()) {
+ result.Truncate();
+ return true;
+ }
+
+ // Note: We want to include the null terminator here.
+ nsDependentCString magic(MAGIC_NUMBER, sizeof(MAGIC_NUMBER));
+
+ if (lz4.Length() < HEADER_SIZE || StringHead(lz4, magic.Length()) != magic) {
+ return false;
+ }
+
+ auto size = LittleEndian::readUint32(lz4.get() + magic.Length());
+
+ if (!result.SetLength(size, fallible) ||
+ !LZ4::decompress(lz4.get() + HEADER_SIZE, result.BeginWriting(), size)) {
+ result.Truncate();
+ return false;
+ }
+
+ return true;
+}
+
+
+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, 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, 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 CheckStartupModifications() { return GetBool("checkStartupModifications"); }
+
+
+private:
+ JS::RootedObject mAddonsObj;
+ Maybe<PropertyIter> 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<InstallLocation*>(iter.Context()))
+ {}
+
+ Addon(const Addon& other)
+ : WrapperBase(other.mCx, other.mObject)
+ , mId(other.mId)
+ , mLocation(other.mLocation)
+ {}
+
+ nsString Id() { return mId; }
+
+ nsString Path() { return GetString("path"); }
+
+ bool Bootstrapped() { return GetBool("bootstrapped"); }
+
+ bool Enabled() { return GetBool("enabled"); }
+
+ bool EnableShims() { return GetBool("enableShims"); }
+
+ double LastModifiedTime() { return GetNumber("lastModifiedTime"); }
+
+
+ already_AddRefed<nsIFile> FullPath();
+
+ NSLocationType LocationType();
+
+ bool UpdateLastModifiedTime();
+
+
+private:
+ nsString mId;
+ InstallLocation& mLocation;
+};
+
+already_AddRefed<nsIFile>
+Addon::FullPath()
+{
+ nsString path = mLocation.Path();
+
+ nsCOMPtr<nsIFile> 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<nsIFile> file = FullPath();
+
+ bool result;
+ if (NS_FAILED(file->Exists(&result)) || !result) {
+ return true;
+ }
+
+ PRTime time;
+
+ nsCOMPtr<nsIFile> 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, 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(nsAString& addonId)
+{
+ NS_ConvertUTF16toUTF8 id(addonId);
+
+ nsCOMPtr<nsIAddonInterposition> 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<nsIFile> 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<nsIFile> manifest = CloneAndAppend(file, "chrome.manifest");
+ XRE_AddManifestLocation(type, manifest);
+ }
+}
+
+nsresult
+AddonManagerStartup::ReadStartupData(JSContext* cx, JS::MutableHandleValue locations)
+{
+ nsresult rv;
+
+ locations.set(JS::UndefinedValue());
+
+ nsCOMPtr<nsIFile> file = CloneAndAppend(ProfileDir(), "addonStartup.json.lz4");
+
+ nsCString path;
+ rv = file->GetNativePath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString data;
+ if (!ReadFileLZ4(path.get(), data)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (data.IsEmpty() || !ParseJSON(cx, data, locations)) {
+ return NS_OK;
+ }
+
+ if (!locations.isObject()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ JS::RootedObject locs(cx, &locations.toObject());
+ for (InstallLocation loc : PropertyIter(cx, locs)) {
+ if (!loc.CheckStartupModifications()) {
+ continue;
+ }
+
+ for (Addon addon : loc.Addons()) {
+ 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 (InstallLocation loc : PropertyIter(cx, locs)) {
+ for (Addon addon : loc.Addons()) {
+ if (!addon.Bootstrapped()) {
+ AddInstallLocation(addon);
+ }
+
+ if (enableInterpositions && addon.EnableShims()) {
+ nsString addonId = addon.Id();
+ EnableShims(addonId);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+AddonManagerStartup::Reset()
+{
+ MOZ_RELEASE_ASSERT(xpc::IsInAutomation());
+
+ mInitialized = false;
+
+ mExtensionPaths.Clear();
+ mThemePaths.Clear();
+
+ return NS_OK;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/AddonManagerStartup.h
@@ -0,0 +1,73 @@
+/* -*- 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<AddonManagerStartup> GetInstance()
+ {
+ RefPtr<AddonManagerStartup> inst = GetSingleton();
+ return inst.forget();
+ }
+
+ const nsCOMArray<nsIFile>& ExtensionPaths()
+ {
+ return mExtensionPaths;
+ }
+
+ const nsCOMArray<nsIFile>& ThemePaths()
+ {
+ return mExtensionPaths;
+ }
+
+private:
+ void AddInstallLocation(Addon& addon);
+
+ nsIFile* ProfileDir();
+
+ nsCOMPtr<nsIFile> mProfileDir;
+
+ nsCOMArray<nsIFile> mExtensionPaths;
+ nsCOMArray<nsIFile> 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
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/amIAddonManagerStartup.idl
@@ -0,0 +1,36 @@
+/* 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();
+};
+
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -25,28 +25,33 @@ Cu.import("resource://gre/modules/Task.j
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
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",
"@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");
XPCOMUtils.defineLazyGetter(this, "AppInfo", () => {
let AppInfo = {};
Cu.import("resource://testing-common/AppInfo.jsm", 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",
"nsIArrayBufferInputStream", "setData");
const nsFile = Components.Constructor(
"@mozilla.org/file/local;1",
"nsIFile", "initWithPath");
@@ -124,69 +129,65 @@ function escaped(strings, ...values) {
result.push(escapeXML(values[i]));
}
return result.join("");
}
class AddonsList {
- constructor(extensionsINI) {
+ constructor(file) {
this.multiprocessIncompatibleIDs = new Set();
+ this.extensions = [];
+ this.themes = [];
- if (!extensionsINI.exists()) {
- this.extensions = [];
- this.themes = [];
+ if (!file.exists()) {
return;
}
- let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]
- .getService(Ci.nsIINIParserFactory);
-
- let parser = factory.createINIParser(extensionsINI);
+ let data = aomStartup.readStartupData();
- function readDirectories(section) {
- var dirs = [];
- var keys = parser.getKeys(section);
- for (let key of XPCOMUtils.IterStringEnumerator(keys)) {
- let descriptor = parser.getString(section, key);
+ for (let loc of Object.values(data)) {
+ let dir = loc.path && new nsFile(loc.path);
- 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;
+ for (let [id, addon] of Object.entries(loc.addons)) {
+ if (addon.enabled && !addon.bootstrapped) {
+ let file;
+ if (dir) {
+ file = dir.clone();
+ file.appendRelativePath(addon.path);
+ } else {
+ file = new nsFile(addon.path);
+ }
+
+ 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);
+ }
+ }
}
- 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);
}
}
hasItem(type, dir, id) {
var path = dir.clone();
path.append(id);
var xpiPath = dir.clone();
xpiPath.append(`${id}.xpi`);
return this[type].some(file => {
if (!file.exists())
- throw new Error(`Non-existent path found in extensions.ini: ${file.path}`);
+ throw new Error(`Non-existent path found in addonStartup.json: ${file.path}`);
if (file.isDirectory())
return file.equals(path);
if (file.isFile())
return file.equals(xpiPath);
return false;
});
}
@@ -203,31 +204,31 @@ class AddonsList {
return this.hasItem("extensions", dir, id);
}
}
var AddonTestUtils = {
addonIntegrationService: null,
addonsList: null,
appInfo: null,
- extensionsINI: null,
+ addonStartup: null,
testUnpacked: false,
useRealCertChecks: false,
init(testScope) {
this.testScope = testScope;
// Get the profile directory for tests to use.
this.profileDir = testScope.do_get_profile();
this.profileExtensions = this.profileDir.clone();
this.profileExtensions.append("extensions");
- this.extensionsINI = this.profileDir.clone();
- this.extensionsINI.append("extensions.ini");
+ this.addonStartup = this.profileDir.clone();
+ this.addonStartup.append("addonStartup.json.lz4");
// Register a temporary directory for the tests.
this.tempDir = this.profileDir.clone();
this.tempDir.append("temp");
this.tempDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
this.registerDirectory("TmpD", this.tempDir);
// Create a replacement app directory for the tests.
@@ -546,37 +547,33 @@ var AddonTestUtils = {
/**
* Starts up the add-on manager as if it was started by the application.
*
* @param {boolean} [appChanged = true]
* 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.
*/
- promiseStartupManager(appChanged = true) {
+ async promiseStartupManager(appChanged = true) {
if (this.addonIntegrationService)
throw new Error("Attempting to startup manager that was already started.");
- if (appChanged && this.extensionsINI.exists())
- this.extensionsINI.remove(true);
+ if (appChanged && this.addonStartup.exists())
+ this.addonStartup.remove(true);
this.addonIntegrationService = Cc["@mozilla.org/addons/integration;1"]
.getService(Ci.nsIObserver);
this.addonIntegrationService.observe(null, "addons-startup", null);
this.emit("addon-manager-started");
// Load the add-ons list as it was after extension registration
- this.loadAddonsList();
-
- return Promise.resolve();
+ await this.loadAddonsList(true);
},
promiseShutdownManager() {
if (!this.addonIntegrationService)
return Promise.resolve(false);
Services.obs.notifyObservers(null, "quit-application-granted");
return MockAsyncShutdown.hook()
@@ -596,16 +593,22 @@ var AddonTestUtils = {
let XPIscope = Cu.import("resource://gre/modules/addons/XPIProvider.jsm", {});
// This would be cleaner if I could get it as the rejection reason from
// the AddonManagerInternal.shutdown() promise
let shutdownError = XPIscope.XPIProvider._shutdownError;
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;
return true;
});
},
promiseRestartManager(newVersion) {
@@ -613,18 +616,24 @@ var AddonTestUtils = {
.then(() => {
if (newVersion)
this.appInfo.version = newVersion;
return this.promiseStartupManager(!!newVersion);
});
},
- loadAddonsList() {
- this.addonsList = new AddonsList(this.extensionsINI);
+ 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);
},
/**
* Creates an update.rdf structure as a string using for the update data passed.
*
* @param {Object} data
* The update data as a JS object. Each property name is an add-on ID,
* the property value is an array of each version of the add-on. Each
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -53,16 +53,18 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyModuleGetter(this, "ProductAddonChecker",
"resource://gre/modules/addons/ProductAddonChecker.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
"resource://gre/modules/UpdateUtils.jsm");
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");
const {nsIBlocklistService} = Ci;
XPCOMUtils.defineLazyServiceGetter(this, "Blocklist",
"@mozilla.org/extensions/blocklist;1",
"nsIBlocklistService");
XPCOMUtils.defineLazyServiceGetter(this,
@@ -76,16 +78,19 @@ XPCOMUtils.defineLazyServiceGetter(this,
XPCOMUtils.defineLazyServiceGetter(this,
"AddonPolicyService",
"@mozilla.org/addons/policy-service;1",
"nsIAddonPolicyService");
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 = {};
Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
return certUtils;
});
XPCOMUtils.defineLazyGetter(this, "IconDetails", () => {
@@ -94,33 +99,84 @@ XPCOMUtils.defineLazyGetter(this, "IconD
});
Cu.importGlobalProperties(["URL"]);
const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
"initWithPath");
-function getFile(descriptor) {
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- file.persistentDescriptor = descriptor;
- return file;
+/**
+ * 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;
+ }
+}
+
+/**
+ * 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.
+ * @returns {string}
+ * The file's path.
+ */
+function descriptorToPath(descriptor) {
+ try {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.persistentDescriptor = descriptor;
+ return file.path;
+ } catch (e) {
+ return null;
+ }
}
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";
const PREF_SKIN_SWITCHPENDING = "extensions.dss.switchPending";
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";
const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest";
const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest";
@@ -145,26 +201,34 @@ const PREF_EM_MIN_COMPAT_APP_VERSION
const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
const PREF_CHECKCOMAT_THEMEOVERRIDE = "extensions.checkCompatibility.temporaryThemeOverride_minAppVersion";
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";
const STRING_TYPE_NAME = "type.%ID%.name";
const DIR_EXTENSIONS = "extensions";
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";
const FILE_WEB_MANIFEST = "manifest.json";
const FILE_XPI_ADDONS_LIST = "extensions.ini";
const KEY_PROFILEDIR = "ProfD";
const KEY_ADDON_APP_DIR = "XREAddonAppDir";
@@ -176,16 +240,21 @@ const KEY_APP_PROFILE =
const KEY_APP_SYSTEM_ADDONS = "app-system-addons";
const KEY_APP_SYSTEM_DEFAULTS = "app-system-defaults";
const KEY_APP_GLOBAL = "app-global";
const KEY_APP_SYSTEM_LOCAL = "app-system-local";
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";
const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest";
const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
const TOOLKIT_ID = "toolkit@mozilla.org";
@@ -333,34 +402,31 @@ XPCOMUtils.defineLazyPreferenceGetter(th
function loadLazyObjects() {
let uri = "resource://gre/modules/addons/XPIProviderUtils.js";
let scope = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), {
sandboxName: uri,
wantGlobalProperties: ["TextDecoder"],
});
- let shared = {
+ Object.assign(scope, {
ADDON_SIGNING,
SIGNED_TYPES,
BOOTSTRAP_REASONS,
DB_SCHEMA,
AddonInternal,
XPIProvider,
XPIStates,
syncLoadManifestFromFile,
isUsableAddon,
recordAddonTelemetry,
applyBlocklistChanges,
flushChromeCaches,
- canRunInSafeMode,
- }
-
- for (let key of Object.keys(shared))
- scope[key] = shared[key];
+ descriptorToPath,
+ });
Services.scriptloader.loadSubScript(uri, scope);
for (let name of LAZY_OBJECTS) {
delete gGlobalScope[name];
gGlobalScope[name] = scope[name];
}
gLazyObjectsLoaded = true;
@@ -1663,17 +1729,17 @@ function syncLoadManifestFromFile(aFile,
* The file containing the resources, must be either a directory or an
* XPI file
* @param aPath
* The path to find the resource at, "/" separated. If aPath is empty
* then the uri to the root of the contained files will be returned
* @return an nsIURI pointing at the resource
*/
function getURIForResourceInFile(aFile, aPath) {
- if (aFile.isDirectory()) {
+ if (aFile.exists() && aFile.isDirectory()) {
let resource = aFile.clone();
if (aPath)
aPath.split("/").forEach(part => resource.append(part));
return Services.io.newFileURI(resource);
}
return buildJarURI(aFile, aPath);
@@ -2070,17 +2136,19 @@ function getDirectoryEntries(aDir, aSort
if (aSortEntries) {
entries.sort(function(a, b) {
return a.path > b.path ? -1 : 1;
});
}
return entries
} catch (e) {
- logger.warn("Can't iterate directory " + aDir.path, e);
+ if (aDir.exists()) {
+ logger.warn("Can't iterate directory " + aDir.path, e);
+ }
return [];
} finally {
if (dirEnum) {
dirEnum.close();
}
}
}
@@ -2095,93 +2163,183 @@ function recordAddonTelemetry(aAddon) {
XPIProvider.setTelemetry(aAddon.id, "name", locale.name);
if (locale.creator)
XPIProvider.setTelemetry(aAddon.id, "creator", locale.creator);
}
}
/**
* The on-disk state of an individual XPI, created from an Object
- * as stored in the 'extensions.xpiState' pref.
+ * as stored in the addonStartup.json file.
*/
-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"]],
- /**
- * Return the last modified time, based on enabled/disabled
- */
+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;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ static migrate(location, id, saved, bootstrapped) {
+ let data = {
+ enabled: saved.e,
+ path: descriptorToPath(saved.d),
+ 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);
+
+ 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() {
- if (!this.enabled && ("manifestTime" in this) && this.manifestTime > this.scanTime) {
- return this.manifestTime;
- }
- return this.scanTime;
- },
-
+ 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) {
+ return this.file.getRelativePath(this.location.dir);
+ }
+ return this.path;
+ }
+
+ /**
+ * Returns a JSON-compatible representation of this add-on's state
+ * data, to be saved to addonStartup.json.
+ */
toJSON() {
- let json = {};
- for (let [short, long] of XPIState.prototype.fields) {
- if (long in this) {
- json[short] = this[long];
- }
+ 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;
}
return json;
- },
+ }
/**
* Update the last modified time for an add-on on disk.
* @param aFile: nsIFile path of the add-on.
* @param aId: The add-on ID.
* @return True if the time stamp has changed.
*/
getModTime(aFile, aId) {
- let changed = false;
- let scanStarted = Cu.now();
// 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);
- let maniTime = maniFile.lastModifiedTime;
- if (maniTime != this.manifestTime) {
- this.manifestTime = maniTime;
- this.scanTime = 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));
-
- return changed;
- },
+ let mtime = (tryGetMtime(getManifestFileForDir(aFile)) ||
+ tryGetMtime(aFile));
+ if (!mtime) {
+ logger.warn("Can't get modified time of ${file}", {file: aFile.path});
+ }
+
+ this.changed = mtime != this.lastModifiedTime;
+ this.lastModifiedTime = mtime;
+ return this.changed;
+ }
/**
* Update the XPIState to match an XPIDatabase entry; if 'enabled' is changed to true,
* update the last-modified time. This should probably be made async, but for now we
* don't want to maintain parallel sync and async versions of the scan.
* Caller is responsible for doing XPIStates.save() if necessary.
* @param aDBAddon The DBAddonInternal for this add-on.
* @param aUpdated The add-on was updated, so we must record new modified time.
@@ -2191,48 +2349,150 @@ XPIState.prototype = {
// If the add-on changes from disabled to enabled, we should re-check the modified time.
// If this is a newly found add-on, it won't have an 'enabled' field but we
// 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.version = aDBAddon.version;
- // XXX Eventually also copy bootstrap, etc.
+ 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);
+ }
+
if (aUpdated || mustGetMod) {
- this.getModTime(new nsIFile(this.descriptor), aDBAddon.id);
- if (this.scanTime != aDBAddon.updateDate) {
- aDBAddon.updateDate = this.scanTime;
- XPIDatabase.saveChanges();
- }
- }
- },
-};
-
-// 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;
- }
- return out;
- };
- return m;
+ this.getModTime(this.file, aDBAddon.id);
+ if (this.lastModifiedTime != aDBAddon.updateDate) {
+ aDBAddon.updateDate = this.lastModifiedTime;
+ if (XPIDatabase.initialized) {
+ 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;
+ }
+ }
+
+ /**
+ * 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));
+ }
}
/**
* Keeps track of the state of XPI add-ons on the file system.
*/
this.XPIStates = {
// Map(location name -> Map(add-on ID -> XPIState))
db: null,
+ _jsonFile: null,
+
/**
* @property {Map<string, XPIState>} 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(),
@@ -2242,134 +2502,188 @@ this.XPIStates = {
for (let location of this.db.values()) {
count += location.size;
}
}
return count;
},
/**
- * Load extension state data from preferences.
+ * 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.
*/
loadExtensionState() {
- let state = {};
-
- // Clear out old directory state cache.
- Preferences.reset(PREF_INSTALL_CACHE);
-
- let cache = Preferences.get(PREF_XPI_STATE, "{}");
+ let state;
try {
- state = JSON.parse(cache);
+ state = aomStartup.readStartupData();
} catch (e) {
- logger.warn("Error parsing extensions.xpiState ${state}: ${error}",
- {state: cache, error: e});
- }
- logger.debug("Loaded add-on state from prefs: ${}", state);
- return state;
+ logger.warn("Error parsing extensions state: ${error}",
+ {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 || {};
},
/**
* 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) {
- let oldState = this.loadExtensionState();
+ if (!this.db) {
+ this.db = new Map();
+ }
+
+ let oldState = this.initialStateData || this.loadExtensionState();
+ this.initialStateData = oldState;
+
let changed = false;
- this.db = new SerializableMap();
+ let oldLocations = new Set(Object.keys(oldState));
for (let location of XPIProvider.installLocations) {
+ oldLocations.delete(location.name);
+
// The results of scanning this location.
- let foundAddons = new SerializableMap();
+ let loc = this.getLocation(location.name, location.path || null,
+ oldState[location.name]);
+ changed = changed || loc.changed;
// Don't bother checking scopes where we don't accept side-loads.
if (ignoreSideloads && !(location.scope & gStartupScanScopes)) {
- if (location.name in oldState) {
- for (let [id, state] of Object.entries(oldState[location.name])) {
- foundAddons.set(id, new XPIState(state));
+ 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) {
+ 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);
}
-
- this.db.set(location.name, foundAddons);
- delete oldState[location.name];
- }
- continue;
- }
-
- // The list of add-on like file/directory names in the install location.
- let addons = location.getAddonLocations(!ignoreSideloads);
-
- // 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];
- }
-
- for (let [id, file] of addons) {
- if (!(id in locState)) {
- logger.debug("New add-on ${id} in ${location}", {id, location: location.name});
- let xpiState = new XPIState({d: file.persistentDescriptor});
- changed = xpiState.getModTime(file, id) || changed;
- foundAddons.set(id, xpiState);
- this.sideLoadedAddons.set(id, xpiState);
} else {
- let xpiState = new XPIState(locState[id]);
- // We found this add-on in the file system
- delete locState[id];
-
- changed = xpiState.getModTime(file, id) || changed;
-
- if (file.persistentDescriptor != xpiState.descriptor) {
- xpiState.descriptor = file.persistentDescriptor;
+ let addonChanged = (xpiState.getModTime(file, id) ||
+ file.path != xpiState.path);
+ xpiState.file = file.clone();
+
+ if (addonChanged) {
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.
- if (Object.keys(locState).length) {
+ for (let id of knownIds) {
+ loc.delete(id);
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.
- if (Object.keys(oldState).length) {
- changed = true;
- }
+ changed = changed || oldLocations.size > 0;
logger.debug("getInstallState changed: ${rv}, state: ${state}",
{rv: changed, state: this.db});
return changed;
},
/**
* Get the Map of XPI states for a particular 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(aLocation) {
- return this.db.get(aLocation);
+ * @param name The name of the install location.
+ * @return XPIStateLocation (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;
},
/**
* Get the XPI state for a specific add-on in a location.
* If the state is not in our cache, return null.
* @param aLocation The name of the location where the add-on is installed.
* @param aId The add-on ID
* @return The XPIState entry for the add-on, or null.
@@ -2378,58 +2692,98 @@ this.XPIStates = {
let location = this.db.get(aLocation);
return location && 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 [locationName, XPIState] if the add-on is found, [undefined, undefined]
- * if the add-on is not found.
+ * @return {XPIState?}
*/
findAddon(aId) {
// Fortunately the Map iterator returns in order of insertion, which is
// also our highest -> lowest priority order.
- for (let [name, location] of this.db) {
+ for (let location of this.db.values()) {
if (location.has(aId)) {
- return [name, location.get(aId)];
- }
- }
- return [undefined, undefined];
+ 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;
+ }
+ }
},
/**
* Add a new XPIState for an add-on and synchronize it with the DBAddonInternal.
* @param aAddon DBAddonInternal for the new add-on.
*/
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);
+ let location = this.getLocation(aAddon._installLocation.name);
+ location.addAddon(aAddon);
},
/**
* Save the current state of installed add-ons.
- * XXX this *totally* should be a .json file using DeferredSave...
*/
save() {
- let db = new SerializableMap(this.db);
- db.delete(TemporaryInstallLocation.name);
-
- let cache = JSON.stringify(db);
- Services.prefs.setCharPref(PREF_XPI_STATE, cache);
+ 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;
+ }
+
+ 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;
},
/**
* Remove the XPIState for an add-on and save the new state.
* @param aLocation The name of the add-on location.
* @param aId The ID of the add-on.
*/
removeAddon(aLocation, aId) {
@@ -2440,18 +2794,16 @@ this.XPIStates = {
if (location.size == 0) {
this.db.delete(aLocation);
}
this.save();
}
},
};
-const hasOwnProperty = Function.call.bind({}.hasOwnProperty);
-
this.XPIProvider = {
get name() {
return "XPIProvider";
},
// An array of known install locations
installLocations: null,
// A dictionary of known install locations by name
@@ -2465,27 +2817,23 @@ this.XPIProvider = {
// The selected skin to be used by the application when it is restarted. This
// will be the same as currentSkin when it is the skin to be used when the
// application is restarted
selectedSkin: null,
// The value of the minCompatibleAppVersion preference
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
extensionsActive: false,
// 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,
// Per-addon telemetry information
_telemetryDetails: {},
// A Map from an add-on install to its ID
_addonFileMap: new Map(),
// Flag to know if ToolboxProcess.jsm has already been loaded by someone or not
_toolboxProcessLoaded: false,
@@ -2496,41 +2844,39 @@ this.XPIProvider = {
* Returns true if the add-on with the given ID is currently active,
* without forcing the add-ons database to load.
*
* @param {string} addonId
* The ID of the add-on to check.
* @returns {boolean}
*/
addonIsActive(addonId) {
- if (hasOwnProperty(this.bootstrappedAddons, addonId)) {
- return true;
- }
-
- let [, state] = XPIStates.findAddon(addonId);
+ let state = XPIStates.findAddon(addonId);
return state && state.enabled;
},
/**
* Returns an array of the add-on values in `bootstrappedAddons`,
* sorted so that all of an add-on's dependencies appear in the array
* before itself.
*
* @returns {Array<object>}
* A sorted array of add-on objects. Each value is a copy of the
* corresponding value in the `bootstrappedAddons` object, with an
* additional `id` property, which corresponds to the key in that
* 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 = {};
-
- // 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]);
+ for (let entry of list) {
+ addons[entry.id] = entry;
}
let res = new Set();
let seen = new Set();
let add = addon => {
seen.add(addon.id);
@@ -2804,17 +3150,16 @@ this.XPIProvider = {
this.defaultSkin);
this.selectedSkin = this.currentSkin;
this.applyThemeChange();
this.minCompatibleAppVersion = Preferences.get(PREF_EM_MIN_COMPAT_APP_VERSION,
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)
Services.prefs.addObserver(PREF_XPI_SIGNATURES_REQUIRED, this);
Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS);
@@ -2829,16 +3174,17 @@ this.XPIProvider = {
BrowserToolboxProcess.on("connectionchange",
this.onDebugConnectionChange.bind(this));
} else {
// Else, wait for it to load
Services.obs.addObserver(this, NOTIFICATION_TOOLBOXPROCESS_LOADED);
}
}
+
let flushCaches = this.checkForChanges(aAppChanged, aOldAppVersion,
aOldPlatformVersion);
// Changes to installed extensions may have changed which theme is selected
this.applyThemeChange();
AddonManagerPrivate.markProviderSafe(this);
@@ -2856,18 +3202,16 @@ this.XPIProvider = {
// UI displayed early in startup (like the compatibility UI) may have
// caused us to cache parts of the skin or locale in memory. These must
// be flushed to allow extension provided skins and locales to take full
// effect
Services.obs.notifyObservers(null, "chrome-flush-skin-caches");
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.
try {
Services.appinfo.annotateCrashReport("Theme", this.currentSkin);
} catch (e) { }
try {
Services.appinfo.annotateCrashReport("EMCheckCompatibility",
@@ -2876,25 +3220,24 @@ this.XPIProvider = {
this.addAddonsToCrashReporter();
}
try {
AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_begin");
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
if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
.indexOf(addon.id) !== -1)
reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
this.callBootstrapMethod(createAddonDetails(addon.id, addon),
- file, "startup", reason);
+ addon.file, "startup", reason);
} catch (e) {
logger.error("Failed to load bootstrap addon " + addon.id + " from " +
addon.descriptor, e);
}
}
AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_end");
} catch (e) {
logger.error("bootstrap startup failed", e);
@@ -2908,27 +3251,25 @@ this.XPIProvider = {
XPIProvider._closing = true;
for (let addon of XPIProvider.sortBootstrappedAddons().reverse()) {
// If no scope has been loaded for this add-on then there is no need
// to shut it down (should only happen when a bootstrapped add-on is
// pending enable)
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, file, "shutdown",
+ XPIProvider.callBootstrapMethod(addonDetails, addon.file, "shutdown",
BOOTSTRAP_REASONS.ADDON_DISABLE);
- delete XPIProvider.bootstrappedAddons[addon.id];
} else {
- XPIProvider.callBootstrapMethod(addonDetails, file, "shutdown",
+ XPIProvider.callBootstrapMethod(addonDetails, addon.file, "shutdown",
BOOTSTRAP_REASONS.APP_SHUTDOWN);
}
}
Services.obs.removeObserver(this, "quit-application-granted");
}
}, "quit-application-granted");
// Detect final-ui-startup for telemetry reporting
@@ -2969,49 +3310,44 @@ this.XPIProvider = {
this.cancelAll();
// Uninstall any temporary add-ons.
let tempLocation = XPIStates.getLocation(TemporaryInstallLocation.name);
if (tempLocation) {
for (let [id, addon] of tempLocation.entries()) {
tempLocation.delete(id);
- let file = getFile(addon.descriptor);
-
- this.callBootstrapMethod(createAddonDetails(id, this.bootstrappedAddons[id]),
- file, "uninstall",
+ this.callBootstrapMethod(createAddonDetails(id, addon),
+ addon.file, "uninstall",
BOOTSTRAP_REASONS.ADDON_UNINSTALL);
this.unloadBootstrapScope(id);
TemporaryInstallLocation.uninstallAddon(id);
- let [locationName, ] = XPIStates.findAddon(id);
- if (locationName) {
- let newAddon = XPIDatabase.makeAddonLocationVisible(id, locationName);
-
- let file = getFile(newAddon.descriptor);
+ let state = XPIStates.findAddon(id);
+ if (state) {
+ let newAddon = XPIDatabase.makeAddonLocationVisible(id, state.location.name);
+
+ let file = new nsIFile(newAddon.path);
this.callBootstrapMethod(createAddonDetails(id, newAddon),
file, "install",
BOOTSTRAP_REASONS.ADDON_INSTALL);
}
}
}
- this.bootstrappedAddons = {};
this.activeAddons.clear();
- this.enabledAddons = null;
this.allAppGlobal = true;
// If there are pending operations then we must update the list of active
// add-ons
if (Preferences.get(PREF_PENDING_OPERATIONS, false)) {
AddonManagerPrivate.recordSimpleMeasure("XPIDB_pending_ops", 1);
XPIDatabase.updateActiveAddons();
- Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
- !XPIDatabase.writeAddonsList());
+ Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false);
}
this.installs = null;
this.installLocations = null;
this.installLocationsByName = null;
// This is needed to allow xpcshell tests to simulate a restart
this.extensionsActive = false;
@@ -3117,19 +3453,17 @@ this.XPIProvider = {
variant.setFromVariant(aAddonIDs);
// This *must* be modal as it has to block startup.
var features = "chrome,centerscreen,dialog,titlebar,modal";
var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
getService(Ci.nsIWindowWatcher);
ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, variant);
- // Ensure any changes to the add-ons list are flushed to disk
- Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
- !XPIDatabase.writeAddonsList());
+ Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false);
},
async updateSystemAddons() {
let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS];
if (!systemAddonLocation)
return;
// Don't do anything in safe mode
@@ -3295,52 +3629,30 @@ this.XPIProvider = {
Services.obs.notifyObservers(null, "xpi-signature-changed", JSON.stringify(changes));
}).then(null, err => {
logger.error("XPI_verifySignature: " + err);
})
});
},
/**
- * 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.
*/
addAddonsToCrashReporter() {
if (!("nsICrashReporter" in Ci) ||
!(Services.appinfo instanceof Ci.nsICrashReporter))
return;
// In safe mode no add-ons are loaded so we should not include them in the
// crash report
if (Services.appinfo.inSafeMode)
return;
- let data = this.enabledAddons;
- for (let id in this.bootstrappedAddons) {
- data += (data ? "," : "") + encodeURIComponent(id) + ":" +
- encodeURIComponent(this.bootstrappedAddons[id].version);
- }
+ let data = Array.from(XPIStates.enabledAddons(),
+ a => `${a.id}:${a.version}`).join(",");
try {
Services.appinfo.annotateCrashReport("Add-ons", data);
} catch (e) { }
let TelemetrySession =
Cu.import("resource://gre/modules/TelemetrySession.jsm", {}).TelemetrySession;
TelemetrySession.setAddOns(data);
@@ -3434,16 +3746,17 @@ this.XPIProvider = {
"uninstall", BOOTSTRAP_REASONS.ADDON_UNINSTALL);
}
} catch (e) {
logger.warn("Failed to call uninstall for " + id, e);
}
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);
}
// The file check later will spot the removal and cleanup the database
continue;
}
}
@@ -3502,46 +3815,48 @@ this.XPIProvider = {
}
}
seenFiles.push(jsonfile.leafName);
existingAddonID = addon.existingAddonID || id;
var oldBootstrap = null;
logger.debug("Processing install of " + id + " in " + location.name);
- if (existingAddonID in this.bootstrappedAddons) {
+ let existingAddon = XPIStates.findAddon(existingAddonID);
+ if (existingAddon && existingAddon.bootstrapped) {
try {
- var existingAddon = getFile(this.bootstrappedAddons[existingAddonID].descriptor);
- if (existingAddon.exists()) {
- oldBootstrap = this.bootstrappedAddons[existingAddonID];
+ var file = existingAddon.file;
+ if (file.exists()) {
+ oldBootstrap = existingAddon;
// We'll be replacing a currently active bootstrapped add-on so
// call its uninstall method
let newVersion = addon.version;
- let oldVersion = oldBootstrap.version;
+ let oldVersion = existingAddon;
let uninstallReason = Services.vc.compare(oldVersion, newVersion) < 0 ?
BOOTSTRAP_REASONS.ADDON_UPGRADE :
BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
- this.callBootstrapMethod(createAddonDetails(existingAddonID, oldBootstrap),
- existingAddon, "uninstall", uninstallReason,
+ this.callBootstrapMethod(createAddonDetails(existingAddonID, existingAddon),
+ file, "uninstall", uninstallReason,
{ newVersion });
this.unloadBootstrapScope(existingAddonID);
flushChromeCaches();
}
} catch (e) {
}
}
try {
addon._sourceBundle = location.installAddon({
id,
source: stageDirEntry,
existingAddonID
});
+ XPIStates.addAddon(addon);
} catch (e) {
logger.error("Failed to install staged add-on " + id + " in " + location.name,
e);
// Re-create the staged install
new StagedAddonInstall(location, stageDirEntry, addon);
// Make sure not to delete the cached manifest json file
seenFiles.pop();
@@ -3663,16 +3978,17 @@ this.XPIProvider = {
}
} else if (Preferences.get(PREF_BRANCH_INSTALLED_ADDON + id, false)) {
continue;
}
// 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)
// aManifests may contain a copy of a newly installed add-on's manifest
// and we'll have overwritten that so instead cache our install manifest
// which will later be put into the database in processFileChanges
if (!(KEY_APP_PROFILE in aManifests))
@@ -3726,24 +4042,19 @@ this.XPIProvider = {
// Keep track of whether and why we need to open and update the database at
// startup time.
let updateReasons = [];
if (aAppChanged) {
updateReasons.push("appChanged");
}
- // 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);
+ let installChanged = XPIStates.getInstallState(aAppChanged === false);
+ if (installChanged) {
+ updateReasons.push("directoryState");
}
// First install any new add-ons into the locations, if there are any
// changes then we must update the database with the information in the
// install locations
let manifests = {};
let updated = this.processPendingFileChanges(manifests);
if (updated) {
@@ -3761,25 +4072,16 @@ this.XPIProvider = {
// If the application has changed then check for new distribution add-ons
if (Preferences.get(PREF_INSTALL_DISTRO_ADDONS, true)) {
updated = this.installDistributionAddons(manifests, aAppChanged);
if (updated) {
updateReasons.push("installDistributionAddons");
}
}
- // Telemetry probe added around getInstallState() to check perf
- let telemetryCaptureTime = Cu.now();
- let installChanged = XPIStates.getInstallState(aAppChanged === false);
- 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
if (DB_SCHEMA != Preferences.get(PREF_DB_SCHEMA, 0)) {
// If we don't have any add-ons, just update the pref, since we don't need to
// write the database
if (!haveAnyAddons) {
logger.debug("Empty XPI database, setting schema version preference to " + DB_SCHEMA);
@@ -3792,34 +4094,16 @@ this.XPIProvider = {
// If the database doesn't exist and there are add-ons installed then we
// must update the database however if there are no add-ons then there is
// no need to update the database.
let dbFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
if (!dbFile.exists() && haveAnyAddons) {
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;
// If the database needs to be updated then open it and then update it
// from the filesystem
if (updateReasons.length > 0) {
AddonManagerPrivate.recordSimpleMeasure("XPIDB_startup_load_reasons", updateReasons);
XPIDatabase.syncLoadDB(false);
@@ -3853,43 +4137,37 @@ this.XPIProvider = {
logger.warn("Unable to remove old extension cache " + oldCache.path, e);
}
}
// 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);
return true;
}
+ aomStartup.initializeExtensions(XPIStates.initialStateData);
+
logger.debug("No changes found");
} catch (e) {
logger.error("Error during startup file checks", e);
}
- // 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();
- }
-
return false;
},
_updateActiveAddons() {
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));
+ 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<Array<Addon>>}
@@ -4802,36 +5080,27 @@ this.XPIProvider = {
* An array of add-on IDs on which this add-on depends.
* @param hasEmbeddedWebExtension
* Boolean indicating whether the add-on has an embedded webextension.
* @return a JavaScript scope
*/
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,
bootstrapScope: null,
// 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
if (aType == "locale") {
return;
}
logger.debug("Loading bootstrap scope from " + aFile.path);
@@ -4905,18 +5174,16 @@ this.XPIProvider = {
*/
unloadBootstrapScope(aId) {
// In case the add-on was not multiprocess-compatible, deregister
// any interpositions for it.
Cu.setAddonInterposition(aId, null);
Cu.allowCPOWsInAddon(aId, false);
this.activeAddons.delete(aId);
- delete this.bootstrappedAddons[aId];
- this.persistBootstrappedAddons();
this.addAddonsToCrashReporter();
// Only access BrowserToolboxProcess if ToolboxProcess.jsm has been
// initialized as otherwise, there won't be any addon globals added to it
if (this._toolboxProcessLoaded) {
BrowserToolboxProcess.setAddonOptions(aId, { global: null });
}
},
@@ -5154,33 +5421,16 @@ this.XPIProvider = {
AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
} else {
if (aAddon.bootstrap) {
this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup",
BOOTSTRAP_REASONS.ADDON_ENABLE);
}
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();
@@ -5301,19 +5551,19 @@ this.XPIProvider = {
}
// We always send onInstalled even if a restart is required to enable
// the revealed add-on
AddonManagerPrivate.callAddonListeners("onInstalled", wrappedAddon);
}
function findAddonAndReveal(aId) {
- let [locationName, ] = XPIStates.findAddon(aId);
- if (locationName) {
- XPIDatabase.getAddonInLocation(aId, locationName, revealAddon);
+ let state = XPIStates.findAddon(aId);
+ if (state) {
+ XPIDatabase.getAddonInLocation(aId, state.location.name, revealAddon);
}
}
if (!makePending) {
if (aAddon.bootstrap) {
if (aAddon.active) {
this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
BOOTSTRAP_REASONS.ADDON_UNINSTALL);
@@ -5359,16 +5609,19 @@ this.XPIProvider = {
XPIDatabase.setAddonProperties(aAddon, {
pendingUninstall: false
});
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)
let wrapper = aAddon.wrapper;
AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);
if (aAddon.bootstrap && !aAddon.disabled && !this.enableRequiresRestart(aAddon)) {
this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup",
@@ -8038,16 +8291,20 @@ class DirectoryInstallLocation {
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;
+ }
+
/**
* Reads a directory linked to in a file.
*
* @param file
* The file containing the directory path
* @return An nsIFile object representing the linked directory.
*/
_readDirectoryFromFile(aFile) {
@@ -8495,16 +8752,18 @@ class MutableDirectoryInstallLocation ex
// rolling back the uninstall at this point
try {
recursiveRemove(trashDir);
} catch (e) {
logger.warn("Failed to remove trash directory when uninstalling " + aId, e);
}
}
+ XPIStates.removeAddon(this.name, aId);
+
delete this._IDToFileMap[aId];
}
}
/**
* An object which identifies a directory install location for system add-ons
* updates.
*/
@@ -8687,17 +8946,18 @@ class SystemAddonInstallLocation extends
* Resets the add-on set so on the next startup the default set will be used.
*/
resetAddonSet() {
logger.info("Removing all system add-on upgrades.");
// remove everything from the pref first, if uninstall
// fails then at least they will not be re-activated on
// next restart.
- SystemAddonInstallLocation._saveAddonSet({ schema: 1, addons: {} });
+ this._addonSet = { schema: 1, addons: {} };
+ SystemAddonInstallLocation._saveAddonSet(this._addonSet);
// If this is running at app startup, the pref being cleared
// will cause later stages of startup to notice that the
// old updates are now gone.
//
// Updates will only be explicitly uninstalled if they are
// removed restartlessly, for instance if they are no longer
// part of the latest update set.
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -3,17 +3,17 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// These are injected from XPIProvider.jsm
/* globals ADDON_SIGNING, SIGNED_TYPES, BOOTSTRAP_REASONS, DB_SCHEMA,
AddonInternal, XPIProvider, XPIStates, syncLoadManifestFromFile,
isUsableAddon, recordAddonTelemetry, applyBlocklistChanges,
- flushChromeCaches, canRunInSafeMode*/
+ flushChromeCaches, descriptorToPath */
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
@@ -33,49 +33,48 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "Blocklist",
"@mozilla.org/extensions/blocklist;1",
Ci.nsIBlocklistService);
Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.xpi-utils";
-const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile");
+const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
+ "initWithPath");
// 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_JSON_DB = "extensions.json";
-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";
const PREF_E10S_HAS_NONEXEMPT_ADDON = "extensions.e10s.rollout.hasAddon";
const KEY_APP_PROFILE = "app-profile";
const KEY_APP_SYSTEM_ADDONS = "app-system-addons";
const KEY_APP_SYSTEM_DEFAULTS = "app-system-defaults";
const KEY_APP_GLOBAL = "app-global";
const KEY_APP_TEMPORARY = "app-temporary";
// 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", "descriptor", "installDate",
- "updateDate", "applyBackgroundUpdates", "bootstrap",
+ "appDisabled", "pendingUninstall", "installDate",
+ "updateDate", "applyBackgroundUpdates", "bootstrap", "path",
"skinnable", "size", "sourceURI", "releaseNotesURI",
"softDisabled", "foreignInstall", "hasBinaryComponents",
"strictCompatibility", "locales", "targetApplications",
"targetPlatforms", "multiprocessCompatible", "signedState",
"seen", "dependencies", "hasEmbeddedWebExtension", "mpcOptedOut",
"userPermissions", "icons", "iconURL", "icon64URL"];
// Time to wait before async save of XPI JSON database, in milliseconds
@@ -184,33 +183,40 @@ function copyProperties(aObject, aProper
* XPIProvider.AddonInternal created from an addon's manifest
* @constructor
* @param aLoaded
* Addon data fields loaded from JSON or the addon manifest.
*/
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)
this.dependencies = [];
Object.freeze(this.dependencies);
if (aLoaded._installLocation) {
this._installLocation = aLoaded._installLocation;
this.location = aLoaded._installLocation.name;
} else if (aLoaded.location) {
this._installLocation = XPIProvider.installLocationsByName[this.location];
}
this._key = this.location + ":" + this.id;
if (!aLoaded._sourceBundle) {
- throw new Error("Expected passed argument to contain a descriptor");
+ throw new Error("Expected passed argument to contain a path");
}
this._sourceBundle = aLoaded._sourceBundle;
XPCOMUtils.defineLazyGetter(this, "pendingUpgrade", function() {
for (let install of XPIProvider.installs) {
if (install.state == AddonManager.STATE_INSTALLED &&
!(install.addon.inDatabase) &&
@@ -487,21 +493,20 @@ this.XPIDatabase = {
// When we rev the schema of the JSON database, we need to make sure we
// force the DB to save so that the DB_SCHEMA value in the JSON file and
// the preference are updated.
}
// If we got here, we probably have good data
// 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.persistentDescriptor = loadedAddon.descriptor;
+ loadedAddon._sourceBundle = new nsIFile(loadedAddon.path);
} catch (e) {
- // We can fail here when the descriptor is invalid, usually from the
+ // We can fail here when the path is invalid, usually from the
// wrong OS
logger.warn("Could not find source bundle for add-on " + loadedAddon.id, e);
}
let newAddon = new DBAddonInternal(loadedAddon);
addonDB.set(newAddon._key, newAddon);
}
parseTimer.done();
@@ -627,72 +632,37 @@ this.XPIDatabase = {
if (XPIStates.size == 0) {
// No extensions installed, so we're done
logger.debug("Rebuilding XPI database with no extensions");
return;
}
// 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 = this.getActiveBundles();
+ if (!this.migrateData) {
+ this.activeBundles = Array.from(XPIStates.initialEnabledAddons(),
+ addon => addon.path);
+ if (!this.activeBundles.length)
+ this.activeBundles = null;
+ }
+
if (aRebuildOnError) {
logger.warn("Rebuilding add-ons database from installed extensions.");
try {
XPIDatabaseReconcile.processFileChanges({}, false);
} catch (e) {
logger.error("Failed to rebuild XPI database from installed extensions", e);
}
// Make sure to update the active add-ons and add-ons list on shutdown
Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
}
},
/**
- * 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;
- },
-
- /**
* Shuts down the database connection and releases all cached objects.
* Return: Promise{integer} resolves / rejects with the result of the DB
* flush after the database is flushed and
* all cleanup is done
*/
shutdown() {
logger.debug("shutdown");
if (this.initialized) {
@@ -925,29 +895,29 @@ this.XPIDatabase = {
return _filterDB(this.addonDB, aAddon => true);
},
/**
* Synchronously adds an AddonInternal's metadata to the database.
*
* @param aAddon
* AddonInternal to add
- * @param aDescriptor
- * The file descriptor of the add-on
+ * @param aPath
+ * The file path of the add-on
* @return The DBAddonInternal that was added to the database
*/
- addAddonMetadata(aAddon, aDescriptor) {
+ addAddonMetadata(aAddon, aPath) {
if (!this.addonDB) {
AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_addMetadata",
XPIProvider.runPhase);
this.syncLoadDB(false);
}
let newAddon = new DBAddonInternal(aAddon);
- newAddon.descriptor = aDescriptor;
+ newAddon.path = aPath;
this.addonDB.set(newAddon._key, newAddon);
if (newAddon.visible) {
this.makeAddonVisible(newAddon);
}
this.saveChanges();
return newAddon;
},
@@ -955,61 +925,72 @@ this.XPIDatabase = {
/**
* Synchronously updates an add-on's metadata in the database. Currently just
* removes and recreates.
*
* @param aOldAddon
* The DBAddonInternal to be replaced
* @param aNewAddon
* The new AddonInternal to add
- * @param aDescriptor
- * The file descriptor of the add-on
+ * @param aPath
+ * The file path of the add-on
* @return The DBAddonInternal that was added to the database
*/
- updateAddonMetadata(aOldAddon, aNewAddon, aDescriptor) {
+ updateAddonMetadata(aOldAddon, aNewAddon, aPath) {
this.removeAddonMetadata(aOldAddon);
aNewAddon.syncGUID = aOldAddon.syncGUID;
aNewAddon.installDate = aOldAddon.installDate;
aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates;
aNewAddon.foreignInstall = aOldAddon.foreignInstall;
aNewAddon.seen = aOldAddon.seen;
aNewAddon.active = (aNewAddon.visible && !aNewAddon.disabled && !aNewAddon.pendingUninstall);
// addAddonMetadata does a saveChanges()
- return this.addAddonMetadata(aNewAddon, aDescriptor);
+ return this.addAddonMetadata(aNewAddon, aPath);
},
/**
* Synchronously removes an add-on from the database.
*
* @param aAddon
* The DBAddonInternal being removed
*/
removeAddonMetadata(aAddon) {
this.addonDB.delete(aAddon._key);
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.
*
* @param aAddon
* The DBAddonInternal to make visible
*/
makeAddonVisible(aAddon) {
logger.debug("Make addon " + aAddon._key + " visible");
for (let [, otherAddon] of this.addonDB) {
if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) {
logger.debug("Hide addon " + otherAddon._key);
otherAddon.visible = false;
otherAddon.active = false;
+
+ this.updateXPIStates(otherAddon);
}
}
aAddon.visible = true;
+ this.updateXPIStates(aAddon);
this.saveChanges();
},
/**
* Synchronously marks a given add-on ID visible in a given location,
* instances with the same ID as not visible.
*
* @param aAddon
@@ -1021,21 +1002,23 @@ this.XPIDatabase = {
for (let [, addon] of this.addonDB) {
if (addon.id != aId) {
continue;
}
if (addon.location == aLocation) {
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();
return result;
},
/**
* Synchronously sets properties for an add-on.
@@ -1085,16 +1068,25 @@ this.XPIDatabase = {
updateAddonActive(aAddon, aActive) {
logger.debug("Updating active state for add-on " + aAddon.id + " to " + aActive);
aAddon.active = aActive;
this.saveChanges();
},
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);
for (let [, addon] of this.addonDB) {
let active = (addon.visible && !addon.disabled && !addon.pendingUninstall);
if (active && XPIProvider.isBlockingE10s(addon)) {
blockE10s = true;
@@ -1133,104 +1125,16 @@ this.XPIDatabase = {
for (let [, addon] of this.addonDB) {
let newActive = (addon.visible && !addon.disabled && !addon.pendingUninstall);
if (newActive != addon.active) {
addon.active = newActive;
this.saveChanges();
}
}
},
-
- /**
- * 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 = {
/**
* Returns a map of ID -> add-on. When the same add-on ID exists in multiple
* install locations the highest priority location is chosen.
*/
flattenByID(addonMap, hideLocation) {
@@ -1313,18 +1217,17 @@ this.XPIDatabaseReconcile = {
// 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
let isDetectedInstall = isNewInstall && !aNewAddon;
// Load the manifest if necessary and sanity check the add-on ID
try {
if (!aNewAddon) {
// Load the manifest from the add-on.
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- file.persistentDescriptor = aAddonState.descriptor;
+ let file = new nsIFile(aAddonState.path);
aNewAddon = syncLoadManifestFromFile(file, aInstallLocation);
}
// The add-on in the manifest should match the add-on ID.
if (aNewAddon.id != aId) {
throw new Error("Invalid addon ID: expected addon ID " + aId +
", found " + aNewAddon.id + " in manifest");
}
} catch (e) {
@@ -1369,17 +1272,17 @@ 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);
}
}
- return XPIDatabase.addAddonMetadata(aNewAddon, aAddonState.descriptor);
+ return XPIDatabase.addAddonMetadata(aNewAddon, aAddonState.path);
},
/**
* Called when an add-on has been removed.
*
* @param aOldAddon
* The AddonInternal as it appeared the last time the application
* ran
@@ -1410,18 +1313,17 @@ this.XPIDatabaseReconcile = {
* changing this add-on
*/
updateMetadata(aInstallLocation, aOldAddon, aAddonState, aNewAddon) {
logger.debug("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name);
try {
// If there isn't an updated install manifest for this add-on then load it.
if (!aNewAddon) {
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- file.persistentDescriptor = aAddonState.descriptor;
+ let file = new nsIFile(aAddonState.path);
aNewAddon = syncLoadManifestFromFile(file, aInstallLocation);
applyBlocklistChanges(aOldAddon, aNewAddon);
// Carry over any pendingUninstall state to add-ons modified directly
// in the profile. This is important when the attempt to remove the
// add-on in processPendingFileChanges failed and caused an mtime
// change to the add-ons files.
aNewAddon.pendingUninstall = aOldAddon.pendingUninstall;
@@ -1442,37 +1344,37 @@ this.XPIDatabaseReconcile = {
return null;
}
// Set the additional properties on the new AddonInternal
aNewAddon.updateDate = aAddonState.mtime;
// Update the database
- return XPIDatabase.updateAddonMetadata(aOldAddon, aNewAddon, aAddonState.descriptor);
+ return XPIDatabase.updateAddonMetadata(aOldAddon, aNewAddon, aAddonState.path);
},
/**
- * Updates an add-on's descriptor for when the add-on has moved in the
+ * Updates an add-on's path for when the add-on has moved in the
* filesystem but hasn't changed in any other way.
*
* @param aInstallLocation
* The install location containing the add-on
* @param aOldAddon
* The AddonInternal as it appeared the last time the application
* ran
* @param aAddonState
* The new state of the add-on
* @return a boolean indicating if flushing caches is required to complete
* changing this add-on
*/
- updateDescriptor(aInstallLocation, aOldAddon, aAddonState) {
- logger.debug("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor);
- aOldAddon.descriptor = aAddonState.descriptor;
- aOldAddon._sourceBundle.persistentDescriptor = aAddonState.descriptor;
+ updatePath(aInstallLocation, aOldAddon, aAddonState) {
+ logger.debug("Add-on " + aOldAddon.id + " moved to " + aAddonState.path);
+ aOldAddon.path = aAddonState.path;
+ aOldAddon._sourceBundle = new nsIFile(aAddonState.path);
return aOldAddon;
},
/**
* Called when no change has been detected for an add-on's metadata but the
* application has changed so compatibility may have changed.
*
@@ -1497,27 +1399,25 @@ this.XPIDatabaseReconcile = {
updateCompatibility(aInstallLocation, aOldAddon, aAddonState, aOldAppVersion,
aOldPlatformVersion, aReloadMetadata) {
logger.debug("Updating compatibility for add-on " + aOldAddon.id + " in " + aInstallLocation.name);
// If updating from a version of the app that didn't support signedState
// then fetch that property now
if (aOldAddon.signedState === undefined && ADDON_SIGNING &&
SIGNED_TYPES.has(aOldAddon.type)) {
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- file.persistentDescriptor = aAddonState.descriptor;
+ let file = new nsIFile(aAddonState.path);
let manifest = syncLoadManifestFromFile(file, aInstallLocation);
aOldAddon.signedState = manifest.signedState;
}
// 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()
- file.persistentDescriptor = aAddonState.descriptor;
+ let file = new nsIFile(aAddonState.path);
let manifest = syncLoadManifestFromFile(file, aInstallLocation);
// Avoid re-reading these properties from manifest,
// use existing addon instead.
// TODO - consider re-scanning for targetApplications.
let remove = ["syncGUID", "foreignInstall", "visible", "active",
"userDisabled", "applyBackgroundUpdates", "sourceURI",
"releaseNotesURI", "targetApplications"];
@@ -1564,17 +1464,17 @@ this.XPIDatabaseReconcile = {
if (!(aInstallLocation.name in aManifests))
return null;
if (!(aId in aManifests[aInstallLocation.name]))
return null;
return aManifests[aInstallLocation.name][aId];
};
// Add-ons loaded from the database can have an uninitialized _sourceBundle
- // if the descriptor was invalid. Swallow that error and say they don't exist.
+ // if the path was invalid. Swallow that error and say they don't exist.
let exists = (aAddon) => {
try {
return aAddon._sourceBundle.exists();
} catch (e) {
if (e.result == Cr.NS_ERROR_NOT_INITIALIZED)
return false;
throw e;
}
@@ -1620,29 +1520,31 @@ this.XPIDatabaseReconcile = {
if (xpiState.mtime < oldAddon.updateDate) {
XPIProvider.setTelemetry(oldAddon.id, "olderFile", {
mtime: xpiState.mtime,
oldtime: oldAddon.updateDate
});
}
}
+ 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
// changed.
//
// Also reload the metadata for add-ons in the application directory
// when the application version has changed.
let newAddon = loadedManifest(installLocation, id);
if (newAddon || oldAddon.updateDate != xpiState.mtime ||
(aUpdateCompatibility && (installLocation.name == KEY_APP_GLOBAL ||
installLocation.name == KEY_APP_SYSTEM_DEFAULTS))) {
newAddon = this.updateMetadata(installLocation, oldAddon, xpiState, newAddon);
- } else if (oldAddon.descriptor != xpiState.descriptor) {
- newAddon = this.updateDescriptor(installLocation, oldAddon, xpiState);
+ } else if (oldPath != xpiState.path) {
+ newAddon = this.updatePath(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
// the manifests.
newAddon = this.updateCompatibility(installLocation, oldAddon, xpiState,
aOldAppVersion, aOldPlatformVersion,
aSchemaChange);
} else {
@@ -1701,17 +1603,16 @@ this.XPIDatabaseReconcile = {
// Hide the system add-on updates if any are invalid.
logger.info("One or more updated system add-ons invalid, falling back to defaults.");
hideLocation = KEY_APP_SYSTEM_ADDONS;
}
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
for (let [id, currentAddon] of currentVisible) {
let previousAddon = previousVisible.get(id);
// Note if any visible add-on is not in the application install location
if (currentAddon._installLocation.name != KEY_APP_GLOBAL)
@@ -1727,17 +1628,17 @@ this.XPIDatabaseReconcile = {
// We might be recovering from a corrupt database, if so use the
// list of known active add-ons to update the new add-on
if (!wasStaged && XPIDatabase.activeBundles) {
// For themes we know which is active by the current skin setting
if (currentAddon.type == "theme")
isActive = currentAddon.internalName == XPIProvider.currentSkin;
else
- isActive = XPIDatabase.activeBundles.indexOf(currentAddon.descriptor) != -1;
+ isActive = XPIDatabase.activeBundles.includes(currentAddon.path);
// If the add-on wasn't active and it isn't already disabled in some way
// then it was probably either softDisabled or userDisabled
if (!isActive && !currentAddon.disabled) {
// If the add-on is softblocked then assume it is softDisabled
if (currentAddon.blocklistState == Blocklist.STATE_SOFTBLOCKED)
currentAddon.softDisabled = true;
else
@@ -1779,18 +1680,17 @@ this.XPIDatabaseReconcile = {
XPIProvider.unloadBootstrapScope(previousAddon.id);
}
// Make sure to flush the cache when an old add-on has gone away
flushChromeCaches();
if (currentAddon.bootstrap) {
// Visible bootstrapped add-ons need to have their install method called
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- file.persistentDescriptor = currentAddon._sourceBundle.persistentDescriptor;
+ let file = currentAddon._sourceBundle.clone();
XPIProvider.callBootstrapMethod(currentAddon, file,
"install", installReason,
{ oldVersion: previousAddon.version });
if (currentAddon.disabled)
XPIProvider.unloadBootstrapScope(currentAddon.id);
}
}
@@ -1799,29 +1699,16 @@ this.XPIDatabaseReconcile = {
: AddonManager.STARTUP_CHANGE_DISABLED;
AddonManagerPrivate.addStartupChange(change, id);
}
}
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;
}
// Pass over the set of previously visible add-ons that have now gone away
// and record the change.
for (let [id, previousAddon] of previousVisible) {
if (currentVisible.has(id))
@@ -1832,16 +1719,17 @@ this.XPIDatabaseReconcile = {
// If the previous add-on was bootstrapped and still exists then call its
// uninstall method.
if (previousAddon.bootstrap && exists(previousAddon)) {
XPIProvider.callBootstrapMethod(previousAddon, previousAddon._sourceBundle,
"uninstall", BOOTSTRAP_REASONS.ADDON_UNINSTALL);
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();
}
// Make sure add-ons from hidden locations are marked invisible and inactive
let locationAddonMap = currentAddons.get(hideLocation);
if (locationAddonMap) {
@@ -1862,17 +1750,15 @@ this.XPIDatabaseReconcile = {
for (let [locationName, locationAddonMap] of currentAddons) {
for (let [id, addon] of locationAddonMap) {
let xpiState = XPIStates.getAddon(locationName, id);
xpiState.syncWithDB(addon);
}
}
XPIStates.save();
- XPIProvider.persistBootstrappedAddons();
-
// Clear out any cached migration data.
XPIDatabase.migrateData = null;
XPIDatabase.saveChanges();
return true;
},
}
--- a/toolkit/mozapps/extensions/moz.build
+++ b/toolkit/mozapps/extensions/moz.build
@@ -9,16 +9,17 @@ SPHINX_TREES['addon-manager'] = 'docs'
if CONFIG['MOZ_BUILD_APP'] == 'mobile/android':
DEFINES['MOZ_FENNEC'] = True
DIRS += ['internal']
TEST_DIRS += ['test']
XPIDL_SOURCES += [
'amIAddonManager.idl',
+ 'amIAddonManagerStartup.idl',
'amIAddonPathService.idl',
'amIWebInstallPrompt.idl',
]
XPIDL_MODULE = 'extensions'
EXTRA_COMPONENTS += [
'addonManager.js',
@@ -39,22 +40,24 @@ EXTRA_JS_MODULES += [
'DeferredSave.jsm',
'LightweightThemeManager.jsm',
]
JAR_MANIFESTS += ['jar.mn']
EXPORTS.mozilla += [
'AddonContentPolicy.h',
+ 'AddonManagerStartup.h',
'AddonManagerWebAPI.h',
'AddonPathService.h',
]
UNIFIED_SOURCES += [
'AddonContentPolicy.cpp',
+ 'AddonManagerStartup.cpp',
'AddonManagerWebAPI.cpp',
'AddonPathService.cpp',
]
LOCAL_INCLUDES += [
'/dom/base',
]
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -58,16 +58,20 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource://testing-common/httpd.js");
XPCOMUtils.defineLazyModuleGetter(this, "MockAsyncShutdown",
"resource://testing-common/AddonTestUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MockRegistrar",
"resource://testing-common/MockRegistrar.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MockRegistry",
"resource://testing-common/MockRegistry.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "aomStartup",
+ "@mozilla.org/addons/addon-manager-startup;1",
+ "amIAddonManagerStartup");
+
const {
awaitPromise,
createAppInfo,
createInstallRDF,
createTempWebExtensionFile,
createUpdateRDF,
getFileForAddon,
manuallyUninstall,
@@ -100,19 +104,19 @@ AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();
Object.defineProperty(this, "gAppInfo", {
get() {
return AddonTestUtils.appInfo;
},
});
-Object.defineProperty(this, "gExtensionsINI", {
+Object.defineProperty(this, "gAddonStartup", {
get() {
- return AddonTestUtils.extensionsINI.clone();
+ return AddonTestUtils.addonStartup.clone();
},
});
Object.defineProperty(this, "gInternalManager", {
get() {
return AddonTestUtils.addonIntegrationService.QueryInterface(AM_Ci.nsITimerCallback);
},
});
@@ -198,16 +202,18 @@ this.BootstrapMonitor = {
// Contain the last state of shutdown and uninstall calls for an add-on
stopped: new Map(),
uninstalled: new Map(),
startupPromises: [],
installPromises: [],
+ restartfulIds: new Set(),
+
init() {
this.inited = true;
Services.obs.addObserver(this, "bootstrapmonitor-event");
},
shutdownCheck() {
if (!this.inited)
return;
@@ -314,19 +320,23 @@ this.BootstrapMonitor = {
// consistent.
if (info.reason == 2 /* APP_SHUTDOWN */)
Components.manager.removeBootstrappedManifestLocation(installPath);
} else {
this.checkAddonNotStarted(id);
}
if (info.event == "uninstall") {
- // Chrome should be unregistered at this point
- let isRegistered = isManifestRegistered(installPath);
- do_check_false(isRegistered);
+ // 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);
+ }
this.installed.delete(id);
this.uninstalled.set(id, info)
} else if (info.event == "startup") {
this.started.set(id, info);
// Chrome should be registered at this point
let isRegistered = isManifestRegistered(installPath);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js
@@ -99,16 +99,22 @@ function checkChange(XS, aPath, aChange)
}
// Get a reference to the XPIState (loaded by startupManager) so we can unit test it.
function getXS() {
let XPI = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm", {});
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([
"packed-enabled@tests.mozilla.org",
"packed-disabled@tests.mozilla.org",
"unpacked-enabled@tests.mozilla.org",
"unpacked-disabled@tests.mozilla.org"
]);
@@ -164,49 +170,50 @@ add_task(function* detect_touches() {
/*
* When we enable an unpacked add-on that was modified while it was
* disabled, we reflect the new timestamp in the add-on DB (otherwise, we'll
* think it changed on next restart).
*/
ud.userDisabled = false;
let xState = XS.getAddon("app-profile", ud.id);
do_check_true(xState.enabled);
- do_check_eq(xState.scanTime, ud.updateDate.getTime());
+ do_check_eq(xState.mtime, ud.updateDate.getTime());
});
/*
* Uninstalling bootstrap add-ons should immediately remove them from the
* extensions.xpiState preference.
*/
add_task(function* uninstall_bootstrap() {
let [pe, /* pd, ue, ud */] = yield promiseAddonsByIDs([
"packed-enabled@tests.mozilla.org",
"packed-disabled@tests.mozilla.org",
"unpacked-enabled@tests.mozilla.org",
"unpacked-disabled@tests.mozilla.org"
]);
pe.uninstall();
- let xpiState = Services.prefs.getCharPref("extensions.xpiState");
- do_check_false(xpiState.includes("\"packed-enabled@tests.mozilla.org\""));
+
+ let xpiState = yield getXSJSON();
+ do_check_false("packed-enabled@tests.mozilla.org" in xpiState["app-profile"].addons);
});
/*
* Installing a restartless add-on should immediately add it to XPIState
*/
add_task(function* install_bootstrap() {
let XS = getXS();
let installer = yield promiseInstallFile(
do_get_addon("test_bootstrap1_1"));
let newAddon = installer.addon;
let xState = XS.getAddon("app-profile", newAddon.id);
do_check_true(!!xState);
do_check_true(xState.enabled);
- do_check_eq(xState.scanTime, newAddon.updateDate.getTime());
+ do_check_eq(xState.mtime, newAddon.updateDate.getTime());
newAddon.uninstall();
});
/*
* Installing an add-on that requires restart doesn't add to XPIState
* until after the restart; disable and enable happen immediately so that
* the next restart won't / will scan as necessary on the next restart,
* uninstalling it marks XPIState as disabled immediately
@@ -229,17 +236,17 @@ add_task(function* install_restart() {
newAddon = null;
yield promiseRestartManager();
XS = getXS();
newAddon = yield promiseAddonByID(newID);
xState = XS.getAddon("app-profile", newID);
do_check_true(xState);
do_check_true(xState.enabled);
- do_check_eq(xState.scanTime, newAddon.updateDate.getTime());
+ do_check_eq(xState.mtime, newAddon.updateDate.getTime());
// Check that XPIState enabled flag is updated immediately,
// and doesn't change over restart.
newAddon.userDisabled = true;
do_check_false(xState.enabled);
XS = null;
newAddon = null;
yield promiseRestartManager();
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
@@ -76,18 +76,22 @@ function getInstallOldVersion() {
}
function getUninstallNewVersion() {
let info = BootstrapMonitor.uninstalled.get(ID1);
return info ? info.data.newVersion : undefined;
}
function do_check_bootstrappedPref(aCallback) {
- let data = Services.prefs.getCharPref("extensions.bootstrappedAddons");
- data = JSON.parse(data);
+ let XPIScope = AM_Cu.import("resource://gre/modules/addons/XPIProvider.jsm", {});
+
+ let data = {};
+ for (let entry of XPIScope.XPIStates.bootstrappedAddons()) {
+ data[entry.id] = entry;
+ }
AddonManager.getAddonsByTypes(["extension"], function(aAddons) {
for (let addon of aAddons) {
if (!addon.id.endsWith("@tests.mozilla.org"))
continue;
if (!addon.isActive)
continue;
if (addon.operationsRequiringRestart != AddonManager.OP_NEEDS_RESTART_NONE)
@@ -95,33 +99,33 @@ function do_check_bootstrappedPref(aCall
do_check_true(addon.id in data);
let addonData = data[addon.id];
delete data[addon.id];
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.descriptor, file.persistentDescriptor);
+ do_check_eq(addonData.path, file.path);
}
do_check_eq(Object.keys(data).length, 0);
do_execute_soon(aCallback);
});
}
function run_test() {
do_test_pending();
startupManager();
do_check_false(gExtensionsJSON.exists());
- do_check_false(gExtensionsINI.exists());
+ do_check_false(gAddonStartup.exists());
run_test_1();
}
// Tests that installing doesn't require a restart
function run_test_1() {
prepare_test({ }, [
"onNewInstall"
@@ -165,18 +169,16 @@ function run_test_1() {
// startup should not have been called yet.
BootstrapMonitor.checkAddonNotStarted(ID1);
});
install.install();
});
}
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.
do_check_eq(installs.length, 0);
AddonManager.getAddonByID(ID1, function(b1) {
do_check_neq(b1, null);
do_check_eq(b1.version, "1.0");
@@ -254,17 +256,17 @@ function run_test_3() {
do_check_eq(getShutdownNewVersion(), undefined);
startupManager(false);
BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
BootstrapMonitor.checkAddonNotStarted(ID1);
do_check_eq(getShutdownReason(), ADDON_DISABLE);
do_check_eq(getShutdownNewVersion(), undefined);
do_check_not_in_crash_annotation(ID1, "1.0");
- do_check_false(gExtensionsINI.exists());
+ do_check_true(gAddonStartup.exists());
AddonManager.getAddonByID(ID1, function(b1) {
do_check_neq(b1, null);
do_check_eq(b1.version, "1.0");
do_check_false(b1.appDisabled);
do_check_true(b1.userDisabled);
do_check_false(b1.isActive);
@@ -1199,17 +1201,17 @@ function check_test_23() {
}
// Tests that we recover from a broken preference
function run_test_24() {
do_print("starting 24");
Promise.all([BootstrapMonitor.promiseAddonStartup(ID2),
promiseInstallAllFiles([do_get_addon("test_bootstrap1_1"), do_get_addon("test_bootstrap2_1")])])
- .then(function test_24_pref() {
+ .then(async function test_24_pref() {
do_print("test 24 got prefs");
BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
BootstrapMonitor.checkAddonStarted(ID1, "1.0");
BootstrapMonitor.checkAddonInstalled(ID2, "1.0");
BootstrapMonitor.checkAddonStarted(ID2, "1.0");
restartManager();
@@ -1220,20 +1222,23 @@ function run_test_24() {
shutdownManager();
BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
BootstrapMonitor.checkAddonNotStarted(ID1);
BootstrapMonitor.checkAddonInstalled(ID2, "1.0");
BootstrapMonitor.checkAddonNotStarted(ID2);
- // Break the preference
- let bootstrappedAddons = JSON.parse(Services.prefs.getCharPref("extensions.bootstrappedAddons"));
- bootstrappedAddons[ID1].descriptor += "foo";
- Services.prefs.setCharPref("extensions.bootstrappedAddons", JSON.stringify(bootstrappedAddons));
+ // 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"});
startupManager(false);
BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
BootstrapMonitor.checkAddonStarted(ID1, "1.0");
BootstrapMonitor.checkAddonInstalled(ID2, "1.0");
BootstrapMonitor.checkAddonStarted(ID2, "1.0");
@@ -1327,16 +1332,18 @@ function run_test_27() {
do_check_neq(b1, null);
b1.userDisabled = true;
do_check_eq(b1.version, "1.0");
do_check_false(b1.isActive);
do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE);
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);
do_check_eq(getUninstallReason(), ADDON_UPGRADE);
do_check_eq(getUninstallNewVersion(), 4);
BootstrapMonitor.checkAddonNotStarted(ID1);
AddonManager.getAddonByID(ID1, callback_soon(function(b1_2) {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js
@@ -4,17 +4,17 @@
// This verifies that the themes switch as expected
const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
const profileDir = gProfD.clone();
profileDir.append("extensions");
-function run_test() {
+async function run_test() {
do_test_pending();
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
writeInstallRDFForExtension({
id: "default@tests.mozilla.org",
version: "1.0",
name: "Default",
internalName: "classic/1.0",
@@ -33,17 +33,17 @@ function run_test() {
internalName: "alternate/1.0",
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "1",
maxVersion: "2"
}]
}, profileDir);
- startupManager();
+ await promiseStartupManager();
do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0");
AddonManager.getAddonsByIDs(["default@tests.mozilla.org",
"alternate@tests.mozilla.org"], function([d, a]) {
do_check_neq(d, null);
do_check_false(d.userDisabled);
do_check_false(d.appDisabled);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js
@@ -3,17 +3,17 @@
*/
// Tests that upgrading an incompatible add-on to a compatible one forces an
// EM restart
const profileDir = gProfD.clone();
profileDir.append("extensions");
-function run_test() {
+async function run_test() {
do_test_pending();
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "1.9.2");
var dest = writeInstallRDFForExtension({
id: "addon1@tests.mozilla.org",
version: "1.0",
name: "Test",
targetApplications: [{
@@ -21,19 +21,19 @@ function run_test() {
minVersion: "1",
maxVersion: "1"
}]
}, profileDir);
// Attempt to make this look like it was added some time in the past so
// the update makes the last modified time change.
setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000);
- startupManager();
+ await promiseStartupManager();
- AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a) {
+ AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(a) {
do_check_neq(a, null);
do_check_eq(a.version, "1.0");
do_check_false(a.userDisabled);
do_check_true(a.appDisabled);
do_check_false(a.isActive);
do_check_false(isExtensionInAddonsList(profileDir, a.id));
writeInstallRDFForExtension({
@@ -42,17 +42,17 @@ function run_test() {
name: "Test",
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "1",
maxVersion: "2"
}]
}, profileDir);
- restartManager();
+ await promiseRestartManager();
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a2) {
do_check_neq(a2, null);
do_check_eq(a2.version, "2.0");
do_check_false(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_true(a2.isActive);
do_check_true(isExtensionInAddonsList(profileDir, a2.id));
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js
@@ -11,18 +11,18 @@ function run_test() {
do_test_pending();
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
startupManager();
run_test_1();
}
function run_test_1() {
- installAllFiles([do_get_addon("test_bug595573")], function() {
- restartManager();
+ installAllFiles([do_get_addon("test_bug595573")], async function() {
+ await promiseRestartManager();
AddonManager.getAddonByID("{2f69dacd-03df-4150-a9f1-e8a7b2748829}", function(a1) {
do_check_neq(a1, null);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_execute_soon(run_test_2);
});
});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js
@@ -53,59 +53,59 @@ function run_test() {
do_test_pending();
run_test_1();
}
function end_test() {
testserver.stop(do_test_finished);
}
-function run_test_1() {
+async 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");
- startupManager();
+ await promiseStartupManager();
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org"], function([a1, a2]) {
do_check_neq(a1, null);
do_check_true(a1.appDisabled);
do_check_false(a1.isActive);
do_check_false(isExtensionInAddonsList(userDir, a1.id));
do_check_neq(a2, null);
do_check_false(a2.appDisabled);
do_check_true(a2.isActive);
do_check_false(isExtensionInAddonsList(userDir, a2.id));
do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 1);
a1.findUpdates({
- onUpdateFinished() {
- restartManager();
+ async onUpdateFinished() {
+ await promiseRestartManager();
- AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1_2) {
+ AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(a1_2) {
do_check_neq(a1_2, null);
do_check_false(a1_2.appDisabled);
do_check_true(a1_2.isActive);
do_check_true(isExtensionInAddonsList(userDir, a1_2.id));
shutdownManager();
do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 0);
userDir.parent.moveTo(gProfD, "extensions3");
userDir = gProfD.clone();
userDir.append("extensions3");
userDir.append(gAppInfo.ID);
do_check_true(userDir.exists());
- startupManager(false);
+ await promiseStartupManager(false);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org"], function([a1_3, a2_3]) {
do_check_neq(a1_3, null);
do_check_false(a1_3.appDisabled);
do_check_true(a1_3.isActive);
do_check_true(isExtensionInAddonsList(userDir, a1_3.id));
@@ -120,17 +120,17 @@ function run_test_1() {
}));
}
}, AddonManager.UPDATE_WHEN_USER_REQUESTED);
});
}
// Set up the profile
function run_test_2() {
- AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) {
+ AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(async function(a2) {
do_check_neq(a2, null);
do_check_false(a2.appDisabled);
do_check_true(a2.isActive);
do_check_false(isExtensionInAddonsList(userDir, a2.id));
do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 1);
a2.userDisabled = true;
do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 0);
@@ -138,17 +138,17 @@ function run_test_2() {
shutdownManager();
userDir.parent.moveTo(gProfD, "extensions4");
userDir = gProfD.clone();
userDir.append("extensions4");
userDir.append(gAppInfo.ID);
do_check_true(userDir.exists());
- startupManager(false);
+ await promiseStartupManager(false);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org"], function([a1_2, a2_2]) {
do_check_neq(a1_2, null);
do_check_false(a1_2.appDisabled);
do_check_true(a1_2.isActive);
do_check_true(isExtensionInAddonsList(userDir, a1_2.id));
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js
@@ -54,23 +54,23 @@ profileDir.append("extensions");
function run_test() {
do_test_pending();
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
run_test_1();
}
// Tests whether a schema migration without app version change works
-function run_test_1() {
+async function run_test_1() {
writeInstallRDFForExtension(addon1, profileDir);
writeInstallRDFForExtension(addon2, profileDir);
writeInstallRDFForExtension(addon3, profileDir);
writeInstallRDFForExtension(addon4, profileDir);
- startupManager();
+ await promiseStartupManager();
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org"],
function([a1, a2, a3, a4]) {
do_check_neq(a1, null);
do_check_eq(a1.version, "2.0");
@@ -99,17 +99,17 @@ function run_test_1() {
do_check_false(a4.userDisabled);
do_check_false(a4.isActive);
do_check_false(isExtensionInAddonsList(profileDir, addon4.id));
// Prepare the add-on update, and a bootstrapped addon (bug 693714)
installAllFiles([
do_get_addon("test_bug659772"),
do_get_addon("test_bootstrap1_1")
- ], function() {
+ ], async function() {
shutdownManager();
// Make it look like the next time the app is started it has a new DB schema
changeXPIDBVersion(1);
Services.prefs.setIntPref("extensions.databaseSchema", 1);
let jsonfile = gProfD.clone();
jsonfile.append("extensions");
@@ -137,17 +137,17 @@ function run_test_1() {
converter.init(stream, "UTF-8", 0, 0x0000);
converter.writeString(JSON.stringify(addonObj));
converter.close();
stream.close();
Services.prefs.clearUserPref("bootstraptest.install_reason");
Services.prefs.clearUserPref("bootstraptest.uninstall_reason");
- startupManager(false);
+ await promiseStartupManager(false);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org"],
function([a1_2, a2_2, a3_2, a4_2]) {
do_check_neq(a1_2, null);
do_check_eq(a1_2.version, "2.0");
@@ -189,27 +189,27 @@ function run_test_1() {
a4_2.uninstall();
do_execute_soon(run_test_2);
});
});
});
}
// Tests whether a schema migration with app version change works
-function run_test_2() {
+async function run_test_2() {
restartManager();
shutdownManager();
writeInstallRDFForExtension(addon1, profileDir);
writeInstallRDFForExtension(addon2, profileDir);
writeInstallRDFForExtension(addon3, profileDir);
writeInstallRDFForExtension(addon4, profileDir);
- startupManager();
+ await promiseStartupManager();
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org"],
function([a1, a2, a3, a4]) {
do_check_neq(a1, null);
do_check_eq(a1.version, "2.0");
@@ -240,17 +240,17 @@ function run_test_2() {
do_check_false(isExtensionInAddonsList(profileDir, addon4.id));
// Prepare the add-on update, and a bootstrapped addon (bug 693714)
installAllFiles([
do_get_addon("test_bug659772"),
do_get_addon("test_bootstrap1_1")
], function() { do_execute_soon(prepare_schema_migrate); });
- function prepare_schema_migrate() {
+ async function prepare_schema_migrate() {
shutdownManager();
// Make it look like the next time the app is started it has a new DB schema
changeXPIDBVersion(1);
Services.prefs.setIntPref("extensions.databaseSchema", 1);
let jsonfile = gProfD.clone();
jsonfile.append("extensions");
@@ -279,17 +279,17 @@ function run_test_2() {
converter.writeString(JSON.stringify(addonObj));
converter.close();
stream.close();
Services.prefs.clearUserPref("bootstraptest.install_reason");
Services.prefs.clearUserPref("bootstraptest.uninstall_reason");
gAppInfo.version = "2";
- startupManager(true);
+ await promiseStartupManager(true);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org"],
callback_soon(function([a1_2, a2_2, a3_2, a4_2]) {
do_check_neq(a1_2, null);
do_check_eq(a1_2.version, "2.0");
--- a/toolkit/mozapps/extensions/test/xpcshell/test_default_providers_pref.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_default_providers_pref.js
@@ -1,13 +1,13 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests the extensions.defaultProviders.enabled pref which turns
// off the default XPIProvider and LightweightThemeManager.
-function run_test() {
+async function run_test() {
Services.prefs.setBoolPref("extensions.defaultProviders.enabled", false);
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
- startupManager();
+ await promiseStartupManager();
do_check_false(AddonManager.isInstallEnabled("application/x-xpinstall"));
Services.prefs.clearUserPref("extensions.defaultProviders.enabled");
}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_disable.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_disable.js
@@ -27,24 +27,24 @@ var gIconURL = null;
// Sets up the profile by installing an add-on.
function run_test() {
do_test_pending();
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
startupManager();
- AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
+ AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async 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";
- restartManager();
+ await promiseRestartManager();
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(newa1) {
do_check_neq(newa1, null);
do_check_true(newa1.isActive);
do_check_false(newa1.userDisabled);
do_check_eq(newa1.aboutURL, "chrome://foo/content/about.xul");
do_check_eq(newa1.optionsURL, "chrome://foo/content/options.xul");
do_check_eq(newa1.iconURL, "chrome://foo/content/icon.png");
--- a/toolkit/mozapps/extensions/test/xpcshell/test_experiment.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_experiment.js
@@ -1,17 +1,28 @@
/* 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");
startupManager();
gIsNightly = isNightlyChannel();
run_next_test();
@@ -75,32 +86,32 @@ add_task(function* test_userDisabledNotP
BootstrapMonitor.checkAddonInstalled(ID, "1.0");
BootstrapMonitor.checkAddonStarted(ID, "1.0");
Assert.equal(addon2.id, addon.id, "Changed add-on matches expected.");
Assert.equal(addon2.userDisabled, false, "Add-on is no longer user disabled.");
Assert.ok(addon2.isActive, "Add-on is active.");
- Assert.ok(ID in XPIProvider.bootstrappedAddons,
+ Assert.ok(ID in getBootstrappedAddons(),
"Experiment add-on listed in XPIProvider bootstrapped list.");
addon = yield promiseAddonByID(ID);
Assert.ok(addon, "Add-on retrieved.");
Assert.equal(addon.userDisabled, false, "Add-on is still enabled after API retrieve.");
Assert.ok(addon.isActive, "Add-on is still active.");
Assert.ok(!(addon.pendingOperations & AddonManager.PENDING_ENABLE),
"Should not be pending enable");
Assert.ok(!(addon.pendingOperations & AddonManager.PENDING_DISABLE),
"Should not be pending disable");
// Now when we restart the manager the add-on should revert state.
yield promiseRestartManager();
- let persisted = JSON.parse(Services.prefs.getCharPref("extensions.bootstrappedAddons"));
- Assert.ok(!(ID in persisted),
+
+ Assert.ok(!(ID in getBootstrappedAddons()),
"Experiment add-on not persisted to bootstrappedAddons.");
BootstrapMonitor.checkAddonInstalled(ID, "1.0");
BootstrapMonitor.checkAddonNotStarted(ID);
addon = yield promiseAddonByID(ID);
Assert.ok(addon, "Add-on retrieved.");
Assert.ok(addon.userDisabled, "Add-on is disabled after restart.");
--- a/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js
@@ -13,26 +13,26 @@ function run_test() {
startupManager();
installAllFiles([do_get_addon("test_chromemanifest_1"),
do_get_addon("test_chromemanifest_2"),
do_get_addon("test_chromemanifest_3"),
do_get_addon("test_chromemanifest_4"),
do_get_addon("test_chromemanifest_5")],
- function() {
+ async function() {
- restartManager();
+ await promiseRestartManager();
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org"],
- function([a1, a2, a3, a4, a5]) {
+ async function([a1, a2, a3, a4, a5]) {
// addon1 has no binary components
do_check_neq(a1, null);
do_check_false(a1.userDisabled);
do_check_false(a1.hasBinaryComponents);
do_check_true(a1.isCompatible);
do_check_false(a1.appDisabled);
do_check_true(a1.isActive);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
--- a/toolkit/mozapps/extensions/test/xpcshell/test_install.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_install.js
@@ -112,17 +112,17 @@ function run_test_1() {
});
}
function check_test_1(installSyncGUID) {
ensure_test_completed();
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) {
do_check_eq(olda1, null);
- AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(pendingAddons) {
+ AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(async 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);
if (uri instanceof AM_Ci.nsIJARURI) {
let jarURI = uri.QueryInterface(AM_Ci.nsIJARURI);
let archiveURI = jarURI.JARFile;
let archiveFile = archiveURI.QueryInterface(AM_Ci.nsIFileURL).file;
let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
@@ -145,17 +145,17 @@ function check_test_1(installSyncGUID) {
let extURI = pendingAddons[0].getResourceURI("");
let ext = extURI.QueryInterface(AM_Ci.nsIFileURL).file;
setExtensionModifiedTime(ext, updateDate);
// The pending add-on cannot be disabled or enabled.
do_check_false(hasFlag(pendingAddons[0].permissions, AddonManager.PERM_CAN_ENABLE));
do_check_false(hasFlag(pendingAddons[0].permissions, AddonManager.PERM_CAN_DISABLE));
- restartManager();
+ await promiseRestartManager();
AddonManager.getAllInstalls(function(activeInstalls) {
do_check_eq(activeInstalls, 0);
AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
do_check_neq(a1, null);
do_check_neq(a1.syncGUID, null);
do_check_true(a1.syncGUID.length >= 9);
@@ -271,19 +271,19 @@ function run_test_3(install) {
function check_test_3(aInstall) {
// Make the pending install have a sensible date
let updateDate = Date.now();
let extURI = aInstall.addon.getResourceURI("");
let ext = extURI.QueryInterface(AM_Ci.nsIFileURL).file;
setExtensionModifiedTime(ext, updateDate);
ensure_test_completed();
- AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(olda2) {
+ AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(async function(olda2) {
do_check_eq(olda2, null);
- restartManager();
+ await promiseRestartManager();
AddonManager.getAllInstalls(function(installs) {
do_check_eq(installs, 0);
AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
do_check_neq(a2, null);
do_check_neq(a2.syncGUID, null);
do_check_eq(a2.type, "extension");
@@ -460,19 +460,19 @@ function run_test_7() {
}, [
"onInstallStarted",
"onInstallEnded",
], check_test_7);
}
function check_test_7() {
ensure_test_completed();
- AddonManager.getAddonByID("addon3@tests.mozilla.org", callback_soon(function(olda3) {
+ AddonManager.getAddonByID("addon3@tests.mozilla.org", callback_soon(async function(olda3) {
do_check_eq(olda3, null);
- restartManager();
+ await promiseRestartManager();
AddonManager.getAllInstalls(function(installs) {
do_check_eq(installs, 0);
AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) {
do_check_neq(a3, null);
do_check_neq(a3.syncGUID, null);
do_check_eq(a3.type, "extension");
@@ -509,18 +509,18 @@ function run_test_8() {
}, [
"onInstallStarted",
"onInstallEnded",
], callback_soon(check_test_8));
install.install();
});
}
-function check_test_8() {
- restartManager();
+async function check_test_8() {
+ await promiseRestartManager();
AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) {
do_check_neq(a3, null);
do_check_neq(a3.syncGUID, null);
do_check_eq(a3.type, "extension");
do_check_eq(a3.version, "1.0");
do_check_eq(a3.name, "Real Test 4");
do_check_true(a3.isActive);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js
@@ -109,17 +109,17 @@ function run_test_1() {
});
}
function check_test_1() {
ensure_test_completed();
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) {
do_check_eq(olda1, null);
- AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(pendingAddons) {
+ AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(async 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);
if (uri instanceof AM_Ci.nsIJARURI) {
let jarURI = uri.QueryInterface(AM_Ci.nsIJARURI);
let archiveURI = jarURI.JARFile;
let archiveFile = archiveURI.QueryInterface(AM_Ci.nsIFileURL).file;
let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
@@ -140,17 +140,17 @@ function check_test_1() {
let extURI = pendingAddons[0].getResourceURI("");
let ext = extURI.QueryInterface(AM_Ci.nsIFileURL).file;
setExtensionModifiedTime(ext, updateDate);
// The pending add-on cannot be disabled or enabled.
do_check_false(hasFlag(pendingAddons[0].permissions, AddonManager.PERM_CAN_ENABLE));
do_check_false(hasFlag(pendingAddons[0].permissions, AddonManager.PERM_CAN_DISABLE));
- restartManager();
+ await promiseRestartManager();
AddonManager.getAllInstalls(function(activeInstalls) {
do_check_eq(activeInstalls, 0);
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
do_check_neq(a1, null);
do_check_eq(a1.type, "extension");
do_check_eq(a1.version, "1.0");
@@ -254,19 +254,19 @@ function run_test_3(install) {
function check_test_3(aInstall) {
// Make the pending install have a sensible date
let updateDate = Date.now();
let extURI = aInstall.addon.getResourceURI("");
let ext = extURI.QueryInterface(AM_Ci.nsIFileURL).file;
setExtensionModifiedTime(ext, updateDate);
ensure_test_completed();
- AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(olda2) {
+ AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(async function(olda2) {
do_check_eq(olda2, null);
- restartManager();
+ await promiseRestartManager();
AddonManager.getAllInstalls(function(installs) {
do_check_eq(installs, 0);
AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
do_check_neq(a2, null);
do_check_eq(a2.type, "extension");
do_check_eq(a2.version, "2.0");
@@ -354,20 +354,20 @@ function check_test_5(install) {
ensure_test_completed();
do_check_eq(install.existingAddon.pendingUpgrade.install, install);
AddonManager.getAddonByID("addon2@tests.mozilla.org", function(olda2) {
do_check_neq(olda2, null);
do_check_true(hasFlag(olda2.pendingOperations, AddonManager.PENDING_UPGRADE));
- AddonManager.getInstallsByTypes(null, callback_soon(function(installs) {
+ AddonManager.getInstallsByTypes(null, callback_soon(async function(installs) {
do_check_eq(installs.length, 1);
do_check_eq(installs[0].addon, olda2.pendingUpgrade);
- restartManager();
+ await promiseRestartManager();
AddonManager.getInstallsByTypes(null, function(installs2) {
do_check_eq(installs2.length, 0);
AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
do_check_neq(a2, null);
do_check_eq(a2.type, "extension");
do_check_eq(a2.version, "3.0");
@@ -441,19 +441,19 @@ function run_test_7() {
}, [
"onInstallStarted",
"onInstallEnded",
], check_test_7);
}
function check_test_7() {
ensure_test_completed();
- AddonManager.getAddonByID("addon3@tests.mozilla.org", callback_soon(function(olda3) {
+ AddonManager.getAddonByID("addon3@tests.mozilla.org", callback_soon(async function(olda3) {
do_check_eq(olda3, null);
- restartManager();
+ await promiseRestartManager();
AddonManager.getAllInstalls(function(installs) {
do_check_eq(installs, 0);
AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) {
do_check_neq(a3, null);
do_check_eq(a3.type, "extension");
do_check_eq(a3.version, "1.0");
@@ -489,18 +489,18 @@ function run_test_8() {
}, [
"onInstallStarted",
"onInstallEnded",
], callback_soon(check_test_8));
install.install();
});
}
-function check_test_8() {
- restartManager();
+async function check_test_8() {
+ await promiseRestartManager();
AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) {
do_check_neq(a3, null);
do_check_eq(a3.type, "extension");
do_check_eq(a3.version, "1.0");
do_check_eq(a3.name, "Real Test 4");
do_check_true(a3.isActive);
do_check_false(a3.appDisabled);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js
@@ -177,16 +177,17 @@ add_task(function* init() {
}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
yield deferredUpdateFinished.promise;
});
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",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
@@ -448,18 +449,17 @@ add_task(function* run_test_1() {
try {
shutdownManager();
} catch (e) {
// We're expecting an error here.
}
do_print("Unlocking " + gExtensionsJSON.path);
yield file.close();
gExtensionsJSON.permissions = filePermissions;
- startupManager();
-
+ yield promiseStartupManager();
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
[a1, a2, a3, a4, a5, a6, a7, t1, t2] =
yield promiseAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js
@@ -159,17 +159,17 @@ add_task(function*() {
options.unixFlags = OS.Constants.libc.O_EXLOCK;
let file = yield OS.File.open(gExtensionsJSON.path, {read: true, write: true, existing: true}, options);
let filePermissions = gExtensionsJSON.permissions;
if (!OS.Constants.Win) {
gExtensionsJSON.permissions = 0;
}
- startupManager(false);
+ yield promiseStartupManager(false);
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []);
[a1, a2, a3, a4, a5, a6] =
yield promiseAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
@@ -220,17 +220,17 @@ add_task(function*() {
let shutdownError;
try {
shutdownManager();
} catch (e) {
shutdownError = e;
}
yield file.close();
gExtensionsJSON.permissions = filePermissions;
- startupManager();
+ yield promiseStartupManager();
// On Unix, we can save the DB even when the original file wasn't
// readable, so our changes were saved. On Windows,
// these things happened when we had no access to the database so
// they are seen as external changes when we get the database back
if (shutdownError) {
do_print("Previous XPI save failed");
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED,
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate_state_prefs.js
@@ -0,0 +1,112 @@
+/* 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 } });
+
+ // 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: file1.lastModifiedTime},
+ [ID2]: {e: false, d: file2.persistentDescriptor, v: "0.2", mt: file2.lastModifiedTime},
+ [ID3]: {e: true, d: file3.persistentDescriptor, v: "0.3", mt: file3.lastModifiedTime},
+ [ID4]: {e: false, d: file4.persistentDescriptor, v: "0.4", mt: file4.lastModifiedTime},
+ }
+ }));
+
+ 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, file1.lastModifiedTime, "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, file2.lastModifiedTime, "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, file3.lastModifiedTime, "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, file4.lastModifiedTime, "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();
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_no_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_no_addons.js
@@ -22,33 +22,19 @@ function run_test() {
function checkPending() {
try {
do_check_false(Services.prefs.getBoolPref("extensions.pendingOperations"));
} catch (e) {
// OK
}
}
-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();
}
// After first run with no add-ons, we expect:
// no extensions.json is created
// no extensions.ini
// database schema version preference is set
// bootstrap add-ons preference is not found
--- a/toolkit/mozapps/extensions/test/xpcshell/test_safemode.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_safemode.js
@@ -28,24 +28,24 @@ var gIconURL = null;
// Sets up the profile by installing an add-on.
function run_test() {
do_test_pending();
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
gAppInfo.inSafeMode = true;
startupManager();
- AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
+ AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async 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";
- restartManager();
+ await promiseRestartManager();
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(newa1) {
do_check_neq(newa1, null);
do_check_false(newa1.isActive);
do_check_false(newa1.userDisabled);
do_check_eq(newa1.aboutURL, null);
do_check_eq(newa1.optionsURL, null);
do_check_eq(newa1.iconURL, gIconURL);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js
@@ -256,30 +256,30 @@ add_task(function*() {
add_task(function*() {
let file = manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.signed), profileDir, ID);
// Make it appear to come from the past so when we modify it later it is
// detected during startup. Obviously malware can bypass this method of
// detection but the periodic scan will catch that
yield promiseSetExtensionModifiedTime(file.path, Date.now() - 60000);
- startupManager();
+ yield promiseStartupManager();
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SIGNED);
do_check_true(isExtensionInAddonsList(profileDir, ID));
yield promiseShutdownManager();
clearCache(file);
breakAddon(file);
- startupManager();
+ yield promiseStartupManager();
addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_true(addon.appDisabled);
do_check_false(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
do_check_false(isExtensionInAddonsList(profileDir, ID));
--- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_migrate.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_migrate.js
@@ -99,17 +99,17 @@ function* test_breaking_migrate(addons,
// Now replace it with the version to test. Doing this so quickly shouldn't
// trigger the file modification code to detect the change by itself.
manuallyUninstall(profileDir, ID);
manuallyInstall(do_get_file(DATA + addons[test]), profileDir, ID);
// Update the application
gAppInfo.version = "5";
- startupManager(true);
+ yield promiseStartupManager(true);
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_true(addon.appDisabled);
do_check_false(addon.isActive);
do_check_eq(addon.signedState, expectedSignedState);
// Add-on shouldn't be active
@@ -149,17 +149,17 @@ function* test_working_migrate(addons, t
// Now replace it with the version to test. Doing this so quickly shouldn't
// trigger the file modification code to detect the change by itself.
manuallyUninstall(profileDir, ID);
manuallyInstall(do_get_file(DATA + addons[test]), profileDir, ID);
// Update the application
gAppInfo.version = "5";
- startupManager(true);
+ yield promiseStartupManager(true);
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.signedState, expectedSignedState);
if (addons == ADDONS.bootstrap)
--- a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js
@@ -129,17 +129,17 @@ function run_test() {
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_DISABLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
do_check_false(gExtensionsJSON.exists());
- do_check_false(gExtensionsINI.exists());
+ do_check_false(gAddonStartup.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"],
@@ -158,42 +158,42 @@ function run_test() {
});
}
function end_test() {
do_test_finished("test_startup main");
}
// Try to install all the items into the profile
-function run_test_1() {
+async 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
// the change in run_test_2 makes the last modified time change.
setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000);
writeInstallRDFForExtension(addon3, profileDir);
writeInstallRDFForExtension(addon4, profileDir, "addon4@tests.mozilla.org");
writeInstallRDFForExtension(addon5, profileDir);
writeInstallRDFForExtension(addon6, profileDir);
writeInstallRDFForExtension(addon7, profileDir);
gCachePurged = false;
- restartManager();
+ await promiseRestartManager();
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, ["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org"]);
check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_DISABLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
do_check_true(gCachePurged);
- do_print("Checking for " + gExtensionsINI.path);
- do_check_true(gExtensionsINI.exists());
+ do_print("Checking for " + gAddonStartup.path);
+ do_check_true(gAddonStartup.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"],
@@ -276,39 +276,40 @@ function run_test_1() {
do_execute_soon(run_test_2);
});
});
}
// Test that modified items are detected and items in other install locations
// are ignored
-function run_test_2() {
+async function run_test_2() {
addon1.version = "1.1";
writeInstallRDFForExtension(addon1, userDir);
addon2.version = "2.1";
writeInstallRDFForExtension(addon2, profileDir);
addon2.version = "2.2";
writeInstallRDFForExtension(addon2, globalDir);
addon2.version = "2.3";
writeInstallRDFForExtension(addon2, userDir);
var dest = profileDir.clone();
dest.append(do_get_expected_addon_name("addon3@tests.mozilla.org"));
dest.remove(true);
gCachePurged = false;
- restartManager();
+ await promiseRestartManager();
+
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"]);
check_startup_changes(AddonManager.STARTUP_CHANGE_DISABLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
do_check_true(gCachePurged);
- do_check_true(gExtensionsINI.exists());
+ do_check_true(gAddonStartup.exists());
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org"],
function([a1, a2, a3, a4, a5]) {
@@ -345,27 +346,28 @@ function run_test_2() {
do_check_eq(a5, null);
do_check_false(isExtensionInAddonsList(profileDir, "addon5@tests.mozilla.org"));
do_execute_soon(run_test_3);
});
}
// Check that removing items from the profile reveals their hidden versions.
-function run_test_3() {
+async function run_test_3() {
var dest = profileDir.clone();
dest.append(do_get_expected_addon_name("addon1@tests.mozilla.org"));
dest.remove(true);
dest = profileDir.clone();
dest.append(do_get_expected_addon_name("addon2@tests.mozilla.org"));
dest.remove(true);
writeInstallRDFForExtension(addon3, profileDir, "addon4@tests.mozilla.org");
gCachePurged = false;
- restartManager();
+ await promiseRestartManager();
+
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, ["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org"]);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_DISABLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
do_check_true(gCachePurged);
@@ -410,21 +412,22 @@ function run_test_3() {
dest.append(do_get_expected_addon_name("addon4@tests.mozilla.org"));
do_check_false(dest.exists());
do_execute_soon(run_test_4);
});
}
// Test that disabling an install location works
-function run_test_4() {
+async function run_test_4() {
Services.prefs.setIntPref("extensions.enabledScopes", AddonManager.SCOPE_SYSTEM);
gCachePurged = false;
- restartManager();
+ await promiseRestartManager();
+
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"]);
check_startup_changes(AddonManager.STARTUP_CHANGE_DISABLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
do_check_true(gCachePurged);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
@@ -449,21 +452,22 @@ function run_test_4() {
do_check_in_crash_annotation(addon2.id, a2.version);
do_check_eq(a2.scope, AddonManager.SCOPE_SYSTEM);
do_execute_soon(run_test_5);
});
}
// Switching disabled locations works
-function run_test_5() {
+async function run_test_5() {
Services.prefs.setIntPref("extensions.enabledScopes", AddonManager.SCOPE_USER);
gCachePurged = false;
- restartManager();
+ await promiseRestartManager();
+
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, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_DISABLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
do_check_true(gCachePurged);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
@@ -494,21 +498,22 @@ function run_test_5() {
do_check_in_crash_annotation(addon2.id, a2.version);
do_check_eq(a2.scope, AddonManager.SCOPE_USER);
do_execute_soon(run_test_6);
});
}
// Resetting the pref makes everything visible again
-function run_test_6() {
+async function run_test_6() {
Services.prefs.clearUserPref("extensions.enabledScopes");
gCachePurged = false;
- restartManager();
+ await promiseRestartManager();
+
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_DISABLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
do_check_true(gCachePurged);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
@@ -539,25 +544,26 @@ function run_test_6() {
do_check_in_crash_annotation(addon2.id, a2.version);
do_check_eq(a2.scope, AddonManager.SCOPE_USER);
do_execute_soon(run_test_7);
});
}
// Check that items in the profile hide the others again.
-function run_test_7() {
+async function run_test_7() {
addon1.version = "1.2";
writeInstallRDFForExtension(addon1, profileDir);
var dest = userDir.clone();
dest.append(do_get_expected_addon_name("addon2@tests.mozilla.org"));
dest.remove(true);
gCachePurged = false;
- restartManager();
+ await promiseRestartManager();
+
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, ["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org"]);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_DISABLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
do_check_true(gCachePurged);
@@ -598,21 +604,22 @@ function run_test_7() {
do_check_eq(a5, null);
do_check_false(isExtensionInAddonsList(profileDir, "addon5@tests.mozilla.org"));
do_execute_soon(run_test_8);
});
}
// Disabling all locations still leaves the profile working
-function run_test_8() {
+async function run_test_8() {
Services.prefs.setIntPref("extensions.enabledScopes", 0);
gCachePurged = false;
- restartManager();
+ await promiseRestartManager();
+
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"]);
check_startup_changes(AddonManager.STARTUP_CHANGE_DISABLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
do_check_true(gCachePurged);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
@@ -637,30 +644,31 @@ function run_test_8() {
do_check_false(isExtensionInAddonsList(userDir, "addon2@tests.mozilla.org"));
do_check_false(isExtensionInAddonsList(globalDir, "addon2@tests.mozilla.org"));
do_execute_soon(run_test_9);
});
}
// More hiding and revealing
-function run_test_9() {
+async function run_test_9() {
Services.prefs.clearUserPref("extensions.enabledScopes");
var dest = userDir.clone();
dest.append(do_get_expected_addon_name("addon1@tests.mozilla.org"));
dest.remove(true);
dest = globalDir.clone();
dest.append(do_get_expected_addon_name("addon2@tests.mozilla.org"));
dest.remove(true);
addon2.version = "2.4";
writeInstallRDFForExtension(addon2, profileDir);
gCachePurged = false;
- restartManager();
+ await promiseRestartManager();
+
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, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_DISABLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
do_check_true(gCachePurged);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
@@ -699,25 +707,26 @@ function run_test_9() {
do_check_false(isExtensionInAddonsList(profileDir, "addon5@tests.mozilla.org"));
do_execute_soon(run_test_10);
});
}
// Checks that a removal from one location and an addition in another location
// for the same item is handled
-function run_test_10() {
+async function run_test_10() {
var dest = profileDir.clone();
dest.append(do_get_expected_addon_name("addon1@tests.mozilla.org"));
dest.remove(true);
addon1.version = "1.3";
writeInstallRDFForExtension(addon1, userDir);
gCachePurged = false;
- restartManager();
+ await promiseRestartManager();
+
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, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_DISABLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
do_check_true(gCachePurged);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
@@ -755,26 +764,27 @@ function run_test_10() {
do_check_eq(a5, null);
do_check_false(isExtensionInAddonsList(profileDir, "addon5@tests.mozilla.org"));
do_execute_soon(run_test_11);
});
}
// This should remove any remaining items
-function run_test_11() {
+async function run_test_11() {
var dest = userDir.clone();
dest.append(do_get_expected_addon_name("addon1@tests.mozilla.org"));
dest.remove(true);
dest = profileDir.clone();
dest.append(do_get_expected_addon_name("addon2@tests.mozilla.org"));
dest.remove(true);
gCachePurged = false;
- restartManager();
+ await promiseRestartManager();
+
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",
"addon2@tests.mozilla.org"]);
check_startup_changes(AddonManager.STARTUP_CHANGE_DISABLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
do_check_true(gCachePurged);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js
@@ -84,26 +84,27 @@ var addon5 = {
};
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
const profileDir = gProfD.clone();
profileDir.append("extensions");
// Set up the profile
-function run_test() {
+async function run_test() {
do_test_pending();
writeInstallRDFForExtension(addon1, profileDir);
writeInstallRDFForExtension(addon2, profileDir);
writeInstallRDFForExtension(addon3, profileDir);
writeInstallRDFForExtension(addon4, profileDir);
writeInstallRDFForExtension(addon5, profileDir);
- restartManager();
+ await promiseRestartManager();
+
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org"],
function([a1, a2, a3, a4, a5]) {
do_check_neq(a1, null);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_theme.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_theme.js
@@ -29,17 +29,17 @@ var LightweightThemeObserver = {
}
};
AM_Cc["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService)
.addObserver(LightweightThemeObserver, "lightweight-theme-styling-update");
-function run_test() {
+async function run_test() {
do_test_pending();
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, "theme1/1.0");
writeInstallRDFForExtension({
id: "theme1@tests.mozilla.org",
version: "1.0",
name: "Test 1",
@@ -75,17 +75,17 @@ function run_test() {
internalName: "classic/1.0",
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "1",
maxVersion: "2"
}]
}, profileDir);
- startupManager();
+ await promiseStartupManager();
// Make sure we only register once despite multiple calls
AddonManager.addInstallListener(InstallListener);
AddonManager.addAddonListener(AddonListener);
AddonManager.addInstallListener(InstallListener);
AddonManager.addAddonListener(AddonListener);
AddonManager.addInstallListener(InstallListener);
AddonManager.getAddonsByIDs(["default@tests.mozilla.org",
@@ -153,18 +153,19 @@ function run_test_1() {
do_check_true(t1.userDisabled);
do_check_false(hasFlag(t1.permissions, AddonManager.PERM_CAN_DISABLE));
do_check_true(hasFlag(t1.permissions, AddonManager.PERM_CAN_ENABLE));
do_execute_soon(check_test_1);
});
}
-function check_test_1() {
- restartManager();
+async function check_test_1() {
+ await promiseRestartManager();
+
do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme2/1.0");
AddonManager.getAddonsByIDs(["theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"], function([t1, t2]) {
do_check_neq(t1, null);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_false(t1.isActive);
@@ -185,22 +186,23 @@ function check_test_1() {
do_check_false(gLWThemeChanged);
do_execute_soon(run_test_2);
});
}
// Removing the active theme should fall back to the default (not ideal in this
// case since we don't have the default theme installed)
-function run_test_2() {
+async function run_test_2() {
var dest = profileDir.clone();
dest.append(do_get_expected_addon_name("theme2@tests.mozilla.org"));
dest.remove(true);
- restartManager();
+ await promiseRestartManager();
+
do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0");
AddonManager.getAddonsByIDs(["theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"], function([t1, t2]) {
do_check_neq(t1, null);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_false(t1.isActive);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js
@@ -20,22 +20,22 @@ profileDir.append("extensions");
// Sets up the profile by installing an add-on.
function run_test() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
do_test_pending();
startupManager();
- AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) {
+ AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(olda1) {
do_check_eq(olda1, null);
writeInstallRDFForExtension(addon1, profileDir);
- restartManager();
+ await promiseRestartManager();
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_eq(a1.pendingOperations, 0);
do_check_in_crash_annotation(addon1.id, addon1.version);
@@ -87,18 +87,18 @@ function check_test_1() {
dest.append(do_get_expected_addon_name("addon1@tests.mozilla.org"));
do_check_false(dest.exists());
writeInstallRDFForExtension(addon1, profileDir);
do_execute_soon(run_test_2);
});
}
// Cancelling the uninstall should send onOperationCancelled
-function run_test_2() {
- restartManager();
+async function run_test_2() {
+ await promiseRestartManager();
prepare_test({
"addon1@tests.mozilla.org": [
"onUninstalling"
]
});
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
@@ -121,18 +121,18 @@ function run_test_2() {
do_check_eq(a1.pendingOperations, 0);
ensure_test_completed();
do_execute_soon(check_test_2);
});
}
-function check_test_2() {
- restartManager();
+async function check_test_2() {
+ await promiseRestartManager();
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
run_test_3();
--- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js
@@ -216,24 +216,26 @@ for (let test of testParams) {
});
}
}, AddonManager.UPDATE_WHEN_USER_REQUESTED);
};
check_test_2 = () => {
ensure_test_completed();
- AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) {
+ AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(olda1) {
+ await AddonTestUtils.loadAddonsList(true);
+
do_check_neq(olda1, null);
do_check_eq(olda1.version, "1.0");
do_check_true(isExtensionInAddonsList(profileDir, olda1.id));
shutdownManager();
- startupManager();
+ await promiseStartupManager();
do_check_true(isExtensionInAddonsList(profileDir, "addon1@tests.mozilla.org"));
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
do_check_neq(a1, null);
do_check_eq(a1.version, "2.0");
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js
@@ -211,24 +211,26 @@ for (let test of testParams) {
});
}
}, AddonManager.UPDATE_WHEN_USER_REQUESTED);
};
check_test_2 = () => {
ensure_test_completed();
- AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) {
+ AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(async function(olda1) {
+ await AddonTestUtils.loadAddonsList(true);
+
do_check_neq(olda1, null);
do_check_eq(olda1.version, "1.0");
do_check_true(isExtensionInAddonsList(profileDir, olda1.id));
shutdownManager();
- startupManager();
+ await promiseStartupManager();
do_check_true(isExtensionInAddonsList(profileDir, "addon1@tests.mozilla.org"));
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
do_check_neq(a1, null);
do_check_eq(a1.version, "2.0");
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js
@@ -91,18 +91,18 @@ function end_test() {
} else {
globalDir.append(do_get_expected_addon_name("addon4@tests.mozilla.org"));
globalDir.remove(true);
}
do_execute_soon(do_test_finished);
}
// Test that the test extensions are all installed
-function run_test_1() {
- startupManager();
+async function run_test_1() {
+ await promiseStartupManager();
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org"],
function([a1, a2, a3, a4]) {
do_check_neq(a1, null);
@@ -118,31 +118,31 @@ function run_test_1() {
do_check_true(isExtensionInAddonsList(globalDir, a4.id));
do_check_eq(a4.version, "1.0");
do_execute_soon(run_test_2);
});
}
// Test that upgrading the application doesn't disable now incompatible add-ons
-function run_test_2() {
+async function run_test_2() {
// Upgrade the extension
var dest = writeInstallRDFForExtension({
id: "addon4@tests.mozilla.org",
version: "2.0",
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "2",
maxVersion: "2"
}],
name: "Test Addon 4",
}, globalDir);
setExtensionModifiedTime(dest, gInstallTime);
- restartManager("2");
+ await promiseRestartManager("2");
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org"],
function([a1, a2, a3, a4]) {
do_check_neq(a1, null);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
@@ -173,17 +173,17 @@ function run_test_3() {
maxVersion: "3"
}],
name: "Test Addon 4",
}, globalDir);
setExtensionModifiedTime(dest, gInstallTime);
// Simulates a simple Build ID change, the platform deletes extensions.ini
// whenever the application is changed.
- gExtensionsINI.remove(true);
+ gAddonStartup.remove(true);
restartManager();
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org"],
function([a1, a2, a3, a4]) {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js
@@ -94,18 +94,18 @@ function end_test() {
}
Services.prefs.clearUserPref(PREF_EM_STRICT_COMPATIBILITY);
do_execute_soon(do_test_finished);
}
// Test that the test extensions are all installed
-function run_test_1() {
- startupManager();
+async function run_test_1() {
+ await promiseStartupManager();
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org"],
function([a1, a2, a3, a4]) {
do_check_neq(a1, null);
@@ -121,31 +121,32 @@ function run_test_1() {
do_check_true(isExtensionInAddonsList(globalDir, a4.id));
do_check_eq(a4.version, "1.0");
do_execute_soon(run_test_2);
});
}
// Test that upgrading the application disables now incompatible add-ons
-function run_test_2() {
+async function run_test_2() {
// Upgrade the extension
var dest = writeInstallRDFForExtension({
id: "addon4@tests.mozilla.org",
version: "2.0",
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "2",
maxVersion: "2"
}],
name: "Test Addon 4",
}, globalDir);
setExtensionModifiedTime(dest, gInstallTime);
- restartManager("2");
+ await promiseRestartManager("2");
+
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org"],
function([a1, a2, a3, a4]) {
do_check_neq(a1, null);
do_check_false(isExtensionInAddonsList(profileDir, a1.id));
@@ -176,17 +177,17 @@ function run_test_3() {
maxVersion: "3"
}],
name: "Test Addon 4",
}, globalDir);
setExtensionModifiedTime(dest, gInstallTime);
// Simulates a simple Build ID change, the platform deletes extensions.ini
// whenever the application is changed.
- gExtensionsINI.remove(true);
+ gAddonStartup.remove(true);
restartManager();
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org"],
function([a1, a2, a3, a4]) {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js
@@ -100,17 +100,18 @@ add_task(function* has_embedded_webexten
"Got an webExtension property in the startup bootstrap method params");
ok(("startup" in startupInfo.data.webExtension),
"Got the expected 'startup' property in the webExtension object");
// After restarting the manager, the add-on should still have the
// hasEmbeddedWebExtension property as expected.
yield promiseRestartManager();
- let persisted = JSON.parse(Services.prefs.getCharPref("extensions.bootstrappedAddons"));
+ let persisted = aomStartup.readStartupData()["app-profile"].addons;
+
ok(ID in persisted, "Hybrid add-on persisted to bootstrappedAddons.");
equal(persisted[ID].hasEmbeddedWebExtension, true,
"hasEmbeddedWebExtension flag persisted to bootstrappedAddons.");
// Check that the addon has been installed and started.
BootstrapMonitor.checkAddonInstalled(ID, "1.0");
BootstrapMonitor.checkAddonStarted(ID, "1.0");
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_icons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_icons.js
@@ -22,19 +22,20 @@ function promiseAddonStartup() {
Management.on("startup", listener);
});
}
function* testSimpleIconsetParsing(manifest) {
yield promiseWriteWebManifestForExtension(manifest, profileDir);
- yield promiseRestartManager();
- if (!manifest.theme)
- yield promiseAddonStartup();
+ yield Promise.all([
+ promiseRestartManager(),
+ manifest.theme || promiseAddonStartup(),
+ ]);
let uri = do_get_addon_root_uri(profileDir, ID);
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
function check_icons(addon_copy) {
deepEqual(addon_copy.icons, {
@@ -55,36 +56,36 @@ function* testSimpleIconsetParsing(manif
equal(AddonManager.getPreferredIconURL(addon, 48), uri + "icon48.png");
equal(AddonManager.getPreferredIconURL(addon, 64), uri + "icon64.png");
equal(AddonManager.getPreferredIconURL(addon, 128), uri + "icon64.png");
}
check_icons(addon);
// check if icons are persisted through a restart
- yield promiseRestartManager();
- if (!manifest.theme)
- yield promiseAddonStartup();
+ yield Promise.all([
+ promiseRestartManager(),
+ manifest.theme || promiseAddonStartup(),
+ ]);
addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
check_icons(addon);
addon.uninstall();
-
- yield promiseRestartManager();
}
function* testRetinaIconsetParsing(manifest) {
yield promiseWriteWebManifestForExtension(manifest, profileDir);
- yield promiseRestartManager();
- if (!manifest.theme)
- yield promiseAddonStartup();
+ yield Promise.all([
+ promiseRestartManager(),
+ manifest.theme || promiseAddonStartup(),
+ ]);
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
let uri = do_get_addon_root_uri(profileDir, ID);
// AddonManager displays larger icons for higher pixel density
equal(AddonManager.getPreferredIconURL(addon, 32, {
@@ -95,40 +96,37 @@ function* testRetinaIconsetParsing(manif
devicePixelRatio: 2
}), uri + "icon128.png");
equal(AddonManager.getPreferredIconURL(addon, 64, {
devicePixelRatio: 2
}), uri + "icon128.png");
addon.uninstall();
-
- yield promiseRestartManager();
}
function* testNoIconsParsing(manifest) {
yield promiseWriteWebManifestForExtension(manifest, profileDir);
- yield promiseRestartManager();
- if (!manifest.theme)
- yield promiseAddonStartup();
+ yield Promise.all([
+ promiseRestartManager(),
+ manifest.theme || promiseAddonStartup(),
+ ]);
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
deepEqual(addon.icons, {});
equal(addon.iconURL, null);
equal(addon.icon64URL, null);
equal(AddonManager.getPreferredIconURL(addon, 128), null);
addon.uninstall();
-
- yield promiseRestartManager();
}
// Test simple icon set parsing
add_task(function*() {
yield* testSimpleIconsetParsing({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -302,16 +302,17 @@ skip-if = os == "android"
[test_update_ignorecompat.js]
# Bug 676992: test consistently hangs on Android
skip-if = os == "android"
[test_updatecheck.js]
# Bug 676992: test consistently hangs on 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]
# Bug 676992: test consistently hangs on Android
skip-if = os == "android"
run-sequentially = Uses hardcoded ports in xpi files.
[test_update_compatmode.js]
[test_upgrade.js]
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -1,16 +1,17 @@
/* -*- 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 "nsAppRunner.h"
#include "nsToolkitCompsCID.h"
#include "nsXREDirProvider.h"
+#include "mozilla/AddonManagerStartup.h"
#include "jsapi.h"
#include "xpcpublic.h"
#include "nsIAddonInterposition.h"
#include "nsIAppStartup.h"
#include "nsIDirectoryEnumerator.h"
#include "nsIFile.h"
@@ -22,17 +23,16 @@
#include "nsIXULRuntime.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsXULAppAPI.h"
#include "nsCategoryManagerUtils.h"
-#include "nsINIParser.h"
#include "nsDependentString.h"
#include "nsCOMArray.h"
#include "nsArrayEnumerator.h"
#include "nsEnumeratorUtils.h"
#include "nsReadableUtils.h"
#include "SpecialSystemDirectory.h"
@@ -81,25 +81,16 @@
#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
static already_AddRefed<nsIFile> GetContentProcessSandboxTempDir();
static nsresult DeleteDirIfExists(nsIFile *dir);
static bool IsContentSandboxDisabled();
static const char* GetContentProcessTempBaseDirKey();
static already_AddRefed<nsIFile> CreateContentProcessSandboxTempDir();
#endif
-static already_AddRefed<nsIFile>
-CloneAndAppend(nsIFile* aFile, const char* name)
-{
- nsCOMPtr<nsIFile> file;
- aFile->Clone(getter_AddRefs(file));
- file->AppendNative(nsDependentCString(name));
- return file.forget();
-}
-
nsXREDirProvider* gDirServiceProvider = nullptr;
nsXREDirProvider::nsXREDirProvider() :
mProfileNotified(false)
{
gDirServiceProvider = this;
}
@@ -593,17 +584,17 @@ LoadDirIntoArray(nsIFile* dir,
bool exists;
if (NS_SUCCEEDED(subdir->Exists(&exists)) && exists) {
aDirectories.AppendObject(subdir);
}
}
static void
-LoadDirsIntoArray(nsCOMArray<nsIFile>& aSourceDirs,
+LoadDirsIntoArray(const nsCOMArray<nsIFile>& aSourceDirs,
const char *const* aAppendList,
nsCOMArray<nsIFile>& aDirectories)
{
nsCOMPtr<nsIFile> appended;
bool exists;
for (int32_t i = 0; i < aSourceDirs.Count(); ++i) {
aSourceDirs[i]->Clone(getter_AddRefs(appended));
@@ -654,83 +645,16 @@ nsXREDirProvider::GetFiles(const char* a
rv = NS_NewUnionEnumerator(aResult, appEnum, xreEnum);
if (NS_FAILED(rv))
return rv;
return NS_SUCCESS_AGGREGATE_RESULT;
}
-static void
-RegisterExtensionInterpositions(nsINIParser &parser)
-{
- if (!mozilla::Preferences::GetBool("extensions.interposition.enabled", false))
- return;
-
- nsCOMPtr<nsIAddonInterposition> 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<nsIFile> &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<nsIFile> 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<nsIFile> 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*
GetContentProcessTempBaseDirKey()
{
#if defined(XP_WIN)
return NS_WIN_LOW_INTEGRITY_TEMP_BASE;
#else
@@ -898,69 +822,16 @@ DeleteDirIfExists(nsIFile* dir)
}
}
return NS_OK;
}
#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<nsIFile> extensionsINI;
- mProfileDir->Clone(getter_AddRefs(extensionsINI));
- if (!extensionsINI)
- return;
-
- extensionsINI->AppendNative(NS_LITERAL_CSTRING("extensions.ini"));
-
- nsCOMPtr<nsIFile> 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<nsIFile> 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()
{
nsCOMPtr<nsIFile> dir;
bool persistent = false;
nsresult rv = GetFile(XRE_APP_DISTRIBUTION_DIR, &persistent, getter_AddRefs(dir));
if (NS_FAILED(rv))
@@ -1014,34 +885,34 @@ nsXREDirProvider::GetFilesInternal(const
if (!strcmp(aProperty, XRE_EXTENSIONS_DIR_LIST)) {
nsCOMArray<nsIFile> directories;
static const char *const kAppendNothing[] = { nullptr };
LoadDirsIntoArray(mAppBundleDirectories,
kAppendNothing, directories);
- LoadDirsIntoArray(mExtensionDirectories,
+ LoadDirsIntoArray(AddonManagerStartup::GetSingleton()->ExtensionPaths(),
kAppendNothing, directories);
rv = NS_NewArrayEnumerator(aResult, directories);
}
else if (!strcmp(aProperty, NS_APP_PREFS_DEFAULTS_DIR_LIST)) {
nsCOMArray<nsIFile> directories;
LoadDirIntoArray(mXULAppDir, kAppendPrefDir, directories);
LoadDirsIntoArray(mAppBundleDirectories,
kAppendPrefDir, directories);
rv = NS_NewArrayEnumerator(aResult, directories);
}
else if (!strcmp(aProperty, NS_EXT_PREFS_DEFAULTS_DIR_LIST)) {
nsCOMArray<nsIFile> directories;
- LoadDirsIntoArray(mExtensionDirectories,
+ LoadDirsIntoArray(AddonManagerStartup::GetSingleton()->ExtensionPaths(),
kAppendPrefDir, directories);
if (mProfileDir) {
nsCOMPtr<nsIFile> overrideFile;
mProfileDir->Clone(getter_AddRefs(overrideFile));
overrideFile->AppendNative(NS_LITERAL_CSTRING(PREF_OVERRIDE_DIRNAME));
bool exists;
@@ -1058,17 +929,17 @@ nsXREDirProvider::GetFilesInternal(const
static const char *const kAppendChromeDir[] = { "chrome", nullptr };
nsCOMArray<nsIFile> directories;
LoadDirIntoArray(mXULAppDir,
kAppendChromeDir,
directories);
LoadDirsIntoArray(mAppBundleDirectories,
kAppendChromeDir,
directories);
- LoadDirsIntoArray(mExtensionDirectories,
+ LoadDirsIntoArray(AddonManagerStartup::GetSingleton()->ExtensionPaths(),
kAppendChromeDir,
directories);
rv = NS_NewArrayEnumerator(aResult, directories);
}
else if (!strcmp(aProperty, NS_APP_PLUGINS_DIR_LIST)) {
nsCOMArray<nsIFile> directories;
@@ -1083,17 +954,17 @@ nsXREDirProvider::GetFilesInternal(const
static const char *const kAppendPlugins[] = { "plugins", nullptr };
// The root dirserviceprovider does quite a bit for us: we're mainly
// interested in xulapp and extension-provided plugins.
LoadDirsIntoArray(mAppBundleDirectories,
kAppendPlugins,
directories);
- LoadDirsIntoArray(mExtensionDirectories,
+ LoadDirsIntoArray(AddonManagerStartup::GetSingleton()->ExtensionPaths(),
kAppendPlugins,
directories);
if (mProfileDir) {
nsCOMArray<nsIFile> profileDir;
profileDir.AppendObject(mProfileDir);
LoadDirsIntoArray(profileDir,
kAppendPlugins,
@@ -1156,18 +1027,16 @@ nsXREDirProvider::DoStartup()
// Init the Extension Manager
nsCOMPtr<nsIObserver> em = do_GetService("@mozilla.org/addons/integration;1");
if (em) {
em->Observe(nullptr, "addons-startup", nullptr);
} else {
NS_WARNING("Failed to create Addons Manager.");
}
- LoadExtensionBundleDirectories();
-
obsSvc->NotifyObservers(nullptr, "load-extension-defaults", nullptr);
obsSvc->NotifyObservers(nullptr, "profile-after-change", kStartup);
// Any component that has registered for the profile-after-change category
// should also be created at this time.
(void)NS_CreateServicesFromCategory("profile-after-change", nullptr,
"profile-after-change");
--- a/toolkit/xre/nsXREDirProvider.h
+++ b/toolkit/xre/nsXREDirProvider.h
@@ -121,19 +121,16 @@ protected:
// delimiters.
static inline nsresult AppendProfileString(nsIFile* aFile, const char* aPath);
#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
// Load the temp directory for sandboxed content processes
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();
#endif
void Append(nsIFile* aDirectory);
nsCOMPtr<nsIDirectoryServiceProvider> mAppProvider;
@@ -146,13 +143,11 @@ protected:
nsCOMPtr<nsIFile> mProfileDir;
nsCOMPtr<nsIFile> mProfileLocalDir;
bool mProfileNotified;
#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
nsCOMPtr<nsIFile> mContentTempDir;
nsCOMPtr<nsIFile> mContentProcessSandboxTempDir;
#endif
nsCOMArray<nsIFile> mAppBundleDirectories;
- nsCOMArray<nsIFile> mExtensionDirectories;
- nsCOMArray<nsIFile> mThemeDirectories;
};
#endif