/* -*- 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 "mozilla/intl/LocaleService.h"

#include "nsIObserverService.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "mozilla/Preferences.h"
#include "nsIResProtocolHandler.h"
#include "nsIScriptError.h"
#include "nsIXULRuntime.h"

#define SELECTED_SKIN_PREF   "general.skins.selectedSkin"
#define PACKAGE_OVERRIDE_BRANCH "chrome.override_package."

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;

  mSelectedSkin = NS_LITERAL_CSTRING("classic/1.0");

  bool safeMode = false;
  nsCOMPtr<nsIXULRuntime> xulrun (do_GetService(XULAPPINFO_SERVICE_CONTRACTID));
  if (xulrun)
    xulrun->GetInSafeMode(&safeMode);

  nsCOMPtr<nsIPrefService> prefserv (do_GetService(NS_PREFSERVICE_CONTRACTID));
  nsCOMPtr<nsIPrefBranch> prefs;

  if (prefserv) {
    if (safeMode) {
      prefserv->GetDefaultBranch(nullptr, getter_AddRefs(prefs));
    } else {
      prefs = do_QueryInterface(prefserv);
    }
  }

  if (!prefs) {
    NS_WARNING("Could not get pref service!");
  } else {
    nsAutoCString provider;
    rv = prefs->GetCharPref(SELECTED_SKIN_PREF, provider);
    if (NS_SUCCEEDED(rv))
      mSelectedSkin = provider;

    rv = prefs->AddObserver(SELECTED_SKIN_PREF, this, true);
  }

  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::CheckForOSAccessibility()
{
  int32_t useAccessibilityTheme =
    LookAndFeel::GetInt(LookAndFeel::eIntID_UseAccessibilityTheme, 0);

  if (useAccessibilityTheme) {
    /* Set the skin to classic and remove pref observers */
    if (!mSelectedSkin.EqualsLiteral("classic/1.0")) {
      mSelectedSkin.AssignLiteral("classic/1.0");
      RefreshSkins();
    }

    nsCOMPtr<nsIPrefBranch> prefs (do_GetService(NS_PREFSERVICE_CONTRACTID));
    if (prefs) {
      prefs->RemoveObserver(SELECTED_SKIN_PREF, this);
    }
  }

  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, false, locale);
  if (locale.Length() < 2)
    return NS_OK;

  *aResult = GetDirectionForLocale(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::GetAppLocaleAsLangTag.
 */
nsresult
nsChromeRegistryChrome::GetSelectedLocale(const nsACString& aPackage,
                                          bool aAsBCP47,
                                          nsACString& aLocale)
{
  nsAutoCString reqLocale;
  if (aPackage.EqualsLiteral("global")) {
    LocaleService::GetInstance()->GetAppLocaleAsLangTag(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;

  if (aAsBCP47) {
    SanitizeForBCP47(aLocale);
  }

  return NS_OK;
}

nsresult
nsChromeRegistryChrome::OverrideLocalePackage(const nsACString& aPackage,
                                              nsACString& aOverride)
{
  const nsACString& pref = NS_LITERAL_CSTRING(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(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) {
    nsCOMPtr<nsIPrefBranch> prefs (do_QueryInterface(aSubject));
    NS_ASSERTION(prefs, "Bad observer call!");

    NS_ConvertUTF16toUTF8 pref(someData);

    if (pref.EqualsLiteral(SELECTED_SKIN_PREF)) {
      nsAutoCString provider;
      rv = prefs->GetCharPref(pref.get(), provider);
      if (NS_FAILED(rv)) {
        NS_ERROR("Couldn't get new skin pref!");
        return rv;
      }

      mSelectedSkin = provider;
      RefreshSkins();
    } else {
      NS_ERROR("Unexpected pref!");
    }
  }
  else 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()
{
  mPackagesHash.Clear();
  mOverlayHash.Clear();
  mStyleHash.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)
{
  InfallibleTArray<ChromePackage> packages;
  InfallibleTArray<SubstitutionMapping> resources;
  InfallibleTArray<OverrideMapping> overrides;

  for (auto iter = mPackagesHash.Iter(); !iter.Done(); iter.Next()) {
    ChromePackage chromePackage;
    ChromePackageFromPackageEntry(iter.Key(), iter.UserData(), &chromePackage,
                                  mSelectedSkin);
    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()->GetAppLocaleAsLangTag(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()->GetAppLocaleAsLangTag(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(mSelectedSkin, 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);
  }
}

void
nsChromeRegistryChrome::OverlayListEntry::AddURI(nsIURI* aURI)
{
  int32_t i = mArray.Count();
  while (i--) {
    bool equals;
    if (NS_SUCCEEDED(aURI->Equals(mArray[i], &equals)) && equals)
      return;
  }

  mArray.AppendObject(aURI);
}

void
nsChromeRegistryChrome::OverlayListHash::Add(nsIURI* aBase, nsIURI* aOverlay)
{
  OverlayListEntry* entry = mTable.PutEntry(aBase);
  if (entry)
    entry->AddURI(aOverlay);
}

const nsCOMArray<nsIURI>*
nsChromeRegistryChrome::OverlayListHash::GetArray(nsIURI* aBase)
{
  OverlayListEntry* entry = mTable.GetEntry(aBase);
  if (!entry)
    return nullptr;

  return &entry->mArray;
}

#ifdef MOZ_XUL
NS_IMETHODIMP
nsChromeRegistryChrome::GetStyleOverlays(nsIURI *aChromeURL,
                                         nsISimpleEnumerator **aResult)
{
  nsCOMPtr<nsIURI> chromeURLWithoutHash;
  if (aChromeURL) {
    aChromeURL->CloneIgnoringRef(getter_AddRefs(chromeURLWithoutHash));
  }
  const nsCOMArray<nsIURI>* parray = mStyleHash.GetArray(chromeURLWithoutHash);
  if (!parray)
    return NS_NewEmptyEnumerator(aResult);

  return NS_NewArrayEnumerator(aResult, *parray);
}

NS_IMETHODIMP
nsChromeRegistryChrome::GetXULOverlays(nsIURI *aChromeURL,
                                       nsISimpleEnumerator **aResult)
{
  nsCOMPtr<nsIURI> chromeURLWithoutHash;
  if (aChromeURL) {
    aChromeURL->CloneIgnoringRef(getter_AddRefs(chromeURLWithoutHash));
  }
  const nsCOMArray<nsIURI>* parray = mOverlayHash.GetArray(chromeURLWithoutHash);
  if (!parray)
    return NS_NewEmptyEnumerator(aResult);

  return NS_NewArrayEnumerator(aResult, *parray);
}
#endif // MOZ_XUL

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,
                                  mSelectedSkin);
    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,
                                  mSelectedSkin);
    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(NS_LITERAL_CSTRING("global"), mainPackage);
  if (NS_FAILED(rv)) {
    return;
  }

  if (mainPackage.Equals(package)) {
    // We should refresh the LocaleService, since the available
    // locales changed.
    LocaleService::GetInstance()->AvailableLocalesChanged();
  }
}

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,
                                  mSelectedSkin);
    SendManifestEntry(chromePackage);
  }
}

void
nsChromeRegistryChrome::ManifestOverlay(ManifestProcessingContext& cx, int lineno,
                                        char *const * argv, int flags)
{
  char* base = argv[0];
  char* overlay = argv[1];

  nsCOMPtr<nsIURI> baseuri = cx.ResolveURI(base);
  nsCOMPtr<nsIURI> overlayuri = cx.ResolveURI(overlay);
  if (!baseuri || !overlayuri) {
    LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
                          "During chrome registration, unable to create URI.");
    return;
  }

  if (!CanLoadResource(overlayuri)) {
    LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
                          "Cannot register non-local URI '%s' as an overlay.", overlay);
    return;
  }

  nsCOMPtr<nsIURI> baseuriWithoutHash;
  baseuri->CloneIgnoringRef(getter_AddRefs(baseuriWithoutHash));

  mOverlayHash.Add(baseuriWithoutHash, overlayuri);
}

void
nsChromeRegistryChrome::ManifestStyle(ManifestProcessingContext& cx, int lineno,
                                      char *const * argv, int flags)
{
  char* base = argv[0];
  char* overlay = argv[1];

  nsCOMPtr<nsIURI> baseuri = cx.ResolveURI(base);
  nsCOMPtr<nsIURI> overlayuri = cx.ResolveURI(overlay);
  if (!baseuri || !overlayuri) {
    LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
                          "During chrome registration, unable to create URI.");
    return;
  }

  if (!CanLoadResource(overlayuri)) {
    LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
                          "Cannot register non-local URI '%s' as a style overlay.", overlay);
    return;
  }

  nsCOMPtr<nsIURI> baseuriWithoutHash;
  baseuri->CloneIgnoringRef(getter_AddRefs(baseuriWithoutHash));

  mStyleHash.Add(baseuriWithoutHash, overlayuri);
}

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 = false;
    nsresult rv = chromeuri->SchemeIs("chrome", &chromeSkinOnly);
    chromeSkinOnly = chromeSkinOnly && NS_SUCCEEDED(rv);
    if (chromeSkinOnly) {
      rv = resolveduri->SchemeIs("chrome", &chromeSkinOnly);
      chromeSkinOnly = chromeSkinOnly && NS_SUCCEEDED(rv);
    }
    if (chromeSkinOnly) {
      nsAutoCString chromePath, resolvedPath;
      chromeuri->GetPath(chromePath);
      resolveduri->GetPath(resolvedPath);
      chromeSkinOnly = StringBeginsWith(chromePath, NS_LITERAL_CSTRING("/skin/")) &&
                       StringBeginsWith(resolvedPath, NS_LITERAL_CSTRING("/skin/"));
    }
    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);
  }
}
