/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 sts=2 sw=2 et tw=80: */ /* 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 "mozilla/dom/ContentParent.h" #include "RegistryMessageUtils.h" #include "nsResProtocolHandler.h" #include "nsChromeRegistryChrome.h" #if defined(XP_WIN) # include #elif defined(XP_MACOSX) # include #endif #include "nsArrayEnumerator.h" #include "nsComponentManager.h" #include "nsEnumeratorUtils.h" #include "nsNetUtil.h" #include "nsStringEnumerator.h" #include "nsTextFormatter.h" #include "nsXPCOMCIDInternal.h" #include "mozilla/LookAndFeel.h" #include "mozilla/Unused.h" #include "nsIObserverService.h" #include "mozilla/AppShutdown.h" #include "mozilla/Components.h" #include "mozilla/Preferences.h" #include "nsIResProtocolHandler.h" #include "nsIScriptError.h" #include "nsIXULRuntime.h" #define PACKAGE_OVERRIDE_BRANCH "chrome.override_package." #define SKIN "classic/1.0"_ns using namespace mozilla; using mozilla::dom::ContentParent; using mozilla::dom::PContentParent; using mozilla::intl::LocaleService; // We use a "best-fit" algorithm for matching locales and themes. // 1) the exact selected locale/theme // 2) (locales only) same language, different country // e.g. en-GB is the selected locale, only en-US is available // 3) any available locale/theme /** * Match the language-part of two lang-COUNTRY codes, hopefully but * not guaranteed to be in the form ab-CD or abz-CD. "ab" should also * work, any other garbage-in will produce undefined results as long * as it does not crash. */ static bool LanguagesMatch(const nsACString& a, const nsACString& b) { if (a.Length() < 2 || b.Length() < 2) return false; nsACString::const_iterator as, ae, bs, be; a.BeginReading(as); a.EndReading(ae); b.BeginReading(bs); b.EndReading(be); while (*as == *bs) { if (*as == '-') return true; ++as; ++bs; // reached the end if (as == ae && bs == be) return true; // "a" is short if (as == ae) return (*bs == '-'); // "b" is short if (bs == be) return (*as == '-'); } return false; } nsChromeRegistryChrome::nsChromeRegistryChrome() : mProfileLoaded(false), mDynamicRegistration(true) {} nsChromeRegistryChrome::~nsChromeRegistryChrome() {} nsresult nsChromeRegistryChrome::Init() { nsresult rv = nsChromeRegistry::Init(); if (NS_FAILED(rv)) return rv; bool safeMode = false; nsCOMPtr xulrun(do_GetService(XULAPPINFO_SERVICE_CONTRACTID)); if (xulrun) xulrun->GetInSafeMode(&safeMode); nsCOMPtr obsService = mozilla::services::GetObserverService(); if (obsService) { obsService->AddObserver(this, "profile-initial-state", true); obsService->AddObserver(this, "intl:app-locales-changed", true); } return NS_OK; } NS_IMETHODIMP nsChromeRegistryChrome::GetLocalesForPackage( const nsACString& aPackage, nsIUTF8StringEnumerator** aResult) { nsCString realpackage; nsresult rv = OverrideLocalePackage(aPackage, realpackage); if (NS_FAILED(rv)) return rv; nsTArray* a = new nsTArray; if (!a) return NS_ERROR_OUT_OF_MEMORY; PackageEntry* entry; if (mPackagesHash.Get(realpackage, &entry)) { entry->locales.EnumerateToArray(a); } rv = NS_NewAdoptingUTF8StringEnumerator(aResult, a); if (NS_FAILED(rv)) delete a; return rv; } NS_IMETHODIMP nsChromeRegistryChrome::IsLocaleRTL(const nsACString& package, bool* aResult) { *aResult = false; nsAutoCString locale; GetSelectedLocale(package, locale); if (locale.Length() < 2) return NS_OK; *aResult = LocaleService::IsLocaleRTL(locale); return NS_OK; } /** * This method negotiates only between the app locale and the available * chrome packages. * * If you want to get the current application's UI locale, please use * LocaleService::GetAppLocaleAsBCP47. */ nsresult nsChromeRegistryChrome::GetSelectedLocale(const nsACString& aPackage, nsACString& aLocale) { nsAutoCString reqLocale; if (aPackage.EqualsLiteral("global")) { LocaleService::GetInstance()->GetAppLocaleAsBCP47(reqLocale); } else { AutoTArray requestedLocales; LocaleService::GetInstance()->GetRequestedLocales(requestedLocales); reqLocale.Assign(requestedLocales[0]); } nsCString realpackage; nsresult rv = OverrideLocalePackage(aPackage, realpackage); if (NS_FAILED(rv)) return rv; PackageEntry* entry; if (!mPackagesHash.Get(realpackage, &entry)) return NS_ERROR_FILE_NOT_FOUND; aLocale = entry->locales.GetSelected(reqLocale, nsProviderArray::LOCALE); if (aLocale.IsEmpty()) return NS_ERROR_FAILURE; return NS_OK; } nsresult nsChromeRegistryChrome::OverrideLocalePackage( const nsACString& aPackage, nsACString& aOverride) { const nsACString& pref = nsLiteralCString(PACKAGE_OVERRIDE_BRANCH) + aPackage; nsAutoCString override; nsresult rv = mozilla::Preferences::GetCString(PromiseFlatCString(pref).get(), override); if (NS_SUCCEEDED(rv)) { aOverride = override; } else { aOverride = aPackage; } return NS_OK; } NS_IMETHODIMP nsChromeRegistryChrome::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* someData) { nsresult rv = NS_OK; if (!strcmp("profile-initial-state", aTopic)) { mProfileLoaded = true; } else if (!strcmp("intl:app-locales-changed", aTopic)) { if (mProfileLoaded) { FlushAllCaches(); } } else { NS_ERROR("Unexpected observer topic!"); } return rv; } NS_IMETHODIMP nsChromeRegistryChrome::CheckForNewChrome() { if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { MOZ_ASSERT(false, "checking for new chrome during shutdown"); return NS_ERROR_UNEXPECTED; } mPackagesHash.Clear(); mOverrideTable.Clear(); mDynamicRegistration = false; nsComponentManagerImpl::gComponentManager->RereadChromeManifests(); mDynamicRegistration = true; SendRegisteredChrome(nullptr); return NS_OK; } static void SerializeURI(nsIURI* aURI, SerializedURI& aSerializedURI) { if (!aURI) return; aURI->GetSpec(aSerializedURI.spec); } void nsChromeRegistryChrome::SendRegisteredChrome( mozilla::dom::PContentParent* aParent) { nsTArray packages; nsTArray resources; nsTArray overrides; for (const auto& entry : mPackagesHash) { ChromePackage chromePackage; ChromePackageFromPackageEntry(entry.GetKey(), entry.GetWeak(), &chromePackage, SKIN); packages.AppendElement(chromePackage); } // If we were passed a parent then a new child process has been created and // has requested all of the chrome so send it the resources too. Otherwise // resource mappings are sent by the resource protocol handler dynamically. if (aParent) { nsCOMPtr io(do_GetIOService()); NS_ENSURE_TRUE_VOID(io); nsCOMPtr ph; nsresult rv = io->GetProtocolHandler("resource", getter_AddRefs(ph)); NS_ENSURE_SUCCESS_VOID(rv); nsCOMPtr irph(do_QueryInterface(ph)); nsResProtocolHandler* rph = static_cast(irph.get()); rv = rph->CollectSubstitutions(resources); NS_ENSURE_SUCCESS_VOID(rv); } for (const auto& entry : mOverrideTable) { SerializedURI chromeURI, overrideURI; SerializeURI(entry.GetKey(), chromeURI); SerializeURI(entry.GetWeak(), overrideURI); overrides.AppendElement( OverrideMapping{std::move(chromeURI), std::move(overrideURI)}); } nsAutoCString appLocale; LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale); if (aParent) { bool success = aParent->SendRegisterChrome(packages, resources, overrides, appLocale, false); NS_ENSURE_TRUE_VOID(success); } else { nsTArray parents; ContentParent::GetAll(parents); if (!parents.Length()) return; for (uint32_t i = 0; i < parents.Length(); i++) { DebugOnly success = parents[i]->SendRegisterChrome( packages, resources, overrides, appLocale, true); NS_WARNING_ASSERTION(success, "couldn't reset a child's registered chrome"); } } } /* static */ void nsChromeRegistryChrome::ChromePackageFromPackageEntry( const nsACString& aPackageName, PackageEntry* aPackage, ChromePackage* aChromePackage, const nsCString& aSelectedSkin) { nsAutoCString appLocale; LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale); SerializeURI(aPackage->baseURI, aChromePackage->contentBaseURI); SerializeURI(aPackage->locales.GetBase(appLocale, nsProviderArray::LOCALE), aChromePackage->localeBaseURI); SerializeURI(aPackage->skins.GetBase(aSelectedSkin, nsProviderArray::ANY), aChromePackage->skinBaseURI); aChromePackage->package = aPackageName; aChromePackage->flags = aPackage->flags; } static bool CanLoadResource(nsIURI* aResourceURI) { bool isLocalResource = false; (void)NS_URIChainHasFlags(aResourceURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &isLocalResource); return isLocalResource; } nsIURI* nsChromeRegistryChrome::GetBaseURIFromPackage( const nsCString& aPackage, const nsCString& aProvider, const nsCString& aPath) { PackageEntry* entry; if (!mPackagesHash.Get(aPackage, &entry)) { if (!mInitialized) return nullptr; LogMessage("No chrome package registered for chrome://%s/%s/%s", aPackage.get(), aProvider.get(), aPath.get()); return nullptr; } if (aProvider.EqualsLiteral("locale")) { nsAutoCString appLocale; LocaleService::GetInstance()->GetAppLocaleAsLangTag(appLocale); return entry->locales.GetBase(appLocale, nsProviderArray::LOCALE); } if (aProvider.EqualsLiteral("skin")) { return entry->skins.GetBase(SKIN, nsProviderArray::ANY); } if (aProvider.EqualsLiteral("content")) { return entry->baseURI; } return nullptr; } nsresult nsChromeRegistryChrome::GetFlagsFromPackage(const nsCString& aPackage, uint32_t* aFlags) { PackageEntry* entry; if (!mPackagesHash.Get(aPackage, &entry)) return NS_ERROR_FILE_NOT_FOUND; *aFlags = entry->flags; return NS_OK; } nsChromeRegistryChrome::ProviderEntry* nsChromeRegistryChrome::nsProviderArray::GetProvider( const nsACString& aPreferred, MatchType aType) { size_t i = mArray.Length(); if (!i) return nullptr; ProviderEntry* found = nullptr; // Only set if we find a partial-match locale ProviderEntry* entry = nullptr; while (i--) { entry = &mArray[i]; if (aPreferred.Equals(entry->provider)) return entry; if (aType != LOCALE) continue; if (LanguagesMatch(aPreferred, entry->provider)) { found = entry; continue; } if (!found && entry->provider.EqualsLiteral("en-US")) found = entry; } if (!found && aType != EXACT) return entry; return found; } nsIURI* nsChromeRegistryChrome::nsProviderArray::GetBase( const nsACString& aPreferred, MatchType aType) { ProviderEntry* provider = GetProvider(aPreferred, aType); if (!provider) return nullptr; return provider->baseURI; } const nsACString& nsChromeRegistryChrome::nsProviderArray::GetSelected( const nsACString& aPreferred, MatchType aType) { ProviderEntry* entry = GetProvider(aPreferred, aType); if (entry) return entry->provider; return EmptyCString(); } void nsChromeRegistryChrome::nsProviderArray::SetBase( const nsACString& aProvider, nsIURI* aBaseURL) { ProviderEntry* provider = GetProvider(aProvider, EXACT); if (provider) { provider->baseURI = aBaseURL; return; } // no existing entries, add a new one mArray.AppendElement(ProviderEntry(aProvider, aBaseURL)); } void nsChromeRegistryChrome::nsProviderArray::EnumerateToArray( nsTArray* a) { int32_t i = mArray.Length(); while (i--) { a->AppendElement(mArray[i].provider); } } nsIURI* nsChromeRegistry::ManifestProcessingContext::GetManifestURI() { if (!mManifestURI) { nsCString uri; mFile.GetURIString(uri); NS_NewURI(getter_AddRefs(mManifestURI), uri); } return mManifestURI; } already_AddRefed nsChromeRegistry::ManifestProcessingContext::ResolveURI(const char* uri) { nsIURI* baseuri = GetManifestURI(); if (!baseuri) return nullptr; nsCOMPtr resolved; nsresult rv = NS_NewURI(getter_AddRefs(resolved), uri, baseuri); if (NS_FAILED(rv)) return nullptr; return resolved.forget(); } static void EnsureLowerCase(char* aBuf) { for (; *aBuf; ++aBuf) { char ch = *aBuf; if (ch >= 'A' && ch <= 'Z') *aBuf = ch + 'a' - 'A'; } } static void SendManifestEntry(const ChromeRegistryItem& aItem) { nsTArray parents; ContentParent::GetAll(parents); if (!parents.Length()) return; for (uint32_t i = 0; i < parents.Length(); i++) { Unused << parents[i]->SendRegisterChromeItem(aItem); } } void nsChromeRegistryChrome::ManifestContent(ManifestProcessingContext& cx, int lineno, char* const* argv, int flags) { char* package = argv[0]; char* uri = argv[1]; EnsureLowerCase(package); nsCOMPtr resolved = cx.ResolveURI(uri); if (!resolved) { LogMessageWithContext( cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, "During chrome registration, unable to create URI '%s'.", uri); return; } if (!CanLoadResource(resolved)) { LogMessageWithContext(resolved, lineno, nsIScriptError::warningFlag, "During chrome registration, cannot register " "non-local URI '%s' as content.", uri); return; } nsDependentCString packageName(package); PackageEntry* entry = mPackagesHash.GetOrInsertNew(packageName); entry->baseURI = resolved; entry->flags = flags; if (mDynamicRegistration) { ChromePackage chromePackage; ChromePackageFromPackageEntry(packageName, entry, &chromePackage, SKIN); SendManifestEntry(chromePackage); } } void nsChromeRegistryChrome::ManifestLocale(ManifestProcessingContext& cx, int lineno, char* const* argv, int flags) { char* package = argv[0]; char* provider = argv[1]; char* uri = argv[2]; EnsureLowerCase(package); nsCOMPtr resolved = cx.ResolveURI(uri); if (!resolved) { LogMessageWithContext( cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, "During chrome registration, unable to create URI '%s'.", uri); return; } if (!CanLoadResource(resolved)) { LogMessageWithContext(resolved, lineno, nsIScriptError::warningFlag, "During chrome registration, cannot register " "non-local URI '%s' as content.", uri); return; } nsDependentCString packageName(package); PackageEntry* entry = mPackagesHash.GetOrInsertNew(packageName); entry->locales.SetBase(nsDependentCString(provider), resolved); if (mDynamicRegistration) { ChromePackage chromePackage; ChromePackageFromPackageEntry(packageName, entry, &chromePackage, SKIN); SendManifestEntry(chromePackage); } // We use mainPackage as the package we track for reporting new locales being // registered. For most cases it will be "global", but for Fennec it will be // "browser". nsAutoCString mainPackage; nsresult rv = OverrideLocalePackage("global"_ns, mainPackage); if (NS_FAILED(rv)) { return; } } void nsChromeRegistryChrome::ManifestSkin(ManifestProcessingContext& cx, int lineno, char* const* argv, int flags) { char* package = argv[0]; char* provider = argv[1]; char* uri = argv[2]; EnsureLowerCase(package); nsCOMPtr resolved = cx.ResolveURI(uri); if (!resolved) { LogMessageWithContext( cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, "During chrome registration, unable to create URI '%s'.", uri); return; } if (!CanLoadResource(resolved)) { LogMessageWithContext(resolved, lineno, nsIScriptError::warningFlag, "During chrome registration, cannot register " "non-local URI '%s' as content.", uri); return; } nsDependentCString packageName(package); PackageEntry* entry = mPackagesHash.GetOrInsertNew(packageName); entry->skins.SetBase(nsDependentCString(provider), resolved); if (mDynamicRegistration) { ChromePackage chromePackage; ChromePackageFromPackageEntry(packageName, entry, &chromePackage, SKIN); SendManifestEntry(chromePackage); } } void nsChromeRegistryChrome::ManifestOverride(ManifestProcessingContext& cx, int lineno, char* const* argv, int flags) { char* chrome = argv[0]; char* resolved = argv[1]; nsCOMPtr chromeuri = cx.ResolveURI(chrome); nsCOMPtr resolveduri = cx.ResolveURI(resolved); if (!chromeuri || !resolveduri) { LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, "During chrome registration, unable to create URI."); return; } if (cx.mType == NS_SKIN_LOCATION) { bool chromeSkinOnly = chromeuri->SchemeIs("chrome") && resolveduri->SchemeIs("chrome"); if (chromeSkinOnly) { nsAutoCString chromePath, resolvedPath; chromeuri->GetPathQueryRef(chromePath); resolveduri->GetPathQueryRef(resolvedPath); chromeSkinOnly = StringBeginsWith(chromePath, "/skin/"_ns) && StringBeginsWith(resolvedPath, "/skin/"_ns); } if (!chromeSkinOnly) { LogMessageWithContext( cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, "Cannot register non-chrome://.../skin/ URIs '%s' and '%s' as " "overrides and/or to be overridden from a skin manifest.", chrome, resolved); return; } } if (!CanLoadResource(resolveduri)) { LogMessageWithContext( cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, "Cannot register non-local URI '%s' for an override.", resolved); return; } mOverrideTable.InsertOrUpdate(chromeuri, resolveduri); if (mDynamicRegistration) { SerializedURI serializedChrome; SerializedURI serializedOverride; SerializeURI(chromeuri, serializedChrome); SerializeURI(resolveduri, serializedOverride); OverrideMapping override = {serializedChrome, serializedOverride}; SendManifestEntry(override); } } void nsChromeRegistryChrome::ManifestResource(ManifestProcessingContext& cx, int lineno, char* const* argv, int flags) { char* package = argv[0]; char* uri = argv[1]; EnsureLowerCase(package); nsDependentCString host(package); nsCOMPtr io = mozilla::components::IO::Service(); if (!io) { NS_WARNING("No IO service trying to process chrome manifests"); return; } nsCOMPtr ph; nsresult rv = io->GetProtocolHandler("resource", getter_AddRefs(ph)); if (NS_FAILED(rv)) return; nsCOMPtr rph = do_QueryInterface(ph); nsCOMPtr resolved = cx.ResolveURI(uri); if (!resolved) { LogMessageWithContext( cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, "During chrome registration, unable to create URI '%s'.", uri); return; } if (!CanLoadResource(resolved)) { LogMessageWithContext( cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, "Warning: cannot register non-local URI '%s' as a resource.", uri); return; } // By default, Firefox resources are not content-accessible unless the // manifests opts in. bool contentAccessible = (flags & nsChromeRegistry::CONTENT_ACCESSIBLE); uint32_t substitutionFlags = 0; if (contentAccessible) { substitutionFlags |= nsIResProtocolHandler::ALLOW_CONTENT_ACCESS; } rv = rph->SetSubstitutionWithFlags(host, resolved, substitutionFlags); if (NS_FAILED(rv)) { LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, "Warning: cannot set substitution for '%s'.", uri); } }