Bug 1560342 - Factor Localization out of DOMLocalization. r=smaug
authorZibi Braniecki <zbraniecki@mozilla.com>
Tue, 25 Jun 2019 19:39:53 +0000
changeset 539840 e9a6d6d6a41cd058df01168fcfbac9ea6ab6f6a9
parent 539839 5f306cffa94841a6be8372af9153478ba1514316
child 539841 84ffa3e268c4afcb7289a9f1eb75823717d1cb68
push id11522
push userffxbld-merge
push dateMon, 01 Jul 2019 09:00:55 +0000
treeherdermozilla-beta@53ea74d2bd09 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1560342
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1560342 - Factor Localization out of DOMLocalization. r=smaug Differential Revision: https://phabricator.services.mozilla.com/D35445
dom/bindings/Bindings.conf
dom/chrome-webidl/DOMLocalization.webidl
dom/chrome-webidl/L10nOverlays.webidl
dom/chrome-webidl/Localization.webidl
dom/chrome-webidl/moz.build
dom/l10n/DOMLocalization.cpp
dom/l10n/DOMLocalization.h
dom/l10n/DocumentL10n.cpp
dom/l10n/DocumentL10n.h
dom/l10n/L10nMutations.h
dom/l10n/L10nOverlays.cpp
dom/l10n/L10nOverlays.h
dom/l10n/tests/gtest/TestL10nOverlays.cpp
intl/l10n/Localization.cpp
intl/l10n/Localization.h
intl/l10n/moz.build
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -494,16 +494,21 @@ DOMInterfaces = {
     'binaryNames': { 'constructor': 'ConstructorJS' },
 },
 
 'LegacyMozTCPSocket': {
     'headerFile': 'TCPSocket.h',
     'wrapperCache': False,
 },
 
+'Localization': {
+    'nativeType': 'mozilla::intl::Localization',
+},
+
+
 'MatchGlob': {
     'nativeType': 'mozilla::extensions::MatchGlob',
 },
 
 'MatchPattern': {
     'nativeType': 'mozilla::extensions::MatchPattern',
 },
 
--- a/dom/chrome-webidl/DOMLocalization.webidl
+++ b/dom/chrome-webidl/DOMLocalization.webidl
@@ -1,47 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 /**
- * An object used to carry localization information from and to an
- * Element.
- *
- * Fields:
- *    id - identifier of a message used to localize the element.
- *  args - an optional map of arguments used to format the message.
- *         The argument will be converted to/from JSON, so can
- *         handle any value that JSON can, but in practice, the API
- *         will only use a string or a number for now.
- */
-dictionary L10nKey {
-  DOMString? id = null;
-  object? args = null;
-};
-
-callback GenerateMessages = Promise<any> (sequence<DOMString> aAppLocales, sequence<DOMString> aResourceIds);
-
-/**
  * DOMLocalization is an extension of the Fluent Localization API.
  *
- * An instance of a Localization class stores a state of a mix
- * of localization resources and provides the API to resolve
- * translation value for localization identifiers from the
- * resources.
- *
- * Methods:
- *    - addResourceIds     - add resources
- *    - removeResourceIds  - remove resources
- *    - formatValue        - format a single value
- *    - formatValues       - format multiple values
- *    - formatMessages     - format multiple compound messages
- *
- *
  * DOMLocalization adds a state for storing `roots` - DOM elements
  * which translation status is controlled by the DOMLocalization
  * instance and monitored for mutations.
  * DOMLocalization also adds methods dedicated to DOM manipulation.
  *
  * Methods:
  *    - connectRoot        - add a root
  *    - disconnectRoot     - remove a root
@@ -60,89 +29,17 @@ callback GenerateMessages = Promise<any>
  *    - aResourceids       - a list of localization resource URIs
  *                           which will provide messages for this
  *                           Localization instance.
  *    - aGenerateMessages  - a callback function which will be
  *                           used to generate an iterator
  *                           over FluentBundle instances.
  */
 [ChromeOnly, Constructor(optional sequence<DOMString> aResourceIds, optional GenerateMessages aGenerateMessages)]
-interface DOMLocalization {
-  /**
-   * Localization API
-   */
-
-  /**
-   * A method for adding resources to the localization context.
-   *
-   * Returns a new count of resources used by the context.
-   */
-  unsigned long addResourceIds(sequence<DOMString> aResourceIds, optional boolean aEager = false);
-
-  /**
-   * A method for removing resources from the localization context.
-   *
-   * Returns a new count of resources used by the context.
-   */
-  unsigned long removeResourceIds(sequence<DOMString> aResourceIds);
-
-  /**
-   * Formats a value of a localization message with a given id.
-   * An optional dictionary of arguments can be passed to inform
-   * the message formatting logic.
-   *
-   * Example:
-   *    let value = await document.l10n.formatValue("unread-emails", {count: 5});
-   *    assert.equal(value, "You have 5 unread emails");
-   */
-  [NewObject] Promise<DOMString> formatValue(DOMString aId, optional object aArgs);
-
-  /**
-   * Formats values of a list of messages with given ids.
-   *
-   * Example:
-   *    let values = await document.l10n.formatValues([
-   *      {id: "hello-world"},
-   *      {id: "unread-emails", args: {count: 5}
-   *    ]);
-   *    assert.deepEqual(values, [
-   *      "Hello World",
-   *      "You have 5 unread emails"
-   *    ]);
-   */
-  [NewObject] Promise<sequence<DOMString>> formatValues(sequence<L10nKey> aKeys);
-
-  /**
-   * Formats values and attributes of a list of messages with given ids.
-   *
-   * Example:
-   *    let values = await document.l10n.formatMessages([
-   *      {id: "hello-world"},
-   *      {id: "unread-emails", args: {count: 5}
-   *    ]);
-   *    assert.deepEqual(values, [
-   *      {
-   *        value: "Hello World",
-   *        attributes: null
-   *      },
-   *      {
-   *        value: "You have 5 unread emails",
-   *        attributes: {
-   *          tooltip: "Click to select them all"
-   *        }
-   *      }
-   *    ]);
-   */
-  [NewObject] Promise<sequence<DOMString>> formatMessages(sequence<L10nKey> aKeys);
-
-
-  /**
-   * DOMLocalization API
-   */
-
+interface DOMLocalization : Localization {
   /**
    * Adds a node to nodes observed for localization
    * related changes.
    */
   [Throws] void connectRoot(Element aElement);
 
   /**
    * Removes a node from nodes observed for localization
@@ -233,9 +130,9 @@ interface DOMLocalization {
   /**
    * Triggers translation of all attached roots and sets their
    * locale info and directionality attributes.
    *
    * Example:
    *    await document.l10n.translateRoots();
    */
   [NewObject] Promise<void> translateRoots();
-};
+};
\ No newline at end of file
--- a/dom/chrome-webidl/L10nOverlays.webidl
+++ b/dom/chrome-webidl/L10nOverlays.webidl
@@ -1,31 +1,21 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
-dictionary AttributeNameValue {
-  required DOMString name;
-  required DOMString value;
-};
-
-dictionary L10nValue {
-  DOMString? value = null;
-  sequence<AttributeNameValue>? attributes = null;
-};
-
 dictionary L10nOverlaysError {
   short code;
   DOMString translatedElementName;
   DOMString sourceElementName;
   DOMString l10nName;
 };
 
 [ChromeOnly]
 namespace L10nOverlays {
   const unsigned short ERROR_FORBIDDEN_TYPE = 1;
   const unsigned short ERROR_NAMED_ELEMENT_MISSING = 2;
   const unsigned short ERROR_NAMED_ELEMENT_TYPE_MISMATCH = 3;
   const unsigned short ERROR_UNKNOWN = 4;
 
-  sequence<L10nOverlaysError>? translateElement(Element element, optional L10nValue translation);
-};
+  sequence<L10nOverlaysError>? translateElement(Element element, optional L10nMessage translation);
+};
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/chrome-webidl/Localization.webidl
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/**
+ * An object used to carry localization information from and to an
+ * Element.
+ *
+ * Fields:
+ *    id - identifier of a message used to localize the element.
+ *  args - an optional map of arguments used to format the message.
+ *         The argument will be converted to/from JSON, so can
+ *         handle any value that JSON can, but in practice, the API
+ *         will only use a string or a number for now.
+ */
+dictionary L10nKey {
+  DOMString? id = null;
+  object? args = null;
+};
+
+dictionary AttributeNameValue {
+  required DOMString name;
+  required DOMString value;
+};
+
+dictionary L10nMessage {
+  DOMString? value = null;
+  sequence<AttributeNameValue>? attributes = null;
+};
+
+callback GenerateMessages = Promise<any> (sequence<DOMString> aAppLocales, sequence<DOMString> aResourceIds);
+
+/**
+ * Localization is an implementation of the Fluent Localization API.
+ *
+ * An instance of a Localization class stores a state of a mix
+ * of localization resources and provides the API to resolve
+ * translation value for localization identifiers from the
+ * resources.
+ *
+ * Methods:
+ *    - addResourceIds     - add resources
+ *    - removeResourceIds  - remove resources
+ *    - formatValue        - format a single value
+ *    - formatValues       - format multiple values
+ *    - formatMessages     - format multiple compound messages
+ *
+ */
+
+/**
+ * Constructor arguments:
+ *    - aResourceids       - a list of localization resource URIs
+ *                           which will provide messages for this
+ *                           Localization instance.
+ *    - aGenerateMessages  - a callback function which will be
+ *                           used to generate an iterator
+ *                           over FluentBundle instances.
+ */
+[ChromeOnly, Constructor(optional sequence<DOMString> aResourceIds, optional GenerateMessages aGenerateMessages)]
+interface Localization {
+  /**
+   * A method for adding resources to the localization context.
+   *
+   * Returns a new count of resources used by the context.
+   */
+  unsigned long addResourceIds(sequence<DOMString> aResourceIds, optional boolean aEager = false);
+
+  /**
+   * A method for removing resources from the localization context.
+   *
+   * Returns a new count of resources used by the context.
+   */
+  unsigned long removeResourceIds(sequence<DOMString> aResourceIds);
+
+  /**
+   * Formats a value of a localization message with a given id.
+   * An optional dictionary of arguments can be passed to inform
+   * the message formatting logic.
+   *
+   * Example:
+   *    let value = await document.l10n.formatValue("unread-emails", {count: 5});
+   *    assert.equal(value, "You have 5 unread emails");
+   */
+  [NewObject] Promise<DOMString> formatValue(DOMString aId, optional object aArgs);
+
+  /**
+   * Formats values of a list of messages with given ids.
+   *
+   * Example:
+   *    let values = await document.l10n.formatValues([
+   *      {id: "hello-world"},
+   *      {id: "unread-emails", args: {count: 5}
+   *    ]);
+   *    assert.deepEqual(values, [
+   *      "Hello World",
+   *      "You have 5 unread emails"
+   *    ]);
+   */
+  [NewObject] Promise<sequence<DOMString>> formatValues(sequence<L10nKey> aKeys);
+
+  /**
+   * Formats values and attributes of a list of messages with given ids.
+   *
+   * Example:
+   *    let values = await document.l10n.formatMessages([
+   *      {id: "hello-world"},
+   *      {id: "unread-emails", args: {count: 5}
+   *    ]);
+   *    assert.deepEqual(values, [
+   *      {
+   *        value: "Hello World",
+   *        attributes: null
+   *      },
+   *      {
+   *        value: "You have 5 unread emails",
+   *        attributes: {
+   *          tooltip: "Click to select them all"
+   *        }
+   *      }
+   *    ]);
+   */
+  [NewObject] Promise<sequence<L10nMessage>> formatMessages(sequence<L10nKey> aKeys);
+};
\ No newline at end of file
--- a/dom/chrome-webidl/moz.build
+++ b/dom/chrome-webidl/moz.build
@@ -41,16 +41,17 @@ WEBIDL_FILES = [
     'DominatorTree.webidl',
     'DOMLocalization.webidl',
     'Flex.webidl',
     'HeapSnapshot.webidl',
     'InspectorUtils.webidl',
     'IteratorResult.webidl',
     'JSWindowActor.webidl',
     'L10nOverlays.webidl',
+    'Localization.webidl',
     'MatchGlob.webidl',
     'MatchPattern.webidl',
     'MessageManager.webidl',
     'MozDocumentObserver.webidl',
     'MozSharedMap.webidl',
     'MozStorageAsyncStatementParams.webidl',
     'MozStorageStatementParams.webidl',
     'MozStorageStatementRow.webidl',
--- a/dom/l10n/DOMLocalization.cpp
+++ b/dom/l10n/DOMLocalization.cpp
@@ -1,224 +1,81 @@
 #include "js/ForOfIterator.h"  // JS::ForOfIterator
 #include "js/JSON.h"           // JS_ParseJSON
-#include "nsImportModule.h"
 #include "nsIScriptError.h"
 #include "DOMLocalization.h"
 #include "mozilla/intl/LocaleService.h"
-#include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/L10nOverlays.h"
 
-#define INTL_APP_LOCALES_CHANGED "intl:app-locales-changed"
-
-#define L10N_PSEUDO_PREF "intl.l10n.pseudo"
-
-#define INTL_UI_DIRECTION_PREF "intl.uidirection"
-
-static const char* kObservedPrefs[] = {L10N_PSEUDO_PREF, INTL_UI_DIRECTION_PREF,
-                                       nullptr};
-
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::intl;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMLocalization)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMLocalization)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMLocalization, Localization)
   tmp->DisconnectMutations();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMutations)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalization)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoots)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMLocalization)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMLocalization, Localization)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMutations)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalization)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoots)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(DOMLocalization)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMLocalization)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMLocalization)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMLocalization)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
-  NS_INTERFACE_MAP_ENTRY(nsIObserver)
-  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
-NS_INTERFACE_MAP_END
-
-DOMLocalization::DOMLocalization(nsIGlobalObject* aGlobal) : mGlobal(aGlobal) {
-  mMutations = new L10nMutations(this);
-}
 
-void DOMLocalization::Init(nsTArray<nsString>& aResourceIds, ErrorResult& aRv) {
-  nsCOMPtr<mozILocalizationJSM> jsm =
-      do_ImportModule("resource://gre/modules/Localization.jsm");
-  MOZ_RELEASE_ASSERT(jsm);
-
-  Unused << jsm->GetLocalization(aResourceIds, getter_AddRefs(mLocalization));
-  MOZ_RELEASE_ASSERT(mLocalization);
-
-  RegisterObservers();
-}
+NS_IMPL_ADDREF_INHERITED(DOMLocalization, Localization)
+NS_IMPL_RELEASE_INHERITED(DOMLocalization, Localization)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMLocalization)
+NS_INTERFACE_MAP_END_INHERITING(Localization)
 
-void DOMLocalization::Init(nsTArray<nsString>& aResourceIds,
-                           JS::Handle<JS::Value> aGenerateMessages,
-                           ErrorResult& aRv) {
-  nsCOMPtr<mozILocalizationJSM> jsm =
-      do_ImportModule("resource://gre/modules/Localization.jsm");
-  MOZ_RELEASE_ASSERT(jsm);
-
-  Unused << jsm->GetLocalizationWithCustomGenerateMessages(
-      aResourceIds, aGenerateMessages, getter_AddRefs(mLocalization));
-  MOZ_RELEASE_ASSERT(mLocalization);
-
-  RegisterObservers();
+DOMLocalization::DOMLocalization(nsIGlobalObject* aGlobal)
+    : Localization(aGlobal) {
+  mMutations = new L10nMutations(this);
 }
 
 already_AddRefed<DOMLocalization> DOMLocalization::Constructor(
     const GlobalObject& aGlobal,
     const Optional<Sequence<nsString>>& aResourceIds,
     const Optional<OwningNonNull<GenerateMessages>>& aGenerateMessages,
     ErrorResult& aRv) {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  RefPtr<DOMLocalization> loc = new DOMLocalization(global);
+  RefPtr<DOMLocalization> domLoc = new DOMLocalization(global);
   nsTArray<nsString> resourceIds;
 
   if (aResourceIds.WasPassed()) {
     resourceIds = aResourceIds.Value();
   }
 
   if (aGenerateMessages.WasPassed()) {
     GenerateMessages& generateMessages = aGenerateMessages.Value();
     JS::Rooted<JS::Value> generateMessagesJS(
         aGlobal.Context(), JS::ObjectValue(*generateMessages.CallbackOrNull()));
-    loc->Init(resourceIds, generateMessagesJS, aRv);
+    domLoc->Init(resourceIds, generateMessagesJS, aRv);
   } else {
-    loc->Init(resourceIds, aRv);
+    domLoc->Init(resourceIds, aRv);
   }
 
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
-  return loc.forget();
+  return domLoc.forget();
 }
 
-nsIGlobalObject* DOMLocalization::GetParentObject() const { return mGlobal; }
-
 JSObject* DOMLocalization::WrapObject(JSContext* aCx,
                                       JS::Handle<JSObject*> aGivenProto) {
   return DOMLocalization_Binding::Wrap(aCx, this, aGivenProto);
 }
 
-DOMLocalization::~DOMLocalization() {
-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-  if (obs) {
-    obs->RemoveObserver(this, INTL_APP_LOCALES_CHANGED);
-  }
-
-  Preferences::RemoveObservers(this, kObservedPrefs);
-
-  DisconnectMutations();
-}
-
-/**
- * Localization API
- */
-
-uint32_t DOMLocalization::AddResourceIds(const nsTArray<nsString>& aResourceIds,
-                                         bool aEager) {
-  uint32_t ret = 0;
-  mLocalization->AddResourceIds(aResourceIds, aEager, &ret);
-  return ret;
-}
-
-uint32_t DOMLocalization::RemoveResourceIds(
-    const nsTArray<nsString>& aResourceIds) {
-  // We need to guard against a scenario where the
-  // mLocalization has been unlinked, but the elements
-  // are only now removed from DOM.
-  if (!mLocalization) {
-    return 0;
-  }
-  uint32_t ret = 0;
-  mLocalization->RemoveResourceIds(aResourceIds, &ret);
-  return ret;
-}
-
-already_AddRefed<Promise> DOMLocalization::FormatValue(
-    JSContext* aCx, const nsAString& aId,
-    const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv) {
-  JS::Rooted<JS::Value> args(aCx);
-
-  if (aArgs.WasPassed()) {
-    args = JS::ObjectValue(*aArgs.Value());
-  } else {
-    args = JS::UndefinedValue();
-  }
-
-  RefPtr<Promise> promise;
-  nsresult rv = mLocalization->FormatValue(aId, args, getter_AddRefs(promise));
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
-    return nullptr;
-  }
-  return MaybeWrapPromise(promise);
-}
-
-already_AddRefed<Promise> DOMLocalization::FormatValues(
-    JSContext* aCx, const Sequence<L10nKey>& aKeys, ErrorResult& aRv) {
-  nsTArray<JS::Value> jsKeys;
-  SequenceRooter<JS::Value> rooter(aCx, &jsKeys);
-  for (auto& key : aKeys) {
-    JS::RootedValue jsKey(aCx);
-    if (!ToJSValue(aCx, key, &jsKey)) {
-      aRv.NoteJSContextException(aCx);
-      return nullptr;
-    }
-    jsKeys.AppendElement(jsKey);
-  }
-
-  RefPtr<Promise> promise;
-  aRv = mLocalization->FormatValues(jsKeys, getter_AddRefs(promise));
-  if (aRv.Failed()) {
-    return nullptr;
-  }
-
-  return MaybeWrapPromise(promise);
-}
-
-already_AddRefed<Promise> DOMLocalization::FormatMessages(
-    JSContext* aCx, const Sequence<L10nKey>& aKeys, ErrorResult& aRv) {
-  nsTArray<JS::Value> jsKeys;
-  SequenceRooter<JS::Value> rooter(aCx, &jsKeys);
-  for (auto& key : aKeys) {
-    JS::RootedValue jsKey(aCx);
-    if (!ToJSValue(aCx, key, &jsKey)) {
-      aRv.NoteJSContextException(aCx);
-      return nullptr;
-    }
-    jsKeys.AppendElement(jsKey);
-  }
-
-  RefPtr<Promise> promise;
-  aRv = mLocalization->FormatMessages(jsKeys, getter_AddRefs(promise));
-  if (aRv.Failed()) {
-    return nullptr;
-  }
-
-  return MaybeWrapPromise(promise);
-}
+DOMLocalization::~DOMLocalization() { DisconnectMutations(); }
 
 /**
  * DOMLocalization API
  */
 
 void DOMLocalization::ConnectRoot(Element& aNode, ErrorResult& aRv) {
   nsCOMPtr<nsIGlobalObject> global = aNode.GetOwnerGlobal();
   if (!global) {
@@ -332,17 +189,17 @@ class ElementTranslationHandler : public
   void SetReturnValuePromise(Promise* aReturnValuePromise) {
     mReturnValuePromise = aReturnValuePromise;
   }
 
   virtual void ResolvedCallback(JSContext* aCx,
                                 JS::Handle<JS::Value> aValue) override {
     ErrorResult rv;
 
-    nsTArray<L10nValue> l10nData;
+    nsTArray<L10nMessage> l10nData;
     if (aValue.isObject()) {
       JS::ForOfIterator iter(aCx);
       if (!iter.init(aValue, JS::ForOfIterator::AllowNonIterable)) {
         mReturnValuePromise->MaybeRejectWithUndefined();
         return;
       }
       if (!iter.valueIsIterable()) {
         mReturnValuePromise->MaybeRejectWithUndefined();
@@ -356,17 +213,17 @@ class ElementTranslationHandler : public
           mReturnValuePromise->MaybeRejectWithUndefined();
           return;
         }
 
         if (done) {
           break;
         }
 
-        L10nValue* slotPtr = l10nData.AppendElement(mozilla::fallible);
+        L10nMessage* slotPtr = l10nData.AppendElement(mozilla::fallible);
         if (!slotPtr) {
           mReturnValuePromise->MaybeRejectWithUndefined();
           return;
         }
 
         if (!slotPtr->Init(aCx, temp)) {
           mReturnValuePromise->MaybeRejectWithUndefined();
           return;
@@ -567,17 +424,17 @@ void DOMLocalization::SetRootInfo(Elemen
   uint32_t nameSpace = aElement->GetNameSpaceID();
   nsAtom* dirAtom =
       nameSpace == kNameSpaceID_XUL ? nsGkAtoms::localedir : nsGkAtoms::dir;
 
   aElement->SetAttr(kNameSpaceID_None, dirAtom, dir, true);
 }
 
 void DOMLocalization::ApplyTranslations(nsTArray<nsCOMPtr<Element>>& aElements,
-                                        nsTArray<L10nValue>& aTranslations,
+                                        nsTArray<L10nMessage>& aTranslations,
                                         ErrorResult& aRv) {
   if (aElements.Length() != aTranslations.Length()) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   PauseObserving(aRv);
   if (NS_WARN_IF(aRv.Failed())) {
@@ -601,47 +458,20 @@ void DOMLocalization::ApplyTranslations(
     return;
   }
 
   ReportL10nOverlaysErrors(errors);
 }
 
 /* Protected */
 
-void DOMLocalization::RegisterObservers() {
-  DebugOnly<nsresult> rv = Preferences::AddWeakObservers(this, kObservedPrefs);
-  MOZ_ASSERT(NS_SUCCEEDED(rv), "Adding observers failed.");
-
-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-  if (obs) {
-    obs->AddObserver(this, INTL_APP_LOCALES_CHANGED, true);
-  }
-}
-
-NS_IMETHODIMP
-DOMLocalization::Observe(nsISupports* aSubject, const char* aTopic,
-                         const char16_t* aData) {
-  if (!strcmp(aTopic, INTL_APP_LOCALES_CHANGED)) {
-    OnChange();
-  } else {
-    MOZ_ASSERT(!strcmp("nsPref:changed", aTopic));
-    nsDependentString pref(aData);
-    if (pref.EqualsLiteral(L10N_PSEUDO_PREF) ||
-        pref.EqualsLiteral(INTL_UI_DIRECTION_PREF)) {
-      OnChange();
-    }
-  }
-
-  return NS_OK;
-}
-
 void DOMLocalization::OnChange() {
+  Localization::OnChange();
   if (mLocalization) {
     ErrorResult rv;
-    mLocalization->OnChange();
     RefPtr<Promise> promise = TranslateRoots(rv);
   }
 }
 
 void DOMLocalization::DisconnectMutations() {
   if (mMutations) {
     mMutations->Disconnect();
     DisconnectRoots();
@@ -652,85 +482,16 @@ void DOMLocalization::DisconnectRoots() 
   for (auto iter = mRoots.ConstIter(); !iter.Done(); iter.Next()) {
     Element* elem = iter.Get()->GetKey();
 
     elem->RemoveMutationObserver(mMutations);
   }
   mRoots.Clear();
 }
 
-/**
- * PromiseResolver is a PromiseNativeHandler used
- * by MaybeWrapPromise method.
- */
-class PromiseResolver final : public PromiseNativeHandler {
- public:
-  NS_DECL_ISUPPORTS
-
-  explicit PromiseResolver(Promise* aPromise);
-  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
-  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
-
- protected:
-  virtual ~PromiseResolver();
-
-  RefPtr<Promise> mPromise;
-};
-
-NS_INTERFACE_MAP_BEGIN(PromiseResolver)
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(PromiseResolver)
-NS_IMPL_RELEASE(PromiseResolver)
-
-PromiseResolver::PromiseResolver(Promise* aPromise) { mPromise = aPromise; }
-
-void PromiseResolver::ResolvedCallback(JSContext* aCx,
-                                       JS::Handle<JS::Value> aValue) {
-  mPromise->MaybeResolveWithClone(aCx, aValue);
-}
-
-void PromiseResolver::RejectedCallback(JSContext* aCx,
-                                       JS::Handle<JS::Value> aValue) {
-  mPromise->MaybeRejectWithClone(aCx, aValue);
-}
-
-PromiseResolver::~PromiseResolver() { mPromise = nullptr; }
-
-/**
- * MaybeWrapPromise is a helper method used by Localization
- * API methods to clone the value returned by a promise
- * into a new context.
- *
- * This allows for a promise from a privileged context
- * to be returned into an unprivileged document.
- *
- * This method is only used for promises that carry values.
- */
-already_AddRefed<Promise> DOMLocalization::MaybeWrapPromise(
-    Promise* aInnerPromise) {
-  // For system principal we don't need to wrap the
-  // result promise at all.
-  nsIPrincipal* principal = mGlobal->PrincipalOrNull();
-  if (principal && nsContentUtils::IsSystemPrincipal(principal)) {
-    return RefPtr<Promise>(aInnerPromise).forget();
-  }
-
-  ErrorResult result;
-  RefPtr<Promise> docPromise = Promise::Create(mGlobal, result);
-  if (NS_WARN_IF(result.Failed())) {
-    return nullptr;
-  }
-
-  RefPtr<PromiseResolver> resolver = new PromiseResolver(docPromise);
-  aInnerPromise->AppendNativeHandler(resolver);
-  return docPromise.forget();
-}
-
 void DOMLocalization::ReportL10nOverlaysErrors(
     nsTArray<L10nOverlaysError>& aErrors) {
   nsAutoString msg;
 
   for (auto& error : aErrors) {
     if (error.mCode.WasPassed()) {
       msg = NS_LITERAL_STRING("[fluent-dom] ");
       switch (error.mCode.Value()) {
--- a/dom/l10n/DOMLocalization.h
+++ b/dom/l10n/DOMLocalization.h
@@ -1,74 +1,38 @@
-#ifndef mozilla_dom_l10n_DOMLocalization_h__
-#define mozilla_dom_l10n_DOMLocalization_h__
+#ifndef mozilla_dom_l10n_DOMLocalization_h
+#define mozilla_dom_l10n_DOMLocalization_h
 
-#include "nsWeakReference.h"
-#include "nsIObserver.h"
-#include "mozILocalization.h"
-#include "mozilla/ErrorResult.h"
+#include "mozilla/intl/Localization.h"
+#include "mozilla/dom/DOMLocalizationBinding.h"
 #include "mozilla/dom/Element.h"
-#include "mozilla/dom/Promise.h"
-#include "mozilla/dom/DOMLocalizationBinding.h"
-#include "mozilla/dom/DocumentL10nBinding.h"
+#include "mozilla/dom/L10nMutations.h"
 #include "mozilla/dom/L10nOverlaysBinding.h"
-#include "mozilla/dom/L10nMutations.h"
-
-class nsIGlobalObject;
+#include "mozilla/dom/LocalizationBinding.h"
 
 namespace mozilla {
 namespace dom {
 
-class DOMLocalization : public nsIObserver,
-                        public nsSupportsWeakReference,
-                        public nsWrapperCache {
+class DOMLocalization : public intl::Localization {
  public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(DOMLocalization,
-                                                         nsIObserver)
-  NS_DECL_NSIOBSERVER
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DOMLocalization, Localization)
 
   explicit DOMLocalization(nsIGlobalObject* aGlobal);
-  void Init(nsTArray<nsString>& aResourceIds, ErrorResult& aRv);
-  void Init(nsTArray<nsString>& aResourceIds,
-            JS::Handle<JS::Value> aGenerateMessages, ErrorResult& aRv);
 
   static already_AddRefed<DOMLocalization> Constructor(
       const GlobalObject& aGlobal,
       const Optional<Sequence<nsString>>& aResourceIds,
       const Optional<OwningNonNull<GenerateMessages>>& aGenerateMessages,
       ErrorResult& aRv);
 
-  nsIGlobalObject* GetParentObject() const;
-
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   /**
-   * Localization API
-   *
-   * Methods documentation in DOMLocalization.webidl
-   */
-  uint32_t AddResourceIds(const nsTArray<nsString>& aResourceIds, bool aEager);
-
-  uint32_t RemoveResourceIds(const nsTArray<nsString>& aResourceIds);
-
-  already_AddRefed<Promise> FormatValue(
-      JSContext* aCx, const nsAString& aId,
-      const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv);
-
-  already_AddRefed<Promise> FormatValues(JSContext* aCx,
-                                         const Sequence<L10nKey>& aKeys,
-                                         ErrorResult& aRv);
-
-  already_AddRefed<Promise> FormatMessages(JSContext* aCx,
-                                           const Sequence<L10nKey>& aKeys,
-                                           ErrorResult& aRv);
-
-  /**
    * DOMLocalization API
    *
    * Methods documentation in DOMLocalization.webidl
    */
 
   void ConnectRoot(Element& aNode, ErrorResult& aRv);
   void DisconnectRoot(Element& aNode, ErrorResult& aRv);
 
@@ -105,29 +69,26 @@ class DOMLocalization : public nsIObserv
    * Sets the root information such as locale and direction.
    */
   static void SetRootInfo(Element* aElement);
 
   /**
    * Applies l10n translations on translatable elements.
    */
   void ApplyTranslations(nsTArray<nsCOMPtr<Element>>& aElements,
-                         nsTArray<L10nValue>& aTranslations, ErrorResult& aRv);
+                         nsTArray<L10nMessage>& aTranslations,
+                         ErrorResult& aRv);
 
  protected:
   virtual ~DOMLocalization();
-  void RegisterObservers();
   void OnChange();
   void DisconnectMutations();
   void DisconnectRoots();
-  already_AddRefed<Promise> MaybeWrapPromise(Promise* aPromise);
   void ReportL10nOverlaysErrors(nsTArray<L10nOverlaysError>& aErrors);
 
-  nsCOMPtr<nsIGlobalObject> mGlobal;
   RefPtr<L10nMutations> mMutations;
-  nsCOMPtr<mozILocalization> mLocalization;
   nsTHashtable<nsRefPtrHashKey<Element>> mRoots;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif
--- a/dom/l10n/DocumentL10n.cpp
+++ b/dom/l10n/DocumentL10n.cpp
@@ -1,21 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "DocumentL10n.h"
+#include "nsIContentSink.h"
 #include "mozilla/dom/DocumentL10nBinding.h"
-#include "mozilla/dom/Promise.h"
-#include "mozilla/dom/ScriptSettings.h"
-#include "nsQueryObject.h"
-#include "nsISupports.h"
-#include "nsContentUtils.h"
 
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(DocumentL10n)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocumentL10n, DOMLocalization)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mReady)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mContentSink)
@@ -24,21 +20,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocumentL10n, DOMLocalization)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReady)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentSink)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(DocumentL10n, DOMLocalization)
 NS_IMPL_RELEASE_INHERITED(DocumentL10n, DOMLocalization)
+
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentL10n)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
-  NS_INTERFACE_MAP_ENTRY(nsIObserver)
-  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END_INHERITING(DOMLocalization)
 
 DocumentL10n::DocumentL10n(Document* aDocument)
     : DOMLocalization(aDocument->GetScopeObject()),
       mDocument(aDocument),
       mState(DocumentL10nState::Initialized) {
   mContentSink = do_QueryInterface(aDocument->GetCurrentContentSink());
 }
@@ -139,9 +132,9 @@ void DocumentL10n::InitialDocumentTransl
   // In XUL scenario contentSink is nullptr.
   if (mContentSink) {
     mContentSink->InitialDocumentTranslationCompleted();
   }
 }
 
 Promise* DocumentL10n::Ready() { return mReady; }
 
-void DocumentL10n::OnCreatePresShell() { mMutations->OnCreatePresShell(); }
+void DocumentL10n::OnCreatePresShell() { mMutations->OnCreatePresShell(); }
\ No newline at end of file
--- a/dom/l10n/DocumentL10n.h
+++ b/dom/l10n/DocumentL10n.h
@@ -2,27 +2,17 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
 #ifndef mozilla_dom_l10n_DocumentL10n_h
 #define mozilla_dom_l10n_DocumentL10n_h
 
-#include "nsCycleCollectionParticipant.h"
-#include "nsIContentSink.h"
-#include "nsINode.h"
-#include "nsWrapperCache.h"
-#include "js/TypeDecls.h"
-#include "mozilla/Attributes.h"
-#include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/Document.h"
-#include "mozilla/dom/Element.h"
-#include "mozilla/dom/Promise.h"
-#include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/DOMLocalization.h"
 
 namespace mozilla {
 namespace dom {
 
 enum class DocumentL10nState {
   Initialized = 0,
   InitialTranslationTriggered,
@@ -39,17 +29,16 @@ enum class DocumentL10nState {
  * instance of mozILocalization and maintains a single promise
  * which gets resolved the first time the document gets translated.
  */
 class DocumentL10n final : public DOMLocalization {
  public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DocumentL10n, DOMLocalization)
 
- public:
   explicit DocumentL10n(Document* aDocument);
   void Init(nsTArray<nsString>& aResourceIds, ErrorResult& aRv);
 
  protected:
   virtual ~DocumentL10n() = default;
 
   RefPtr<Document> mDocument;
   RefPtr<Promise> mReady;
--- a/dom/l10n/L10nMutations.h
+++ b/dom/l10n/L10nMutations.h
@@ -1,10 +1,10 @@
-#ifndef mozilla_dom_l10n_L10nMutations_h__
-#define mozilla_dom_l10n_L10nMutations_h__
+#ifndef mozilla_dom_l10n_L10nMutations_h
+#define mozilla_dom_l10n_L10nMutations_h
 
 #include "nsRefreshDriver.h"
 #include "nsStubMutationObserver.h"
 #include "nsTHashtable.h"
 #include "mozilla/dom/DOMLocalization.h"
 
 namespace mozilla {
 namespace dom {
--- a/dom/l10n/L10nOverlays.cpp
+++ b/dom/l10n/L10nOverlays.cpp
@@ -393,17 +393,17 @@ void L10nOverlays::OverlayChildNodes(Doc
   aToElement->AppendChild(*aFromFragment, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 }
 
 void L10nOverlays::TranslateElement(
     const GlobalObject& aGlobal, Element& aElement,
-    const L10nValue& aTranslation,
+    const L10nMessage& aTranslation,
     Nullable<nsTArray<L10nOverlaysError>>& aErrors) {
   nsTArray<L10nOverlaysError> errors;
 
   ErrorResult rv;
 
   TranslateElement(aElement, aTranslation, errors, rv);
   if (NS_WARN_IF(rv.Failed())) {
     L10nOverlaysError error;
@@ -440,17 +440,17 @@ bool L10nOverlays::ContainsMarkup(const 
       ++start;
     }
   }
 
   return false;
 }
 
 void L10nOverlays::TranslateElement(Element& aElement,
-                                    const L10nValue& aTranslation,
+                                    const L10nMessage& aTranslation,
                                     nsTArray<L10nOverlaysError>& aErrors,
                                     ErrorResult& aRv) {
   if (!aTranslation.mValue.IsVoid()) {
     if (!ContainsMarkup(aTranslation.mValue)) {
       // If the translation doesn't contain any markup skip the overlay logic.
       aElement.SetTextContent(aTranslation.mValue, aRv);
       if (NS_WARN_IF(aRv.Failed())) {
         return;
--- a/dom/l10n/L10nOverlays.h
+++ b/dom/l10n/L10nOverlays.h
@@ -1,32 +1,34 @@
-#ifndef mozilla_dom_l10n_L10nOverlays_h__
-#define mozilla_dom_l10n_L10nOverlays_h__
+#ifndef mozilla_dom_l10n_L10nOverlays_h
+#define mozilla_dom_l10n_L10nOverlays_h
 
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/L10nOverlaysBinding.h"
+#include "mozilla/dom/LocalizationBinding.h"
 
 namespace mozilla {
 namespace dom {
 
 class L10nOverlays {
  public:
   /**
    * Translate an element.
    *
    * Translate the element's text content and attributes. Some HTML markup is
    * allowed in the translation. The element's children with the data-l10n-name
    * attribute will be treated as arguments to the translation. If the
    * translation defines the same children, their attributes and text contents
    * will be used for translating the matching source child.
    */
   static void TranslateElement(const GlobalObject& aGlobal, Element& aElement,
-                               const L10nValue& aTranslation,
+                               const L10nMessage& aTranslation,
                                Nullable<nsTArray<L10nOverlaysError>>& aErrors);
-  static void TranslateElement(Element& aElement, const L10nValue& aTranslation,
+  static void TranslateElement(Element& aElement,
+                               const L10nMessage& aTranslation,
                                nsTArray<L10nOverlaysError>& aErrors,
                                ErrorResult& aRv);
 
  private:
   /**
    * Check if attribute is allowed for the given element.
    *
    * This method is used by the sanitizer when the translation markup contains
--- a/dom/l10n/tests/gtest/TestL10nOverlays.cpp
+++ b/dom/l10n/tests/gtest/TestL10nOverlays.cpp
@@ -55,18 +55,18 @@ TEST(DOM_L10n_Overlays, Initial)
   RefPtr<Element> elem = doc->CreateHTMLElement(nsGkAtoms::div);
   RefPtr<Element> span = doc->CreateHTMLElement(nsGkAtoms::a);
   span->SetAttribute(NS_LITERAL_STRING("data-l10n-name"),
                      NS_LITERAL_STRING("link"), rv);
   span->SetAttribute(NS_LITERAL_STRING("href"),
                      NS_LITERAL_STRING("https://www.mozilla.org"), rv);
   elem->AppendChild(*span, rv);
 
-  // 3. Create an L10nValue with a translation for the element.
-  L10nValue translation;
+  // 3. Create an L10nMessage with a translation for the element.
+  L10nMessage translation;
   translation.mValue.AssignLiteral(
       "Hello <a data-l10n-name=\"link\">World</a>.");
 
   // 4. Translate the element.
   nsTArray<L10nOverlaysError> errors;
   L10nOverlays::TranslateElement(*elem, translation, errors, rv);
 
   nsAutoString textContent;
new file mode 100644
--- /dev/null
+++ b/intl/l10n/Localization.cpp
@@ -0,0 +1,304 @@
+#include "Localization.h"
+#include "nsImportModule.h"
+#include "nsContentUtils.h"
+
+#define INTL_APP_LOCALES_CHANGED "intl:app-locales-changed"
+#define L10N_PSEUDO_PREF "intl.l10n.pseudo"
+#define INTL_UI_DIRECTION_PREF "intl.uidirection"
+
+static const char* kObservedPrefs[] = {L10N_PSEUDO_PREF, INTL_UI_DIRECTION_PREF,
+                                       nullptr};
+
+using namespace mozilla::intl;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Localization)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Localization)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalization)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Localization)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalization)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Localization)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Localization)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Localization)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Localization)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+Localization::Localization(nsIGlobalObject* aGlobal) : mGlobal(aGlobal) {}
+
+void Localization::Init(nsTArray<nsString>& aResourceIds, ErrorResult& aRv) {
+  nsCOMPtr<mozILocalizationJSM> jsm =
+      do_ImportModule("resource://gre/modules/Localization.jsm");
+  MOZ_RELEASE_ASSERT(jsm);
+
+  Unused << jsm->GetLocalization(aResourceIds, getter_AddRefs(mLocalization));
+  MOZ_RELEASE_ASSERT(mLocalization);
+
+  RegisterObservers();
+}
+
+void Localization::Init(nsTArray<nsString>& aResourceIds,
+                        JS::Handle<JS::Value> aGenerateMessages,
+                        ErrorResult& aRv) {
+  nsCOMPtr<mozILocalizationJSM> jsm =
+      do_ImportModule("resource://gre/modules/Localization.jsm");
+  MOZ_RELEASE_ASSERT(jsm);
+
+  Unused << jsm->GetLocalizationWithCustomGenerateMessages(
+      aResourceIds, aGenerateMessages, getter_AddRefs(mLocalization));
+  MOZ_RELEASE_ASSERT(mLocalization);
+
+  RegisterObservers();
+}
+
+already_AddRefed<Localization> Localization::Constructor(
+    const GlobalObject& aGlobal,
+    const Optional<Sequence<nsString>>& aResourceIds,
+    const Optional<OwningNonNull<GenerateMessages>>& aGenerateMessages,
+    ErrorResult& aRv) {
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  RefPtr<Localization> loc = new Localization(global);
+  nsTArray<nsString> resourceIds;
+
+  if (aResourceIds.WasPassed()) {
+    resourceIds = aResourceIds.Value();
+  }
+
+  if (aGenerateMessages.WasPassed()) {
+    GenerateMessages& generateMessages = aGenerateMessages.Value();
+    JS::Rooted<JS::Value> generateMessagesJS(
+        aGlobal.Context(), JS::ObjectValue(*generateMessages.CallbackOrNull()));
+    loc->Init(resourceIds, generateMessagesJS, aRv);
+  } else {
+    loc->Init(resourceIds, aRv);
+  }
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  return loc.forget();
+}
+
+nsIGlobalObject* Localization::GetParentObject() const { return mGlobal; }
+
+JSObject* Localization::WrapObject(JSContext* aCx,
+                                   JS::Handle<JSObject*> aGivenProto) {
+  return Localization_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+Localization::~Localization() {
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->RemoveObserver(this, INTL_APP_LOCALES_CHANGED);
+  }
+
+  Preferences::RemoveObservers(this, kObservedPrefs);
+}
+
+/* Protected */
+
+void Localization::RegisterObservers() {
+  DebugOnly<nsresult> rv = Preferences::AddWeakObservers(this, kObservedPrefs);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Adding observers failed.");
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->AddObserver(this, INTL_APP_LOCALES_CHANGED, true);
+  }
+}
+
+NS_IMETHODIMP
+Localization::Observe(nsISupports* aSubject, const char* aTopic,
+                      const char16_t* aData) {
+  if (!strcmp(aTopic, INTL_APP_LOCALES_CHANGED)) {
+    OnChange();
+  } else {
+    MOZ_ASSERT(!strcmp("nsPref:changed", aTopic));
+    nsDependentString pref(aData);
+    if (pref.EqualsLiteral(L10N_PSEUDO_PREF) ||
+        pref.EqualsLiteral(INTL_UI_DIRECTION_PREF)) {
+      OnChange();
+    }
+  }
+
+  return NS_OK;
+}
+
+void Localization::OnChange() {
+  if (mLocalization) {
+    mLocalization->OnChange();
+  }
+}
+
+/**
+ * Localization API
+ */
+
+uint32_t Localization::AddResourceIds(const nsTArray<nsString>& aResourceIds,
+                                      bool aEager) {
+  uint32_t ret = 0;
+  mLocalization->AddResourceIds(aResourceIds, aEager, &ret);
+  return ret;
+}
+
+uint32_t Localization::RemoveResourceIds(
+    const nsTArray<nsString>& aResourceIds) {
+  // We need to guard against a scenario where the
+  // mLocalization has been unlinked, but the elements
+  // are only now removed from DOM.
+  if (!mLocalization) {
+    return 0;
+  }
+  uint32_t ret = 0;
+  mLocalization->RemoveResourceIds(aResourceIds, &ret);
+  return ret;
+}
+
+already_AddRefed<Promise> Localization::FormatValue(
+    JSContext* aCx, const nsAString& aId,
+    const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv) {
+  JS::Rooted<JS::Value> args(aCx);
+
+  if (aArgs.WasPassed()) {
+    args = JS::ObjectValue(*aArgs.Value());
+  } else {
+    args = JS::UndefinedValue();
+  }
+
+  RefPtr<Promise> promise;
+  nsresult rv = mLocalization->FormatValue(aId, args, getter_AddRefs(promise));
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+  return MaybeWrapPromise(promise);
+}
+
+already_AddRefed<Promise> Localization::FormatValues(
+    JSContext* aCx, const Sequence<L10nKey>& aKeys, ErrorResult& aRv) {
+  nsTArray<JS::Value> jsKeys;
+  SequenceRooter<JS::Value> rooter(aCx, &jsKeys);
+  for (auto& key : aKeys) {
+    JS::RootedValue jsKey(aCx);
+    if (!ToJSValue(aCx, key, &jsKey)) {
+      aRv.NoteJSContextException(aCx);
+      return nullptr;
+    }
+    jsKeys.AppendElement(jsKey);
+  }
+
+  RefPtr<Promise> promise;
+  aRv = mLocalization->FormatValues(jsKeys, getter_AddRefs(promise));
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  return MaybeWrapPromise(promise);
+}
+
+already_AddRefed<Promise> Localization::FormatMessages(
+    JSContext* aCx, const Sequence<L10nKey>& aKeys, ErrorResult& aRv) {
+  nsTArray<JS::Value> jsKeys;
+  SequenceRooter<JS::Value> rooter(aCx, &jsKeys);
+  for (auto& key : aKeys) {
+    JS::RootedValue jsKey(aCx);
+    if (!ToJSValue(aCx, key, &jsKey)) {
+      aRv.NoteJSContextException(aCx);
+      return nullptr;
+    }
+    jsKeys.AppendElement(jsKey);
+  }
+
+  RefPtr<Promise> promise;
+  aRv = mLocalization->FormatMessages(jsKeys, getter_AddRefs(promise));
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  return MaybeWrapPromise(promise);
+}
+
+/**
+ * PromiseResolver is a PromiseNativeHandler used
+ * by MaybeWrapPromise method.
+ */
+class PromiseResolver final : public PromiseNativeHandler {
+ public:
+  NS_DECL_ISUPPORTS
+
+  explicit PromiseResolver(Promise* aPromise);
+  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ protected:
+  virtual ~PromiseResolver();
+
+  RefPtr<Promise> mPromise;
+};
+
+NS_INTERFACE_MAP_BEGIN(PromiseResolver)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(PromiseResolver)
+NS_IMPL_RELEASE(PromiseResolver)
+
+PromiseResolver::PromiseResolver(Promise* aPromise) { mPromise = aPromise; }
+
+void PromiseResolver::ResolvedCallback(JSContext* aCx,
+                                       JS::Handle<JS::Value> aValue) {
+  mPromise->MaybeResolveWithClone(aCx, aValue);
+}
+
+void PromiseResolver::RejectedCallback(JSContext* aCx,
+                                       JS::Handle<JS::Value> aValue) {
+  mPromise->MaybeRejectWithClone(aCx, aValue);
+}
+
+PromiseResolver::~PromiseResolver() { mPromise = nullptr; }
+
+/**
+ * MaybeWrapPromise is a helper method used by Localization
+ * API methods to clone the value returned by a promise
+ * into a new context.
+ *
+ * This allows for a promise from a privileged context
+ * to be returned into an unprivileged document.
+ *
+ * This method is only used for promises that carry values.
+ */
+already_AddRefed<Promise> Localization::MaybeWrapPromise(
+    Promise* aInnerPromise) {
+  // For system principal we don't need to wrap the
+  // result promise at all.
+  nsIPrincipal* principal = mGlobal->PrincipalOrNull();
+  if (principal && nsContentUtils::IsSystemPrincipal(principal)) {
+    return RefPtr<Promise>(aInnerPromise).forget();
+  }
+
+  ErrorResult result;
+  RefPtr<Promise> docPromise = Promise::Create(mGlobal, result);
+  if (NS_WARN_IF(result.Failed())) {
+    return nullptr;
+  }
+
+  RefPtr<PromiseResolver> resolver = new PromiseResolver(docPromise);
+  aInnerPromise->AppendNativeHandler(resolver);
+  return docPromise.forget();
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/intl/l10n/Localization.h
@@ -0,0 +1,77 @@
+#ifndef mozilla_intl_l10n_Localization_h
+#define mozilla_intl_l10n_Localization_h
+
+#include "nsWeakReference.h"
+#include "nsIObserver.h"
+#include "mozILocalization.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/LocalizationBinding.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+
+class nsIGlobalObject;
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace intl {
+
+class Localization : public nsIObserver,
+                     public nsSupportsWeakReference,
+                     public nsWrapperCache {
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Localization,
+                                                         nsIObserver)
+  NS_DECL_NSIOBSERVER
+
+  explicit Localization(nsIGlobalObject* aGlobal);
+  void Init(nsTArray<nsString>& aResourceIds, ErrorResult& aRv);
+  void Init(nsTArray<nsString>& aResourceIds,
+            JS::Handle<JS::Value> aGenerateMessages, ErrorResult& aRv);
+
+  static already_AddRefed<Localization> Constructor(
+      const GlobalObject& aGlobal,
+      const Optional<Sequence<nsString>>& aResourceIds,
+      const Optional<OwningNonNull<GenerateMessages>>& aGenerateMessages,
+      ErrorResult& aRv);
+
+  nsIGlobalObject* GetParentObject() const;
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+
+  /**
+   * Localization API
+   *
+   * Methods documentation in Localization.webidl
+   */
+  uint32_t AddResourceIds(const nsTArray<nsString>& aResourceIds, bool aEager);
+
+  uint32_t RemoveResourceIds(const nsTArray<nsString>& aResourceIds);
+
+  already_AddRefed<Promise> FormatValue(
+      JSContext* aCx, const nsAString& aId,
+      const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv);
+
+  already_AddRefed<Promise> FormatValues(JSContext* aCx,
+                                         const Sequence<L10nKey>& aKeys,
+                                         ErrorResult& aRv);
+
+  already_AddRefed<Promise> FormatMessages(JSContext* aCx,
+                                           const Sequence<L10nKey>& aKeys,
+                                           ErrorResult& aRv);
+
+ protected:
+  virtual ~Localization();
+  void RegisterObservers();
+  void OnChange();
+  already_AddRefed<Promise> MaybeWrapPromise(Promise* aInnerPromise);
+
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+  nsCOMPtr<mozILocalization> mLocalization;
+};
+
+}  // namespace intl
+}  // namespace mozilla
+
+#endif
\ No newline at end of file
--- a/intl/l10n/moz.build
+++ b/intl/l10n/moz.build
@@ -1,14 +1,22 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+EXPORTS.mozilla.intl += [
+    'Localization.h',
+]
+
+UNIFIED_SOURCES += [
+    'Localization.cpp',
+]
+
 EXTRA_JS_MODULES += [
     'Fluent.jsm',
     'L10nRegistry.jsm',
     'Localization.jsm',
 ]
 
 TESTING_JS_MODULES += [
     'FluentSyntax.jsm',