зеркало из https://github.com/mozilla/gecko-dev.git
878 строки
25 KiB
C++
878 строки
25 KiB
C++
/* -*- 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 "jsfriendapi.h"
|
|
#include "js/Array.h" // JS::IsArrayObject
|
|
#include "js/ArrayBuffer.h"
|
|
#include "js/JSON.h"
|
|
#include "js/TracingAPI.h"
|
|
#include "xpcpublic.h"
|
|
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/EndianUtils.h"
|
|
#include "mozilla/Components.h"
|
|
#include "mozilla/Compression.h"
|
|
#include "mozilla/LinkedList.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/ResultExtensions.h"
|
|
#include "mozilla/ScopeExit.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/URLPreloader.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "mozilla/ErrorResult.h"
|
|
#include "mozilla/dom/ipc/StructuredCloneData.h"
|
|
|
|
#include "nsAppDirectoryServiceDefs.h"
|
|
#include "nsAppRunner.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsChromeRegistry.h"
|
|
#include "nsIAppStartup.h"
|
|
#include "nsIDOMWindowUtils.h" // for nsIJSRAIIHelper
|
|
#include "nsIFileURL.h"
|
|
#include "nsIIOService.h"
|
|
#include "nsIJARURI.h"
|
|
#include "nsIStringEnumerator.h"
|
|
#include "nsIZipReader.h"
|
|
#include "nsJARProtocolHandler.h"
|
|
#include "nsJSUtils.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsXULAppAPI.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
namespace mozilla {
|
|
|
|
using Compression::LZ4;
|
|
using dom::ipc::StructuredCloneData;
|
|
|
|
AddonManagerStartup& AddonManagerStartup::GetSingleton() {
|
|
static RefPtr<AddonManagerStartup> singleton;
|
|
if (!singleton) {
|
|
singleton = new AddonManagerStartup();
|
|
ClearOnShutdown(&singleton);
|
|
}
|
|
return *singleton;
|
|
}
|
|
|
|
AddonManagerStartup::AddonManagerStartup() = default;
|
|
|
|
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, nsIObserver)
|
|
|
|
/*****************************************************************************
|
|
* URI utils
|
|
*****************************************************************************/
|
|
|
|
static nsresult ParseJARURI(nsIJARURI* uri, nsIURI** jarFile,
|
|
nsCString& entry) {
|
|
MOZ_TRY(uri->GetJARFile(jarFile));
|
|
MOZ_TRY(uri->GetJAREntry(entry));
|
|
|
|
// The entry portion of a jar: URI is required to begin with a '/', but for
|
|
// nested JAR URIs, the leading / of the outer entry is currently stripped.
|
|
// This is a bug which should be fixed in the JAR URI code, but...
|
|
if (entry.IsEmpty() || entry[0] != '/') {
|
|
entry.Insert('/', 0);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
static nsresult ParseJARURI(nsIURI* uri, nsIURI** jarFile, nsCString& entry) {
|
|
nsresult rv;
|
|
nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
|
|
MOZ_TRY(rv);
|
|
|
|
return ParseJARURI(jarURI, jarFile, entry);
|
|
}
|
|
|
|
static Result<nsCOMPtr<nsIFile>, nsresult> GetFile(nsIURI* uri) {
|
|
nsresult rv;
|
|
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri, &rv);
|
|
MOZ_TRY(rv);
|
|
|
|
nsCOMPtr<nsIFile> file;
|
|
MOZ_TRY(fileURL->GetFile(getter_AddRefs(file)));
|
|
MOZ_ASSERT(file);
|
|
|
|
return std::move(file);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* 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 const char STRUCTURED_CLONE_MAGIC[] = "mozJSSCLz40v001";
|
|
|
|
template <typename T>
|
|
static Result<nsCString, nsresult> DecodeLZ4(const nsACString& lz4,
|
|
const T& magicNumber) {
|
|
constexpr auto HEADER_SIZE = sizeof(magicNumber) + 4;
|
|
|
|
// Note: We want to include the null terminator here.
|
|
nsDependentCSubstring magic(magicNumber, sizeof(magicNumber));
|
|
|
|
if (lz4.Length() < HEADER_SIZE || StringHead(lz4, magic.Length()) != magic) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
|
|
auto data = lz4.BeginReading() + magic.Length();
|
|
auto size = LittleEndian::readUint32(data);
|
|
data += 4;
|
|
|
|
size_t dataLen = lz4.EndReading() - data;
|
|
size_t outputSize;
|
|
|
|
nsCString result;
|
|
if (!result.SetLength(size, fallible) ||
|
|
!LZ4::decompress(data, dataLen, result.BeginWriting(), size,
|
|
&outputSize)) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(size == outputSize);
|
|
|
|
return std::move(result);
|
|
}
|
|
|
|
// Our zlib headers redefine this to MOZ_Z_compress, which breaks LZ4::compress
|
|
#undef compress
|
|
|
|
template <typename T>
|
|
static Result<nsCString, nsresult> EncodeLZ4(const nsACString& data,
|
|
const T& magicNumber) {
|
|
// Note: We want to include the null terminator here.
|
|
nsDependentCSubstring magic(magicNumber, sizeof(magicNumber));
|
|
|
|
nsAutoCString result;
|
|
result.Append(magic);
|
|
|
|
auto off = result.Length();
|
|
if (!result.SetLength(off + 4, fallible)) {
|
|
return Err(NS_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
|
|
LittleEndian::writeUint32(result.BeginWriting() + off, data.Length());
|
|
off += 4;
|
|
|
|
auto size = LZ4::maxCompressedSize(data.Length());
|
|
if (!result.SetLength(off + size, fallible)) {
|
|
return Err(NS_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
|
|
size = LZ4::compress(data.BeginReading(), data.Length(),
|
|
result.BeginWriting() + off);
|
|
|
|
if (!result.SetLength(off + size, fallible)) {
|
|
return Err(NS_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
return std::move(result);
|
|
}
|
|
|
|
static_assert(sizeof STRUCTURED_CLONE_MAGIC % 8 == 0,
|
|
"Magic number should be an array of uint64_t");
|
|
|
|
/**
|
|
* Reads the contents of a LZ4-compressed file, as stored by the OS.File
|
|
* module, and returns the decompressed contents on success.
|
|
*/
|
|
static Result<nsCString, nsresult> ReadFileLZ4(nsIFile* file) {
|
|
static const char MAGIC_NUMBER[] = "mozLz40";
|
|
|
|
nsCString lz4;
|
|
MOZ_TRY_VAR(lz4, URLPreloader::ReadFile(file));
|
|
|
|
if (lz4.IsEmpty()) {
|
|
return lz4;
|
|
}
|
|
|
|
return DecodeLZ4(lz4, MAGIC_NUMBER);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static Result<nsCOMPtr<nsIZipReaderCache>, nsresult> GetJarCache() {
|
|
nsCOMPtr<nsIIOService> ios = services::GetIOService();
|
|
NS_ENSURE_TRUE(ios, Err(NS_ERROR_FAILURE));
|
|
|
|
nsCOMPtr<nsIProtocolHandler> jarProto;
|
|
MOZ_TRY(ios->GetProtocolHandler("jar", getter_AddRefs(jarProto)));
|
|
|
|
auto jar = static_cast<nsJARProtocolHandler*>(jarProto.get());
|
|
MOZ_ASSERT(jar);
|
|
|
|
nsCOMPtr<nsIZipReaderCache> zipCache = jar->JarCache();
|
|
return std::move(zipCache);
|
|
}
|
|
|
|
static Result<FileLocation, nsresult> GetFileLocation(nsIURI* uri) {
|
|
FileLocation location;
|
|
|
|
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri);
|
|
nsCOMPtr<nsIFile> file;
|
|
if (fileURL) {
|
|
MOZ_TRY(fileURL->GetFile(getter_AddRefs(file)));
|
|
location.Init(file);
|
|
} else {
|
|
nsCOMPtr<nsIURI> fileURI;
|
|
nsCString entry;
|
|
MOZ_TRY(ParseJARURI(uri, getter_AddRefs(fileURI), entry));
|
|
|
|
MOZ_TRY_VAR(file, GetFile(fileURI));
|
|
|
|
location.Init(file, entry.get());
|
|
}
|
|
|
|
return std::move(location);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* JSON data handling
|
|
*****************************************************************************/
|
|
|
|
class MOZ_STACK_CLASS WrapperBase {
|
|
protected:
|
|
WrapperBase(JSContext* cx, JSObject* object) : mCx(cx), mObject(cx, object) {}
|
|
|
|
WrapperBase(JSContext* cx, const JS::Value& value) : mCx(cx), mObject(cx) {
|
|
if (value.isObject()) {
|
|
mObject = &value.toObject();
|
|
} else {
|
|
mObject = JS_NewPlainObject(cx);
|
|
}
|
|
}
|
|
|
|
protected:
|
|
JSContext* mCx;
|
|
JS::RootedObject mObject;
|
|
|
|
bool GetBool(const char* name, bool defVal = false);
|
|
|
|
double GetNumber(const char* name, double defVal = 0);
|
|
|
|
nsString GetString(const char* name, const char* defVal = "");
|
|
|
|
JSObject* GetObject(const char* name);
|
|
};
|
|
|
|
bool WrapperBase::GetBool(const char* name, bool defVal) {
|
|
JS::RootedObject obj(mCx, mObject);
|
|
|
|
JS::RootedValue val(mCx, JS::UndefinedValue());
|
|
if (!JS_GetProperty(mCx, obj, name, &val)) {
|
|
JS_ClearPendingException(mCx);
|
|
}
|
|
|
|
if (val.isBoolean()) {
|
|
return val.toBoolean();
|
|
}
|
|
return defVal;
|
|
}
|
|
|
|
double WrapperBase::GetNumber(const char* name, double defVal) {
|
|
JS::RootedObject obj(mCx, mObject);
|
|
|
|
JS::RootedValue val(mCx, JS::UndefinedValue());
|
|
if (!JS_GetProperty(mCx, obj, name, &val)) {
|
|
JS_ClearPendingException(mCx);
|
|
}
|
|
|
|
if (val.isNumber()) {
|
|
return val.toNumber();
|
|
}
|
|
return defVal;
|
|
}
|
|
|
|
nsString WrapperBase::GetString(const char* name, const char* defVal) {
|
|
JS::RootedObject obj(mCx, mObject);
|
|
|
|
JS::RootedValue val(mCx, JS::UndefinedValue());
|
|
if (!JS_GetProperty(mCx, obj, name, &val)) {
|
|
JS_ClearPendingException(mCx);
|
|
}
|
|
|
|
nsString res;
|
|
if (val.isString()) {
|
|
AssignJSString(mCx, res, val.toString());
|
|
} else {
|
|
res.AppendASCII(defVal);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
JSObject* WrapperBase::GetObject(const char* name) {
|
|
JS::RootedObject obj(mCx, mObject);
|
|
|
|
JS::RootedValue val(mCx, JS::UndefinedValue());
|
|
if (!JS_GetProperty(mCx, obj, name, &val)) {
|
|
JS_ClearPendingException(mCx);
|
|
}
|
|
|
|
if (val.isObject()) {
|
|
return &val.toObject();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
class MOZ_STACK_CLASS InstallLocation : public WrapperBase {
|
|
public:
|
|
InstallLocation(JSContext* cx, const JS::Value& value);
|
|
|
|
MOZ_IMPLICIT InstallLocation(PropertyIterElem& iter)
|
|
: InstallLocation(iter.Cx(), iter.Value()) {}
|
|
|
|
InstallLocation(const InstallLocation& other)
|
|
: InstallLocation(other.mCx, JS::ObjectValue(*other.mObject)) {}
|
|
|
|
void SetChanged(bool changed) {
|
|
JS::RootedObject obj(mCx, mObject);
|
|
|
|
JS::RootedValue val(mCx, JS::BooleanValue(changed));
|
|
if (!JS_SetProperty(mCx, obj, "changed", val)) {
|
|
JS_ClearPendingException(mCx);
|
|
}
|
|
}
|
|
|
|
PropertyIter& Addons() { return mAddonsIter.ref(); }
|
|
|
|
nsString Path() { return GetString("path"); }
|
|
|
|
bool ShouldCheckStartupModifications() {
|
|
return GetBool("checkStartupModifications");
|
|
}
|
|
|
|
private:
|
|
JS::RootedObject mAddonsObj;
|
|
Maybe<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) {}
|
|
|
|
const nsString& Id() { return mId; }
|
|
|
|
nsString Path() { return GetString("path"); }
|
|
|
|
nsString Type() { return GetString("type", "extension"); }
|
|
|
|
bool Enabled() { return GetBool("enabled"); }
|
|
|
|
double LastModifiedTime() { return GetNumber("lastModifiedTime"); }
|
|
|
|
bool ShouldCheckStartupModifications() {
|
|
return Type().EqualsLiteral("locale");
|
|
}
|
|
|
|
Result<nsCOMPtr<nsIFile>, nsresult> FullPath();
|
|
|
|
Result<bool, nsresult> UpdateLastModifiedTime();
|
|
|
|
private:
|
|
nsString mId;
|
|
InstallLocation& mLocation;
|
|
};
|
|
|
|
Result<nsCOMPtr<nsIFile>, nsresult> Addon::FullPath() {
|
|
nsString path = Path();
|
|
|
|
// First check for an absolute path, in case we have a proxy file.
|
|
nsCOMPtr<nsIFile> file;
|
|
if (NS_SUCCEEDED(NS_NewLocalFile(path, false, getter_AddRefs(file)))) {
|
|
return std::move(file);
|
|
}
|
|
|
|
// If not an absolute path, fall back to a relative path from the location.
|
|
MOZ_TRY(NS_NewLocalFile(mLocation.Path(), false, getter_AddRefs(file)));
|
|
|
|
MOZ_TRY(file->AppendRelativePath(path));
|
|
return std::move(file);
|
|
}
|
|
|
|
Result<bool, nsresult> Addon::UpdateLastModifiedTime() {
|
|
nsCOMPtr<nsIFile> file;
|
|
MOZ_TRY_VAR(file, FullPath());
|
|
|
|
JS::RootedObject obj(mCx, mObject);
|
|
|
|
bool result;
|
|
if (NS_FAILED(file->Exists(&result)) || !result) {
|
|
JS::RootedValue value(mCx, JS::NullValue());
|
|
if (!JS_SetProperty(mCx, obj, "currentModifiedTime", value)) {
|
|
JS_ClearPendingException(mCx);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
PRTime time;
|
|
|
|
nsCOMPtr<nsIFile> manifest = file;
|
|
if (!IsNormalFile(manifest)) {
|
|
manifest = CloneAndAppend(file, "manifest.json");
|
|
if (!IsNormalFile(manifest)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (NS_FAILED(manifest->GetLastModifiedTime(&time))) {
|
|
return true;
|
|
}
|
|
|
|
double lastModified = time;
|
|
JS::RootedValue value(mCx, JS::NumberValue(lastModified));
|
|
if (!JS_SetProperty(mCx, obj, "currentModifiedTime", value)) {
|
|
JS_ClearPendingException(mCx);
|
|
}
|
|
|
|
return lastModified != LastModifiedTime();
|
|
}
|
|
|
|
InstallLocation::InstallLocation(JSContext* cx, const JS::Value& value)
|
|
: WrapperBase(cx, value), mAddonsObj(cx), mAddonsIter() {
|
|
mAddonsObj = GetObject("addons");
|
|
if (!mAddonsObj) {
|
|
mAddonsObj = JS_NewPlainObject(cx);
|
|
}
|
|
mAddonsIter.emplace(cx, mAddonsObj, this);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* XPC interfacing
|
|
*****************************************************************************/
|
|
|
|
nsresult AddonManagerStartup::ReadStartupData(
|
|
JSContext* cx, JS::MutableHandleValue locations) {
|
|
locations.set(JS::UndefinedValue());
|
|
|
|
nsCOMPtr<nsIFile> file =
|
|
CloneAndAppend(ProfileDir(), "addonStartup.json.lz4");
|
|
|
|
nsCString data;
|
|
auto res = ReadFileLZ4(file);
|
|
if (res.isOk()) {
|
|
data = res.unwrap();
|
|
} else if (res.inspectErr() != NS_ERROR_FILE_NOT_FOUND) {
|
|
return res.unwrapErr();
|
|
}
|
|
|
|
if (data.IsEmpty() || !ParseJSON(cx, data, locations)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!locations.isObject()) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
JS::RootedObject locs(cx, &locations.toObject());
|
|
for (auto e1 : PropertyIter(cx, locs)) {
|
|
InstallLocation loc(e1);
|
|
|
|
bool shouldCheck = loc.ShouldCheckStartupModifications();
|
|
|
|
for (auto e2 : loc.Addons()) {
|
|
Addon addon(e2);
|
|
|
|
if (addon.Enabled() &&
|
|
(shouldCheck || addon.ShouldCheckStartupModifications())) {
|
|
bool changed;
|
|
MOZ_TRY_VAR(changed, addon.UpdateLastModifiedTime());
|
|
if (changed) {
|
|
loc.SetChanged(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult AddonManagerStartup::EncodeBlob(JS::HandleValue value, JSContext* cx,
|
|
JS::MutableHandleValue result) {
|
|
StructuredCloneData holder;
|
|
|
|
ErrorResult rv;
|
|
holder.Write(cx, value, rv);
|
|
if (rv.Failed()) {
|
|
return rv.StealNSResult();
|
|
}
|
|
|
|
nsAutoCString scData;
|
|
|
|
holder.Data().ForEachDataChunk([&](const char* aData, size_t aSize) {
|
|
scData.Append(nsDependentCSubstring(aData, aSize));
|
|
return true;
|
|
});
|
|
|
|
nsCString lz4;
|
|
MOZ_TRY_VAR(lz4, EncodeLZ4(scData, STRUCTURED_CLONE_MAGIC));
|
|
|
|
JS::RootedObject obj(cx);
|
|
MOZ_TRY(nsContentUtils::CreateArrayBuffer(cx, lz4, &obj.get()));
|
|
|
|
result.set(JS::ObjectValue(*obj));
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult AddonManagerStartup::DecodeBlob(JS::HandleValue value, JSContext* cx,
|
|
JS::MutableHandleValue result) {
|
|
NS_ENSURE_TRUE(value.isObject() &&
|
|
JS::IsArrayBufferObject(&value.toObject()) &&
|
|
JS::ArrayBufferHasData(&value.toObject()),
|
|
NS_ERROR_INVALID_ARG);
|
|
|
|
StructuredCloneData holder;
|
|
|
|
nsCString data;
|
|
{
|
|
JS::AutoCheckCannotGC nogc;
|
|
|
|
auto obj = &value.toObject();
|
|
bool isShared;
|
|
|
|
nsDependentCSubstring lz4(
|
|
reinterpret_cast<char*>(JS::GetArrayBufferData(obj, &isShared, nogc)),
|
|
JS::GetArrayBufferByteLength(obj));
|
|
|
|
MOZ_TRY_VAR(data, DecodeLZ4(lz4, STRUCTURED_CLONE_MAGIC));
|
|
}
|
|
|
|
bool ok = holder.CopyExternalData(data.get(), data.Length());
|
|
NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
ErrorResult rv;
|
|
holder.Read(cx, result, rv);
|
|
return rv.StealNSResult();
|
|
;
|
|
}
|
|
|
|
static nsresult EnumerateZip(nsIZipReader* zip, const nsACString& pattern,
|
|
nsTArray<nsString>& results) {
|
|
nsCOMPtr<nsIUTF8StringEnumerator> entries;
|
|
MOZ_TRY(zip->FindEntries(pattern, getter_AddRefs(entries)));
|
|
|
|
bool hasMore;
|
|
while (NS_SUCCEEDED(entries->HasMore(&hasMore)) && hasMore) {
|
|
nsAutoCString name;
|
|
MOZ_TRY(entries->GetNext(name));
|
|
|
|
results.AppendElement(NS_ConvertUTF8toUTF16(name));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult AddonManagerStartup::EnumerateJAR(nsIURI* uri,
|
|
const nsACString& pattern,
|
|
nsTArray<nsString>& results) {
|
|
nsCOMPtr<nsIZipReaderCache> zipCache;
|
|
MOZ_TRY_VAR(zipCache, GetJarCache());
|
|
|
|
nsCOMPtr<nsIZipReader> zip;
|
|
nsCOMPtr<nsIFile> file;
|
|
if (nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri)) {
|
|
nsCOMPtr<nsIURI> fileURI;
|
|
nsCString entry;
|
|
MOZ_TRY(ParseJARURI(jarURI, getter_AddRefs(fileURI), entry));
|
|
|
|
MOZ_TRY_VAR(file, GetFile(fileURI));
|
|
MOZ_TRY(
|
|
zipCache->GetInnerZip(file, Substring(entry, 1), getter_AddRefs(zip)));
|
|
} else {
|
|
MOZ_TRY_VAR(file, GetFile(uri));
|
|
MOZ_TRY(zipCache->GetZip(file, getter_AddRefs(zip)));
|
|
}
|
|
MOZ_ASSERT(zip);
|
|
|
|
return EnumerateZip(zip, pattern, results);
|
|
}
|
|
|
|
nsresult AddonManagerStartup::EnumerateJARSubtree(nsIURI* uri,
|
|
nsTArray<nsString>& results) {
|
|
nsCOMPtr<nsIURI> fileURI;
|
|
nsCString entry;
|
|
MOZ_TRY(ParseJARURI(uri, getter_AddRefs(fileURI), entry));
|
|
|
|
// Mangle the path into a pattern to match all child entries by escaping any
|
|
// existing pattern matching metacharacters it contains and appending "/*".
|
|
constexpr auto metaChars = "[]()?*~|$\\"_ns;
|
|
|
|
nsCString pattern;
|
|
pattern.SetCapacity(entry.Length());
|
|
|
|
// The first character of the entry name is "/", which we want to skip.
|
|
for (auto chr : Span(Substring(entry, 1))) {
|
|
if (metaChars.FindChar(chr) >= 0) {
|
|
pattern.Append('\\');
|
|
}
|
|
pattern.Append(chr);
|
|
}
|
|
if (!pattern.IsEmpty() && !StringEndsWith(pattern, "/"_ns)) {
|
|
pattern.Append('/');
|
|
}
|
|
pattern.Append('*');
|
|
|
|
return EnumerateJAR(fileURI, pattern, results);
|
|
}
|
|
|
|
nsresult AddonManagerStartup::InitializeURLPreloader() {
|
|
MOZ_RELEASE_ASSERT(xpc::IsInAutomation());
|
|
|
|
URLPreloader::ReInitialize();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* RegisterChrome
|
|
******************************************************************************/
|
|
|
|
namespace {
|
|
static bool sObserverRegistered;
|
|
|
|
struct ContentEntry final {
|
|
explicit ContentEntry(nsTArray<nsCString>&& aArgs, uint8_t aFlags = 0)
|
|
: mArgs(std::move(aArgs)), mFlags(aFlags) {}
|
|
|
|
AutoTArray<nsCString, 2> mArgs;
|
|
uint8_t mFlags;
|
|
};
|
|
|
|
}; // anonymous namespace
|
|
}; // namespace mozilla
|
|
|
|
MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::ContentEntry);
|
|
|
|
namespace mozilla {
|
|
namespace {
|
|
|
|
class RegistryEntries final : public nsIJSRAIIHelper,
|
|
public LinkedListElement<RegistryEntries> {
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIJSRAIIHELPER
|
|
|
|
using Override = AutoTArray<nsCString, 2>;
|
|
using Locale = AutoTArray<nsCString, 3>;
|
|
|
|
RegistryEntries(FileLocation& location, nsTArray<Override>&& overrides,
|
|
nsTArray<ContentEntry>&& content, nsTArray<Locale>&& locales)
|
|
: mLocation(location),
|
|
mOverrides(std::move(overrides)),
|
|
mContent(std::move(content)),
|
|
mLocales(std::move(locales)) {}
|
|
|
|
void Register();
|
|
|
|
protected:
|
|
virtual ~RegistryEntries() { Unused << Destruct(); }
|
|
|
|
private:
|
|
FileLocation mLocation;
|
|
const nsTArray<Override> mOverrides;
|
|
const nsTArray<ContentEntry> mContent;
|
|
const nsTArray<Locale> mLocales;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(RegistryEntries, nsIJSRAIIHelper)
|
|
|
|
void RegistryEntries::Register() {
|
|
RefPtr<nsChromeRegistry> cr = nsChromeRegistry::GetSingleton();
|
|
|
|
nsChromeRegistry::ManifestProcessingContext context(NS_EXTENSION_LOCATION,
|
|
mLocation);
|
|
|
|
for (auto& override : mOverrides) {
|
|
const char* args[] = {override[0].get(), override[1].get()};
|
|
cr->ManifestOverride(context, 0, const_cast<char**>(args), 0);
|
|
}
|
|
|
|
for (auto& content : mContent) {
|
|
const char* args[] = {content.mArgs[0].get(), content.mArgs[1].get()};
|
|
cr->ManifestContent(context, 0, const_cast<char**>(args), content.mFlags);
|
|
}
|
|
|
|
for (auto& locale : mLocales) {
|
|
const char* args[] = {locale[0].get(), locale[1].get(), locale[2].get()};
|
|
cr->ManifestLocale(context, 0, const_cast<char**>(args), 0);
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RegistryEntries::Destruct() {
|
|
if (isInList()) {
|
|
remove();
|
|
|
|
// No point in doing I/O to check for new chrome during shutdown, return
|
|
// early in that case.
|
|
nsCOMPtr<nsIAppStartup> appStartup = components::AppStartup::Service();
|
|
if (!appStartup || appStartup->GetShuttingDown()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// When we remove dynamic entries from the registry, we need to rebuild it
|
|
// in order to ensure a consistent state. See comments in Observe().
|
|
RefPtr<nsChromeRegistry> cr = nsChromeRegistry::GetSingleton();
|
|
return cr->CheckForNewChrome();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
static LinkedList<RegistryEntries>& GetRegistryEntries() {
|
|
static LinkedList<RegistryEntries> sEntries;
|
|
return sEntries;
|
|
}
|
|
}; // anonymous namespace
|
|
|
|
NS_IMETHODIMP
|
|
AddonManagerStartup::RegisterChrome(nsIURI* manifestURI,
|
|
JS::HandleValue locations, JSContext* cx,
|
|
nsIJSRAIIHelper** result) {
|
|
auto IsArray = [cx](JS::HandleValue val) -> bool {
|
|
bool isArray;
|
|
return JS::IsArrayObject(cx, val, &isArray) && isArray;
|
|
};
|
|
|
|
NS_ENSURE_ARG_POINTER(manifestURI);
|
|
NS_ENSURE_TRUE(IsArray(locations), NS_ERROR_INVALID_ARG);
|
|
|
|
FileLocation location;
|
|
MOZ_TRY_VAR(location, GetFileLocation(manifestURI));
|
|
|
|
nsTArray<RegistryEntries::Locale> locales;
|
|
nsTArray<ContentEntry> content;
|
|
nsTArray<RegistryEntries::Override> overrides;
|
|
|
|
JS::RootedObject locs(cx, &locations.toObject());
|
|
JS::RootedValue arrayVal(cx);
|
|
JS::RootedObject array(cx);
|
|
|
|
for (auto elem : ArrayIter(cx, locs)) {
|
|
arrayVal = elem.Value();
|
|
NS_ENSURE_TRUE(IsArray(arrayVal), NS_ERROR_INVALID_ARG);
|
|
|
|
array = &arrayVal.toObject();
|
|
|
|
AutoTArray<nsCString, 4> vals;
|
|
for (auto val : ArrayIter(cx, array)) {
|
|
nsAutoJSString str;
|
|
NS_ENSURE_TRUE(str.init(cx, val.Value()), NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
vals.AppendElement(NS_ConvertUTF16toUTF8(str));
|
|
}
|
|
NS_ENSURE_TRUE(vals.Length() > 0, NS_ERROR_INVALID_ARG);
|
|
|
|
nsCString type = vals[0];
|
|
vals.RemoveElementAt(0);
|
|
|
|
if (type.EqualsLiteral("override")) {
|
|
NS_ENSURE_TRUE(vals.Length() == 2, NS_ERROR_INVALID_ARG);
|
|
overrides.AppendElement(std::move(vals));
|
|
} else if (type.EqualsLiteral("content")) {
|
|
if (vals.Length() == 3 &&
|
|
vals[2].EqualsLiteral("contentaccessible=yes")) {
|
|
NS_ENSURE_TRUE(xpc::IsInAutomation(), NS_ERROR_INVALID_ARG);
|
|
vals.RemoveElementAt(2);
|
|
content.AppendElement(ContentEntry(
|
|
std::move(vals), nsChromeRegistry::CONTENT_ACCESSIBLE));
|
|
} else {
|
|
NS_ENSURE_TRUE(vals.Length() == 2, NS_ERROR_INVALID_ARG);
|
|
content.AppendElement(ContentEntry(std::move(vals)));
|
|
}
|
|
} else if (type.EqualsLiteral("locale")) {
|
|
NS_ENSURE_TRUE(vals.Length() == 3, NS_ERROR_INVALID_ARG);
|
|
locales.AppendElement(std::move(vals));
|
|
} else {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
}
|
|
|
|
if (!sObserverRegistered) {
|
|
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
|
NS_ENSURE_TRUE(obs, NS_ERROR_UNEXPECTED);
|
|
obs->AddObserver(this, "chrome-manifests-loaded", false);
|
|
|
|
sObserverRegistered = true;
|
|
}
|
|
|
|
auto entry = MakeRefPtr<RegistryEntries>(
|
|
location, std::move(overrides), std::move(content), std::move(locales));
|
|
|
|
entry->Register();
|
|
GetRegistryEntries().insertBack(entry);
|
|
|
|
entry.forget(result);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
AddonManagerStartup::Observe(nsISupports* subject, const char* topic,
|
|
const char16_t* data) {
|
|
// The chrome registry is maintained as a set of global resource mappings
|
|
// generated mainly from manifest files, on-the-fly, as they're parsed.
|
|
// Entries added later override entries added earlier, and no record is kept
|
|
// of the former state.
|
|
//
|
|
// As a result, if we remove a dynamically-added manifest file, or a set of
|
|
// dynamic entries, the registry needs to be rebuilt from scratch, from the
|
|
// manifests and dynamic entries that remain. The chrome registry itself
|
|
// takes care of re-parsing manifes files. This observer notification lets
|
|
// us know when we need to re-register our dynamic entries.
|
|
if (!strcmp(topic, "chrome-manifests-loaded")) {
|
|
for (auto entry : GetRegistryEntries()) {
|
|
entry->Register();
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace mozilla
|