Backed out 4 changesets (bug 1453480) for failing browser-chrome at browser/components/preferences/in-content/tests/browser_search_subdialogs_within_preferences_site_data.js on a CLOSED TREE
authorCoroiu Cristina <ccoroiu@mozilla.com>
Fri, 13 Apr 2018 19:49:14 +0300
changeset 469282 bab10eeb6799a277b7237851293041cbec55a370
parent 469281 f60708e61cf434567254e890126a0602fbfd7f27
child 469283 5bb64d5081d6d23d7f58f470922ba3069cb91a6a
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1453480
milestone61.0a1
backs out623b37fe0fe82d6eff6634cda655252f95b4f8b1
a3c36fa7ac0c831c4412652ba1a6a8d57497c63d
46a634d6853c680bbee9d2be12bab971058ba281
658fedb903d0f037289ffb80026ddea376400caa
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
Backed out 4 changesets (bug 1453480) for failing browser-chrome at browser/components/preferences/in-content/tests/browser_search_subdialogs_within_preferences_site_data.js on a CLOSED TREE Backed out changeset 623b37fe0fe8 (bug 1453480) Backed out changeset a3c36fa7ac0c (bug 1453480) Backed out changeset 46a634d6853c (bug 1453480) Backed out changeset 658fedb903d0 (bug 1453480)
browser/components/preferences/in-content/findInPage.js
browser/components/preferences/in-content/main.js
browser/components/preferences/in-content/main.xul
browser/components/preferences/in-content/privacy.xul
browser/components/preferences/in-content/searchResults.xul
browser/components/preferences/in-content/tests/browser_fluent.js
browser/locales/en-US/browser/preferences/preferences.ftl
dom/base/nsINode.cpp
dom/base/test/chrome/test_node_localize.xul
dom/webidl/L10nUtils.webidl
dom/webidl/Node.webidl
intl/l10n/DOMLocalization.jsm
intl/l10n/Localization.jsm
intl/l10n/MessageContext.jsm
intl/l10n/fluent.js.patch
intl/l10n/test/dom/test_domloc_overlay.html
intl/l10n/test/dom/test_domloc_overlay_missing_children.html
intl/l10n/test/dom/test_domloc_overlay_repeated.html
intl/l10n/test/dom/test_domloc_repeated_l10nid.html
python/l10n/fluent_migrations/bug_1453480_preferences_dom2_resources.py
--- a/browser/components/preferences/in-content/findInPage.js
+++ b/browser/components/preferences/in-content/findInPage.js
@@ -478,17 +478,17 @@ var gSearchResultsPane = {
       // building a string of concatenated translated strings out of it.
       let keywords = messages.map((msg, i) => {
         let [refId, refAttr] = refs[i];
         if (!msg) {
           console.error(`Missing search l10n id "${refId}"`);
           return null;
         }
         if (refAttr) {
-          let attr = msg.attributes && msg.attributes.find(a => a.name === refAttr);
+          let attr = msg.attrs.find(a => a.name === refAttr);
           if (!attr) {
             console.error(`Missing search l10n id "${refId}.${refAttr}"`);
             return null;
           }
           if (attr.value === "") {
             console.error(`Empty value added to search-l10n-ids "${refId}.${refAttr}"`);
           }
           return attr.value;
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -453,17 +453,17 @@ var gMainPane = {
     let archResource = Services.appinfo.is64Bit
       ? "aboutDialog.architecture.sixtyFourBit"
       : "aboutDialog.architecture.thirtyTwoBit";
     let arch = bundle.GetStringFromName(archResource);
     version += ` (${arch})`;
 
     document.l10n.setAttributes(
       document.getElementById("updateAppInfo"),
-      "update-application-version",
+      "update-application-info",
       { version }
     );
 
     // Show a release notes link if we have a URL.
     let relNotesLink = document.getElementById("releasenotes");
     let relNotesPrefType = Services.prefs.getPrefType("app.releaseNotesURL");
     if (relNotesPrefType != Services.prefs.PREF_INVALID) {
       let relNotesURL = Services.urlFormatter.formatURLPref("app.releaseNotesURL");
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -437,17 +437,17 @@
 <!-- Update -->
 <groupbox id="updateApp" data-category="paneGeneral" hidden="true">
   <caption class="search-header" hidden="true"><label data-l10n-id="update-application-title"/></caption>
 
   <label data-l10n-id="update-application-description"/>
   <hbox align="center">
     <vbox flex="1">
       <description id="updateAppInfo">
-        <html:a id="releasenotes" data-l10n-name="learn-more" class="learnMore text-link" hidden="true"/>
+        <html:a id="releasenotes" class="learnMore text-link" hidden="true"/>
       </description>
       <description id="distribution" class="text-blurb" hidden="true"/>
       <description id="distributionId" class="text-blurb" hidden="true"/>
     </vbox>
 #ifdef MOZ_UPDATER
     <spacer flex="1"/>
     <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
     <vbox>
@@ -610,18 +610,18 @@
           <menuitem label="4" value="4"/>
           <menuitem label="5" value="5"/>
           <menuitem label="6" value="6"/>
           <menuitem label="7" value="7"/>
         </menupopup>
       </menulist>
     </hbox>
     <description id="contentProcessCountEnabledDescription" class="tip-caption" data-l10n-id="performance-limit-content-process-enabled-desc"/>
-    <description id="contentProcessCountDisabledDescription" class="tip-caption" data-l10n-id="performance-limit-content-process-blocked-desc">
-      <html:a class="text-link" data-l10n-name="learn-more" href="https://wiki.mozilla.org/Electrolysis"/>
+    <description id="contentProcessCountDisabledDescription" class="tip-caption" data-l10n-id="performance-limit-content-process-disabled-desc">
+      <html:a class="text-link" href="https://wiki.mozilla.org/Electrolysis"/>
     </description>
   </vbox>
 </groupbox>
 
 <hbox id="browsingCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -276,18 +276,18 @@
 </groupbox>
 
 <!-- Tracking -->
 <groupbox id="trackingGroup" data-category="panePrivacy" hidden="true">
   <caption><label data-l10n-id="tracking-header"/></caption>
   <vbox>
     <hbox align="start">
       <vbox flex="1">
-        <description data-l10n-id="tracking-desc">
-          <a id="trackingProtectionLearnMore" data-l10n-name="learn-more" target="_blank" class="learnMore text-link"/>
+        <description data-l10n-id="tracking-description">
+          <a id="trackingProtectionLearnMore" target="_blank" class="learnMore text-link"/>
         </description>
       </vbox>
       <spacer flex="1"/>
     </hbox>
     <hbox>
       <vbox id="trackingProtectionBox" flex="1" hidden="true">
         <vbox>
           <hbox id="trackingProtectionExtensionContentLabel" align="center" hidden="true">
--- a/browser/components/preferences/in-content/searchResults.xul
+++ b/browser/components/preferences/in-content/searchResults.xul
@@ -10,19 +10,19 @@
   <label class="header-name" flex="1" data-l10n-id="search-results-header" />
 </hbox>
 
 <groupbox id="no-results-message"
           data-hidden-from-search="true"
           data-category="paneSearchResults"
           hidden="true">
   <vbox class="no-results-container">
-    <label id="sorry-message" data-l10n-id="search-results-empty-message">
-      <html:span data-l10n-name="query" id="sorry-message-query"/>
+    <label id="sorry-message" data-l10n-id="search-results-sorry-message">
+      <html:span id="sorry-message-query"/>
     </label>
-    <label id="need-help" data-l10n-id="search-results-help-link">
-      <a class="text-link" data-l10n-name="url" target="_blank"></a>
+    <label id="need-help" data-l10n-id="search-results-need-help">
+      <a class="text-link" target="_blank"></a>
     </label>
   </vbox>
   <vbox class="no-results-container" align="center">
     <image></image>
   </vbox>
 </groupbox>
--- a/browser/components/preferences/in-content/tests/browser_fluent.js
+++ b/browser/components/preferences/in-content/tests/browser_fluent.js
@@ -34,15 +34,15 @@ add_task(async function() {
     ["performance-default-content-process-count", { num: defaultProcessCount }]
   ]);
 
   let elem = doc.querySelector(
     `#contentProcessCount > menupopup > menuitem[value="${defaultProcessCount}"]`);
 
   Assert.deepEqual(msg, {
     value: null,
-    attributes: [
+    attrs: [
       {name: "label", value: elem.getAttribute("label")}
     ]
   });
 
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
--- a/browser/locales/en-US/browser/preferences/preferences.ftl
+++ b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -75,24 +75,24 @@ should-restart-title = Restart { -brand-
 should-restart-ok = Restart { -brand-short-name } now
 cancel-no-restart-button = Cancel
 restart-later = Restart Later
 
 ## Preferences UI Search Results
 
 search-results-header = Search Results
 
-# `<span data-l10n-name="query"></span>` will be replaced by the search term.
-search-results-empty-message =
+# `<span></span>` will be replaced by the search term.
+search-results-sorry-message =
     { PLATFORM() ->
-        [windows] Sorry! There are no results in Options for “<span data-l10n-name="query"></span>”.
-       *[other] Sorry! There are no results in Preferences for “<span data-l10n-name="query"></span>”.
+        [windows] Sorry! There are no results in Options for “<span></span>”.
+       *[other] Sorry! There are no results in Preferences for “<span></span>”.
     }
 
-search-results-help-link = Need help? Visit <a data-l10n-name="url">{ -brand-short-name } Support</a>
+search-results-need-help = Need help? Visit <a>{ -brand-short-name } Support</a>
 
 ## General Section
 
 startup-header = Startup
 
 # { -brand-short-name } will be 'Firefox Developer Edition',
 # since this setting is only exposed in Firefox Developer Edition
 separate-profile-mode =
@@ -262,17 +262,17 @@ play-drm-content =
     .accesskey = P
 
 play-drm-content-learn-more = Learn more
 
 update-application-title = { -brand-short-name } Updates
 
 update-application-description = Keep { -brand-short-name } up to date for the best performance, stability, and security.
 
-update-application-version = Version { $version } <a data-l10n-name="learn-more">What’s new</a>
+update-application-info = Version { $version } <a>What's new</a>
 
 update-history =
     .label = Show Update History…
     .accesskey = p
 
 update-application-allow-description = Allow { -brand-short-name } to
 
 update-application-auto =
@@ -310,17 +310,17 @@ performance-settings-learn-more = Learn 
 performance-allow-hw-accel =
     .label = Use hardware acceleration when available
     .accesskey = r
 
 performance-limit-content-process-option = Content process limit
     .accesskey = l
 
 performance-limit-content-process-enabled-desc = Additional content processes can improve performance when using multiple tabs, but will also use more memory.
-performance-limit-content-process-blocked-desc = Modifying the number of content processes is only possible with multiprocess { -brand-short-name }. <a data-l10n-name="learn-more">Learn how to check if multiprocess is enabled</a>
+performance-limit-content-process-disabled-desc = Modifying the number of content processes is only possible with multiprocess { -brand-short-name }. <a>Learn how to check if multiprocess is enabled</a>
 
 # Variables:
 #   $num - default value of the `dom.ipc.processCount` pref.
 performance-default-content-process-count =
     .label = { $num } (default)
 
 ## General Section - Browsing
 
@@ -693,17 +693,17 @@ addressbar-locbar-openpage-option =
     .accesskey = O
 
 addressbar-suggestions-settings = Change preferences for search engine suggestions
 
 ## Privacy Section - Tracking
 
 tracking-header = Tracking Protection
 
-tracking-desc = Tracking Protection blocks online trackers that collect your browsing data across multiple websites. <a data-l10n-name="learn-more">Learn more about Tracking Protection and your privacy</a>
+tracking-description = Tracking Protection blocks online trackers that collect your browsing data across multiple websites. <a>Learn more about Tracking Protection and your privacy</a>
 
 tracking-mode-label = Use Tracking Protection to block known trackers
 
 tracking-mode-always =
     .label = Always
     .accesskey = y
 tracking-mode-private =
     .label = Only in private windows
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -2733,17 +2733,17 @@ public:
         elem->SetTextContent(content, rv);
         if (NS_WARN_IF(rv.Failed())) {
           mReturnValuePromise->MaybeRejectWithUndefined();
           return;
         }
       }
 
       Nullable<Sequence<AttributeNameValue>>& attributes =
-        l10nData[i].mAttributes;
+        l10nData[i].mAttrs;
       if (!attributes.IsNull()) {
         for (size_t j = 0; j < attributes.Value().Length(); ++j) {
           // Use SetAttribute here to validate the attribute name!
           elem->SetAttribute(attributes.Value()[j].mName,
                              attributes.Value()[j].mValue,
                              rv);
           if (rv.Failed()) {
             mReturnValuePromise->MaybeRejectWithUndefined();
--- a/dom/base/test/chrome/test_node_localize.xul
+++ b/dom/base/test/chrome/test_node_localize.xul
@@ -20,35 +20,35 @@ https://bugzilla.mozilla.org/show_bug.cg
   <!-- test code goes here -->
   <script type="application/javascript"><![CDATA[
 
   /** Test for Bug 1363862 **/
 
   const translations = {
     "key1": {
       value: "Value 1",
-      attributes: null,
+      attrs: null,
     },
     "key2": {
       value: null,
-      attributes: [
+      attrs: [
         {name: "label", value: "Value 2"},
         {name: "accesskey", value: "K"},
       ]
     },
     "key3": {
       value: "Value 3",
-      attributes: [
+      attrs: [
         {name: "accesskey", value: "V"},
       ]
     },
     "key4": undefined,
     "key5": {
       value: null,
-      attributes: [
+      attrs: [
         {name: "value", value: "Submit Value"},
       ]
     }
   }
 
   /**
    * This function serves as an approximation of what Localization does.
    */
@@ -113,18 +113,18 @@ https://bugzilla.mozilla.org/show_bug.cg
         continue;
       }
 
       if (translation.value !== null) {
       ok(elem.textContent === translation.value,
         "element's textContent should be populated with the translation value");
       }
 
-      if (translation.attributes !== null) {
-        for (const {name, value} of translation.attributes) {
+      if (translation.attrs !== null) {
+        for (const {name, value} of translation.attrs) {
           ok(elem.getAttribute(name) === value,
             "attribute value should be populated from the translation");
         }
       }
     }
 
     SimpleTest.finish();
   }
--- a/dom/webidl/L10nUtils.webidl
+++ b/dom/webidl/L10nUtils.webidl
@@ -26,13 +26,13 @@ dictionary L10nElement {
 
 dictionary AttributeNameValue {
   required DOMString name;
   required DOMString value;
 };
 
 dictionary L10nValue {
   DOMString? value = null;
-  sequence<AttributeNameValue>? attributes = null;
+  sequence<AttributeNameValue>? attrs = null;
 };
 
 callback L10nCallback =
   Promise<sequence<L10nValue>> (sequence<L10nElement> l10nElements);
--- a/dom/webidl/Node.webidl
+++ b/dom/webidl/Node.webidl
@@ -187,19 +187,19 @@ interface Node : EventTarget {
    *         l10nArgs: null,
    *         l10nAttrs: "title",
    *         name: "window"
    *         namespaceURI: "..."
    *         type: null
    *       },
    *     ]
    * [2] trans == [
-   *       {value: "Key 1", attributes: {accesskey: "K"} },
+   *       {value: "Key 1", attrs: {accesskey: "K"} },
    *       undefined,
-   *       {value: null, attributes: {title: "Unread emails: 5"} },
+   *       {value: null, attrs: {title: "Unread emails: 5"} },
    *     ]
    * [3] untranslatedElements == [
    *       ,
    *       <label>
    *       ,
    *     ]
    *
    * For exact dictionary structures, see `L10nUtils.webidl`.
--- a/intl/l10n/DOMLocalization.jsm
+++ b/intl/l10n/DOMLocalization.jsm
@@ -11,38 +11,35 @@
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 
-/* fluent-dom@0.2.0 */
+/* fluent@0.6.3 */
 
 const { Localization } =
   ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
 
 // Match the opening angle bracket (<) in HTML tags, and HTML entities like
 // &amp;, &#0038;, &#x0026;.
 const reOverlay = /<|&#?\w+;/;
 
 /**
- * Elements allowed in translations even if they are not present in the source
- * HTML. They are text-level elements as defined by the HTML5 spec:
- * https://www.w3.org/TR/html5/text-level-semantics.html with the exception of:
+ * The list of elements that are allowed to be inserted into a localization.
  *
- *   - a - because we don't allow href on it anyways,
- *   - ruby, rt, rp - because we don't allow nested elements to be inserted.
+ * Source: https://www.w3.org/TR/html5/text-level-semantics.html
  */
-const TEXT_LEVEL_ELEMENTS = {
+const LOCALIZABLE_ELEMENTS = {
   "http://www.w3.org/1999/xhtml": [
-    "em", "strong", "small", "s", "cite", "q", "dfn", "abbr", "data",
+    "a", "em", "strong", "small", "s", "cite", "q", "dfn", "abbr", "data",
     "time", "code", "var", "samp", "kbd", "sub", "sup", "i", "b", "u",
-    "mark", "bdi", "bdo", "span", "br", "wbr"
+    "mark", "ruby", "rt", "rp", "bdi", "bdo", "span", "br", "wbr"
   ],
 };
 
 const LOCALIZABLE_ATTRIBUTES = {
   "http://www.w3.org/1999/xhtml": {
     global: ["title", "aria-label", "aria-valuetext", "aria-moz-hint"],
     a: ["download"],
     area: ["download", "alt"],
@@ -64,196 +61,177 @@ const LOCALIZABLE_ATTRIBUTES = {
     key: ["key", "keycode"],
     textbox: ["placeholder"],
     toolbarbutton: ["tooltiptext"],
   }
 };
 
 
 /**
- * Translate an element.
+ * Overlay translation onto a DOM 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.
- *
- * @param   {Element} element
- * @param   {Object} translation
+ * @param   {Element} targetElement
+ * @param   {string|Object} translation
  * @private
  */
-function translateElement(element, translation) {
+function overlayElement(targetElement, translation) {
   const value = translation.value;
 
   if (typeof value === "string") {
     if (!reOverlay.test(value)) {
       // If the translation doesn't contain any markup skip the overlay logic.
-      element.textContent = value;
+      targetElement.textContent = value;
     } else {
       // Else parse the translation's HTML using an inert template element,
-      // sanitize it and replace the element's content.
-      const templateElement = element.ownerDocument.createElementNS(
-        "http://www.w3.org/1999/xhtml", "template"
-      );
+      // sanitize it and replace the targetElement's content.
+      const templateElement = targetElement.ownerDocument.createElementNS(
+        "http://www.w3.org/1999/xhtml", "template");
       // eslint-disable-next-line no-unsanitized/property
       templateElement.innerHTML = value;
-      overlayChildNodes(templateElement.content, element);
+      targetElement.appendChild(
+        // The targetElement will be cleared at the end of sanitization.
+        sanitizeUsing(templateElement.content, targetElement)
+      );
     }
   }
 
-  // Even if the translation doesn't define any localizable attributes, run
-  // overlayAttributes to remove any localizable attributes set by previous
-  // translations.
-  overlayAttributes(translation, element);
-}
-
-/**
- * Replace child nodes of an element with child nodes of another element.
- *
- * The contents of the target element will be cleared and fully replaced with
- * sanitized contents of the source element.
- *
- * @param {DocumentFragment} fromElement - The source of children to overlay.
- * @param {Element} toElement - The target of the overlay.
- * @private
- */
-function overlayChildNodes(fromElement, toElement) {
-  const content = toElement.ownerDocument.createDocumentFragment();
-
-  for (const childNode of fromElement.childNodes) {
-    content.appendChild(sanitizeUsing(toElement, childNode));
-  }
-
-  toElement.textContent = "";
-  toElement.appendChild(content);
-}
-
-/**
- * Transplant localizable attributes of an element to another element.
- *
- * Any localizable attributes already set on the target element will be
- * cleared.
- *
- * @param   {Element|Object} fromElement - The source of child nodes to overlay.
- * @param   {Element} toElement - The target of the overlay.
- * @private
- */
-function overlayAttributes(fromElement, toElement) {
-  const explicitlyAllowed = toElement.hasAttribute("data-l10n-attrs")
-    ? toElement.getAttribute("data-l10n-attrs")
+  const explicitlyAllowed = targetElement.hasAttribute("data-l10n-attrs")
+    ? targetElement.getAttribute("data-l10n-attrs")
       .split(",").map(i => i.trim())
     : null;
 
-  // Remove existing localizable attributes.
-  for (const attr of Array.from(toElement.attributes)) {
-    if (isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed)) {
-      toElement.removeAttribute(attr.name);
+  // Remove localizable attributes which may have been set by a previous
+  // translation.
+  for (const attr of Array.from(targetElement.attributes)) {
+    if (isAttrNameLocalizable(attr.name, targetElement, explicitlyAllowed)) {
+      targetElement.removeAttribute(attr.name);
     }
   }
 
-  // fromElement might be a {value, attributes} object as returned by
-  // Localization.messageFromContext. In which case attributes may be null to
-  // save GC cycles.
-  if (!fromElement.attributes) {
-    return;
-  }
-
-  // Set localizable attributes.
-  for (const attr of Array.from(fromElement.attributes)) {
-    if (isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed)) {
-      toElement.setAttribute(attr.name, attr.value);
+  if (translation.attrs) {
+    for (const {name, value} of translation.attrs) {
+      if (isAttrNameLocalizable(name, targetElement, explicitlyAllowed)) {
+        targetElement.setAttribute(name, value);
+      }
     }
   }
 }
 
 /**
- * Sanitize a child node created by the translation.
+ * Sanitize `translationFragment` using `sourceElement` to add functional
+ * HTML attributes to children.  `sourceElement` will have all its child nodes
+ * removed.
  *
- * If childNode has the data-l10n-name attribute, try to find a corresponding
- * child in sourceElement and use it as the base for the sanitization. This
- * will preserve functional attribtues defined on the child element in the
- * source HTML.
+ * The sanitization is conducted according to the following rules:
+ *
+ *   - Allow text nodes.
+ *   - Replace forbidden children with their textContent.
+ *   - Remove forbidden attributes from allowed children.
  *
- * This function must return new nodes or clones in all code paths. The
- * returned nodes are immediately appended to the intermediate DocumentFragment
- * which also _removes_ them from the constructed <template> containing the
- * translation, which in turn breaks the for…of iteration over its child nodes.
+ * Additionally when a child of the same type is present in `sourceElement` its
+ * attributes will be merged into the translated child.  Whitelisted attributes
+ * of the translated child will then overwrite the ones present in the source.
+ *
+ * The overlay logic is subject to the following limitations:
  *
- * @param   {Element} sourceElement - The source for data-l10n-name lookups.
- * @param   {Element} childNode - The child node to be sanitized.
- * @returns {Element}
+ *   - Children are always cloned.  Event handlers attached to them are lost.
+ *   - Nested HTML in source and in translations is not supported.
+ *   - Multiple children of the same type will be matched in order.
+ *
+ * @param {DocumentFragment} translationFragment
+ * @param {Element} sourceElement
+ * @returns {DocumentFragment}
  * @private
  */
-function sanitizeUsing(sourceElement, childNode) {
-  if (childNode.nodeType === childNode.TEXT_NODE) {
-    return childNode.cloneNode(false);
+function sanitizeUsing(translationFragment, sourceElement) {
+  const ownerDocument = translationFragment.ownerDocument;
+  // Take one node from translationFragment at a time and check it against
+  // the allowed list or try to match it with a corresponding element
+  // in the source.
+  for (const childNode of translationFragment.childNodes) {
+
+    if (childNode.nodeType === childNode.TEXT_NODE) {
+      continue;
+    }
+
+    // If the child is forbidden just take its textContent.
+    if (!isElementLocalizable(childNode)) {
+      const text = ownerDocument.createTextNode(childNode.textContent);
+      translationFragment.replaceChild(text, childNode);
+      continue;
+    }
+
+    // Start the sanitization with an empty element.
+    const mergedChild = ownerDocument.createElement(childNode.localName);
+
+    // Explicitly discard nested HTML by serializing childNode to a TextNode.
+    mergedChild.textContent = childNode.textContent;
+
+    // If a child of the same type exists in sourceElement, take its functional
+    // (i.e. non-localizable) attributes. This also removes the child from
+    // sourceElement.
+    const sourceChild = shiftNamedElement(sourceElement, childNode.localName);
+
+    // Find the union of all safe attributes: localizable attributes from
+    // childNode and functional attributes from sourceChild.
+    const safeAttributes = sanitizeAttrsUsing(childNode, sourceChild);
+
+    for (const attr of safeAttributes) {
+      mergedChild.setAttribute(attr.name, attr.value);
+    }
+
+    translationFragment.replaceChild(mergedChild, childNode);
   }
 
-  if (childNode.hasAttribute("data-l10n-name")) {
-    const childName = childNode.getAttribute("data-l10n-name");
-    const sourceChild = sourceElement.querySelector(
-      `[data-l10n-name="${childName}"]`
-    );
+  // SourceElement might have been already modified by shiftNamedElement.
+  // Let's clear it to make sure other code doesn't rely on random leftovers.
+  sourceElement.textContent = "";
+
+  return translationFragment;
+}
 
-    if (!sourceChild) {
-      console.warn(
-        `An element named "${childName}" wasn't found in the source.`
-      );
-    } else if (sourceChild.localName !== childNode.localName) {
-      console.warn(
-        `An element named "${childName}" was found in the translation ` +
-        `but its type ${childNode.localName} didn't match the element ` +
-        `found in the source (${sourceChild.localName}).`
-      );
-    } else {
-      // Remove it from sourceElement so that the translation cannot use
-      // the same reference name again.
-      sourceElement.removeChild(sourceChild);
-      // We can't currently guarantee that a translation won't remove
-      // sourceChild from the element completely, which could break the app if
-      // it relies on an event handler attached to the sourceChild. Let's make
-      // this limitation explicit for now by breaking the identitiy of the
-      // sourceChild by cloning it. This will destroy all event handlers
-      // attached to sourceChild via addEventListener and via on<name>
-      // properties.
-      const clone = sourceChild.cloneNode(false);
-      return shallowPopulateUsing(childNode, clone);
-    }
+/**
+ * Sanitize and merge attributes.
+ *
+ * Only localizable attributes from the translated child element and only
+ * functional attributes from the source child element are considered safe.
+ *
+ * @param {Element} translatedElement
+ * @param {Element} sourceElement
+ * @returns {Array<Attr>}
+ * @private
+ */
+function sanitizeAttrsUsing(translatedElement, sourceElement) {
+  const localizedAttrs = Array.from(translatedElement.attributes).filter(
+    attr => isAttrNameLocalizable(attr.name, translatedElement)
+  );
+
+  if (!sourceElement) {
+    return localizedAttrs;
   }
 
-  if (isElementAllowed(childNode)) {
-    // Start with an empty element of the same type to remove nested children
-    // and non-localizable attributes defined by the translation.
-    const clone = childNode.ownerDocument.createElement(childNode.localName);
-    return shallowPopulateUsing(childNode, clone);
-  }
-
-  console.warn(
-    `An element of forbidden type "${childNode.localName}" was found in ` +
-    "the translation. Only elements with data-l10n-name can be overlaid " +
-    "onto source elements of the same data-l10n-name."
+  const functionalAttrs = Array.from(sourceElement.attributes).filter(
+    attr => !isAttrNameLocalizable(attr.name, sourceElement)
   );
 
-  // If all else fails, convert the element to its text content.
-  return childNode.ownerDocument.createTextNode(childNode.textContent);
+  return localizedAttrs.concat(functionalAttrs);
 }
 
 /**
  * Check if element is allowed in the translation.
  *
  * This method is used by the sanitizer when the translation markup contains
  * an element which is not present in the source code.
  *
  * @param   {Element} element
  * @returns {boolean}
  * @private
  */
-function isElementAllowed(element) {
-  const allowed = TEXT_LEVEL_ELEMENTS[element.namespaceURI];
+function isElementLocalizable(element) {
+  const allowed = LOCALIZABLE_ELEMENTS[element.namespaceURI];
   return allowed && allowed.includes(element.localName);
 }
 
 /**
  * Check if attribute is allowed for the given element.
  *
  * This method is used by the sanitizer when the translation markup contains
  * DOM attributes, or when the translation has traits which map to DOM
@@ -304,27 +282,31 @@ function isAttrNameLocalizable(name, ele
       return true;
     }
   }
 
   return false;
 }
 
 /**
- * Helper to set textContent and localizable attributes on an element.
+ * Remove and return the first child of the given type.
  *
- * @param   {Element} fromElement
- * @param   {Element} toElement
- * @returns {Element}
+ * @param {DOMFragment} element
+ * @param {string}      localName
+ * @returns {Element | null}
  * @private
  */
-function shallowPopulateUsing(fromElement, toElement) {
-  toElement.textContent = fromElement.textContent;
-  overlayAttributes(fromElement, toElement);
-  return toElement;
+function shiftNamedElement(element, localName) {
+  for (const child of element.children) {
+    if (child.localName === localName) {
+      element.removeChild(child);
+      return child;
+    }
+  }
+  return null;
 }
 
 /**
  * Sanitizes a translation before passing them to Node.localize API.
  *
  * It returns `false` if the translation contains DOM Overlays and should
  * not go into Node.localize.
  *
@@ -345,28 +327,29 @@ function shallowPopulateUsing(fromElemen
  * @returns boolean
  * @private
  */
 function sanitizeTranslationForNodeLocalize(l10nItem, translation) {
   if (reOverlay.test(translation.value)) {
     return false;
   }
 
-  if (translation.attributes) {
+  if (translation.attrs) {
     const explicitlyAllowed = l10nItem.l10nAttrs === null ? null :
       l10nItem.l10nAttrs.split(",").map(i => i.trim());
-    for (const [j, {name}] of translation.attributes.entries()) {
+    for (const [j, {name}] of translation.attrs.entries()) {
       if (!isAttrNameLocalizable(name, l10nItem, explicitlyAllowed)) {
-        translation.attributes.splice(j, 1);
+        translation.attrs.splice(j, 1);
       }
     }
   }
   return true;
 }
 
+
 const L10NID_ATTR_NAME = "data-l10n-id";
 const L10NARGS_ATTR_NAME = "data-l10n-args";
 
 const L10N_ELEMENT_QUERY = `[${L10NID_ATTR_NAME}]`;
 
 /**
  * The `DOMLocalization` class is responsible for fetching resources and
  * formatting translations.
@@ -555,19 +538,17 @@ class DOMLocalization extends Localizati
    * Translate mutations detected by the `MutationObserver`.
    *
    * @private
    */
   translateMutations(mutations) {
     for (const mutation of mutations) {
       switch (mutation.type) {
         case "attributes":
-          if (mutation.target.hasAttribute("data-l10n-id")) {
-            this.pendingElements.add(mutation.target);
-          }
+          this.pendingElements.add(mutation.target);
           break;
         case "childList":
           for (const addedNode of mutation.addedNodes) {
             if (addedNode.nodeType === addedNode.ELEMENT_NODE) {
               if (addedNode.childElementCount) {
                 for (const element of this.getTranslatables(addedNode)) {
                   this.pendingElements.add(element);
                 }
@@ -648,17 +629,17 @@ class DOMLocalization extends Localizati
         return translations;
       };
 
       return frag.localize(getTranslationsForItems.bind(this))
         .then(untranslatedElements => {
           for (let i = 0; i < overlayTranslations.length; i++) {
             if (overlayTranslations[i] !== undefined &&
                 untranslatedElements[i] !== undefined) {
-              translateElement(untranslatedElements[i], overlayTranslations[i]);
+              overlayElement(untranslatedElements[i], overlayTranslations[i]);
             }
           }
           this.resumeObserving();
         })
         .catch(() => this.resumeObserving());
     }
     return this.translateElements(this.getTranslatables(frag));
   }
@@ -693,17 +674,17 @@ class DOMLocalization extends Localizati
    * @param {Array<Object>}  translations
    * @private
    */
   applyTranslations(elements, translations) {
     this.pauseObserving();
 
     for (let i = 0; i < elements.length; i++) {
       if (translations[i] !== undefined) {
-        translateElement(elements[i], translations[i]);
+        overlayElement(elements[i], translations[i]);
       }
     }
 
     this.resumeObserving();
   }
 
   /**
    * Collects all translatable child elements of the element.
--- a/intl/l10n/Localization.jsm
+++ b/intl/l10n/Localization.jsm
@@ -11,17 +11,17 @@
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 
-/* fluent-dom@0.2.0 */
+/* fluent@0.6.3 */
 
 /* eslint no-console: ["error", { allow: ["warn", "error"] }] */
 /* global console */
 
 const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", {});
 const { L10nRegistry } = ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
 const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
@@ -322,25 +322,25 @@ function valueFromContext(ctx, errors, i
  * @returns {Object}
  * @private
  */
 function messageFromContext(ctx, errors, id, args) {
   const msg = ctx.getMessage(id);
 
   const formatted = {
     value: ctx.format(msg, args, errors),
-    attributes: null,
+    attrs: null,
   };
 
   if (msg.attrs) {
-    formatted.attributes = [];
-    for (const [name, attr] of Object.entries(msg.attrs)) {
-      const value = ctx.format(attr, args, errors);
+    formatted.attrs = [];
+    for (const name in msg.attrs) {
+      const value = ctx.format(msg.attrs[name], args, errors);
       if (value !== null) {
-        formatted.attributes.push({name, value});
+        formatted.attrs.push({ name, value });
       }
     }
   }
 
   return formatted;
 }
 
 /**
--- a/intl/l10n/MessageContext.jsm
+++ b/intl/l10n/MessageContext.jsm
@@ -1603,45 +1603,41 @@ function Pattern(env, ptn) {
     errors.push(new RangeError("Cyclic reference"));
     return new FluentNone();
   }
 
   // Tag the pattern as dirty for the purpose of the current resolution.
   dirty.add(ptn);
   const result = [];
 
-  // Wrap interpolations with Directional Isolate Formatting characters
-  // only when the pattern has more than one element.
-  const useIsolating = ctx._useIsolating && ptn.length > 1;
-
   for (const elem of ptn) {
     if (typeof elem === "string") {
       result.push(elem);
       continue;
     }
 
     const part = Type(env, elem).toString(ctx);
 
-    if (useIsolating) {
+    if (ctx._useIsolating) {
       result.push(FSI);
     }
 
     if (part.length > MAX_PLACEABLE_LENGTH) {
       errors.push(
         new RangeError(
           "Too many characters in placeable " +
           `(${part.length}, max allowed is ${MAX_PLACEABLE_LENGTH})`
         )
       );
       result.push(part.slice(MAX_PLACEABLE_LENGTH));
     } else {
       result.push(part);
     }
 
-    if (useIsolating) {
+    if (ctx._useIsolating) {
       result.push(PDI);
     }
   }
 
   dirty.delete(ptn);
   return result.join("");
 }
 
@@ -1774,26 +1770,18 @@ class MessageContext {
    * @returns {Array<Error>}
    */
   addMessages(source) {
     const [entries, errors] = parse(source);
     for (const id in entries) {
       if (id.startsWith("-")) {
         // Identifiers starting with a dash (-) define terms. Terms are private
         // and cannot be retrieved from MessageContext.
-        if (this._terms.has(id)) {
-          errors.push(`Attempt to override an existing term: "${id}"`);
-          continue;
-        }
         this._terms.set(id, entries[id]);
       } else {
-        if (this._messages.has(id)) {
-          errors.push(`Attempt to override an existing message: "${id}"`);
-          continue;
-        }
         this._messages.set(id, entries[id]);
       }
     }
 
     return errors;
   }
 
   /**
--- a/intl/l10n/fluent.js.patch
+++ b/intl/l10n/fluent.js.patch
@@ -1,145 +1,26 @@
 diff -uNr ./dist/DOMLocalization.jsm /home/zbraniecki/projects/mozilla-unified/intl/l10n/DOMLocalization.jsm
---- ./dist/DOMLocalization.jsm	2018-04-13 08:25:21.143138950 -0700
-+++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/DOMLocalization.jsm	2018-04-13 08:27:11.658083766 -0700
-@@ -18,10 +18,8 @@
+--- ./dist/DOMLocalization.jsm	2018-01-30 13:46:58.589811108 -0800
++++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/DOMLocalization.jsm	2018-01-30 13:46:13.613146435 -0800
+@@ -18,7 +18,8 @@
  
- /* fluent-dom@0.2.0 */
+ /* fluent@0.6.0 */
  
 -import Localization from '../../fluent-dom/src/localization.js';
--
--/* eslint no-console: ["error", {allow: ["warn"]}] */
--/* global console */
 +const { Localization } =
-+  ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
++  Components.utils.import("resource://gre/modules/Localization.jsm", {});
  
  // Match the opening angle bracket (<) in HTML tags, and HTML entities like
  // &amp;, &#0038;, &#x0026;.
-@@ -96,6 +94,7 @@
-       const templateElement = element.ownerDocument.createElementNS(
-         "http://www.w3.org/1999/xhtml", "template"
-       );
-+      // eslint-disable-next-line no-unsanitized/property
-       templateElement.innerHTML = value;
-       overlayChildNodes(templateElement.content, element);
-     }
-@@ -323,6 +322,46 @@
-   return toElement;
- }
- 
-+/**
-+ * Sanitizes a translation before passing them to Node.localize API.
-+ *
-+ * It returns `false` if the translation contains DOM Overlays and should
-+ * not go into Node.localize.
-+ *
-+ * Note: There's a third item of work that JS DOM Overlays do - removal
-+ * of attributes from the previous translation.
-+ * This is not trivial to implement for Node.localize scenario, so
-+ * at the moment it is not supported.
-+ *
-+ * @param {{
-+ *          localName: string,
-+ *          namespaceURI: string,
-+ *          type: string || null
-+ *          l10nId: string,
-+ *          l10nArgs: Array<Object> || null,
-+ *          l10nAttrs: string ||null,
-+ *        }}                                     l10nItems
-+ * @param {{value: string, attrs: Object}} translations
-+ * @returns boolean
-+ * @private
-+ */
-+function sanitizeTranslationForNodeLocalize(l10nItem, translation) {
-+  if (reOverlay.test(translation.value)) {
-+    return false;
-+  }
-+
-+  if (translation.attributes) {
-+    const explicitlyAllowed = l10nItem.l10nAttrs === null ? null :
-+      l10nItem.l10nAttrs.split(",").map(i => i.trim());
-+    for (const [j, {name}] of translation.attributes.entries()) {
-+      if (!isAttrNameLocalizable(name, l10nItem, explicitlyAllowed)) {
-+        translation.attributes.splice(j, 1);
-+      }
-+    }
-+  }
-+  return true;
-+}
-+
- const L10NID_ATTR_NAME = "data-l10n-id";
- const L10NARGS_ATTR_NAME = "data-l10n-args";
- 
-@@ -568,6 +607,59 @@
-    * @returns {Promise}
-    */
-   translateFragment(frag) {
-+    if (frag.localize) {
-+      // This is a temporary fast-path offered by Gecko to workaround performance
-+      // issues coming from Fluent and XBL+Stylo performing unnecesary
-+      // operations during startup.
-+      // For details see bug 1441037, bug 1442262, and bug 1363862.
-+
-+      // A sparse array which will store translations separated out from
-+      // all translations that is needed for DOM Overlay.
-+      const overlayTranslations = [];
-+
-+      const getTranslationsForItems = async l10nItems => {
-+        const keys = l10nItems.map(l10nItem => [l10nItem.l10nId, l10nItem.l10nArgs]);
-+        const translations = await this.formatMessages(keys);
-+
-+        // Here we want to separate out elements that require DOM Overlays.
-+        // Those elements will have to be translated using our JS
-+        // implementation, while everything else is going to use the fast-path.
-+        for (const [i, translation] of translations.entries()) {
-+          if (translation === undefined) {
-+            continue;
-+          }
-+
-+          const hasOnlyText =
-+            sanitizeTranslationForNodeLocalize(l10nItems[i], translation);
-+          if (!hasOnlyText) {
-+            // Removing from translations to make Node.localize skip it.
-+            // We will translate it below using JS DOM Overlays.
-+            overlayTranslations[i] = translations[i];
-+            translations[i] = undefined;
-+          }
-+        }
-+
-+        // We pause translation observing here because Node.localize
-+        // will translate the whole DOM next, using the `translations`.
-+        //
-+        // The observer will be resumed after DOM Overlays are localized
-+        // in the next microtask.
-+        this.pauseObserving();
-+        return translations;
-+      };
-+
-+      return frag.localize(getTranslationsForItems.bind(this))
-+        .then(untranslatedElements => {
-+          for (let i = 0; i < overlayTranslations.length; i++) {
-+            if (overlayTranslations[i] !== undefined &&
-+                untranslatedElements[i] !== undefined) {
-+              translateElement(untranslatedElements[i], overlayTranslations[i]);
-+            }
-+          }
-+          this.resumeObserving();
-+        })
-+        .catch(() => this.resumeObserving());
-+    }
-     return this.translateElements(this.getTranslatables(frag));
-   }
- 
-@@ -647,37 +739,5 @@
+@@ -623,36 +624,5 @@
    }
  }
  
 -/* global L10nRegistry, Services */
--
 -/**
 - * The default localization strategy for Gecko. It comabines locales
 - * available in L10nRegistry, with locales requested by the user to
 - * generate the iterator over MessageContexts.
 - *
 - * In the future, we may want to allow certain modules to override this
 - * with a different negotitation strategy to allow for the module to
 - * be localized into a different language - for example DevTools.
@@ -161,94 +42,50 @@ diff -uNr ./dist/DOMLocalization.jsm /ho
 -    resourceIds,
 -    generateMessages = defaultGenerateMessages
 -  ) {
 -    super(windowElement, resourceIds, generateMessages);
 -  }
 -}
 -
 -this.DOMLocalization = GeckoDOMLocalization;
--this.EXPORTED_SYMBOLS = ["DOMLocalization"];
 +this.DOMLocalization = DOMLocalization;
-+var EXPORTED_SYMBOLS = ["DOMLocalization"];
+ this.EXPORTED_SYMBOLS = ['DOMLocalization'];
 diff -uNr ./dist/l10n.js /home/zbraniecki/projects/mozilla-unified/intl/l10n/l10n.js
---- ./dist/l10n.js	2018-04-13 08:25:21.307139138 -0700
-+++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/l10n.js	2018-04-13 08:27:25.230296529 -0700
-@@ -1,20 +1,26 @@
+--- ./dist/l10n.js	2018-01-30 13:46:58.749811101 -0800
++++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/l10n.js	2018-01-26 20:52:09.106650798 -0800
+@@ -1,7 +1,6 @@
 -/* global Components, document, window */
  {
    const { DOMLocalization } =
--    Components.utils.import("resource://gre/modules/DOMLocalization.jsm");
-+    ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
+-    Components.utils.import('resource://gre/modules/DOMLocalization.jsm');
++    Components.utils.import("resource://gre/modules/DOMLocalization.jsm");
  
    /**
     * Polyfill for document.ready polyfill.
-    * See: https://github.com/whatwg/html/issues/127 for details.
-    *
-+   * XXX: The callback is a temporary workaround for bug 1193394. Once Promises in Gecko
-+   *      start beeing a microtask and stop pushing translation post-layout, we can
-+   *      remove it and start using the returned Promise again.
-+   *
-+   * @param {Function} callback - function to be called when the document is ready.
-    * @returns {Promise}
-    */
--  function documentReady() {
-+  function documentReady(callback) {
-     if (document.contentType === "application/vnd.mozilla.xul+xml") {
-       // XUL
-       return new Promise(
-         resolve => document.addEventListener(
--          "MozBeforeInitialXULLayout", resolve, { once: true }
-+          "MozBeforeInitialXULLayout", () => {
-+            resolve(callback());
-+          }, { once: true }
-         )
-       );
-     }
-@@ -22,11 +28,13 @@
-     // HTML
-     const rs = document.readyState;
-     if (rs === "interactive" || rs === "completed") {
--      return Promise.resolve();
-+      return Promise.resolve(callback);
-     }
-     return new Promise(
-       resolve => document.addEventListener(
--        "readystatechange", resolve, { once: true }
-+        "readystatechange", () => {
-+          resolve(callback());
-+        }, { once: true }
-       )
-     );
-   }
-@@ -50,11 +58,8 @@
-   // trigger first context to be fetched eagerly
-   document.l10n.ctxs.touchNext();
+diff -uNr ./dist/Localization.jsm /home/zbraniecki/projects/mozilla-unified/intl/l10n/Localization.jsm
+--- ./dist/Localization.jsm	2018-01-30 13:46:58.393144450 -0800
++++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/Localization.jsm	2018-01-30 13:46:04.593146834 -0800
+@@ -18,92 +18,16 @@
  
--  document.l10n.ready = documentReady().then(() => {
-+  document.l10n.ready = documentReady(() => {
-     document.l10n.registerObservers();
--    window.addEventListener("unload", () => {
--      document.l10n.unregisterObservers();
--    });
-     document.l10n.connectRoot(document.documentElement);
-     return document.l10n.translateRoots();
-   });
-diff -uNr ./dist/Localization.jsm /home/zbraniecki/projects/mozilla-unified/intl/l10n/Localization.jsm
---- ./dist/Localization.jsm	2018-04-13 08:25:20.946138732 -0700
-+++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/Localization.jsm	2018-04-13 08:27:16.396155987 -0700
-@@ -18,70 +18,13 @@
- 
- /* fluent-dom@0.2.0 */
+ /* fluent@0.6.0 */
  
 -/*  eslint no-magic-numbers: [0]  */
 -
 -/* global Intl */
 -
 -/**
+- * The `FluentType` class is the base of Fluent's type system.
+- *
+- * Fluent types wrap JavaScript values and store additional configuration for
+- * them, which can then be used in the `toString` method together with a proper
+- * `Intl` formatter.
+- */
+-
+-/**
 - * @overview
 - *
 - * The FTL resolver ships with a number of functions built-in.
 - *
 - * Each function take two arguments:
 - *   - args - an array of positional args
 - *   - opts - an object of key-value args
 - *
@@ -300,24 +137,40 @@ diff -uNr ./dist/Localization.jsm /home/
 - *  * {Object} args
 - *      list of developer provided arguments that can be used
 - *  * {Array} errors
 - *      list of errors collected while resolving
 - *  * {WeakSet} dirty
 - *      Set of patterns already encountered during this resolution.
 - *      This is used to prevent cyclic resolutions.
 - */
-+const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", {});
-+const { L10nRegistry } = ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
-+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
-+const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
++const Cu = Components.utils;
++const Cc = Components.classes;
++const Ci = Components.interfaces;
+ 
+-/**
+- * Message contexts are single-language stores of translations.  They are
+- * responsible for parsing translation resources in the Fluent syntax and can
+- * format translation units (entities) to strings.
+- *
+- * Always use `MessageContext.format` to retrieve translation units from
+- * a context.  Translations can contain references to other entities or
+- * external arguments, conditional logic in form of select expressions, traits
+- * which describe their grammatical features, and can use Fluent builtins which
+- * make use of the `Intl` formatters to format numbers, dates, lists and more
+- * into the context's language.  See the documentation of the Fluent syntax for
+- * more information.
+- */
++const { L10nRegistry } = Cu.import("resource://gre/modules/L10nRegistry.jsm", {});
++const LocaleService = Cc["@mozilla.org/intl/localeservice;1"].getService(Ci.mozILocaleService);
++const ObserverService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
  
  /*
   * CachedIterable caches the elements yielded by an iterable.
-@@ -148,58 +91,19 @@
+@@ -170,87 +94,6 @@
    }
  }
  
 -/*
 - * @overview
 - *
 - * Functions for managing ordered sequences of MessageContexts.
 - *
@@ -354,133 +207,171 @@ diff -uNr ./dist/Localization.jsm /home/
 - *     }
 - *
 - *     const contexts = new CachedIterable(generateMessages());
 - *     const ctx = mapContextSync(contexts, id);
 - *
 - */
 -
 -/*
+- * Synchronously map an identifier or an array of identifiers to the best
+- * `MessageContext` instance(s).
+- *
+- * @param {Iterable} iterable
+- * @param {string|Array<string>} ids
+- * @returns {MessageContext|Array<MessageContext>}
+- */
+-
+-
+-/*
+- * Asynchronously map an identifier or an array of identifiers to the best
+- * `MessageContext` instance(s).
+- *
+- * @param {AsyncIterable} iterable
+- * @param {string|Array<string>} ids
+- * @returns {Promise<MessageContext|Array<MessageContext>>}
+- */
+-
+-/**
+- * Template literal tag for dedenting FTL code.
+- *
+- * Strip the common indent of non-blank lines. Remove blank lines.
+- *
+- * @param {Array<string>} strings
+- */
+-
+-/*
 - * @module fluent
 - * @overview
 - *
 - * `fluent` is a JavaScript implementation of Project Fluent, a localization
 - * framework designed to unleash the expressive power of the natural language.
-+/**
+- *
+- */
+-
+-/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
+-/* global console */
+-
+ /**
+  * Specialized version of an Error used to indicate errors that are result
+  * of a problem during the localization process.
+@@ -269,6 +112,26 @@
+   }
+ }
+ 
++ /**
 + * The default localization strategy for Gecko. It comabines locales
 + * available in L10nRegistry, with locales requested by the user to
 + * generate the iterator over MessageContexts.
-  *
++ *
 + * In the future, we may want to allow certain modules to override this
 + * with a different negotitation strategy to allow for the module to
 + * be localized into a different language - for example DevTools.
-  */
--
--/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
++ */
 +function defaultGenerateMessages(resourceIds) {
-+  const appLocales = Services.locale.getAppLocalesAsLangTags();
-+  return L10nRegistry.generateContexts(appLocales, resourceIds);
++  const availableLocales = L10nRegistry.getAvailableLocales();
++
++  const requestedLocales = LocaleService.getRequestedLocales();
++  const defaultLocale = LocaleService.defaultLocale;
++  const locales = LocaleService.negotiateLanguages(
++    requestedLocales, availableLocales, defaultLocale,
++  );
++  return L10nRegistry.generateContexts(locales, resourceIds);
 +}
- 
++
  /**
   * The `Localization` class is a central high-level API for vanilla
-@@ -215,7 +119,7 @@
+  * JavaScript use of Fluent.
+@@ -283,7 +146,7 @@
     *
     * @returns {Localization}
     */
 -  constructor(resourceIds, generateMessages) {
 +  constructor(resourceIds, generateMessages = defaultGenerateMessages) {
      this.resourceIds = resourceIds;
      this.generateMessages = generateMessages;
      this.ctxs = new CachedIterable(this.generateMessages(this.resourceIds));
-@@ -236,7 +140,7 @@
+@@ -303,7 +166,7 @@
+    */
    async formatWithFallback(keys, method) {
      const translations = [];
- 
 -    for (let ctx of this.ctxs) {
 +    for await (let ctx of this.ctxs) {
        // This can operate on synchronous and asynchronous
        // contexts coming from the iterator.
-       if (typeof ctx.then === "function") {
-@@ -248,7 +152,7 @@
-         break;
-       }
- 
--      if (typeof console !== "undefined") {
-+      if (AppConstants.NIGHTLY_BUILD) {
-         const locale = ctx.locales[0];
-         const ids = Array.from(missingIds).join(", ");
-         console.warn(`Missing translations in ${locale}: ${ids}`);
-@@ -335,8 +239,28 @@
+       if (typeof ctx.then === 'function') {
+@@ -394,8 +257,38 @@
      return val;
    }
  
 -  handleEvent() {
 -    this.onLanguageChange();
 +  /**
-+   * Register weak observers on events that will trigger cache invalidation
++   * Register observers on events that will trigger cache invalidation
 +   */
 +  registerObservers() {
-+    Services.obs.addObserver(this, "intl:app-locales-changed", true);
++    ObserverService.addObserver(this, 'l10n:available-locales-changed', false);
++    ObserverService.addObserver(this, 'intl:requested-locales-changed', false);
++  }
++
++  /**
++   * Unregister observers on events that will trigger cache invalidation
++   */
++  unregisterObservers() {
++    ObserverService.removeObserver(this, 'l10n:available-locales-changed');
++    ObserverService.removeObserver(this, 'intl:requested-locales-changed');
 +  }
 +
 +  /**
 +   * Default observer handler method.
 +   *
 +   * @param {String} subject
 +   * @param {String} topic
 +   * @param {Object} data
 +   */
 +  observe(subject, topic, data) {
 +    switch (topic) {
-+      case "intl:app-locales-changed":
++      case 'l10n:available-locales-changed':
++      case 'intl:requested-locales-changed':
 +        this.onLanguageChange();
 +        break;
 +      default:
 +        break;
 +    }
    }
  
    /**
-@@ -348,6 +272,10 @@
-   }
- }
+@@ -538,7 +431,8 @@
+       hasErrors = true;
+     }
  
-+Localization.prototype.QueryInterface = XPCOMUtils.generateQI([
-+  Ci.nsISupportsWeakReference
-+]);
-+
- /**
-  * Format the value of a message into a string.
-  *
-@@ -368,6 +296,7 @@
-  */
- function valueFromContext(ctx, errors, id, args) {
-   const msg = ctx.getMessage(id);
-+
-   return ctx.format(msg, args, errors);
- }
- 
-@@ -467,44 +396,5 @@
-   return missingIds;
+-    if (messageErrors.length && typeof console !== 'undefined') {
++    if (messageErrors.length) {
++      const { console } = Cu.import("resource://gre/modules/Console.jsm", {});
+       messageErrors.forEach(error => console.warn(error));
+     }
+   });
+@@ -546,45 +440,5 @@
+   return hasErrors;
  }
  
 -/* global Components */
 -/* eslint no-unused-vars: 0 */
 -
 -const Cu = Components.utils;
 -const Cc = Components.classes;
 -const Ci = Components.interfaces;
 -
 -const { L10nRegistry } =
--  Cu.import("resource://gre/modules/L10nRegistry.jsm", {});
+-  Cu.import('resource://gre/modules/L10nRegistry.jsm', {});
 -const ObserverService =
--  Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+-  Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
 -const { Services } =
--  Cu.import("resource://gre/modules/Services.jsm", {});
+-  Cu.import('resource://gre/modules/Services.jsm', {});
+-
 -
 -/**
 - * The default localization strategy for Gecko. It comabines locales
 - * available in L10nRegistry, with locales requested by the user to
 - * generate the iterator over MessageContexts.
 - *
 - * In the future, we may want to allow certain modules to override this
 - * with a different negotitation strategy to allow for the module to
@@ -498,32 +389,22 @@ diff -uNr ./dist/Localization.jsm /home/
 -
 -class GeckoLocalization extends Localization {
 -  constructor(resourceIds, generateMessages = defaultGenerateMessages) {
 -    super(resourceIds, generateMessages);
 -  }
 -}
 -
 -this.Localization = GeckoLocalization;
--this.EXPORTED_SYMBOLS = ["Localization"];
 +this.Localization = Localization;
-+var EXPORTED_SYMBOLS = ["Localization"];
+ this.EXPORTED_SYMBOLS = ['Localization'];
 diff -uNr ./dist/MessageContext.jsm /home/zbraniecki/projects/mozilla-unified/intl/l10n/MessageContext.jsm
---- ./dist/MessageContext.jsm	2018-04-13 08:25:20.698138486 -0700
-+++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/MessageContext.jsm	2018-04-13 08:27:20.944227388 -0700
-@@ -16,7 +16,7 @@
-  */
- 
- 
--/* fluent-dom@0.2.0 */
-+/* fluent@0.6.3 */
- 
- /*  eslint no-magic-numbers: [0]  */
- 
-@@ -1858,63 +1858,5 @@
+--- ./dist/MessageContext.jsm	2018-01-30 13:46:58.119811129 -0800
++++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/MessageContext.jsm	2018-01-30 13:53:23.036460739 -0800
+@@ -1838,90 +1838,5 @@
    }
  }
  
 -/*
 - * CachedIterable caches the elements yielded by an iterable.
 - *
 - * It can be used to iterate over an iterable many times without depleting the
 - * iterable.
@@ -567,19 +448,45 @@ diff -uNr ./dist/MessageContext.jsm /hom
 - *     }
 - *
 - *     const contexts = new CachedIterable(generateMessages());
 - *     const ctx = mapContextSync(contexts, id);
 - *
 - */
 -
 -/*
+- * Synchronously map an identifier or an array of identifiers to the best
+- * `MessageContext` instance(s).
+- *
+- * @param {Iterable} iterable
+- * @param {string|Array<string>} ids
+- * @returns {MessageContext|Array<MessageContext>}
+- */
+-
+-
+-/*
+- * Asynchronously map an identifier or an array of identifiers to the best
+- * `MessageContext` instance(s).
+- *
+- * @param {AsyncIterable} iterable
+- * @param {string|Array<string>} ids
+- * @returns {Promise<MessageContext|Array<MessageContext>>}
+- */
+-
+-/**
+- * Template literal tag for dedenting FTL code.
+- *
+- * Strip the common indent of non-blank lines. Remove blank lines.
+- *
+- * @param {Array<string>} strings
+- */
+-
+-/*
 - * @module fluent
 - * @overview
 - *
 - * `fluent` is a JavaScript implementation of Project Fluent, a localization
 - * framework designed to unleash the expressive power of the natural language.
 - *
 - */
 -
  this.MessageContext = MessageContext;
--this.EXPORTED_SYMBOLS = ["MessageContext"];
-+var EXPORTED_SYMBOLS = ["MessageContext"];
+ this.EXPORTED_SYMBOLS = ['MessageContext'];
--- a/intl/l10n/test/dom/test_domloc_overlay.html
+++ b/intl/l10n/test/dom/test_domloc_overlay.html
@@ -10,17 +10,17 @@
   const { DOMLocalization } =
     ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
   const { MessageContext } =
     ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
     const mc = new MessageContext(locales);
     mc.addMessages("title = <strong>Hello</strong> World");
-    mc.addMessages(`title2 = This is <a data-l10n-name="link">a link</a>!`);
+    mc.addMessages("title2 = This is <a>a link</a>!");
     yield mc;
   }
 
   window.onload = async function() {
     SimpleTest.waitForExplicitFinish();
 
     const domLoc = new DOMLocalization(
       window,
@@ -45,12 +45,12 @@
 
     a.click();
   };
   </script>
 </head>
 <body>
   <p data-l10n-id="title" />
   <p data-l10n-id="title2">
-    <a href="http://www.mozilla.org" data-l10n-name="link"></a>
+    <a href="http://www.mozilla.org"></a>
   </p>
 </body>
 </html>
--- a/intl/l10n/test/dom/test_domloc_overlay_missing_children.html
+++ b/intl/l10n/test/dom/test_domloc_overlay_missing_children.html
@@ -9,17 +9,17 @@
   "use strict";
   const { DOMLocalization } =
     ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
   const { MessageContext } =
     ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
     const mc = new MessageContext(locales);
-    mc.addMessages(`title = Visit <a data-l10n-name="mozilla-link">Mozilla</a> or <a data-l10n-name="firefox-link">Firefox</a> website!`);
+    mc.addMessages("title = Visit <a>Mozilla</a> or <a>Firefox</a> website!");
     yield mc;
   }
 
   window.onload = async function() {
     SimpleTest.waitForExplicitFinish();
 
     const domLoc = new DOMLocalization(
       window,
@@ -41,14 +41,14 @@
     is(linkList.length, 2, "There should be exactly two links in the result.");
 
     SimpleTest.finish();
   };
   </script>
 </head>
 <body>
   <p data-l10n-id="title">
-    <a href="http://www.mozilla.org" data-l10n-name="mozilla-link"></a>
-    <a href="http://www.firefox.com" data-l10n-name="firefox-link"></a>
-    <a href="http://www.w3.org" data-l10n-name="w3-link"></a>
+    <a href="http://www.mozilla.org"></a>
+    <a href="http://www.firefox.com"></a>
+    <a href="http://www.w3.org"></a>
   </p>
 </body>
 </html>
--- a/intl/l10n/test/dom/test_domloc_overlay_repeated.html
+++ b/intl/l10n/test/dom/test_domloc_overlay_repeated.html
@@ -9,17 +9,17 @@
   "use strict";
   const { DOMLocalization } =
     ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
   const { MessageContext } =
     ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
     const mc = new MessageContext(locales);
-    mc.addMessages(`title = Visit <a data-l10n-name="mozilla-link">Mozilla</a> or <a data-l10n-name="firefox-link">Firefox</a> website!`);
+    mc.addMessages("title = Visit <a>Mozilla</a> or <a>Firefox</a> website!");
     yield mc;
   }
 
   window.onload = async function() {
     SimpleTest.waitForExplicitFinish();
 
     const domLoc = new DOMLocalization(
       window,
@@ -39,13 +39,13 @@
     is(linkList[1].textContent, "Firefox");
 
     SimpleTest.finish();
   };
   </script>
 </head>
 <body>
   <p data-l10n-id="title">
-    <a href="http://www.mozilla.org" data-l10n-name="mozilla-link"></a>
-    <a href="http://www.firefox.com" data-l10n-name="firefox-link"></a>
+    <a href="http://www.mozilla.org"></a>
+    <a href="http://www.firefox.com"></a>
   </p>
 </body>
 </html>
--- a/intl/l10n/test/dom/test_domloc_repeated_l10nid.html
+++ b/intl/l10n/test/dom/test_domloc_repeated_l10nid.html
@@ -12,17 +12,17 @@
   const { MessageContext } =
     ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
     const mc = new MessageContext(locales);
     mc.addMessages(`
 key1 = Translation For Key 1
 
-key2 = Visit <a data-l10n-name="link">this link<a/>.
+key2 = Visit <a>this link<a/>.
     `);
     yield mc;
   }
 
   SimpleTest.waitForExplicitFinish();
   addLoadEvent(async () => {
     const domLoc = new DOMLocalization(
       window,
@@ -48,16 +48,16 @@ key2 = Visit <a data-l10n-name="link">th
   });
   </script>
 </head>
 <body>
   <h1 id="elem1" data-l10n-id="key1"></h1>
   <h2 id="elem2" data-l10n-id="key1"></h2>
 
   <p id="elem3" data-l10n-id="key2">
-    <a href="http://www.mozilla.org" data-l10n-name="link"></a>
+    <a href="http://www.mozilla.org"></a>
   </p>
 
   <p id="elem4" data-l10n-id="key2">
-    <a href="http://www.firefox.com" data-l10n-name="link"></a>
+    <a href="http://www.firefox.com"></a>
   </p>
 </body>
 </html>
deleted file mode 100644
--- a/python/l10n/fluent_migrations/bug_1453480_preferences_dom2_resources.py
+++ /dev/null
@@ -1,130 +0,0 @@
-# coding=utf8
-
-# Any copyright is dedicated to the Public Domain.
-# http://creativecommons.org/publicdomain/zero/1.0/
-
-from __future__ import absolute_import
-import fluent.syntax.ast as FTL
-from fluent.migrate.helpers import MESSAGE_REFERENCE, EXTERNAL_ARGUMENT
-from fluent.migrate import COPY, CONCAT, REPLACE
-
-def migrate(ctx):
-    """Bug 1453480 - Migrate Fluent resources to use DOM Overlays, part {index}."""
-
-    ctx.add_transforms(
-        'browser/browser/preferences/preferences.ftl',
-        'browser/browser/preferences/preferences.ftl',
-        [
-            FTL.Message(
-                id=FTL.Identifier('search-results-empty-message'),
-                value=FTL.Pattern(
-                    elements=[
-                        FTL.Placeable(
-                            expression=FTL.SelectExpression(
-                                expression=FTL.CallExpression(
-                                    callee=FTL.Function('PLATFORM')
-                                ),
-                                variants=[
-                                    FTL.Variant(
-                                        key=FTL.VariantName('windows'),
-                                        default=False,
-                                        value=REPLACE(
-                                            'browser/chrome/browser/preferences/preferences.properties',
-                                            'searchResults.sorryMessageWin',
-                                            {
-                                                '%S': FTL.TextElement('<span data-l10n-name="query"></span>')
-                                            }
-                                        )
-                                    ),
-                                    FTL.Variant(
-                                        key=FTL.VariantName('other'),
-                                        default=True,
-                                        value=REPLACE(
-                                            'browser/chrome/browser/preferences/preferences.properties',
-                                            'searchResults.sorryMessageUnix',
-                                            {
-                                                '%S': FTL.TextElement('<span data-l10n-name="query"></span>')
-                                            }
-                                        )
-                                    )
-                                ]
-                            )
-                        )
-                    ]
-                )
-            ),
-            FTL.Message(
-                id=FTL.Identifier('search-results-help-link'),
-                value=REPLACE(
-                    'browser/chrome/browser/preferences/preferences.properties',
-                    'searchResults.needHelp3',
-                    {
-                        '%S': CONCAT(
-                            FTL.TextElement('<a data-l10n-name="url">'),
-                            REPLACE(
-                                'browser/chrome/browser/preferences/preferences.properties',
-                                'searchResults.needHelpSupportLink',
-                                {
-                                    '%S': MESSAGE_REFERENCE('-brand-short-name'),
-                                }
-                            ),
-                            FTL.TextElement('</a>')
-                        )
-                    }
-                )
-            ),
-            FTL.Message(
-                id=FTL.Identifier('update-application-version'),
-                value=CONCAT(
-                    COPY(
-                        'browser/chrome/browser/preferences/advanced.dtd',
-                        'updateApplication.version.pre'
-                    ),
-                    EXTERNAL_ARGUMENT('version'),
-                    COPY(
-                        'browser/chrome/browser/preferences/advanced.dtd',
-                        'updateApplication.version.post'
-                    ),
-                    FTL.TextElement(' <a data-l10n-name="learn-more">'),
-                    COPY(
-                        'browser/chrome/browser/aboutDialog.dtd',
-                        'releaseNotes.link'
-                    ),
-                    FTL.TextElement('</a>')
-                )
-            ),
-            FTL.Message(
-                id=FTL.Identifier('performance-limit-content-process-blocked-desc'),
-                value=CONCAT(
-                    REPLACE(
-                        'browser/chrome/browser/preferences/advanced.dtd',
-                        'limitContentProcessOption.disabledDescription',
-                        {
-                            '&brandShortName;': MESSAGE_REFERENCE('-brand-short-name')
-                        }
-                    ),
-                    FTL.TextElement(' <a data-l10n-name="learn-more">'),
-                    COPY(
-                        'browser/chrome/browser/preferences/advanced.dtd',
-                        'limitContentProcessOption.disabledDescriptionLink'
-                    ),
-                    FTL.TextElement('</a>')
-                )
-            ),
-            FTL.Message(
-                id=FTL.Identifier('tracking-desc'),
-                value=CONCAT(
-                    COPY(
-                        'browser/chrome/browser/preferences/privacy.dtd',
-                        'trackingProtection3.description'
-                    ),
-                    FTL.TextElement(' <a data-l10n-name="learn-more">'),
-                    COPY(
-                        'browser/chrome/browser/preferences/privacy.dtd',
-                        'trackingProtectionLearnMore2.label'
-                    ),
-                    FTL.TextElement('</a>')
-                )
-            ),
-        ]
-    )