Bug 1348042 - Refactor LocaleService to operate in server-client mode. r?qdot draft
authorZibi Braniecki <gandalf@mozilla.com>
Sun, 26 Mar 2017 07:09:45 +0200
changeset 566506 2f0ffdf7c5d222d910ce6f1a095b68f152ffaaf2
parent 566501 96f30acfb287dee6a904957b7545cfc6459b15d0
child 625337 150991671b84c8ee7048a3e29b3380a9e6277c62
push id55241
push userzbraniecki@mozilla.com
push dateFri, 21 Apr 2017 17:50:07 +0000
reviewersqdot
bugs1348042
milestone55.0a1
Bug 1348042 - Refactor LocaleService to operate in server-client mode. r?qdot LocaleService serves two main functions. It is a central place for all code in the engine to learn about locales, but it also does the language negotiation and selection. The former is relevant in all processes, but the latter should only be performed by the "main" process. In case of current Desktop Firefox, the parent process is the one performing all the language negotiation, and content processes should operate in the "client" mode. In Fennec, there's a Java app on top of Gecko which should work as a "server" and then all processes, including parent process of Gecko is merely a "client" for that. This refactor finalizes this duality making it easily configurable to define in which mode a given LocaleService operates. The server-client model allows all clients to stay in sync with the server, but operate transparently for all callers just returning the right values. In order to initialize LocaleService in the client mode in child process with the right locales I'm adding the list of app locales to the XPCOMInitData, and then fire LocaleService::SetAppLocales in the child process initialization. In order to keep the list up to date, I'm adding intl:app-locales-changed to the list of observed topics, and when triggered, I send the updated list to the child process, which updates LocaleService::SetAppLocales with the new list. MozReview-Commit-ID: K9X6berF3IO
chrome/nsChromeRegistryChrome.cpp
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/PContent.ipdl
intl/locale/LocaleService.cpp
intl/locale/LocaleService.h
--- a/chrome/nsChromeRegistryChrome.cpp
+++ b/chrome/nsChromeRegistryChrome.cpp
@@ -735,17 +735,17 @@ nsChromeRegistryChrome::ManifestLocale(M
     ChromePackageFromPackageEntry(packageName, entry, &chromePackage,
                                   mSelectedSkin);
     SendManifestEntry(chromePackage);
   }
 
   if (strcmp(package, "global") == 0) {
     // We should refresh the LocaleService, since the available
     // locales changed.
-    LocaleService::GetInstance()->Refresh();
+    LocaleService::GetInstance()->OnAvailableLocalesChanged();
   }
 }
 
 void
 nsChromeRegistryChrome::ManifestSkin(ManifestProcessingContext& cx, int lineno,
                                      char *const * argv, int flags)
 {
   char* package = argv[0];
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -45,16 +45,17 @@
 #include "mozilla/psm/PSMContentListener.h"
 #include "mozilla/hal_sandbox/PHalChild.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/FileDescriptorSetChild.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/ipc/ProcessChild.h"
 #include "mozilla/ipc/PChildToParentStreamChild.h"
+#include "mozilla/intl/LocaleService.h"
 #include "mozilla/ipc/TestShellChild.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layers/APZChild.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/ContentProcessController.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layout/RenderFrameChild.h"
 #include "mozilla/net/NeckoChild.h"
@@ -217,16 +218,17 @@ using namespace mozilla;
 using namespace mozilla::docshell;
 using namespace mozilla::dom::ipc;
 using namespace mozilla::dom::workers;
 using namespace mozilla::media;
 using namespace mozilla::embedding;
 using namespace mozilla::gmp;
 using namespace mozilla::hal_sandbox;
 using namespace mozilla::ipc;
+using namespace mozilla::intl;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::net;
 using namespace mozilla::jsipc;
 using namespace mozilla::psm;
 using namespace mozilla::widget;
 #if defined(MOZ_WIDGET_GONK)
 using namespace mozilla::system;
@@ -993,16 +995,19 @@ ContentChild::InitXPCOM(const XPCOMInitD
   mConsoleListener = new ConsoleListener(this);
   if (NS_FAILED(svc->RegisterListener(mConsoleListener)))
     NS_WARNING("Couldn't register console listener for child process");
 
   mAvailableDictionaries = aXPCOMInit.dictionaries();
 
   RecvSetOffline(aXPCOMInit.isOffline());
   RecvSetConnectivity(aXPCOMInit.isConnected());
+  LocaleService::GetInstance()->AssignAppLocales(aXPCOMInit.appLocales());
+  LocaleService::GetInstance()->AssignRequestedLocales(aXPCOMInit.requestedLocales());
+
   RecvSetCaptivePortalState(aXPCOMInit.captivePortalState());
   RecvBidiKeyboardNotify(aXPCOMInit.isLangRTL(), aXPCOMInit.haveBidiKeyboards());
 
   // Create the CPOW manager as soon as possible.
   SendPJavaScriptConstructor();
 
   if (aXPCOMInit.domainPolicy().active()) {
     nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
@@ -2254,16 +2259,30 @@ mozilla::ipc::IPCResult
 ContentChild::RecvUpdateDictionaryList(InfallibleTArray<nsString>&& aDictionaries)
 {
   mAvailableDictionaries = aDictionaries;
   mozInlineSpellChecker::UpdateCanEnableInlineSpellChecking();
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+ContentChild::RecvUpdateAppLocales(nsTArray<nsCString>&& aAppLocales)
+{
+  LocaleService::GetInstance()->AssignAppLocales(aAppLocales);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ContentChild::RecvUpdateRequestedLocales(nsTArray<nsCString>&& aRequestedLocales)
+{
+  LocaleService::GetInstance()->AssignRequestedLocales(aRequestedLocales);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 ContentChild::RecvAddPermission(const IPC::Permission& permission)
 {
 #if MOZ_PERMISSIONS
   nsCOMPtr<nsIPermissionManager> permissionManagerIface =
     services::GetPermissionManager();
   nsPermissionManager* permissionManager =
     static_cast<nsPermissionManager*>(permissionManagerIface.get());
   MOZ_ASSERT(permissionManager,
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -361,16 +361,19 @@ public:
                                                    const ClonedMessageData& aData) override;
 
   virtual mozilla::ipc::IPCResult RecvGeolocationUpdate(const GeoPosition& somewhere) override;
 
   virtual mozilla::ipc::IPCResult RecvGeolocationError(const uint16_t& errorCode) override;
 
   virtual mozilla::ipc::IPCResult RecvUpdateDictionaryList(InfallibleTArray<nsString>&& aDictionaries) override;
 
+  virtual mozilla::ipc::IPCResult RecvUpdateAppLocales(nsTArray<nsCString>&& aAppLocales) override;
+  virtual mozilla::ipc::IPCResult RecvUpdateRequestedLocales(nsTArray<nsCString>&& aRequestedLocales) override;
+
   virtual mozilla::ipc::IPCResult RecvAddPermission(const IPC::Permission& permission) override;
 
   virtual mozilla::ipc::IPCResult RecvFlushMemory(const nsString& reason) override;
 
   virtual mozilla::ipc::IPCResult RecvActivateA11y(const uint32_t& aMsaaID) override;
   virtual mozilla::ipc::IPCResult RecvShutdownA11y() override;
 
   virtual mozilla::ipc::IPCResult RecvGarbageCollect() override;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -72,16 +72,17 @@
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/hal_sandbox/PHalParent.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/PChildToParentStreamParent.h"
 #include "mozilla/ipc/TestShellParent.h"
 #include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/intl/LocaleService.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layers/PAPZParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/LayerTreeOwnerTracker.h"
 #include "mozilla/layout/RenderFrameParent.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/media/MediaParent.h"
@@ -270,16 +271,17 @@ using namespace CrashReporter;
 #endif
 using namespace mozilla::dom::power;
 using namespace mozilla::media;
 using namespace mozilla::embedding;
 using namespace mozilla::gfx;
 using namespace mozilla::gmp;
 using namespace mozilla::hal;
 using namespace mozilla::ipc;
+using namespace mozilla::intl;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::net;
 using namespace mozilla::jsipc;
 using namespace mozilla::psm;
 using namespace mozilla::widget;
 
 // XXX Workaround for bug 986973 to maintain the existing broken semantics
@@ -558,16 +560,18 @@ static const char* sObserverTopics[] = {
   "child-cc-request",
   "child-mmu-request",
   "last-pb-context-exited",
   "file-watcher-update",
 #ifdef ACCESSIBILITY
   "a11y-init-or-shutdown",
 #endif
   "cacheservice:empty-cache",
+  "intl:app-locales-changed",
+  "intl:requested-locales-changed",
 };
 
 // PreallocateProcess is called by the PreallocatedProcessManager.
 // ContentParent then takes this process back within GetNewOrUsedBrowserProcess.
 /*static*/ already_AddRefed<ContentParent>
 ContentParent::PreallocateProcess()
 {
   RefPtr<ContentParent> process =
@@ -2166,16 +2170,19 @@ ContentParent::InitInternal(ProcessPrior
     bidi->GetHaveBidiKeyboards(&xpcomInit.haveBidiKeyboards());
   }
 
   nsCOMPtr<nsISpellChecker> spellChecker(do_GetService(NS_SPELLCHECKER_CONTRACTID));
   MOZ_ASSERT(spellChecker, "No spell checker?");
 
   spellChecker->GetDictionaryList(&xpcomInit.dictionaries());
 
+  LocaleService::GetInstance()->GetAppLocalesAsLangTags(xpcomInit.appLocales());
+  LocaleService::GetInstance()->GetRequestedLocales(xpcomInit.requestedLocales());
+
   nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1"));
   MOZ_ASSERT(clipboard, "No clipboard?");
 
   rv = clipboard->SupportsSelectionClipboard(&xpcomInit.clipboardCaps().supportsSelectionClipboard());
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   rv = clipboard->SupportsFindClipboard(&xpcomInit.clipboardCaps().supportsFindClipboard());
   MOZ_ASSERT(NS_SUCCEEDED(rv));
@@ -2766,16 +2773,26 @@ ContentParent::Observe(nsISupports* aSub
       // accessibility gets shutdown in chrome process.
       Unused << SendShutdownA11y();
     }
   }
 #endif
   else if (!strcmp(aTopic, "cacheservice:empty-cache")) {
     Unused << SendNotifyEmptyHTTPCache();
   }
+  else if (!strcmp(aTopic, "intl:app-locales-changed")) {
+    nsTArray<nsCString> appLocales;
+    LocaleService::GetInstance()->GetAppLocalesAsLangTags(appLocales);
+    Unused << SendUpdateAppLocales(appLocales);
+  }
+  else if (!strcmp(aTopic, "intl:requested-locales-changed")) {
+    nsTArray<nsCString> requestedLocales;
+    LocaleService::GetInstance()->GetRequestedLocales(requestedLocales);
+    Unused << SendUpdateRequestedLocales(requestedLocales);
+  }
   return NS_OK;
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvInitBackground(Endpoint<PBackgroundParent>&& aEndpoint)
 {
   if (!BackgroundParent::Alloc(this, Move(aEndpoint))) {
     return IPC_FAIL(this, "BackgroundParent::Alloc failed");
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -263,16 +263,18 @@ struct XPCOMInitData
     DomainPolicyClone domainPolicy;
     /* used on MacOSX only */
     FontFamilyListEntry[] fontFamilies;
     OptionalURIParams userContentSheetURL;
     PrefSetting[] prefs;
     ContentDeviceData contentDeviceData;
     GfxInfoFeatureStatus[] gfxFeatureStatus;
     DataStorageEntry[] dataStorage;
+    nsCString[] appLocales;
+    nsCString[] requestedLocales;
 };
 
 /**
  * The PContent protocol is a top-level protocol between the UI process
  * and a content process. There is exactly one PContentParent/PContentChild pair
  * for each content process.
  */
 nested(upto inside_cpow) sync protocol PContent
@@ -419,16 +421,19 @@ child:
     async NotifyAlertsObserver(nsCString topic, nsString data);
 
     async GeolocationUpdate(GeoPosition somewhere);
 
     async GeolocationError(uint16_t errorCode);
 
     async UpdateDictionaryList(nsString[] dictionaries);
 
+    async UpdateAppLocales(nsCString[] appLocales);
+    async UpdateRequestedLocales(nsCString[] requestedLocales);
+
     // nsIPermissionManager messages
     async AddPermission(Permission permission);
 
     async FlushMemory(nsString reason);
 
     async GarbageCollect();
     async CycleCollect();
 
--- a/intl/locale/LocaleService.cpp
+++ b/intl/locale/LocaleService.cpp
@@ -29,16 +29,17 @@
 static const char* kObservedPrefs[] = {
   MATCH_OS_LOCALE_PREF,
   SELECTED_LOCALE_PREF,
   ANDROID_OS_LOCALE_PREF,
   nullptr
 };
 
 using namespace mozilla::intl;
+using namespace mozilla;
 
 NS_IMPL_ISUPPORTS(LocaleService, mozILocaleService, nsIObserver)
 
 mozilla::StaticRefPtr<LocaleService> LocaleService::sInstance;
 
 /**
  * This function transforms a canonical Mozilla Language Tag, into it's
  * BCP47 compilant form.
@@ -72,102 +73,18 @@ SanitizeForBCP47(nsACString& aLocale)
   // But let's fix up the single anomalous code we actually ship,
   // just in case:
   if (aLocale.EqualsLiteral("ja-JP-mac")) {
     aLocale.AssignLiteral("ja-JP");
   }
 #endif
 }
 
-/**
- * This function performs the actual language negotiation for the API.
- *
- * Currently it collects the locale ID used by nsChromeRegistry and
- * adds hardcoded "en-US" locale as a fallback.
- */
-void
-LocaleService::NegotiateAppLocales(nsTArray<nsCString>& aRetVal)
-{
-  nsAutoCString defaultLocale;
-  GetDefaultLocale(defaultLocale);
-
-  if (XRE_IsParentProcess()) {
-    AutoTArray<nsCString, 100> availableLocales;
-    AutoTArray<nsCString, 10> requestedLocales;
-    GetAvailableLocales(availableLocales);
-    GetRequestedLocales(requestedLocales);
-
-    NegotiateLanguages(requestedLocales, availableLocales, defaultLocale,
-                       LangNegStrategy::Filtering, aRetVal);
-  } else {
-    //XXX: In bug 1348042 we're working on getting the content process
-    //     to follow the parent process negotiated locales.
-    //     Until we have it, we're going to match the behavior of following
-    //     the ChromeRegistry locale in the content process.
-
-    nsAutoCString uaLangTag;
-    nsCOMPtr<nsIToolkitChromeRegistry> cr =
-      mozilla::services::GetToolkitChromeRegistryService();
-    if (cr) {
-      cr->GetSelectedLocale(NS_LITERAL_CSTRING("global"), false, uaLangTag);
-    }
-    if (!uaLangTag.IsEmpty()) {
-      aRetVal.AppendElement(uaLangTag);
-    }
-
-    if (!uaLangTag.Equals(defaultLocale)) {
-      aRetVal.AppendElement(defaultLocale);
-    }
-  }
-}
-
-LocaleService*
-LocaleService::GetInstance()
-{
-  if (!sInstance) {
-    sInstance = new LocaleService();
-
-    // We're going to observe for requested languages changes which come
-    // from prefs.
-    DebugOnly<nsresult> rv = Preferences::AddStrongObservers(sInstance, kObservedPrefs);
-    MOZ_ASSERT(NS_SUCCEEDED(rv), "Adding observers failed.");
-    ClearOnShutdown(&sInstance);
-  }
-  return sInstance;
-}
-
-LocaleService::~LocaleService()
-{
-  Preferences::RemoveObservers(sInstance, kObservedPrefs);
-}
-
-void
-LocaleService::GetAppLocalesAsLangTags(nsTArray<nsCString>& aRetVal)
-{
-  if (mAppLocales.IsEmpty()) {
-    NegotiateAppLocales(mAppLocales);
-  }
-  aRetVal = mAppLocales;
-}
-
-void
-LocaleService::GetAppLocalesAsBCP47(nsTArray<nsCString>& aRetVal)
-{
-  if (mAppLocales.IsEmpty()) {
-    NegotiateAppLocales(mAppLocales);
-  }
-  for (uint32_t i = 0; i < mAppLocales.Length(); i++) {
-    nsAutoCString locale(mAppLocales[i]);
-    SanitizeForBCP47(locale);
-    aRetVal.AppendElement(locale);
-  }
-}
-
-bool
-LocaleService::GetRequestedLocales(nsTArray<nsCString>& aRetVal)
+static bool
+ReadRequestedLocales(nsTArray<nsCString>& aRetVal)
 {
   nsAutoCString locale;
 
   // First, we'll try to check if the user has `matchOS` pref selected
   bool matchOSLocale = Preferences::GetBool(MATCH_OS_LOCALE_PREF);
 
   if (matchOSLocale) {
     // If he has, we'll pick the locale from the system
@@ -183,21 +100,24 @@ LocaleService::GetRequestedLocales(nsTAr
   }
 
   // At the moment we just take a single locale, but in the future
   // we'll want to allow user to specify a list of requested locales.
   aRetVal.AppendElement(locale);
   return true;
 }
 
-bool
-LocaleService::GetAvailableLocales(nsTArray<nsCString>& aRetVal)
+static bool
+ReadAvailableLocales(nsTArray<nsCString>& aRetVal)
 {
   nsCOMPtr<nsIToolkitChromeRegistry> cr =
     mozilla::services::GetToolkitChromeRegistryService();
+  if (!cr) {
+    return false;
+  }
 
   nsCOMPtr<nsIUTF8StringEnumerator> localesEnum;
 
   nsresult rv =
     cr->GetLocalesForPackage(NS_LITERAL_CSTRING("global"), getter_AddRefs(localesEnum));
   if (!NS_SUCCEEDED(rv)) {
     return false;
   }
@@ -210,19 +130,179 @@ LocaleService::GetAvailableLocales(nsTAr
       return false;
     }
 
     aRetVal.AppendElement(localeStr);
   }
   return !aRetVal.IsEmpty();
 }
 
+LocaleService::LocaleService(bool aIsServer)
+  :mIsServer(aIsServer)
+{
+}
+
+/**
+ * This function performs the actual language negotiation for the API.
+ *
+ * Currently it collects the locale ID used by nsChromeRegistry and
+ * adds hardcoded "en-US" locale as a fallback.
+ */
 void
-LocaleService::Refresh()
+LocaleService::NegotiateAppLocales(nsTArray<nsCString>& aRetVal)
+{
+  nsAutoCString defaultLocale;
+  GetDefaultLocale(defaultLocale);
+
+  if (mIsServer) {
+    AutoTArray<nsCString, 100> availableLocales;
+    AutoTArray<nsCString, 10> requestedLocales;
+    GetAvailableLocales(availableLocales);
+    GetRequestedLocales(requestedLocales);
+
+    NegotiateLanguages(requestedLocales, availableLocales, defaultLocale,
+                       LangNegStrategy::Filtering, aRetVal);
+  } else {
+    // In content process, we will not do any language negotiation.
+    // Instead, the language is set manually by SetAppLocales.
+    //
+    // If this method has been called, it means that we did not fire
+    // SetAppLocales yet (happens during initialization).
+    // In that case, all we can do is return the default locale.
+    // Once SetAppLocales will be called later, it'll fire an event
+    // allowing callers to update the locale.
+    aRetVal.AppendElement(defaultLocale);
+  }
+}
+
+LocaleService*
+LocaleService::GetInstance()
+{
+  if (!sInstance) {
+    sInstance = new LocaleService(XRE_IsParentProcess());
+
+    if (sInstance->IsServer()) {
+      // We're going to observe for requested languages changes which come
+      // from prefs.
+      DebugOnly<nsresult> rv = Preferences::AddStrongObservers(sInstance, kObservedPrefs);
+      MOZ_ASSERT(NS_SUCCEEDED(rv), "Adding observers failed.");
+    }
+    ClearOnShutdown(&sInstance);
+  }
+  return sInstance;
+}
+
+LocaleService::~LocaleService()
+{
+  if (mIsServer) {
+    Preferences::RemoveObservers(sInstance, kObservedPrefs);
+  }
+}
+
+void
+LocaleService::GetAppLocalesAsLangTags(nsTArray<nsCString>& aRetVal)
+{
+  if (mAppLocales.IsEmpty()) {
+    NegotiateAppLocales(mAppLocales);
+  }
+  aRetVal = mAppLocales;
+}
+
+void
+LocaleService::GetAppLocalesAsBCP47(nsTArray<nsCString>& aRetVal)
+{
+  if (mAppLocales.IsEmpty()) {
+    NegotiateAppLocales(mAppLocales);
+  }
+  for (uint32_t i = 0; i < mAppLocales.Length(); i++) {
+    nsAutoCString locale(mAppLocales[i]);
+    SanitizeForBCP47(locale);
+    aRetVal.AppendElement(locale);
+  }
+}
+
+void
+LocaleService::AssignAppLocales(const nsTArray<nsCString>& aAppLocales)
 {
+  MOZ_ASSERT(!mIsServer, "This should only be called for LocaleService in client mode.");
+
+  mAppLocales = aAppLocales;
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->NotifyObservers(nullptr, "intl:app-locales-changed", nullptr);
+  }
+}
+
+void
+LocaleService::AssignRequestedLocales(const nsTArray<nsCString>& aRequestedLocales)
+{
+  MOZ_ASSERT(!mIsServer, "This should only be called for LocaleService in client mode.");
+
+  mRequestedLocales = aRequestedLocales;
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->NotifyObservers(nullptr, "intl:requested-locales-changed", nullptr);
+  }
+}
+
+bool
+LocaleService::GetRequestedLocales(nsTArray<nsCString>& aRetVal)
+{
+  if (mRequestedLocales.IsEmpty()) {
+    ReadRequestedLocales(mRequestedLocales);
+  }
+
+  aRetVal = mRequestedLocales;
+  return true;
+}
+
+bool
+LocaleService::GetAvailableLocales(nsTArray<nsCString>& aRetVal)
+{
+  if (mAvailableLocales.IsEmpty()) {
+    ReadAvailableLocales(mAvailableLocales);
+  }
+
+  aRetVal = mAvailableLocales;
+  return true;
+}
+
+
+void
+LocaleService::OnAvailableLocalesChanged()
+{
+  MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
+  mAvailableLocales.Clear();
+  // In the future we may want to trigger here intl:available-locales-changed
+  OnLocalesChanged();
+}
+
+void
+LocaleService::OnRequestedLocalesChanged()
+{
+  MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
+
+  nsTArray<nsCString> newLocales;
+  ReadRequestedLocales(newLocales);
+
+  if (mRequestedLocales != newLocales) {
+    mRequestedLocales = Move(newLocales);
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      obs->NotifyObservers(nullptr, "intl:requested-locales-changed", nullptr);
+    }
+    OnLocalesChanged();
+  }
+}
+
+void
+LocaleService::OnLocalesChanged()
+{
+  MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
+
   // if mAppLocales has not been initialized yet, just return
   if (mAppLocales.IsEmpty()) {
     return;
   }
 
   nsTArray<nsCString> newLocales;
   NegotiateAppLocales(newLocales);
 
@@ -431,39 +511,43 @@ LocaleService::IsAppLocaleRTL()
   return dir.EqualsLiteral("rtl");
 #endif
 }
 
 NS_IMETHODIMP
 LocaleService::Observe(nsISupports *aSubject, const char *aTopic,
                       const char16_t *aData)
 {
+  MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
+
   // At the moment the only thing we're observing are settings indicating
   // user requested locales.
   NS_ConvertUTF16toUTF8 pref(aData);
   if (pref.EqualsLiteral(MATCH_OS_LOCALE_PREF) ||
-      pref.EqualsLiteral(SELECTED_LOCALE_PREF)) {
-    Refresh();
-    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-    if (obs) {
-      obs->NotifyObservers(nullptr, "intl:requested-locales-changed", nullptr);
-    }
-  } else if (pref.EqualsLiteral(ANDROID_OS_LOCALE_PREF)) {
-    Refresh();
+      pref.EqualsLiteral(SELECTED_LOCALE_PREF) ||
+      pref.EqualsLiteral(ANDROID_OS_LOCALE_PREF)) {
+    OnRequestedLocalesChanged();
   }
   return NS_OK;
 }
 
 bool
 LocaleService::LanguagesMatch(const nsCString& aRequested,
                               const nsCString& aAvailable)
 {
   return Locale(aRequested, true).LanguageMatches(Locale(aAvailable, true));
 }
 
+
+bool
+LocaleService::IsServer()
+{
+  return mIsServer;
+}
+
 /**
  * mozILocaleService methods
  */
 
 static char**
 CreateOutArray(const nsTArray<nsCString>& aArray)
 {
   uint32_t n = aArray.Length();
@@ -518,16 +602,17 @@ LocaleService::GetAppLocaleAsLangTag(nsA
 
 NS_IMETHODIMP
 LocaleService::GetAppLocaleAsBCP47(nsACString& aRetVal)
 {
   if (mAppLocales.IsEmpty()) {
     NegotiateAppLocales(mAppLocales);
   }
   aRetVal = mAppLocales[0];
+
   SanitizeForBCP47(aRetVal);
   return NS_OK;
 }
 
 static LocaleService::LangNegStrategy
 ToLangNegStrategy(int32_t aStrategy)
 {
   switch (aStrategy) {
--- a/intl/locale/LocaleService.h
+++ b/intl/locale/LocaleService.h
@@ -17,16 +17,46 @@ namespace intl {
 
 /**
  * LocaleService is a manager of language negotiation in Gecko.
  *
  * It's intended to be the core place for collecting available and
  * requested languages and negotiating them to produce a fallback
  * chain of locales for the application.
  *
+ * Client / Server
+ *
+ * LocaleService may operate in one of two modes:
+ *
+ *   server
+ *     in the server mode, LocaleService is collecting and negotiating
+ *     languages. It also subscribes to relevant observers.
+ *     There should be at most one server per application instance.
+ *
+ *   client
+ *     in the client mode, LocaleService is not responsible for collecting
+ *     or reacting to any system changes. It still distributes information
+ *     about locales, but internally, it gets information from the server instance
+ *     instead of collecting it on its own.
+ *     This prevents any data desynchronization and minimizes the cost
+ *     of running the service.
+ *
+ *   In both modes, all get* methods should work the same way and all
+ *   static methods are available.
+ *
+ *   In the server mode, other components may inform LocaleService about their
+ *   status either via calls to set* methods or via observer events.
+ *   In the client mode, only the process communication should provide data
+ *   to the LocaleService.
+ *
+ *   At the moment desktop apps use the parent process in the server mode, and
+ *   content processes in the client mode.
+ *
+ * Locale / Language
+ *
  * The terms `Locale ID` and `Language ID` are used slightly differently
  * by different organizations. Mozilla uses the term `Language ID` to describe
  * a string that contains information about the language itself, script,
  * region and variant. For example "en-Latn-US-mac" is a correct Language ID.
  *
  * Locale ID contains a Language ID plus a number of extension tags that
  * contain information that go beyond language inforamation such as
  * preferred currency, date/time formatting etc.
@@ -52,16 +82,18 @@ public:
    * strategies.
    */
   enum class LangNegStrategy {
     Filtering,
     Matching,
     Lookup
   };
 
+  explicit LocaleService(bool aIsServer);
+
   /**
    * Create (if necessary) and return a raw pointer to the singleton instance.
    * Use this accessor in C++ code that just wants to call a method on the
    * instance, but does not need to hold a reference, as in
    *    nsAutoCString str;
    *    LocaleService::GetInstance()->GetAppLocaleAsLangTag(str);
    */
   static LocaleService* GetInstance();
@@ -90,16 +122,28 @@ public:
    *   LocaleService::GetInstance()->GetAppLocalesAsLangTags(appLocales);
    *
    * (See mozILocaleService.idl for a JS-callable version of this.)
    */
   void GetAppLocalesAsLangTags(nsTArray<nsCString>& aRetVal);
   void GetAppLocalesAsBCP47(nsTArray<nsCString>& aRetVal);
 
   /**
+   * This method should only be called in the client mode.
+   *
+   * It replaces all the language negotiation and is supposed to be called
+   * in order to bring the client LocaleService in sync with the server
+   * LocaleService.
+   *
+   * Currently, it's called by the IPC code.
+   */
+  void AssignAppLocales(const nsTArray<nsCString>& aAppLocales);
+  void AssignRequestedLocales(const nsTArray<nsCString>& aRequestedLocales);
+
+  /**
    * Returns a list of locales that the user requested the app to be
    * localized to.
    *
    * The result is a sorted list of valid locale IDs and it should be
    * used as a requestedLocales input list for languages negotiation.
    *
    * Example: ["en-US", "de", "pl", "sr-Cyrl", "zh-Hans-HK"]
    *
@@ -130,22 +174,31 @@ public:
    * Returns a boolean indicating if the attempt to retrieve at least
    * one locale was successful.
    *
    * (See mozILocaleService.idl for a JS-callable version of this.)
    */
   bool GetAvailableLocales(nsTArray<nsCString>& aRetVal);
 
   /**
-   * Triggers a refresh of the language negotiation process.
+   * Those three functions allow to trigger cache invalidation on one of the
+   * three cached values.
+   *
+   * In most cases, the functions will be called by the observer in
+   * LocaleService itself, but in a couple special cases, we have the
+   * other component call this manually instead of sending a global event.
    *
    * If the result differs from the previous list, it will additionally
-   * trigger a global event "intl:app-locales-changed".
+   * trigger a corresponding event
+   *
+   * This code should be called only in the server mode..
    */
-  void Refresh();
+  void OnAvailableLocalesChanged();
+  void OnRequestedLocalesChanged();
+  void OnLocalesChanged();
 
   /**
    * Negotiates the best locales out of an ordered list of requested locales and
    * a list of available locales.
    *
    * Internally it uses the following naming scheme:
    *
    *  Requested - locales requested by the user
@@ -170,16 +223,18 @@ public:
   /**
    * Returns whether the current app locale is RTL.
    */
   bool IsAppLocaleRTL();
 
   static bool LanguagesMatch(const nsCString& aRequested,
                              const nsCString& aAvailable);
 
+  bool IsServer();
+
 private:
   /**
    * Locale object, a BCP47-style tag decomposed into subtags for
    * matching purposes.
    *
    * If constructed with aRange = true, any missing subtags will be
    * set to "*".
    */
@@ -221,15 +276,18 @@ private:
                      LangNegStrategy aStrategy,
                      nsTArray<nsCString>& aRetVal);
 
   void NegotiateAppLocales(nsTArray<nsCString>& aRetVal);
 
   virtual ~LocaleService();
 
   nsTArray<nsCString> mAppLocales;
+  nsTArray<nsCString> mRequestedLocales;
+  nsTArray<nsCString> mAvailableLocales;
+  const bool mIsServer;
 
   static StaticRefPtr<LocaleService> sInstance;
 };
 } // intl
 } // namespace mozilla
 
 #endif /* mozilla_intl_LocaleService_h__ */