Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / toolkit / mozapps / extensions / AddonManagerStartup.cpp
blob40d7c6ddafee7a639fbfb7f4d5bc0f55ac457ee9
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "AddonManagerStartup.h"
7 #include "AddonManagerStartup-inlines.h"
9 #include "jsapi.h"
10 #include "jsfriendapi.h"
11 #include "js/Array.h" // JS::IsArrayObject
12 #include "js/ArrayBuffer.h"
13 #include "js/Exception.h"
14 #include "js/JSON.h"
15 #include "js/PropertyAndElement.h" // JS_GetProperty, JS_SetProperty
16 #include "js/TracingAPI.h"
17 #include "xpcpublic.h"
19 #include "mozilla/AppShutdown.h"
20 #include "mozilla/ClearOnShutdown.h"
21 #include "mozilla/EndianUtils.h"
22 #include "mozilla/Components.h"
23 #include "mozilla/Compression.h"
24 #include "mozilla/LinkedList.h"
25 #include "mozilla/Preferences.h"
26 #include "mozilla/ResultExtensions.h"
27 #include "mozilla/URLPreloader.h"
28 #include "mozilla/Unused.h"
29 #include "mozilla/ErrorResult.h"
30 #include "mozilla/Services.h"
31 #include "mozilla/Try.h"
32 #include "mozilla/dom/ipc/StructuredCloneData.h"
33 #include "mozilla/dom/TypedArray.h"
35 #include "nsAppDirectoryServiceDefs.h"
36 #include "nsAppRunner.h"
37 #include "nsChromeRegistry.h"
38 #include "nsIDOMWindowUtils.h" // for nsIJSRAIIHelper
39 #include "nsIFileURL.h"
40 #include "nsIIOService.h"
41 #include "nsIJARURI.h"
42 #include "nsIStringEnumerator.h"
43 #include "nsIZipReader.h"
44 #include "nsJARProtocolHandler.h"
45 #include "nsJSUtils.h"
46 #include "nsIObserverService.h"
47 #include "nsReadableUtils.h"
48 #include "nsXULAppAPI.h"
50 #include <stdlib.h>
52 namespace mozilla {
54 using Compression::LZ4;
55 using dom::ipc::StructuredCloneData;
57 AddonManagerStartup& AddonManagerStartup::GetSingleton() {
58 static RefPtr<AddonManagerStartup> singleton;
59 if (!singleton) {
60 singleton = new AddonManagerStartup();
61 ClearOnShutdown(&singleton);
63 return *singleton;
66 AddonManagerStartup::AddonManagerStartup() = default;
68 nsIFile* AddonManagerStartup::ProfileDir() {
69 if (!mProfileDir) {
70 nsresult rv;
72 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
73 getter_AddRefs(mProfileDir));
74 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
77 return mProfileDir;
80 NS_IMPL_ISUPPORTS(AddonManagerStartup, amIAddonManagerStartup, nsIObserver)
82 /*****************************************************************************
83 * URI utils
84 *****************************************************************************/
86 static nsresult ParseJARURI(nsIJARURI* uri, nsIURI** jarFile,
87 nsCString& entry) {
88 MOZ_TRY(uri->GetJARFile(jarFile));
89 MOZ_TRY(uri->GetJAREntry(entry));
91 // The entry portion of a jar: URI is required to begin with a '/', but for
92 // nested JAR URIs, the leading / of the outer entry is currently stripped.
93 // This is a bug which should be fixed in the JAR URI code, but...
94 if (entry.IsEmpty() || entry[0] != '/') {
95 entry.Insert('/', 0);
97 return NS_OK;
100 static nsresult ParseJARURI(nsIURI* uri, nsIURI** jarFile, nsCString& entry) {
101 nsresult rv;
102 nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
103 MOZ_TRY(rv);
105 return ParseJARURI(jarURI, jarFile, entry);
108 static Result<nsCOMPtr<nsIFile>, nsresult> GetFile(nsIURI* uri) {
109 nsresult rv;
110 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri, &rv);
111 MOZ_TRY(rv);
113 nsCOMPtr<nsIFile> file;
114 MOZ_TRY(fileURL->GetFile(getter_AddRefs(file)));
115 MOZ_ASSERT(file);
117 return std::move(file);
120 /*****************************************************************************
121 * File utils
122 *****************************************************************************/
124 static already_AddRefed<nsIFile> CloneAndAppend(nsIFile* aFile,
125 const char* name) {
126 nsCOMPtr<nsIFile> file;
127 aFile->Clone(getter_AddRefs(file));
128 file->AppendNative(nsDependentCString(name));
129 return file.forget();
132 static bool IsNormalFile(nsIFile* file) {
133 bool result;
134 return NS_SUCCEEDED(file->IsFile(&result)) && result;
137 static const char STRUCTURED_CLONE_MAGIC[] = "mozJSSCLz40v001";
139 template <typename T>
140 static Result<nsCString, nsresult> DecodeLZ4(const nsACString& lz4,
141 const T& magicNumber) {
142 constexpr auto HEADER_SIZE = sizeof(magicNumber) + 4;
144 // Note: We want to include the null terminator here.
145 nsDependentCSubstring magic(magicNumber, sizeof(magicNumber));
147 if (lz4.Length() < HEADER_SIZE || StringHead(lz4, magic.Length()) != magic) {
148 return Err(NS_ERROR_UNEXPECTED);
151 auto data = lz4.BeginReading() + magic.Length();
152 auto size = LittleEndian::readUint32(data);
153 data += 4;
155 size_t dataLen = lz4.EndReading() - data;
156 size_t outputSize;
158 nsCString result;
159 if (!result.SetLength(size, fallible) ||
160 !LZ4::decompress(data, dataLen, result.BeginWriting(), size,
161 &outputSize)) {
162 return Err(NS_ERROR_UNEXPECTED);
165 MOZ_DIAGNOSTIC_ASSERT(size == outputSize);
167 return std::move(result);
170 // Our zlib headers redefine this to MOZ_Z_compress, which breaks LZ4::compress
171 #undef compress
173 template <typename T>
174 static Result<nsCString, nsresult> EncodeLZ4(const nsACString& data,
175 const T& magicNumber) {
176 // Note: We want to include the null terminator here.
177 nsDependentCSubstring magic(magicNumber, sizeof(magicNumber));
179 nsAutoCString result;
180 result.Append(magic);
182 auto off = result.Length();
183 if (!result.SetLength(off + 4, fallible)) {
184 return Err(NS_ERROR_OUT_OF_MEMORY);
187 LittleEndian::writeUint32(result.BeginWriting() + off, data.Length());
188 off += 4;
190 auto size = LZ4::maxCompressedSize(data.Length());
191 if (!result.SetLength(off + size, fallible)) {
192 return Err(NS_ERROR_OUT_OF_MEMORY);
195 size = LZ4::compress(data.BeginReading(), data.Length(),
196 result.BeginWriting() + off);
198 if (!result.SetLength(off + size, fallible)) {
199 return Err(NS_ERROR_OUT_OF_MEMORY);
201 return std::move(result);
204 static_assert(sizeof STRUCTURED_CLONE_MAGIC % 8 == 0,
205 "Magic number should be an array of uint64_t");
208 * Reads the contents of a LZ4-compressed file, as stored by the IOUtils
209 * module, and returns the decompressed contents on success.
211 static Result<nsCString, nsresult> ReadFileLZ4(nsIFile* file) {
212 static const char MAGIC_NUMBER[] = "mozLz40";
214 nsCString lz4;
215 MOZ_TRY_VAR(lz4, URLPreloader::ReadFile(file));
217 if (lz4.IsEmpty()) {
218 return lz4;
221 return DecodeLZ4(lz4, MAGIC_NUMBER);
224 static bool ParseJSON(JSContext* cx, nsACString& jsonData,
225 JS::MutableHandle<JS::Value> result) {
226 NS_ConvertUTF8toUTF16 str(jsonData);
227 jsonData.Truncate();
229 return JS_ParseJSON(cx, str.Data(), str.Length(), result);
232 static Result<nsCOMPtr<nsIZipReaderCache>, nsresult> GetJarCache() {
233 nsCOMPtr<nsIIOService> ios = components::IO::Service();
234 NS_ENSURE_TRUE(ios, Err(NS_ERROR_FAILURE));
236 nsCOMPtr<nsIProtocolHandler> jarProto;
237 MOZ_TRY(ios->GetProtocolHandler("jar", getter_AddRefs(jarProto)));
239 auto jar = static_cast<nsJARProtocolHandler*>(jarProto.get());
240 MOZ_ASSERT(jar);
242 nsCOMPtr<nsIZipReaderCache> zipCache = jar->JarCache();
243 return std::move(zipCache);
246 static Result<FileLocation, nsresult> GetFileLocation(nsIURI* uri) {
247 FileLocation location;
249 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri);
250 nsCOMPtr<nsIFile> file;
251 if (fileURL) {
252 MOZ_TRY(fileURL->GetFile(getter_AddRefs(file)));
253 location.Init(file);
254 } else {
255 nsCOMPtr<nsIURI> fileURI;
256 nsCString entry;
257 MOZ_TRY(ParseJARURI(uri, getter_AddRefs(fileURI), entry));
259 MOZ_TRY_VAR(file, GetFile(fileURI));
261 location.Init(file, entry.get());
264 return std::move(location);
267 /*****************************************************************************
268 * JSON data handling
269 *****************************************************************************/
271 class MOZ_STACK_CLASS WrapperBase {
272 protected:
273 WrapperBase(JSContext* cx, JSObject* object) : mCx(cx), mObject(cx, object) {}
275 WrapperBase(JSContext* cx, const JS::Value& value) : mCx(cx), mObject(cx) {
276 if (value.isObject()) {
277 mObject = &value.toObject();
278 } else {
279 mObject = JS_NewPlainObject(cx);
283 protected:
284 JSContext* mCx;
285 JS::Rooted<JSObject*> mObject;
287 bool GetBool(const char* name, bool defVal = false);
289 double GetNumber(const char* name, double defVal = 0);
291 nsString GetString(const char* name, const char* defVal = "");
293 JSObject* GetObject(const char* name);
296 bool WrapperBase::GetBool(const char* name, bool defVal) {
297 JS::Rooted<JSObject*> obj(mCx, mObject);
299 JS::Rooted<JS::Value> val(mCx, JS::UndefinedValue());
300 if (!JS_GetProperty(mCx, obj, name, &val)) {
301 JS_ClearPendingException(mCx);
304 if (val.isBoolean()) {
305 return val.toBoolean();
307 return defVal;
310 double WrapperBase::GetNumber(const char* name, double defVal) {
311 JS::Rooted<JSObject*> obj(mCx, mObject);
313 JS::Rooted<JS::Value> val(mCx, JS::UndefinedValue());
314 if (!JS_GetProperty(mCx, obj, name, &val)) {
315 JS_ClearPendingException(mCx);
318 if (val.isNumber()) {
319 return val.toNumber();
321 return defVal;
324 nsString WrapperBase::GetString(const char* name, const char* defVal) {
325 JS::Rooted<JSObject*> obj(mCx, mObject);
327 JS::Rooted<JS::Value> val(mCx, JS::UndefinedValue());
328 if (!JS_GetProperty(mCx, obj, name, &val)) {
329 JS_ClearPendingException(mCx);
332 nsString res;
333 if (val.isString()) {
334 AssignJSString(mCx, res, val.toString());
335 } else {
336 res.AppendASCII(defVal);
338 return res;
341 JSObject* WrapperBase::GetObject(const char* name) {
342 JS::Rooted<JSObject*> obj(mCx, mObject);
344 JS::Rooted<JS::Value> val(mCx, JS::UndefinedValue());
345 if (!JS_GetProperty(mCx, obj, name, &val)) {
346 JS_ClearPendingException(mCx);
349 if (val.isObject()) {
350 return &val.toObject();
352 return nullptr;
355 class MOZ_STACK_CLASS InstallLocation : public WrapperBase {
356 public:
357 InstallLocation(JSContext* cx, const JS::Value& value);
359 MOZ_IMPLICIT InstallLocation(PropertyIterElem& iter)
360 : InstallLocation(iter.Cx(), iter.Value()) {}
362 InstallLocation(const InstallLocation& other)
363 : InstallLocation(other.mCx, JS::ObjectValue(*other.mObject)) {}
365 void SetChanged(bool changed) {
366 JS::Rooted<JSObject*> obj(mCx, mObject);
368 JS::Rooted<JS::Value> val(mCx, JS::BooleanValue(changed));
369 if (!JS_SetProperty(mCx, obj, "changed", val)) {
370 JS_ClearPendingException(mCx);
374 PropertyIter& Addons() { return mAddonsIter.ref(); }
376 nsString Path() { return GetString("path"); }
378 bool ShouldCheckStartupModifications() {
379 return GetBool("checkStartupModifications");
382 private:
383 JS::Rooted<JSObject*> mAddonsObj;
384 Maybe<PropertyIter> mAddonsIter;
387 class MOZ_STACK_CLASS Addon : public WrapperBase {
388 public:
389 Addon(JSContext* cx, InstallLocation& location, const nsAString& id,
390 JSObject* object)
391 : WrapperBase(cx, object), mId(id), mLocation(location) {}
393 MOZ_IMPLICIT Addon(PropertyIterElem& iter)
394 : WrapperBase(iter.Cx(), iter.Value()),
395 mId(iter.Name()),
396 mLocation(*static_cast<InstallLocation*>(iter.Context())) {}
398 Addon(const Addon& other)
399 : WrapperBase(other.mCx, other.mObject),
400 mId(other.mId),
401 mLocation(other.mLocation) {}
403 const nsString& Id() { return mId; }
405 nsString Path() { return GetString("path"); }
407 nsString Type() { return GetString("type", "extension"); }
409 bool Enabled() { return GetBool("enabled"); }
411 double LastModifiedTime() { return GetNumber("lastModifiedTime"); }
413 bool ShouldCheckStartupModifications() {
414 return Type().EqualsLiteral("locale");
417 Result<nsCOMPtr<nsIFile>, nsresult> FullPath();
419 Result<bool, nsresult> UpdateLastModifiedTime();
421 private:
422 nsString mId;
423 InstallLocation& mLocation;
426 Result<nsCOMPtr<nsIFile>, nsresult> Addon::FullPath() {
427 nsString path = Path();
429 // First check for an absolute path, in case we have a proxy file.
430 nsCOMPtr<nsIFile> file;
431 if (NS_SUCCEEDED(NS_NewLocalFile(path, false, getter_AddRefs(file)))) {
432 return std::move(file);
435 // If not an absolute path, fall back to a relative path from the location.
436 MOZ_TRY(NS_NewLocalFile(mLocation.Path(), false, getter_AddRefs(file)));
438 MOZ_TRY(file->AppendRelativePath(path));
439 return std::move(file);
442 Result<bool, nsresult> Addon::UpdateLastModifiedTime() {
443 nsCOMPtr<nsIFile> file;
444 MOZ_TRY_VAR(file, FullPath());
446 JS::Rooted<JSObject*> obj(mCx, mObject);
448 bool result;
449 if (NS_FAILED(file->Exists(&result)) || !result) {
450 JS::Rooted<JS::Value> value(mCx, JS::NullValue());
451 if (!JS_SetProperty(mCx, obj, "currentModifiedTime", value)) {
452 JS_ClearPendingException(mCx);
455 return true;
458 PRTime time;
460 nsCOMPtr<nsIFile> manifest = file;
461 if (!IsNormalFile(manifest)) {
462 manifest = CloneAndAppend(file, "manifest.json");
463 if (!IsNormalFile(manifest)) {
464 return true;
468 if (NS_FAILED(manifest->GetLastModifiedTime(&time))) {
469 return true;
472 double lastModified = time;
473 JS::Rooted<JS::Value> value(mCx, JS::NumberValue(lastModified));
474 if (!JS_SetProperty(mCx, obj, "currentModifiedTime", value)) {
475 JS_ClearPendingException(mCx);
478 return lastModified != LastModifiedTime();
481 InstallLocation::InstallLocation(JSContext* cx, const JS::Value& value)
482 : WrapperBase(cx, value), mAddonsObj(cx) {
483 mAddonsObj = GetObject("addons");
484 if (!mAddonsObj) {
485 mAddonsObj = JS_NewPlainObject(cx);
487 mAddonsIter.emplace(cx, mAddonsObj, this);
490 /*****************************************************************************
491 * XPC interfacing
492 *****************************************************************************/
494 nsresult AddonManagerStartup::ReadStartupData(
495 JSContext* cx, JS::MutableHandle<JS::Value> locations) {
496 locations.set(JS::UndefinedValue());
498 nsCOMPtr<nsIFile> file =
499 CloneAndAppend(ProfileDir(), "addonStartup.json.lz4");
501 nsCString data;
502 auto res = ReadFileLZ4(file);
503 if (res.isOk()) {
504 data = res.unwrap();
505 } else if (res.inspectErr() != NS_ERROR_FILE_NOT_FOUND) {
506 return res.unwrapErr();
509 if (data.IsEmpty() || !ParseJSON(cx, data, locations)) {
510 return NS_OK;
513 if (!locations.isObject()) {
514 return NS_ERROR_UNEXPECTED;
517 JS::Rooted<JSObject*> locs(cx, &locations.toObject());
518 for (auto e1 : PropertyIter(cx, locs)) {
519 InstallLocation loc(e1);
521 bool shouldCheck = loc.ShouldCheckStartupModifications();
523 for (auto e2 : loc.Addons()) {
524 Addon addon(e2);
526 if (addon.Enabled() &&
527 (shouldCheck || addon.ShouldCheckStartupModifications())) {
528 bool changed;
529 MOZ_TRY_VAR(changed, addon.UpdateLastModifiedTime());
530 if (changed) {
531 loc.SetChanged(true);
537 return NS_OK;
540 nsresult AddonManagerStartup::EncodeBlob(JS::Handle<JS::Value> value,
541 JSContext* cx,
542 JS::MutableHandle<JS::Value> result) {
543 StructuredCloneData holder;
545 ErrorResult rv;
546 holder.Write(cx, value, rv);
547 if (rv.Failed()) {
548 return rv.StealNSResult();
551 nsAutoCString scData;
553 bool ok =
554 holder.Data().ForEachDataChunk([&](const char* aData, size_t aSize) {
555 return scData.Append(nsDependentCSubstring(aData, aSize),
556 mozilla::fallible);
558 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
560 nsCString lz4;
561 MOZ_TRY_VAR(lz4, EncodeLZ4(scData, STRUCTURED_CLONE_MAGIC));
563 JS::Rooted<JSObject*> obj(cx, dom::ArrayBuffer::Create(cx, lz4, rv));
564 ENSURE_SUCCESS(rv, rv.StealNSResult());
566 result.set(JS::ObjectValue(*obj));
567 return NS_OK;
570 nsresult AddonManagerStartup::DecodeBlob(JS::Handle<JS::Value> value,
571 JSContext* cx,
572 JS::MutableHandle<JS::Value> result) {
573 NS_ENSURE_TRUE(value.isObject() &&
574 JS::IsArrayBufferObject(&value.toObject()) &&
575 JS::ArrayBufferHasData(&value.toObject()),
576 NS_ERROR_INVALID_ARG);
578 StructuredCloneData holder;
580 nsCString data;
582 JS::AutoCheckCannotGC nogc;
584 auto obj = &value.toObject();
585 bool isShared;
587 size_t len = JS::GetArrayBufferByteLength(obj);
588 NS_ENSURE_TRUE(len <= INT32_MAX, NS_ERROR_INVALID_ARG);
589 nsDependentCSubstring lz4(
590 reinterpret_cast<char*>(JS::GetArrayBufferData(obj, &isShared, nogc)),
591 uint32_t(len));
593 MOZ_TRY_VAR(data, DecodeLZ4(lz4, STRUCTURED_CLONE_MAGIC));
596 bool ok = holder.CopyExternalData(data.get(), data.Length());
597 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
599 ErrorResult rv;
600 holder.Read(cx, result, rv);
601 return rv.StealNSResult();
604 static nsresult EnumerateZip(nsIZipReader* zip, const nsACString& pattern,
605 nsTArray<nsString>& results) {
606 nsCOMPtr<nsIUTF8StringEnumerator> entries;
607 MOZ_TRY(zip->FindEntries(pattern, getter_AddRefs(entries)));
609 bool hasMore;
610 while (NS_SUCCEEDED(entries->HasMore(&hasMore)) && hasMore) {
611 nsAutoCString name;
612 MOZ_TRY(entries->GetNext(name));
614 results.AppendElement(NS_ConvertUTF8toUTF16(name));
617 return NS_OK;
620 nsresult AddonManagerStartup::EnumerateJAR(nsIURI* uri,
621 const nsACString& pattern,
622 nsTArray<nsString>& results) {
623 nsCOMPtr<nsIZipReaderCache> zipCache;
624 MOZ_TRY_VAR(zipCache, GetJarCache());
626 nsCOMPtr<nsIZipReader> zip;
627 nsCOMPtr<nsIFile> file;
628 if (nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri)) {
629 nsCOMPtr<nsIURI> fileURI;
630 nsCString entry;
631 MOZ_TRY(ParseJARURI(jarURI, getter_AddRefs(fileURI), entry));
633 MOZ_TRY_VAR(file, GetFile(fileURI));
634 MOZ_TRY(
635 zipCache->GetInnerZip(file, Substring(entry, 1), getter_AddRefs(zip)));
636 } else {
637 MOZ_TRY_VAR(file, GetFile(uri));
638 MOZ_TRY(zipCache->GetZip(file, getter_AddRefs(zip)));
640 MOZ_ASSERT(zip);
642 return EnumerateZip(zip, pattern, results);
645 nsresult AddonManagerStartup::EnumerateJARSubtree(nsIURI* uri,
646 nsTArray<nsString>& results) {
647 nsCOMPtr<nsIURI> fileURI;
648 nsCString entry;
649 MOZ_TRY(ParseJARURI(uri, getter_AddRefs(fileURI), entry));
651 // Mangle the path into a pattern to match all child entries by escaping any
652 // existing pattern matching metacharacters it contains and appending "/*".
653 constexpr auto metaChars = "[]()?*~|$\\"_ns;
655 nsCString pattern;
656 pattern.SetCapacity(entry.Length());
658 // The first character of the entry name is "/", which we want to skip.
659 for (auto chr : Span(Substring(entry, 1))) {
660 if (metaChars.FindChar(chr) >= 0) {
661 pattern.Append('\\');
663 pattern.Append(chr);
665 if (!pattern.IsEmpty() && !StringEndsWith(pattern, "/"_ns)) {
666 pattern.Append('/');
668 pattern.Append('*');
670 return EnumerateJAR(fileURI, pattern, results);
673 nsresult AddonManagerStartup::InitializeURLPreloader() {
674 MOZ_RELEASE_ASSERT(xpc::IsInAutomation());
676 URLPreloader::ReInitialize();
678 return NS_OK;
681 /******************************************************************************
682 * RegisterChrome
683 ******************************************************************************/
685 namespace {
686 static bool sObserverRegistered;
688 struct ContentEntry final {
689 explicit ContentEntry(nsTArray<nsCString>&& aArgs, uint8_t aFlags = 0)
690 : mArgs(std::move(aArgs)), mFlags(aFlags) {}
692 AutoTArray<nsCString, 2> mArgs;
693 uint8_t mFlags;
696 }; // anonymous namespace
697 }; // namespace mozilla
699 MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::ContentEntry);
701 namespace mozilla {
702 namespace {
704 class RegistryEntries final : public nsIJSRAIIHelper,
705 public LinkedListElement<RegistryEntries> {
706 public:
707 NS_DECL_ISUPPORTS
708 NS_DECL_NSIJSRAIIHELPER
710 using Override = AutoTArray<nsCString, 2>;
711 using Locale = AutoTArray<nsCString, 3>;
713 RegistryEntries(FileLocation& location, nsTArray<Override>&& overrides,
714 nsTArray<ContentEntry>&& content, nsTArray<Locale>&& locales)
715 : mLocation(location),
716 mOverrides(std::move(overrides)),
717 mContent(std::move(content)),
718 mLocales(std::move(locales)) {}
720 void Register();
722 protected:
723 virtual ~RegistryEntries() { Unused << Destruct(); }
725 private:
726 FileLocation mLocation;
727 const nsTArray<Override> mOverrides;
728 const nsTArray<ContentEntry> mContent;
729 const nsTArray<Locale> mLocales;
732 NS_IMPL_ISUPPORTS(RegistryEntries, nsIJSRAIIHelper)
734 void RegistryEntries::Register() {
735 RefPtr<nsChromeRegistry> cr = nsChromeRegistry::GetSingleton();
737 nsChromeRegistry::ManifestProcessingContext context(NS_EXTENSION_LOCATION,
738 mLocation);
740 for (auto& override : mOverrides) {
741 const char* args[] = {override[0].get(), override[1].get()};
742 cr->ManifestOverride(context, 0, const_cast<char**>(args), 0);
745 for (auto& content : mContent) {
746 const char* args[] = {content.mArgs[0].get(), content.mArgs[1].get()};
747 cr->ManifestContent(context, 0, const_cast<char**>(args), content.mFlags);
750 for (auto& locale : mLocales) {
751 const char* args[] = {locale[0].get(), locale[1].get(), locale[2].get()};
752 cr->ManifestLocale(context, 0, const_cast<char**>(args), 0);
756 NS_IMETHODIMP
757 RegistryEntries::Destruct() {
758 if (isInList()) {
759 remove();
761 // No point in doing I/O to check for new chrome during shutdown, return
762 // early in that case.
763 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
764 return NS_OK;
767 // When we remove dynamic entries from the registry, we need to rebuild it
768 // in order to ensure a consistent state. See comments in Observe().
769 RefPtr<nsChromeRegistry> cr = nsChromeRegistry::GetSingleton();
770 return cr->CheckForNewChrome();
772 return NS_OK;
775 static LinkedList<RegistryEntries>& GetRegistryEntries() {
776 static LinkedList<RegistryEntries> sEntries;
777 return sEntries;
779 }; // anonymous namespace
781 NS_IMETHODIMP
782 AddonManagerStartup::RegisterChrome(nsIURI* manifestURI,
783 JS::Handle<JS::Value> locations,
784 JSContext* cx, nsIJSRAIIHelper** result) {
785 auto IsArray = [cx](JS::Handle<JS::Value> val) -> bool {
786 bool isArray;
787 return JS::IsArrayObject(cx, val, &isArray) && isArray;
790 NS_ENSURE_ARG_POINTER(manifestURI);
791 NS_ENSURE_TRUE(IsArray(locations), NS_ERROR_INVALID_ARG);
793 FileLocation location;
794 MOZ_TRY_VAR(location, GetFileLocation(manifestURI));
796 nsTArray<RegistryEntries::Locale> locales;
797 nsTArray<ContentEntry> content;
798 nsTArray<RegistryEntries::Override> overrides;
800 JS::Rooted<JSObject*> locs(cx, &locations.toObject());
801 JS::Rooted<JS::Value> arrayVal(cx);
802 JS::Rooted<JSObject*> array(cx);
804 for (auto elem : ArrayIter(cx, locs)) {
805 arrayVal = elem.Value();
806 NS_ENSURE_TRUE(IsArray(arrayVal), NS_ERROR_INVALID_ARG);
808 array = &arrayVal.toObject();
810 AutoTArray<nsCString, 4> vals;
811 for (auto val : ArrayIter(cx, array)) {
812 nsAutoJSString str;
813 NS_ENSURE_TRUE(str.init(cx, val.Value()), NS_ERROR_OUT_OF_MEMORY);
815 vals.AppendElement(NS_ConvertUTF16toUTF8(str));
817 NS_ENSURE_TRUE(vals.Length() > 0, NS_ERROR_INVALID_ARG);
819 nsCString type = vals[0];
820 vals.RemoveElementAt(0);
822 if (type.EqualsLiteral("override")) {
823 NS_ENSURE_TRUE(vals.Length() == 2, NS_ERROR_INVALID_ARG);
824 overrides.AppendElement(std::move(vals));
825 } else if (type.EqualsLiteral("content")) {
826 if (vals.Length() == 3 &&
827 vals[2].EqualsLiteral("contentaccessible=yes")) {
828 NS_ENSURE_TRUE(xpc::IsInAutomation(), NS_ERROR_INVALID_ARG);
829 vals.RemoveElementAt(2);
830 content.AppendElement(ContentEntry(
831 std::move(vals), nsChromeRegistry::CONTENT_ACCESSIBLE));
832 } else {
833 NS_ENSURE_TRUE(vals.Length() == 2, NS_ERROR_INVALID_ARG);
834 content.AppendElement(ContentEntry(std::move(vals)));
836 } else if (type.EqualsLiteral("locale")) {
837 NS_ENSURE_TRUE(vals.Length() == 3, NS_ERROR_INVALID_ARG);
838 locales.AppendElement(std::move(vals));
839 } else {
840 return NS_ERROR_INVALID_ARG;
844 if (!sObserverRegistered) {
845 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
846 NS_ENSURE_TRUE(obs, NS_ERROR_UNEXPECTED);
847 obs->AddObserver(this, "chrome-manifests-loaded", false);
849 sObserverRegistered = true;
852 auto entry = MakeRefPtr<RegistryEntries>(
853 location, std::move(overrides), std::move(content), std::move(locales));
855 entry->Register();
856 GetRegistryEntries().insertBack(entry);
858 entry.forget(result);
859 return NS_OK;
862 NS_IMETHODIMP
863 AddonManagerStartup::Observe(nsISupports* subject, const char* topic,
864 const char16_t* data) {
865 // The chrome registry is maintained as a set of global resource mappings
866 // generated mainly from manifest files, on-the-fly, as they're parsed.
867 // Entries added later override entries added earlier, and no record is kept
868 // of the former state.
870 // As a result, if we remove a dynamically-added manifest file, or a set of
871 // dynamic entries, the registry needs to be rebuilt from scratch, from the
872 // manifests and dynamic entries that remain. The chrome registry itself
873 // takes care of re-parsing manifes files. This observer notification lets
874 // us know when we need to re-register our dynamic entries.
875 if (!strcmp(topic, "chrome-manifests-loaded")) {
876 for (auto entry : GetRegistryEntries()) {
877 entry->Register();
881 return NS_OK;
884 } // namespace mozilla