diff options
Diffstat (limited to 'chrome/nsChromeRegistryChrome.cpp')
-rw-r--r-- | chrome/nsChromeRegistryChrome.cpp | 676 |
1 files changed, 676 insertions, 0 deletions
diff --git a/chrome/nsChromeRegistryChrome.cpp b/chrome/nsChromeRegistryChrome.cpp new file mode 100644 index 0000000000..85ca64a103 --- /dev/null +++ b/chrome/nsChromeRegistryChrome.cpp @@ -0,0 +1,676 @@ +/* -*- 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 <windows.h> +#elif defined(XP_MACOSX) +# include <CoreServices/CoreServices.h> +#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 "nsIAppStartup.h" +#include "nsIObserverService.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<nsIXULRuntime> xulrun(do_GetService(XULAPPINFO_SERVICE_CONTRACTID)); + if (xulrun) xulrun->GetInSafeMode(&safeMode); + + nsCOMPtr<nsIObserverService> 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<nsCString>* a = new nsTArray<nsCString>; + 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<nsCString, 10> 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() { + nsCOMPtr<nsIAppStartup> appStartup = components::AppStartup::Service(); + if (appStartup->GetShuttingDown()) { + 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<ChromePackage> packages; + nsTArray<SubstitutionMapping> resources; + nsTArray<OverrideMapping> overrides; + + for (auto iter = mPackagesHash.Iter(); !iter.Done(); iter.Next()) { + ChromePackage chromePackage; + ChromePackageFromPackageEntry(iter.Key(), iter.UserData(), &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<nsIIOService> io(do_GetIOService()); + NS_ENSURE_TRUE_VOID(io); + + nsCOMPtr<nsIProtocolHandler> ph; + nsresult rv = io->GetProtocolHandler("resource", getter_AddRefs(ph)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIResProtocolHandler> irph(do_QueryInterface(ph)); + nsResProtocolHandler* rph = static_cast<nsResProtocolHandler*>(irph.get()); + rv = rph->CollectSubstitutions(resources); + NS_ENSURE_SUCCESS_VOID(rv); + } + + for (auto iter = mOverrideTable.Iter(); !iter.Done(); iter.Next()) { + SerializedURI chromeURI, overrideURI; + + SerializeURI(iter.Key(), chromeURI); + SerializeURI(iter.UserData(), overrideURI); + + OverrideMapping override = {chromeURI, overrideURI}; + overrides.AppendElement(override); + } + + nsAutoCString appLocale; + LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale); + + if (aParent) { + bool success = aParent->SendRegisterChrome(packages, resources, overrides, + appLocale, false); + NS_ENSURE_TRUE_VOID(success); + } else { + nsTArray<ContentParent*> parents; + ContentParent::GetAll(parents); + if (!parents.Length()) return; + + for (uint32_t i = 0; i < parents.Length(); i++) { + DebugOnly<bool> 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); + } else if (aProvider.EqualsLiteral("skin")) { + return entry->skins.GetBase(SKIN, nsProviderArray::ANY); + } else 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<nsCString>* 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<nsIURI> +nsChromeRegistry::ManifestProcessingContext::ResolveURI(const char* uri) { + nsIURI* baseuri = GetManifestURI(); + if (!baseuri) return nullptr; + + nsCOMPtr<nsIURI> 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<ContentParent*> 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<nsIURI> 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.LookupOrAdd(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<nsIURI> 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.LookupOrAdd(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<nsIURI> 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.LookupOrAdd(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<nsIURI> chromeuri = cx.ResolveURI(chrome); + nsCOMPtr<nsIURI> 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.Put(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<nsIIOService> io = mozilla::services::GetIOService(); + if (!io) { + NS_WARNING("No IO service trying to process chrome manifests"); + return; + } + + nsCOMPtr<nsIProtocolHandler> ph; + nsresult rv = io->GetProtocolHandler("resource", getter_AddRefs(ph)); + if (NS_FAILED(rv)) return; + + nsCOMPtr<nsIResProtocolHandler> rph = do_QueryInterface(ph); + + nsCOMPtr<nsIURI> 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); + } +} |