1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sts=2 sw=2 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/ContentParent.h"
8 #include "RegistryMessageUtils.h"
9 #include "nsResProtocolHandler.h"
11 #include "nsChromeRegistryChrome.h"
15 #elif defined(XP_MACOSX)
16 # include <CoreServices/CoreServices.h>
19 #include "nsArrayEnumerator.h"
20 #include "nsComponentManager.h"
21 #include "nsEnumeratorUtils.h"
22 #include "nsNetUtil.h"
23 #include "nsStringEnumerator.h"
24 #include "nsTextFormatter.h"
25 #include "nsXPCOMCIDInternal.h"
27 #include "mozilla/LookAndFeel.h"
28 #include "mozilla/Unused.h"
30 #include "nsIObserverService.h"
31 #include "mozilla/AppShutdown.h"
32 #include "mozilla/Components.h"
33 #include "mozilla/Preferences.h"
34 #include "nsIResProtocolHandler.h"
35 #include "nsIScriptError.h"
36 #include "nsIXULRuntime.h"
38 #define PACKAGE_OVERRIDE_BRANCH "chrome.override_package."
39 #define SKIN "classic/1.0"_ns
41 using namespace mozilla
;
42 using mozilla::dom::ContentParent
;
43 using mozilla::dom::PContentParent
;
44 using mozilla::intl::LocaleService
;
46 // We use a "best-fit" algorithm for matching locales and themes.
47 // 1) the exact selected locale/theme
48 // 2) (locales only) same language, different country
49 // e.g. en-GB is the selected locale, only en-US is available
50 // 3) any available locale/theme
53 * Match the language-part of two lang-COUNTRY codes, hopefully but
54 * not guaranteed to be in the form ab-CD or abz-CD. "ab" should also
55 * work, any other garbage-in will produce undefined results as long
56 * as it does not crash.
58 static bool LanguagesMatch(const nsACString
& a
, const nsACString
& b
) {
59 if (a
.Length() < 2 || b
.Length() < 2) return false;
61 nsACString::const_iterator as
, ae
, bs
, be
;
68 if (*as
== '-') return true;
74 if (as
== ae
&& bs
== be
) return true;
77 if (as
== ae
) return (*bs
== '-');
80 if (bs
== be
) return (*as
== '-');
86 nsChromeRegistryChrome::nsChromeRegistryChrome()
87 : mProfileLoaded(false), mDynamicRegistration(true) {}
89 nsChromeRegistryChrome::~nsChromeRegistryChrome() {}
91 nsresult
nsChromeRegistryChrome::Init() {
92 nsresult rv
= nsChromeRegistry::Init();
93 if (NS_FAILED(rv
)) return rv
;
95 bool safeMode
= false;
96 nsCOMPtr
<nsIXULRuntime
> xulrun(do_GetService(XULAPPINFO_SERVICE_CONTRACTID
));
97 if (xulrun
) xulrun
->GetInSafeMode(&safeMode
);
99 nsCOMPtr
<nsIObserverService
> obsService
=
100 mozilla::services::GetObserverService();
102 obsService
->AddObserver(this, "profile-initial-state", true);
103 obsService
->AddObserver(this, "intl:app-locales-changed", true);
110 nsChromeRegistryChrome::GetLocalesForPackage(
111 const nsACString
& aPackage
, nsIUTF8StringEnumerator
** aResult
) {
112 nsCString realpackage
;
113 nsresult rv
= OverrideLocalePackage(aPackage
, realpackage
);
114 if (NS_FAILED(rv
)) return rv
;
116 nsTArray
<nsCString
>* a
= new nsTArray
<nsCString
>;
117 if (!a
) return NS_ERROR_OUT_OF_MEMORY
;
120 if (mPackagesHash
.Get(realpackage
, &entry
)) {
121 entry
->locales
.EnumerateToArray(a
);
124 rv
= NS_NewAdoptingUTF8StringEnumerator(aResult
, a
);
125 if (NS_FAILED(rv
)) delete a
;
131 nsChromeRegistryChrome::IsLocaleRTL(const nsACString
& package
, bool* aResult
) {
134 nsAutoCString locale
;
135 GetSelectedLocale(package
, locale
);
136 if (locale
.Length() < 2) return NS_OK
;
138 *aResult
= LocaleService::IsLocaleRTL(locale
);
143 * This method negotiates only between the app locale and the available
146 * If you want to get the current application's UI locale, please use
147 * LocaleService::GetAppLocaleAsBCP47.
149 nsresult
nsChromeRegistryChrome::GetSelectedLocale(const nsACString
& aPackage
,
150 nsACString
& aLocale
) {
151 nsAutoCString reqLocale
;
152 if (aPackage
.EqualsLiteral("global")) {
153 LocaleService::GetInstance()->GetAppLocaleAsBCP47(reqLocale
);
155 AutoTArray
<nsCString
, 10> requestedLocales
;
156 LocaleService::GetInstance()->GetRequestedLocales(requestedLocales
);
157 reqLocale
.Assign(requestedLocales
[0]);
160 nsCString realpackage
;
161 nsresult rv
= OverrideLocalePackage(aPackage
, realpackage
);
162 if (NS_FAILED(rv
)) return rv
;
164 if (!mPackagesHash
.Get(realpackage
, &entry
)) return NS_ERROR_FILE_NOT_FOUND
;
166 aLocale
= entry
->locales
.GetSelected(reqLocale
, nsProviderArray::LOCALE
);
167 if (aLocale
.IsEmpty()) return NS_ERROR_FAILURE
;
172 nsresult
nsChromeRegistryChrome::OverrideLocalePackage(
173 const nsACString
& aPackage
, nsACString
& aOverride
) {
174 const nsACString
& pref
= nsLiteralCString(PACKAGE_OVERRIDE_BRANCH
) + aPackage
;
175 nsAutoCString override
;
176 nsresult rv
= mozilla::Preferences::GetCString(PromiseFlatCString(pref
).get(),
178 if (NS_SUCCEEDED(rv
)) {
179 aOverride
= override
;
181 aOverride
= aPackage
;
187 nsChromeRegistryChrome::Observe(nsISupports
* aSubject
, const char* aTopic
,
188 const char16_t
* someData
) {
191 if (!strcmp("profile-initial-state", aTopic
)) {
192 mProfileLoaded
= true;
193 } else if (!strcmp("intl:app-locales-changed", aTopic
)) {
194 if (mProfileLoaded
) {
198 NS_ERROR("Unexpected observer topic!");
205 nsChromeRegistryChrome::CheckForNewChrome() {
206 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed
)) {
207 MOZ_ASSERT(false, "checking for new chrome during shutdown");
208 return NS_ERROR_UNEXPECTED
;
211 mPackagesHash
.Clear();
212 mOverrideTable
.Clear();
214 mDynamicRegistration
= false;
216 nsComponentManagerImpl::gComponentManager
->RereadChromeManifests();
218 mDynamicRegistration
= true;
220 SendRegisteredChrome(nullptr);
224 static void SerializeURI(nsIURI
* aURI
, SerializedURI
& aSerializedURI
) {
227 aURI
->GetSpec(aSerializedURI
.spec
);
230 void nsChromeRegistryChrome::SendRegisteredChrome(
231 mozilla::dom::PContentParent
* aParent
) {
232 nsTArray
<ChromePackage
> packages
;
233 nsTArray
<SubstitutionMapping
> resources
;
234 nsTArray
<OverrideMapping
> overrides
;
236 for (const auto& entry
: mPackagesHash
) {
237 ChromePackage chromePackage
;
238 ChromePackageFromPackageEntry(entry
.GetKey(), entry
.GetWeak(),
239 &chromePackage
, SKIN
);
240 packages
.AppendElement(chromePackage
);
243 // If we were passed a parent then a new child process has been created and
244 // has requested all of the chrome so send it the resources too. Otherwise
245 // resource mappings are sent by the resource protocol handler dynamically.
247 nsCOMPtr
<nsIIOService
> io(do_GetIOService());
248 NS_ENSURE_TRUE_VOID(io
);
250 nsCOMPtr
<nsIProtocolHandler
> ph
;
251 nsresult rv
= io
->GetProtocolHandler("resource", getter_AddRefs(ph
));
252 NS_ENSURE_SUCCESS_VOID(rv
);
254 nsCOMPtr
<nsIResProtocolHandler
> irph(do_QueryInterface(ph
));
255 nsResProtocolHandler
* rph
= static_cast<nsResProtocolHandler
*>(irph
.get());
256 rv
= rph
->CollectSubstitutions(resources
);
257 NS_ENSURE_SUCCESS_VOID(rv
);
260 for (const auto& entry
: mOverrideTable
) {
261 SerializedURI chromeURI
, overrideURI
;
263 SerializeURI(entry
.GetKey(), chromeURI
);
264 SerializeURI(entry
.GetWeak(), overrideURI
);
266 overrides
.AppendElement(
267 OverrideMapping
{std::move(chromeURI
), std::move(overrideURI
)});
270 nsAutoCString appLocale
;
271 LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale
);
274 bool success
= aParent
->SendRegisterChrome(packages
, resources
, overrides
,
276 NS_ENSURE_TRUE_VOID(success
);
278 nsTArray
<ContentParent
*> parents
;
279 ContentParent::GetAll(parents
);
280 if (!parents
.Length()) return;
282 for (uint32_t i
= 0; i
< parents
.Length(); i
++) {
283 DebugOnly
<bool> success
= parents
[i
]->SendRegisterChrome(
284 packages
, resources
, overrides
, appLocale
, true);
285 NS_WARNING_ASSERTION(success
,
286 "couldn't reset a child's registered chrome");
292 void nsChromeRegistryChrome::ChromePackageFromPackageEntry(
293 const nsACString
& aPackageName
, PackageEntry
* aPackage
,
294 ChromePackage
* aChromePackage
, const nsCString
& aSelectedSkin
) {
295 nsAutoCString appLocale
;
296 LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale
);
298 SerializeURI(aPackage
->baseURI
, aChromePackage
->contentBaseURI
);
299 SerializeURI(aPackage
->locales
.GetBase(appLocale
, nsProviderArray::LOCALE
),
300 aChromePackage
->localeBaseURI
);
301 SerializeURI(aPackage
->skins
.GetBase(aSelectedSkin
, nsProviderArray::ANY
),
302 aChromePackage
->skinBaseURI
);
303 aChromePackage
->package
= aPackageName
;
304 aChromePackage
->flags
= aPackage
->flags
;
307 static bool CanLoadResource(nsIURI
* aResourceURI
) {
308 bool isLocalResource
= false;
309 (void)NS_URIChainHasFlags(aResourceURI
,
310 nsIProtocolHandler::URI_IS_LOCAL_RESOURCE
,
312 return isLocalResource
;
315 nsIURI
* nsChromeRegistryChrome::GetBaseURIFromPackage(
316 const nsCString
& aPackage
, const nsCString
& aProvider
,
317 const nsCString
& aPath
) {
319 if (!mPackagesHash
.Get(aPackage
, &entry
)) {
320 if (!mInitialized
) return nullptr;
322 LogMessage("No chrome package registered for chrome://%s/%s/%s",
323 aPackage
.get(), aProvider
.get(), aPath
.get());
328 if (aProvider
.EqualsLiteral("locale")) {
329 nsAutoCString appLocale
;
330 LocaleService::GetInstance()->GetAppLocaleAsLangTag(appLocale
);
331 return entry
->locales
.GetBase(appLocale
, nsProviderArray::LOCALE
);
332 } else if (aProvider
.EqualsLiteral("skin")) {
333 return entry
->skins
.GetBase(SKIN
, nsProviderArray::ANY
);
334 } else if (aProvider
.EqualsLiteral("content")) {
335 return entry
->baseURI
;
340 nsresult
nsChromeRegistryChrome::GetFlagsFromPackage(const nsCString
& aPackage
,
343 if (!mPackagesHash
.Get(aPackage
, &entry
)) return NS_ERROR_FILE_NOT_FOUND
;
345 *aFlags
= entry
->flags
;
349 nsChromeRegistryChrome::ProviderEntry
*
350 nsChromeRegistryChrome::nsProviderArray::GetProvider(
351 const nsACString
& aPreferred
, MatchType aType
) {
352 size_t i
= mArray
.Length();
353 if (!i
) return nullptr;
355 ProviderEntry
* found
= nullptr; // Only set if we find a partial-match locale
356 ProviderEntry
* entry
= nullptr;
360 if (aPreferred
.Equals(entry
->provider
)) return entry
;
362 if (aType
!= LOCALE
) continue;
364 if (LanguagesMatch(aPreferred
, entry
->provider
)) {
369 if (!found
&& entry
->provider
.EqualsLiteral("en-US")) found
= entry
;
372 if (!found
&& aType
!= EXACT
) return entry
;
377 nsIURI
* nsChromeRegistryChrome::nsProviderArray::GetBase(
378 const nsACString
& aPreferred
, MatchType aType
) {
379 ProviderEntry
* provider
= GetProvider(aPreferred
, aType
);
381 if (!provider
) return nullptr;
383 return provider
->baseURI
;
386 const nsACString
& nsChromeRegistryChrome::nsProviderArray::GetSelected(
387 const nsACString
& aPreferred
, MatchType aType
) {
388 ProviderEntry
* entry
= GetProvider(aPreferred
, aType
);
390 if (entry
) return entry
->provider
;
392 return EmptyCString();
395 void nsChromeRegistryChrome::nsProviderArray::SetBase(
396 const nsACString
& aProvider
, nsIURI
* aBaseURL
) {
397 ProviderEntry
* provider
= GetProvider(aProvider
, EXACT
);
400 provider
->baseURI
= aBaseURL
;
404 // no existing entries, add a new one
405 mArray
.AppendElement(ProviderEntry(aProvider
, aBaseURL
));
408 void nsChromeRegistryChrome::nsProviderArray::EnumerateToArray(
409 nsTArray
<nsCString
>* a
) {
410 int32_t i
= mArray
.Length();
412 a
->AppendElement(mArray
[i
].provider
);
416 nsIURI
* nsChromeRegistry::ManifestProcessingContext::GetManifestURI() {
419 mFile
.GetURIString(uri
);
420 NS_NewURI(getter_AddRefs(mManifestURI
), uri
);
425 already_AddRefed
<nsIURI
>
426 nsChromeRegistry::ManifestProcessingContext::ResolveURI(const char* uri
) {
427 nsIURI
* baseuri
= GetManifestURI();
428 if (!baseuri
) return nullptr;
430 nsCOMPtr
<nsIURI
> resolved
;
431 nsresult rv
= NS_NewURI(getter_AddRefs(resolved
), uri
, baseuri
);
432 if (NS_FAILED(rv
)) return nullptr;
434 return resolved
.forget();
437 static void EnsureLowerCase(char* aBuf
) {
438 for (; *aBuf
; ++aBuf
) {
440 if (ch
>= 'A' && ch
<= 'Z') *aBuf
= ch
+ 'a' - 'A';
444 static void SendManifestEntry(const ChromeRegistryItem
& aItem
) {
445 nsTArray
<ContentParent
*> parents
;
446 ContentParent::GetAll(parents
);
447 if (!parents
.Length()) return;
449 for (uint32_t i
= 0; i
< parents
.Length(); i
++) {
450 Unused
<< parents
[i
]->SendRegisterChromeItem(aItem
);
454 void nsChromeRegistryChrome::ManifestContent(ManifestProcessingContext
& cx
,
455 int lineno
, char* const* argv
,
457 char* package
= argv
[0];
460 EnsureLowerCase(package
);
462 nsCOMPtr
<nsIURI
> resolved
= cx
.ResolveURI(uri
);
464 LogMessageWithContext(
465 cx
.GetManifestURI(), lineno
, nsIScriptError::warningFlag
,
466 "During chrome registration, unable to create URI '%s'.", uri
);
470 if (!CanLoadResource(resolved
)) {
471 LogMessageWithContext(resolved
, lineno
, nsIScriptError::warningFlag
,
472 "During chrome registration, cannot register "
473 "non-local URI '%s' as content.",
478 nsDependentCString
packageName(package
);
479 PackageEntry
* entry
= mPackagesHash
.GetOrInsertNew(packageName
);
480 entry
->baseURI
= resolved
;
481 entry
->flags
= flags
;
483 if (mDynamicRegistration
) {
484 ChromePackage chromePackage
;
485 ChromePackageFromPackageEntry(packageName
, entry
, &chromePackage
, SKIN
);
486 SendManifestEntry(chromePackage
);
490 void nsChromeRegistryChrome::ManifestLocale(ManifestProcessingContext
& cx
,
491 int lineno
, char* const* argv
,
493 char* package
= argv
[0];
494 char* provider
= argv
[1];
497 EnsureLowerCase(package
);
499 nsCOMPtr
<nsIURI
> resolved
= cx
.ResolveURI(uri
);
501 LogMessageWithContext(
502 cx
.GetManifestURI(), lineno
, nsIScriptError::warningFlag
,
503 "During chrome registration, unable to create URI '%s'.", uri
);
507 if (!CanLoadResource(resolved
)) {
508 LogMessageWithContext(resolved
, lineno
, nsIScriptError::warningFlag
,
509 "During chrome registration, cannot register "
510 "non-local URI '%s' as content.",
515 nsDependentCString
packageName(package
);
516 PackageEntry
* entry
= mPackagesHash
.GetOrInsertNew(packageName
);
517 entry
->locales
.SetBase(nsDependentCString(provider
), resolved
);
519 if (mDynamicRegistration
) {
520 ChromePackage chromePackage
;
521 ChromePackageFromPackageEntry(packageName
, entry
, &chromePackage
, SKIN
);
522 SendManifestEntry(chromePackage
);
525 // We use mainPackage as the package we track for reporting new locales being
526 // registered. For most cases it will be "global", but for Fennec it will be
528 nsAutoCString mainPackage
;
529 nsresult rv
= OverrideLocalePackage("global"_ns
, mainPackage
);
535 void nsChromeRegistryChrome::ManifestSkin(ManifestProcessingContext
& cx
,
536 int lineno
, char* const* argv
,
538 char* package
= argv
[0];
539 char* provider
= argv
[1];
542 EnsureLowerCase(package
);
544 nsCOMPtr
<nsIURI
> resolved
= cx
.ResolveURI(uri
);
546 LogMessageWithContext(
547 cx
.GetManifestURI(), lineno
, nsIScriptError::warningFlag
,
548 "During chrome registration, unable to create URI '%s'.", uri
);
552 if (!CanLoadResource(resolved
)) {
553 LogMessageWithContext(resolved
, lineno
, nsIScriptError::warningFlag
,
554 "During chrome registration, cannot register "
555 "non-local URI '%s' as content.",
560 nsDependentCString
packageName(package
);
561 PackageEntry
* entry
= mPackagesHash
.GetOrInsertNew(packageName
);
562 entry
->skins
.SetBase(nsDependentCString(provider
), resolved
);
564 if (mDynamicRegistration
) {
565 ChromePackage chromePackage
;
566 ChromePackageFromPackageEntry(packageName
, entry
, &chromePackage
, SKIN
);
567 SendManifestEntry(chromePackage
);
571 void nsChromeRegistryChrome::ManifestOverride(ManifestProcessingContext
& cx
,
572 int lineno
, char* const* argv
,
574 char* chrome
= argv
[0];
575 char* resolved
= argv
[1];
577 nsCOMPtr
<nsIURI
> chromeuri
= cx
.ResolveURI(chrome
);
578 nsCOMPtr
<nsIURI
> resolveduri
= cx
.ResolveURI(resolved
);
579 if (!chromeuri
|| !resolveduri
) {
580 LogMessageWithContext(cx
.GetManifestURI(), lineno
,
581 nsIScriptError::warningFlag
,
582 "During chrome registration, unable to create URI.");
586 if (cx
.mType
== NS_SKIN_LOCATION
) {
587 bool chromeSkinOnly
=
588 chromeuri
->SchemeIs("chrome") && resolveduri
->SchemeIs("chrome");
589 if (chromeSkinOnly
) {
590 nsAutoCString chromePath
, resolvedPath
;
591 chromeuri
->GetPathQueryRef(chromePath
);
592 resolveduri
->GetPathQueryRef(resolvedPath
);
593 chromeSkinOnly
= StringBeginsWith(chromePath
, "/skin/"_ns
) &&
594 StringBeginsWith(resolvedPath
, "/skin/"_ns
);
596 if (!chromeSkinOnly
) {
597 LogMessageWithContext(
598 cx
.GetManifestURI(), lineno
, nsIScriptError::warningFlag
,
599 "Cannot register non-chrome://.../skin/ URIs '%s' and '%s' as "
600 "overrides and/or to be overridden from a skin manifest.",
606 if (!CanLoadResource(resolveduri
)) {
607 LogMessageWithContext(
608 cx
.GetManifestURI(), lineno
, nsIScriptError::warningFlag
,
609 "Cannot register non-local URI '%s' for an override.", resolved
);
612 mOverrideTable
.InsertOrUpdate(chromeuri
, resolveduri
);
614 if (mDynamicRegistration
) {
615 SerializedURI serializedChrome
;
616 SerializedURI serializedOverride
;
618 SerializeURI(chromeuri
, serializedChrome
);
619 SerializeURI(resolveduri
, serializedOverride
);
621 OverrideMapping override
= {serializedChrome
, serializedOverride
};
622 SendManifestEntry(override
);
626 void nsChromeRegistryChrome::ManifestResource(ManifestProcessingContext
& cx
,
627 int lineno
, char* const* argv
,
629 char* package
= argv
[0];
632 EnsureLowerCase(package
);
633 nsDependentCString
host(package
);
635 nsCOMPtr
<nsIIOService
> io
= mozilla::components::IO::Service();
637 NS_WARNING("No IO service trying to process chrome manifests");
641 nsCOMPtr
<nsIProtocolHandler
> ph
;
642 nsresult rv
= io
->GetProtocolHandler("resource", getter_AddRefs(ph
));
643 if (NS_FAILED(rv
)) return;
645 nsCOMPtr
<nsIResProtocolHandler
> rph
= do_QueryInterface(ph
);
647 nsCOMPtr
<nsIURI
> resolved
= cx
.ResolveURI(uri
);
649 LogMessageWithContext(
650 cx
.GetManifestURI(), lineno
, nsIScriptError::warningFlag
,
651 "During chrome registration, unable to create URI '%s'.", uri
);
655 if (!CanLoadResource(resolved
)) {
656 LogMessageWithContext(
657 cx
.GetManifestURI(), lineno
, nsIScriptError::warningFlag
,
658 "Warning: cannot register non-local URI '%s' as a resource.", uri
);
662 // By default, Firefox resources are not content-accessible unless the
663 // manifests opts in.
664 bool contentAccessible
= (flags
& nsChromeRegistry::CONTENT_ACCESSIBLE
);
666 uint32_t substitutionFlags
= 0;
667 if (contentAccessible
) {
668 substitutionFlags
|= nsIResProtocolHandler::ALLOW_CONTENT_ACCESS
;
670 rv
= rph
->SetSubstitutionWithFlags(host
, resolved
, substitutionFlags
);
672 LogMessageWithContext(cx
.GetManifestURI(), lineno
,
673 nsIScriptError::warningFlag
,
674 "Warning: cannot set substitution for '%s'.", uri
);