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"
29 #include "mozilla/intl/LocaleService.h"
31 #include "nsIAppStartup.h"
32 #include "nsIObserverService.h"
33 #include "nsIPrefBranch.h"
34 #include "nsIPrefService.h"
35 #include "mozilla/Preferences.h"
36 #include "nsIResProtocolHandler.h"
37 #include "nsIScriptError.h"
38 #include "nsIXULRuntime.h"
40 #define PACKAGE_OVERRIDE_BRANCH "chrome.override_package."
41 #define SKIN NS_LITERAL_CSTRING("classic/1.0")
43 using namespace mozilla
;
44 using mozilla::dom::ContentParent
;
45 using mozilla::dom::PContentParent
;
46 using mozilla::intl::LocaleService
;
48 // We use a "best-fit" algorithm for matching locales and themes.
49 // 1) the exact selected locale/theme
50 // 2) (locales only) same language, different country
51 // e.g. en-GB is the selected locale, only en-US is available
52 // 3) any available locale/theme
55 * Match the language-part of two lang-COUNTRY codes, hopefully but
56 * not guaranteed to be in the form ab-CD or abz-CD. "ab" should also
57 * work, any other garbage-in will produce undefined results as long
58 * as it does not crash.
60 static bool LanguagesMatch(const nsACString
& a
, const nsACString
& b
) {
61 if (a
.Length() < 2 || b
.Length() < 2) return false;
63 nsACString::const_iterator as
, ae
, bs
, be
;
70 if (*as
== '-') return true;
76 if (as
== ae
&& bs
== be
) return true;
79 if (as
== ae
) return (*bs
== '-');
82 if (bs
== be
) return (*as
== '-');
88 nsChromeRegistryChrome::nsChromeRegistryChrome()
89 : mProfileLoaded(false), mDynamicRegistration(true) {}
91 nsChromeRegistryChrome::~nsChromeRegistryChrome() {}
93 nsresult
nsChromeRegistryChrome::Init() {
94 nsresult rv
= nsChromeRegistry::Init();
95 if (NS_FAILED(rv
)) return rv
;
97 bool safeMode
= false;
98 nsCOMPtr
<nsIXULRuntime
> xulrun(do_GetService(XULAPPINFO_SERVICE_CONTRACTID
));
99 if (xulrun
) xulrun
->GetInSafeMode(&safeMode
);
101 nsCOMPtr
<nsIObserverService
> obsService
=
102 mozilla::services::GetObserverService();
104 obsService
->AddObserver(this, "profile-initial-state", true);
105 obsService
->AddObserver(this, "intl:app-locales-changed", true);
112 nsChromeRegistryChrome::GetLocalesForPackage(
113 const nsACString
& aPackage
, nsIUTF8StringEnumerator
** aResult
) {
114 nsCString realpackage
;
115 nsresult rv
= OverrideLocalePackage(aPackage
, realpackage
);
116 if (NS_FAILED(rv
)) return rv
;
118 nsTArray
<nsCString
>* a
= new nsTArray
<nsCString
>;
119 if (!a
) return NS_ERROR_OUT_OF_MEMORY
;
122 if (mPackagesHash
.Get(realpackage
, &entry
)) {
123 entry
->locales
.EnumerateToArray(a
);
126 rv
= NS_NewAdoptingUTF8StringEnumerator(aResult
, a
);
127 if (NS_FAILED(rv
)) delete a
;
133 nsChromeRegistryChrome::IsLocaleRTL(const nsACString
& package
, bool* aResult
) {
136 nsAutoCString locale
;
137 GetSelectedLocale(package
, false, locale
);
138 if (locale
.Length() < 2) return NS_OK
;
140 *aResult
= GetDirectionForLocale(locale
);
145 * This method negotiates only between the app locale and the available
148 * If you want to get the current application's UI locale, please use
149 * LocaleService::GetAppLocaleAsLangTag.
151 nsresult
nsChromeRegistryChrome::GetSelectedLocale(const nsACString
& aPackage
,
153 nsACString
& aLocale
) {
154 nsAutoCString reqLocale
;
155 if (aPackage
.EqualsLiteral("global")) {
156 LocaleService::GetInstance()->GetAppLocaleAsLangTag(reqLocale
);
158 AutoTArray
<nsCString
, 10> requestedLocales
;
159 LocaleService::GetInstance()->GetRequestedLocales(requestedLocales
);
160 reqLocale
.Assign(requestedLocales
[0]);
163 nsCString realpackage
;
164 nsresult rv
= OverrideLocalePackage(aPackage
, realpackage
);
165 if (NS_FAILED(rv
)) return rv
;
167 if (!mPackagesHash
.Get(realpackage
, &entry
)) return NS_ERROR_FILE_NOT_FOUND
;
169 aLocale
= entry
->locales
.GetSelected(reqLocale
, nsProviderArray::LOCALE
);
170 if (aLocale
.IsEmpty()) return NS_ERROR_FAILURE
;
173 SanitizeForBCP47(aLocale
);
179 nsresult
nsChromeRegistryChrome::OverrideLocalePackage(
180 const nsACString
& aPackage
, nsACString
& aOverride
) {
181 const nsACString
& pref
=
182 NS_LITERAL_CSTRING(PACKAGE_OVERRIDE_BRANCH
) + aPackage
;
183 nsAutoCString override
;
184 nsresult rv
= mozilla::Preferences::GetCString(PromiseFlatCString(pref
).get(),
186 if (NS_SUCCEEDED(rv
)) {
187 aOverride
= override
;
189 aOverride
= aPackage
;
195 nsChromeRegistryChrome::Observe(nsISupports
* aSubject
, const char* aTopic
,
196 const char16_t
* someData
) {
199 if (!strcmp("profile-initial-state", aTopic
)) {
200 mProfileLoaded
= true;
201 } else if (!strcmp("intl:app-locales-changed", aTopic
)) {
202 if (mProfileLoaded
) {
206 NS_ERROR("Unexpected observer topic!");
213 nsChromeRegistryChrome::CheckForNewChrome() {
214 nsCOMPtr
<nsIAppStartup
> appStartup
= components::AppStartup::Service();
215 if (appStartup
->GetShuttingDown()) {
216 MOZ_ASSERT(false, "checking for new chrome during shutdown");
217 return NS_ERROR_UNEXPECTED
;
220 mPackagesHash
.Clear();
221 mOverrideTable
.Clear();
223 mDynamicRegistration
= false;
225 nsComponentManagerImpl::gComponentManager
->RereadChromeManifests();
227 mDynamicRegistration
= true;
229 SendRegisteredChrome(nullptr);
233 static void SerializeURI(nsIURI
* aURI
, SerializedURI
& aSerializedURI
) {
236 aURI
->GetSpec(aSerializedURI
.spec
);
239 void nsChromeRegistryChrome::SendRegisteredChrome(
240 mozilla::dom::PContentParent
* aParent
) {
241 nsTArray
<ChromePackage
> packages
;
242 nsTArray
<SubstitutionMapping
> resources
;
243 nsTArray
<OverrideMapping
> overrides
;
245 for (auto iter
= mPackagesHash
.Iter(); !iter
.Done(); iter
.Next()) {
246 ChromePackage chromePackage
;
247 ChromePackageFromPackageEntry(iter
.Key(), iter
.UserData(), &chromePackage
,
249 packages
.AppendElement(chromePackage
);
252 // If we were passed a parent then a new child process has been created and
253 // has requested all of the chrome so send it the resources too. Otherwise
254 // resource mappings are sent by the resource protocol handler dynamically.
256 nsCOMPtr
<nsIIOService
> io(do_GetIOService());
257 NS_ENSURE_TRUE_VOID(io
);
259 nsCOMPtr
<nsIProtocolHandler
> ph
;
260 nsresult rv
= io
->GetProtocolHandler("resource", getter_AddRefs(ph
));
261 NS_ENSURE_SUCCESS_VOID(rv
);
263 nsCOMPtr
<nsIResProtocolHandler
> irph(do_QueryInterface(ph
));
264 nsResProtocolHandler
* rph
= static_cast<nsResProtocolHandler
*>(irph
.get());
265 rv
= rph
->CollectSubstitutions(resources
);
266 NS_ENSURE_SUCCESS_VOID(rv
);
269 for (auto iter
= mOverrideTable
.Iter(); !iter
.Done(); iter
.Next()) {
270 SerializedURI chromeURI
, overrideURI
;
272 SerializeURI(iter
.Key(), chromeURI
);
273 SerializeURI(iter
.UserData(), overrideURI
);
275 OverrideMapping override
= {chromeURI
, overrideURI
};
276 overrides
.AppendElement(override
);
279 nsAutoCString appLocale
;
280 LocaleService::GetInstance()->GetAppLocaleAsLangTag(appLocale
);
283 bool success
= aParent
->SendRegisterChrome(packages
, resources
, overrides
,
285 NS_ENSURE_TRUE_VOID(success
);
287 nsTArray
<ContentParent
*> parents
;
288 ContentParent::GetAll(parents
);
289 if (!parents
.Length()) return;
291 for (uint32_t i
= 0; i
< parents
.Length(); i
++) {
292 DebugOnly
<bool> success
= parents
[i
]->SendRegisterChrome(
293 packages
, resources
, overrides
, appLocale
, true);
294 NS_WARNING_ASSERTION(success
,
295 "couldn't reset a child's registered chrome");
301 void nsChromeRegistryChrome::ChromePackageFromPackageEntry(
302 const nsACString
& aPackageName
, PackageEntry
* aPackage
,
303 ChromePackage
* aChromePackage
, const nsCString
& aSelectedSkin
) {
304 nsAutoCString appLocale
;
305 LocaleService::GetInstance()->GetAppLocaleAsLangTag(appLocale
);
307 SerializeURI(aPackage
->baseURI
, aChromePackage
->contentBaseURI
);
308 SerializeURI(aPackage
->locales
.GetBase(appLocale
, nsProviderArray::LOCALE
),
309 aChromePackage
->localeBaseURI
);
310 SerializeURI(aPackage
->skins
.GetBase(aSelectedSkin
, nsProviderArray::ANY
),
311 aChromePackage
->skinBaseURI
);
312 aChromePackage
->package
= aPackageName
;
313 aChromePackage
->flags
= aPackage
->flags
;
316 static bool CanLoadResource(nsIURI
* aResourceURI
) {
317 bool isLocalResource
= false;
318 (void)NS_URIChainHasFlags(aResourceURI
,
319 nsIProtocolHandler::URI_IS_LOCAL_RESOURCE
,
321 return isLocalResource
;
324 nsIURI
* nsChromeRegistryChrome::GetBaseURIFromPackage(
325 const nsCString
& aPackage
, const nsCString
& aProvider
,
326 const nsCString
& aPath
) {
328 if (!mPackagesHash
.Get(aPackage
, &entry
)) {
329 if (!mInitialized
) return nullptr;
331 LogMessage("No chrome package registered for chrome://%s/%s/%s",
332 aPackage
.get(), aProvider
.get(), aPath
.get());
337 if (aProvider
.EqualsLiteral("locale")) {
338 nsAutoCString appLocale
;
339 LocaleService::GetInstance()->GetAppLocaleAsLangTag(appLocale
);
340 return entry
->locales
.GetBase(appLocale
, nsProviderArray::LOCALE
);
341 } else if (aProvider
.EqualsLiteral("skin")) {
342 return entry
->skins
.GetBase(SKIN
, nsProviderArray::ANY
);
343 } else if (aProvider
.EqualsLiteral("content")) {
344 return entry
->baseURI
;
349 nsresult
nsChromeRegistryChrome::GetFlagsFromPackage(const nsCString
& aPackage
,
352 if (!mPackagesHash
.Get(aPackage
, &entry
)) return NS_ERROR_FILE_NOT_FOUND
;
354 *aFlags
= entry
->flags
;
358 nsChromeRegistryChrome::ProviderEntry
*
359 nsChromeRegistryChrome::nsProviderArray::GetProvider(
360 const nsACString
& aPreferred
, MatchType aType
) {
361 size_t i
= mArray
.Length();
362 if (!i
) return nullptr;
364 ProviderEntry
* found
= nullptr; // Only set if we find a partial-match locale
365 ProviderEntry
* entry
= nullptr;
369 if (aPreferred
.Equals(entry
->provider
)) return entry
;
371 if (aType
!= LOCALE
) continue;
373 if (LanguagesMatch(aPreferred
, entry
->provider
)) {
378 if (!found
&& entry
->provider
.EqualsLiteral("en-US")) found
= entry
;
381 if (!found
&& aType
!= EXACT
) return entry
;
386 nsIURI
* nsChromeRegistryChrome::nsProviderArray::GetBase(
387 const nsACString
& aPreferred
, MatchType aType
) {
388 ProviderEntry
* provider
= GetProvider(aPreferred
, aType
);
390 if (!provider
) return nullptr;
392 return provider
->baseURI
;
395 const nsACString
& nsChromeRegistryChrome::nsProviderArray::GetSelected(
396 const nsACString
& aPreferred
, MatchType aType
) {
397 ProviderEntry
* entry
= GetProvider(aPreferred
, aType
);
399 if (entry
) return entry
->provider
;
401 return EmptyCString();
404 void nsChromeRegistryChrome::nsProviderArray::SetBase(
405 const nsACString
& aProvider
, nsIURI
* aBaseURL
) {
406 ProviderEntry
* provider
= GetProvider(aProvider
, EXACT
);
409 provider
->baseURI
= aBaseURL
;
413 // no existing entries, add a new one
414 mArray
.AppendElement(ProviderEntry(aProvider
, aBaseURL
));
417 void nsChromeRegistryChrome::nsProviderArray::EnumerateToArray(
418 nsTArray
<nsCString
>* a
) {
419 int32_t i
= mArray
.Length();
421 a
->AppendElement(mArray
[i
].provider
);
425 nsIURI
* nsChromeRegistry::ManifestProcessingContext::GetManifestURI() {
428 mFile
.GetURIString(uri
);
429 NS_NewURI(getter_AddRefs(mManifestURI
), uri
);
434 already_AddRefed
<nsIURI
>
435 nsChromeRegistry::ManifestProcessingContext::ResolveURI(const char* uri
) {
436 nsIURI
* baseuri
= GetManifestURI();
437 if (!baseuri
) return nullptr;
439 nsCOMPtr
<nsIURI
> resolved
;
440 nsresult rv
= NS_NewURI(getter_AddRefs(resolved
), uri
, baseuri
);
441 if (NS_FAILED(rv
)) return nullptr;
443 return resolved
.forget();
446 static void EnsureLowerCase(char* aBuf
) {
447 for (; *aBuf
; ++aBuf
) {
449 if (ch
>= 'A' && ch
<= 'Z') *aBuf
= ch
+ 'a' - 'A';
453 static void SendManifestEntry(const ChromeRegistryItem
& aItem
) {
454 nsTArray
<ContentParent
*> parents
;
455 ContentParent::GetAll(parents
);
456 if (!parents
.Length()) return;
458 for (uint32_t i
= 0; i
< parents
.Length(); i
++) {
459 Unused
<< parents
[i
]->SendRegisterChromeItem(aItem
);
463 void nsChromeRegistryChrome::ManifestContent(ManifestProcessingContext
& cx
,
464 int lineno
, char* const* argv
,
466 char* package
= argv
[0];
469 EnsureLowerCase(package
);
471 nsCOMPtr
<nsIURI
> resolved
= cx
.ResolveURI(uri
);
473 LogMessageWithContext(
474 cx
.GetManifestURI(), lineno
, nsIScriptError::warningFlag
,
475 "During chrome registration, unable to create URI '%s'.", uri
);
479 if (!CanLoadResource(resolved
)) {
480 LogMessageWithContext(resolved
, lineno
, nsIScriptError::warningFlag
,
481 "During chrome registration, cannot register "
482 "non-local URI '%s' as content.",
487 nsDependentCString
packageName(package
);
488 PackageEntry
* entry
= mPackagesHash
.LookupOrAdd(packageName
);
489 entry
->baseURI
= resolved
;
490 entry
->flags
= flags
;
492 if (mDynamicRegistration
) {
493 ChromePackage chromePackage
;
494 ChromePackageFromPackageEntry(packageName
, entry
, &chromePackage
, SKIN
);
495 SendManifestEntry(chromePackage
);
499 void nsChromeRegistryChrome::ManifestLocale(ManifestProcessingContext
& cx
,
500 int lineno
, char* const* argv
,
502 char* package
= argv
[0];
503 char* provider
= argv
[1];
506 EnsureLowerCase(package
);
508 nsCOMPtr
<nsIURI
> resolved
= cx
.ResolveURI(uri
);
510 LogMessageWithContext(
511 cx
.GetManifestURI(), lineno
, nsIScriptError::warningFlag
,
512 "During chrome registration, unable to create URI '%s'.", uri
);
516 if (!CanLoadResource(resolved
)) {
517 LogMessageWithContext(resolved
, lineno
, nsIScriptError::warningFlag
,
518 "During chrome registration, cannot register "
519 "non-local URI '%s' as content.",
524 nsDependentCString
packageName(package
);
525 PackageEntry
* entry
= mPackagesHash
.LookupOrAdd(packageName
);
526 entry
->locales
.SetBase(nsDependentCString(provider
), resolved
);
528 if (mDynamicRegistration
) {
529 ChromePackage chromePackage
;
530 ChromePackageFromPackageEntry(packageName
, entry
, &chromePackage
, SKIN
);
531 SendManifestEntry(chromePackage
);
534 // We use mainPackage as the package we track for reporting new locales being
535 // registered. For most cases it will be "global", but for Fennec it will be
537 nsAutoCString mainPackage
;
539 OverrideLocalePackage(NS_LITERAL_CSTRING("global"), mainPackage
);
545 void nsChromeRegistryChrome::ManifestSkin(ManifestProcessingContext
& cx
,
546 int lineno
, char* const* argv
,
548 char* package
= argv
[0];
549 char* provider
= argv
[1];
552 EnsureLowerCase(package
);
554 nsCOMPtr
<nsIURI
> resolved
= cx
.ResolveURI(uri
);
556 LogMessageWithContext(
557 cx
.GetManifestURI(), lineno
, nsIScriptError::warningFlag
,
558 "During chrome registration, unable to create URI '%s'.", uri
);
562 if (!CanLoadResource(resolved
)) {
563 LogMessageWithContext(resolved
, lineno
, nsIScriptError::warningFlag
,
564 "During chrome registration, cannot register "
565 "non-local URI '%s' as content.",
570 nsDependentCString
packageName(package
);
571 PackageEntry
* entry
= mPackagesHash
.LookupOrAdd(packageName
);
572 entry
->skins
.SetBase(nsDependentCString(provider
), resolved
);
574 if (mDynamicRegistration
) {
575 ChromePackage chromePackage
;
576 ChromePackageFromPackageEntry(packageName
, entry
, &chromePackage
, SKIN
);
577 SendManifestEntry(chromePackage
);
581 void nsChromeRegistryChrome::ManifestOverride(ManifestProcessingContext
& cx
,
582 int lineno
, char* const* argv
,
584 char* chrome
= argv
[0];
585 char* resolved
= argv
[1];
587 nsCOMPtr
<nsIURI
> chromeuri
= cx
.ResolveURI(chrome
);
588 nsCOMPtr
<nsIURI
> resolveduri
= cx
.ResolveURI(resolved
);
589 if (!chromeuri
|| !resolveduri
) {
590 LogMessageWithContext(cx
.GetManifestURI(), lineno
,
591 nsIScriptError::warningFlag
,
592 "During chrome registration, unable to create URI.");
596 if (cx
.mType
== NS_SKIN_LOCATION
) {
597 bool chromeSkinOnly
=
598 chromeuri
->SchemeIs("chrome") && resolveduri
->SchemeIs("chrome");
599 if (chromeSkinOnly
) {
600 nsAutoCString chromePath
, resolvedPath
;
601 chromeuri
->GetPathQueryRef(chromePath
);
602 resolveduri
->GetPathQueryRef(resolvedPath
);
604 StringBeginsWith(chromePath
, NS_LITERAL_CSTRING("/skin/")) &&
605 StringBeginsWith(resolvedPath
, NS_LITERAL_CSTRING("/skin/"));
607 if (!chromeSkinOnly
) {
608 LogMessageWithContext(
609 cx
.GetManifestURI(), lineno
, nsIScriptError::warningFlag
,
610 "Cannot register non-chrome://.../skin/ URIs '%s' and '%s' as "
611 "overrides and/or to be overridden from a skin manifest.",
617 if (!CanLoadResource(resolveduri
)) {
618 LogMessageWithContext(
619 cx
.GetManifestURI(), lineno
, nsIScriptError::warningFlag
,
620 "Cannot register non-local URI '%s' for an override.", resolved
);
623 mOverrideTable
.Put(chromeuri
, resolveduri
);
625 if (mDynamicRegistration
) {
626 SerializedURI serializedChrome
;
627 SerializedURI serializedOverride
;
629 SerializeURI(chromeuri
, serializedChrome
);
630 SerializeURI(resolveduri
, serializedOverride
);
632 OverrideMapping override
= {serializedChrome
, serializedOverride
};
633 SendManifestEntry(override
);
637 void nsChromeRegistryChrome::ManifestResource(ManifestProcessingContext
& cx
,
638 int lineno
, char* const* argv
,
640 char* package
= argv
[0];
643 EnsureLowerCase(package
);
644 nsDependentCString
host(package
);
646 nsCOMPtr
<nsIIOService
> io
= mozilla::services::GetIOService();
648 NS_WARNING("No IO service trying to process chrome manifests");
652 nsCOMPtr
<nsIProtocolHandler
> ph
;
653 nsresult rv
= io
->GetProtocolHandler("resource", getter_AddRefs(ph
));
654 if (NS_FAILED(rv
)) return;
656 nsCOMPtr
<nsIResProtocolHandler
> rph
= do_QueryInterface(ph
);
658 nsCOMPtr
<nsIURI
> resolved
= cx
.ResolveURI(uri
);
660 LogMessageWithContext(
661 cx
.GetManifestURI(), lineno
, nsIScriptError::warningFlag
,
662 "During chrome registration, unable to create URI '%s'.", uri
);
666 if (!CanLoadResource(resolved
)) {
667 LogMessageWithContext(
668 cx
.GetManifestURI(), lineno
, nsIScriptError::warningFlag
,
669 "Warning: cannot register non-local URI '%s' as a resource.", uri
);
673 // By default, Firefox resources are not content-accessible unless the
674 // manifests opts in.
675 bool contentAccessible
= (flags
& nsChromeRegistry::CONTENT_ACCESSIBLE
);
677 uint32_t substitutionFlags
= 0;
678 if (contentAccessible
) {
679 substitutionFlags
|= nsIResProtocolHandler::ALLOW_CONTENT_ACCESS
;
681 rv
= rph
->SetSubstitutionWithFlags(host
, resolved
, substitutionFlags
);
683 LogMessageWithContext(cx
.GetManifestURI(), lineno
,
684 nsIScriptError::warningFlag
,
685 "Warning: cannot set substitution for '%s'.", uri
);