Merge autoland to mozilla-central. a=merge
authorDorel Luca <dluca@mozilla.com>
Tue, 06 Mar 2018 20:33:50 +0200
changeset 461799 b498494cfac55b99668607280756bee3cb60791a
parent 461789 a4ef1082c51d5b4508882c22487f6c8de5b35e2a (current diff)
parent 461770 a10eb278c3b247242196b015e5e71a5058ef1166 (diff)
child 461800 efe1f380d0279d7786b0c6340a60aaf6c76eabbe
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.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
Merge autoland to mozilla-central. a=merge
dom/base/nsINode.cpp
dom/base/nsINode.h
--- a/browser/components/migration/.eslintrc.js
+++ b/browser/components/migration/.eslintrc.js
@@ -10,16 +10,16 @@ module.exports = {
     "new-parens": "error",
     "no-extend-native": "error",
     "no-fallthrough": ["error", { "commentPattern": ".*[Ii]ntentional(?:ly)?\\s+fall(?:ing)?[\\s-]*through.*" }],
     "no-multi-str": "error",
     "no-return-assign": "error",
     "no-sequences": "error",
     "no-shadow": "error",
     "no-throw-literal": "error",
-    "no-unused-vars": ["error", { "args": "after-used", "varsIgnorePattern": "^EXPORTED_SYMBOLS$", "vars": "all" }],
+    "no-unused-vars": ["error", { "args": "after-used", "vars": "all" }],
     "padded-blocks": ["error", "never"],
     "semi-spacing": ["error", {"before": false, "after": true}],
     "space-in-parens": ["error", "never"],
     "strict": ["error", "global"],
     "yoda": "error"
   }
 };
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -1067,17 +1067,17 @@ var gEditItemOverlay = {
     }
 
     if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId) {
       return;
     }
 
     switch (aProperty) {
     case "uri":
-      let newURI = Services.ui.newURI(aValue);
+      let newURI = Services.io.newURI(aValue);
       if (!newURI.equals(this._paneInfo.uri)) {
         this._paneInfo.uri = newURI;
         if (this._paneInfo.visibleRows.has("locationRow"))
           this._initLocationField();
 
         if (this._paneInfo.visibleRows.has("tagsRow")) {
           delete this._paneInfo._cachedCommonTags;
           this._onTagsChange(aGuid, newURI).catch(Cu.reportError);
--- a/browser/components/places/tests/browser/browser_bookmark_change_location.js
+++ b/browser/components/places/tests/browser/browser_bookmark_change_location.js
@@ -58,20 +58,22 @@ add_task(async function test_change_loca
       // Check the initial location.
       let locationPicker = dialogWin.document.getElementById("editBMPanel_locationField");
       Assert.equal(locationPicker.value, TEST_URL, "The location is the expected one.");
 
       let promiseLocationChange = PlacesTestUtils.waitForNotification("onItemChanged", (id, parentId, index, itemUrl) => itemUrl === TEST_URL2);
       // Update the "location" field.
       fillBookmarkTextField("editBMPanel_locationField", TEST_URL2, dialogWin, false);
       await waitForCondition(() => locationPicker.value === TEST_URL2, "The location is correct after update.");
-
+      locationPicker.blur();
+      await promiseLocationChange;
+      Assert.equal(dialogWin.gEditItemOverlay.uri.spec, TEST_URL2, "The location is the expected one.");
+      locationPicker.focus();
       // Confirm and close the dialog.
       EventUtils.synthesizeKey("VK_RETURN", {}, dialogWin);
-      await promiseLocationChange;
 
       let updatedBm = await PlacesUtils.bookmarks.fetch(toolbarBookmark.guid);
       Assert.equal(updatedBm.url, TEST_URL2, "Should have updated the bookmark location in the database.");
     }
   );
 });
 
 add_task(async function test_change_location_from_Sidebar() {
--- a/browser/components/preferences/in-content/search.js
+++ b/browser/components/preferences/in-content/search.js
@@ -61,17 +61,17 @@ var gSearchPane = {
 
     let suggestsPref = Preferences.get("browser.search.suggest.enabled");
     let urlbarSuggestsPref = Preferences.get("browser.urlbar.suggest.searches");
     let updateSuggestionCheckboxes = this._updateSuggestionCheckboxes.bind(this);
     suggestsPref.on("change", updateSuggestionCheckboxes);
     urlbarSuggestsPref.on("change", updateSuggestionCheckboxes);
     let urlbarSuggests = document.getElementById("urlBarSuggestion");
     urlbarSuggests.addEventListener("command", () => {
-      urlbarSuggestsPref.value = !urlbarSuggests.checked;
+      urlbarSuggestsPref.value = urlbarSuggests.checked;
     });
 
     this._initShowSearchSuggestionsFirst();
     this._updateSuggestionCheckboxes();
   },
 
   _initShowSearchSuggestionsFirst() {
     this._urlbarSuggestionsPosPref = Preferences.get("browser.urlbar.matchBuckets");
--- a/browser/components/preferences/in-content/tests/browser_fluent.js
+++ b/browser/components/preferences/in-content/tests/browser_fluent.js
@@ -36,14 +36,14 @@ add_task(async function() {
   ]);
 
   let elem = doc.querySelector(
     `#contentProcessCount > menupopup > menuitem[value="${defaultProcessCount}"]`);
 
   Assert.deepEqual(msg, {
     value: null,
     attrs: [
-      ["label", elem.getAttribute("label")]
+      {name: "label", value: elem.getAttribute("label")}
     ]
   });
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
--- a/browser/components/preferences/in-content/tests/browser_searchsuggestions.js
+++ b/browser/components/preferences/in-content/tests/browser_searchsuggestions.js
@@ -14,23 +14,23 @@ add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
 
   let doc = gBrowser.selectedBrowser.contentDocument;
   let urlbarBox = doc.getElementById("urlBarSuggestion");
   ok(!urlbarBox.disabled, "Checkbox should be enabled");
   is(urlbarBox.checked, INITIAL_URLBAR_SUGGEST_VALUE,
      "Checkbox should match initial pref value: " + INITIAL_URLBAR_SUGGEST_VALUE);
 
-  urlbarBox.doCommand();
+  await BrowserTestUtils.synthesizeMouseAtCenter("#urlBarSuggestion", {}, gBrowser.selectedBrowser);
   is(urlbarBox.checked, !INITIAL_URLBAR_SUGGEST_VALUE,
      "Checkbox should be flipped after clicking it");
   let prefValue = Services.prefs.getBoolPref(URLBAR_SUGGEST_PREF_NAME);
   is(prefValue, urlbarBox.checked, "Pref should match checkbox. Pref: " + prefValue);
 
-  urlbarBox.doCommand();
+  await BrowserTestUtils.synthesizeMouseAtCenter("#urlBarSuggestion", {}, gBrowser.selectedBrowser);
   is(urlbarBox.checked, INITIAL_URLBAR_SUGGEST_VALUE,
      "Checkbox should be back to initial value after clicking it");
   prefValue = Services.prefs.getBoolPref(URLBAR_SUGGEST_PREF_NAME);
   is(prefValue, urlbarBox.checked, "Pref should match checkbox. Pref: " + prefValue);
 
   Services.prefs.setBoolPref(SUGGEST_PREF_NAME, false);
   ok(!urlbarBox.checked, "Checkbox should now be unchecked");
   ok(urlbarBox.disabled, "Checkbox should be disabled");
--- a/browser/experiments/.eslintrc.js
+++ b/browser/experiments/.eslintrc.js
@@ -1,11 +1,10 @@
 "use strict";
 
 module.exports = {
   "rules": {
     "no-unused-vars": ["error", {
       "args": "none",
-      "vars": "all",
-      "varsIgnorePattern": "^EXPORTED_SYMBOLS$",
+      "vars": "all"
     }]
   }
 };
--- a/browser/experiments/test/xpcshell/.eslintrc.js
+++ b/browser/experiments/test/xpcshell/.eslintrc.js
@@ -3,13 +3,12 @@
 module.exports = {
   "extends": [
     "plugin:mozilla/xpcshell-test"
   ],
 
   "rules": {
     "no-unused-vars": ["error", {
       "vars": "all",
-      "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$",
       "args": "none"
     }]
   }
 };
--- a/browser/extensions/formautofill/.eslintrc.js
+++ b/browser/extensions/formautofill/.eslintrc.js
@@ -40,17 +40,17 @@ module.exports = {
     // Always require parenthesis for new calls
     "new-parens": "error",
 
     // No expressions where a statement is expected
     "no-unused-expressions": "error",
 
     // No declaring variables that are never used
     "no-unused-vars": ["error", {
-      "args": "none", "vars": "all", "varsIgnorePattern": "^EXPORTED_SYMBOLS$"
+      "args": "none", "vars": "all"
     }],
 
     // No using variables before defined
     "no-use-before-define": "error",
 
     // Disallow using variables outside the blocks they are defined (especially
     // since only let and const are used, see "no-var").
     "block-scoped-var": "error",
--- a/browser/tools/mozscreenshots/.eslintrc.js
+++ b/browser/tools/mozscreenshots/.eslintrc.js
@@ -3,13 +3,12 @@
 module.exports = {
   "extends": [
     "plugin:mozilla/browser-test"
   ],
 
   "rules": {
     "no-unused-vars": ["error", {
       "args": "none",
-      "vars": "all",
-      "varsIgnorePattern": "^EXPORTED_SYMBOLS$",
+      "vars": "all"
     }]
   }
 };
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -4,16 +4,18 @@
 
 "use strict";
 
 const Services = require("Services");
 
 const SwatchColorPickerTooltip = require("devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip");
 const { throttle } = require("devtools/client/inspector/shared/utils");
 const { compareFragmentsGeometry } = require("devtools/client/inspector/grids/utils/utils");
+const asyncStorage = require("devtools/shared/async-storage");
+const { parseURL } = require("devtools/client/shared/source-utils");
 
 const {
   updateGridColor,
   updateGridHighlighted,
   updateGrids,
 } = require("./actions/grids");
 const {
   updateShowGridAreas,
@@ -143,26 +145,30 @@ class GridInspector {
   }
 
   /**
    * Returns the initial color linked to a grid container. Will attempt to check the
    * current grid highlighter state and the store.
    *
    * @param  {NodeFront} nodeFront
    *         The NodeFront for which we need the color.
+   * @param  {String} customColor
+   *         The color fetched from the custom palette, if it exists.
    * @param  {String} fallbackColor
    *         The color to use if no color could be found for the node front.
    * @return {String} color
    *         The color to use.
    */
-  getInitialGridColor(nodeFront, fallbackColor) {
+  getInitialGridColor(nodeFront, customColor, fallbackColor) {
     let highlighted = nodeFront == this.highlighters.gridHighlighterShown;
 
     let color;
-    if (highlighted && this.highlighters.state.grid.options) {
+    if (customColor) {
+      color = customColor;
+    } else if (highlighted && this.highlighters.state.grid.options) {
       // If the node front is currently highlighted, use the color from the highlighter
       // options.
       color = this.highlighters.state.grid.options.color;
     } else {
       // Otherwise use the color defined in the store for this node front.
       color = this.getGridColorForNodeFront(nodeFront);
     }
 
@@ -283,32 +289,37 @@ class GridInspector {
    * Updates the grid panel by dispatching the new grid data. This is called when the
    * layout view becomes visible or the view needs to be updated with new grid data.
    */
   async updateGridPanel() {
     // Stop refreshing if the inspector or store is already destroyed.
     if (!this.inspector || !this.store) {
       return;
     }
+    let currentUrl = this.inspector.target.url;
+    // Get the hostname, if there is no hostname, fall back on protocol
+    // ex: `data:` uri, and `about:` pages
+    let hostname = parseURL(currentUrl).hostname || parseURL(currentUrl).protocol;
+    let customColors = await asyncStorage.getItem("gridInspectorHostColors") || {};
 
     // Get all the GridFront from the server if no gridFronts were provided.
     let gridFronts;
     try {
       gridFronts = await this.layoutInspector.getGrids(this.walker.rootNode);
     } catch (e) {
       // This call might fail if called asynchrously after the toolbox is finished
       // closing.
       return;
     }
 
     // Log how many CSS Grid elements DevTools sees.
     if (gridFronts.length > 0 &&
-        this.inspector.target.url != this.inspector.previousURL) {
+        currentUrl != this.inspector.previousURL) {
       this.telemetry.log(CSS_GRID_COUNT_HISTOGRAM_ID, gridFronts.length);
-      this.inspector.previousURL = this.inspector.target.url;
+      this.inspector.previousURL = currentUrl;
     }
 
     let grids = [];
     for (let i = 0; i < gridFronts.length; i++) {
       let grid = gridFronts[i];
 
       let nodeFront = grid.containerNodeFront;
 
@@ -320,32 +331,34 @@ class GridInspector {
           nodeFront = await this.walker.getNodeFromActor(grid.actorID, ["containerEl"]);
         } catch (e) {
           // This call might fail if called asynchrously after the toolbox is finished
           // closing.
           return;
         }
       }
 
+      let colorForHost = customColors[hostname] ? customColors[hostname][i] : undefined;
       let fallbackColor = GRID_COLORS[i % GRID_COLORS.length];
-      let color = this.getInitialGridColor(nodeFront, fallbackColor);
+      let color = this.getInitialGridColor(nodeFront, colorForHost, fallbackColor);
 
       grids.push({
         id: i,
         actorID: grid.actorID,
         color,
         direction: grid.direction,
         gridFragments: grid.gridFragments,
         highlighted: nodeFront == this.highlighters.gridHighlighterShown,
         nodeFront,
         writingMode: grid.writingMode,
       });
     }
 
     this.store.dispatch(updateGrids(grids));
+    this.inspector.emit("grid-panel-updated");
   }
 
   /**
    * Handler for "grid-highlighter-shown" and "grid-highlighter-hidden" events emitted
    * from the HighlightersOverlay. Updates the NodeFront's grid highlighted state.
    *
    * @param  {Event} event
    *         Event that was triggered.
@@ -450,26 +463,40 @@ class GridInspector {
    * Handler for a change in the grid overlay color picker for a grid container.
    *
    * @param  {NodeFront} node
    *         The NodeFront of the grid container element for which the grid color is
    *         being updated.
    * @param  {String} color
    *         A hex string representing the color to use.
    */
-  onSetGridOverlayColor(node, color) {
+  async onSetGridOverlayColor(node, color) {
     this.store.dispatch(updateGridColor(node, color));
     let { grids } = this.store.getState();
+    let currentUrl = this.inspector.target.url;
+    // Get the hostname, if there is no hostname, fall back on protocol
+    // ex: `data:` uri, and `about:` pages
+    let hostname = parseURL(currentUrl).hostname || parseURL(currentUrl).protocol;
+    let customGridColors = await asyncStorage.getItem("gridInspectorHostColors") || {};
 
-    // If the grid for which the color was updated currently has a highlighter, update
-    // the color.
     for (let grid of grids) {
-      if (grid.nodeFront === node && grid.highlighted) {
-        let highlighterSettings = this.getGridHighlighterSettings(node);
-        this.showGridHighlighter(node, highlighterSettings);
+      if (grid.nodeFront === node) {
+        if (!customGridColors[hostname]) {
+          customGridColors[hostname] = [];
+        }
+        // Update the custom color for the grid in this position.
+        customGridColors[hostname][grid.id] = color;
+        await asyncStorage.setItem("gridInspectorHostColors", customGridColors);
+
+        // If the grid for which the color was updated currently has a highlighter, update
+        // the color.
+        if (grid.highlighted) {
+          let highlighterSettings = this.getGridHighlighterSettings(node);
+          this.showGridHighlighter(node, highlighterSettings);
+        }
       }
     }
   }
 
   /**
    * Highlights the grid area in the CSS Grid Highlighter for the given grid.
    *
    * @param  {NodeFront} node
--- a/devtools/client/inspector/grids/test/browser.ini
+++ b/devtools/client/inspector/grids/test/browser.ini
@@ -29,9 +29,10 @@ support-files =
 [browser_grids_grid-outline-cannot-show-outline.js]
 [browser_grids_grid-outline-highlight-area.js]
 [browser_grids_grid-outline-highlight-cell.js]
 [browser_grids_grid-outline-selected-grid.js]
 [browser_grids_grid-outline-updates-on-grid-change.js]
 [browser_grids_grid-outline-writing-mode.js]
 [browser_grids_highlighter-setting-rules-grid-toggle.js]
 [browser_grids_number-of-css-grids-telemetry.js]
+[browser_grids_persist-color-palette.js]
 [browser_grids_restored-after-reload.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser_grids_persist-color-palette.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the when a custom color has been previously set, we initialize
+// the grid with that color.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #grid {
+      display: grid;
+    }
+  </style>
+  <div id="grid">
+    <div class="cell1">cell1</div>
+    <div class="cell2">cell2</div>
+  </div>
+`;
+
+add_task(async function () {
+  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, gridInspector, toolbox } = await openLayoutView();
+  let { document: doc } = gridInspector;
+  let { store } = inspector;
+  let cPicker = gridInspector.getSwatchColorPickerTooltip();
+  let swatch = doc.querySelector(".grid-color-swatch");
+
+  info("Scrolling into view of the #grid color swatch.");
+  swatch.scrollIntoView();
+
+  info("Opening the color picker by clicking on the #grid color swatch.");
+  let onColorPickerReady = cPicker.once("ready");
+  swatch.click();
+  await onColorPickerReady;
+
+  await simulateColorPickerChange(cPicker, [51, 48, 0, 1]);
+
+  info("Closing the toolbox.");
+  await toolbox.destroy();
+  info("Open the toolbox again.");
+  await openLayoutView();
+
+  info("Check that the previously set custom color is used.");
+  is(swatch.style.backgroundColor, "rgb(51, 48, 0)",
+    "The color swatch's background is correct.");
+  is(store.getState().grids[0].color, "#333000", "The grid color state is correct.");
+});
--- a/devtools/client/inspector/grids/test/head.js
+++ b/devtools/client/inspector/grids/test/head.js
@@ -16,16 +16,17 @@ Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-redux-head.js",
   this);
 
 Services.prefs.setIntPref("devtools.toolbox.footer.height", 350);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.toolbox.footer.height");
 });
 
+const asyncStorage = require("devtools/shared/async-storage");
 const HIGHLIGHTER_TYPE = "CssGridHighlighter";
 
 /**
  * Simulate a color change in a given color picker tooltip.
  *
  * @param  {Spectrum|ColorWidget} colorPicker
  *         The color picker widget.
  * @param  {Array} newRgba
@@ -35,8 +36,12 @@ var simulateColorPickerChange = Task.asy
   info("Getting the spectrum colorpicker object");
   let spectrum = yield colorPicker.spectrum;
   info("Setting the new color");
   spectrum.rgb = newRgba;
   info("Applying the change");
   spectrum.updateUI();
   spectrum.onChange();
 });
+
+registerCleanupFunction(async function () {
+  await asyncStorage.removeItem("gridInspectorHostColors");
+});
--- a/devtools/client/inspector/test/shared-head.js
+++ b/devtools/client/inspector/test/shared-head.js
@@ -49,18 +49,22 @@ var openInspectorSidebarTab = Task.async
   let {toolbox, inspector, testActor} = yield openInspector();
 
   info("Selecting the " + id + " sidebar");
 
   let onSidebarSelect = inspector.sidebar.once("select");
   if (id === "computedview" || id === "layoutview") {
     // The layout and computed views should wait until the box-model widget is ready.
     let onBoxModelViewReady = inspector.once("boxmodel-view-updated");
+    // The layout view also needs to wait for the grid panel to be fully updated.
+    let onGridPanelReady = id === "layoutview" ?
+      inspector.once("grid-panel-updated") : Promise.resolve();
     inspector.sidebar.select(id);
     yield onBoxModelViewReady;
+    yield onGridPanelReady;
   } else {
     inspector.sidebar.select(id);
   }
   yield onSidebarSelect;
 
   return {
     toolbox,
     inspector,
--- a/devtools/client/storage/test/browser_storage_dynamic_updates_localStorage.js
+++ b/devtools/client/storage/test/browser_storage_dynamic_updates_localStorage.js
@@ -45,21 +45,19 @@ add_task(function* () {
   // Updating a row
   gWindow.localStorage.setItem("ls2", "ls2-changed");
 
   yield gUI.once("store-objects-updated");
   yield gUI.once("store-objects-updated");
 
   checkCell("ls2", "value", "ls2-changed");
 
-  // Clearing items. Bug 1233497 makes it so that we can no longer yield
-  // CPOWs from Tasks. We work around this by calling clear via a ContentTask
-  // instead.
-  yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
-    return Task.spawn(content.wrappedJSObject.clear);
+  // Clearing items.
+  yield ContentTask.spawn(gBrowser.selectedBrowser, null, function () {
+    content.wrappedJSObject.clear();
   });
 
   yield gUI.once("store-objects-cleared");
 
   yield checkState([
     [
       ["localStorage", "http://test1.example.org"],
       [ ]
--- a/devtools/client/storage/test/browser_storage_dynamic_updates_sessionStorage.js
+++ b/devtools/client/storage/test/browser_storage_dynamic_updates_sessionStorage.js
@@ -59,21 +59,19 @@ add_task(function* () {
   gWindow.sessionStorage.setItem("ss2", "changed=ss2");
 
   yield gUI.once("sidebar-updated");
 
   checkCell("ss2", "value", "changed=ss2");
 
   yield findVariableViewProperties([{name: "ss2", value: "changed=ss2"}]);
 
-  // Clearing items. Bug 1233497 makes it so that we can no longer yield
-  // CPOWs from Tasks. We work around this by calling clear via a ContentTask
-  // instead.
-  yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
-    return Task.spawn(content.wrappedJSObject.clear);
+  // Clearing items.
+  yield ContentTask.spawn(gBrowser.selectedBrowser, null, function () {
+    content.wrappedJSObject.clear();
   });
 
   yield gUI.once("store-objects-cleared");
 
   yield checkState([
     [
       ["sessionStorage", "http://test1.example.org"],
       [ ]
--- a/devtools/client/storage/test/storage-updates.html
+++ b/devtools/client/storage/test/storage-updates.html
@@ -32,17 +32,17 @@ window.removeCookie = function(name, pat
     name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=" + path;
 };
 
 /**
  * We keep this method here even though these items are automatically cleared
  * after the test is complete. this is so that the store-objects-cleared event
  * can be tested.
  */
-window.clear = function*() {
+window.clear = function() {
   localStorage.clear();
   dump("removed localStorage from " + document.location + "\n");
 
   sessionStorage.clear();
   dump("removed sessionStorage from " + document.location + "\n");
 };
 
 window.onload = function() {
--- a/devtools/client/styleeditor/StyleEditorUI.jsm
+++ b/devtools/client/styleeditor/StyleEditorUI.jsm
@@ -236,17 +236,17 @@ StyleEditorUI.prototype = {
     this._clear();
     this._suppressAdd = false;
 
     for (let sheet of styleSheets) {
       try {
         yield this._addStyleSheet(sheet);
       } catch (e) {
         console.error(e);
-        this.emit("error", { key: LOAD_ERROR });
+        this.emit("error", { key: LOAD_ERROR, level: "warning" });
       }
     }
 
     this._root.classList.remove("loading");
 
     this.emit("stylesheets-reset");
   }),
 
@@ -402,17 +402,17 @@ StyleEditorUI.prototype = {
       }
       NetUtil.asyncFetch({
         uri: NetUtil.newURI(selectedFile),
         loadingNode: this._window.document,
         securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
         contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
       }, (stream, status) => {
         if (!Components.isSuccessCode(status)) {
-          this.emit("error", { key: LOAD_ERROR });
+          this.emit("error", { key: LOAD_ERROR, level: "warning" });
           return;
         }
         let source =
             NetUtil.readInputStreamToString(stream, stream.available());
         stream.close();
 
         this._suppressAdd = true;
         this._debuggee.addStyleSheet(source).then((styleSheet) => {
--- a/devtools/client/styleeditor/StyleSheetEditor.jsm
+++ b/devtools/client/styleeditor/StyleSheetEditor.jsm
@@ -288,17 +288,18 @@ StyleSheetEditor.prototype = {
     }).catch(e => {
       if (this._isDestroyed) {
         console.warn("Could not fetch the source for " +
                      this.styleSheet.href +
                      ", the editor was destroyed");
         console.error(e);
       } else {
         console.error(e);
-        this.emit("error", { key: LOAD_ERROR, append: this.styleSheet.href });
+        this.emit("error", { key: LOAD_ERROR, append: this.styleSheet.href,
+                             level: "warning" });
         throw e;
       }
     });
   },
 
   /**
    * Add markup to a region. UNUSED_CLASS is added to specified lines
    * @param region An object shaped like
--- a/devtools/client/styleeditor/styleeditor-panel.js
+++ b/devtools/client/styleeditor/styleeditor-panel.js
@@ -82,19 +82,23 @@ StyleEditorPanel.prototype = {
     let errorMessage = getString(data.key);
     if (data.append) {
       errorMessage += " " + data.append;
     }
 
     let notificationBox = this._toolbox.getNotificationBox();
     let notification =
         notificationBox.getNotificationWithValue("styleeditor-error");
-    let level = (data.level === "info") ?
-                notificationBox.PRIORITY_INFO_LOW :
-                notificationBox.PRIORITY_CRITICAL_LOW;
+
+    let level = notificationBox.PRIORITY_CRITICAL_LOW;
+    if (data.level === "info") {
+      level = notificationBox.PRIORITY_INFO_LOW;
+    } else if (data.level === "warning") {
+      level = notificationBox.PRIORITY_WARNING_LOW;
+    }
 
     if (!notification) {
       notificationBox.appendNotification(errorMessage, "styleeditor-error",
                                          "", level);
     }
   },
 
   /**
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -162,17 +162,16 @@ support-files =
   !/devtools/client/netmonitor/test/sjs_cors-test-server.sjs
   !/image/test/mochitest/blue.png
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_console.js]
 [browser_console_addonsdk_loader_exception.js]
-skip-if = true # Bug 1437807
 [browser_console_clear_method.js]
 skip-if = true # Bug 1437843
 [browser_console_consolejsm_output.js]
 [browser_console_context_menu_entries.js]
 [browser_console_dead_objects.js]
 [browser_console_error_source_click.js]
 [browser_console_filters.js]
 [browser_console_nsiconsolemessage.js]
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_console_addonsdk_loader_exception.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_console_addonsdk_loader_exception.js
@@ -2,93 +2,63 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* import-globals-from head.js */
 
 // Check that exceptions from scripts loaded with the addon-sdk loader are
 // opened correctly in View Source from the Browser Console.
-// See bug 866950.
 
 "use strict";
 
-const TEST_URI = "data:text/html;charset=utf8,<p>hello world from bug 866950";
-
-function test() {
-  requestLongerTimeout(2);
-
-  let webconsole, browserconsole;
+const TEST_URI =
+  "data:text/html;charset=utf8,<p>browser_console_addonsdk_loader_exception.js</p>";
 
-  Task.spawn(runner).then(finishTest);
+add_task(async function () {
+  let wcHud = await openNewTabAndConsole(TEST_URI);
+  ok(wcHud, "web console opened");
 
-  function* runner() {
-    let {tab} = yield loadTab(TEST_URI);
-    webconsole = yield openConsole(tab);
-    ok(webconsole, "web console opened");
-
-    browserconsole = yield HUDService.toggleBrowserConsole();
-    ok(browserconsole, "browser console opened");
+  let bcHud = await HUDService.toggleBrowserConsole();
+  ok(bcHud, "browser console opened");
 
-    // Cause an exception in a script loaded with the addon-sdk loader.
-    let toolbox = gDevTools.getToolbox(webconsole.target);
-    let oldPanels = toolbox._toolPanels;
-    // non-iterable
-    toolbox._toolPanels = {};
+  // Cause an exception in a script loaded with the addon-sdk loader.
+  let toolbox = gDevTools.getToolbox(wcHud.target);
+  let oldPanels = toolbox._toolPanels;
+  // non-iterable
+  toolbox._toolPanels = {};
 
-    function fixToolbox() {
-      toolbox._toolPanels = oldPanels;
-    }
-
-    info("generate exception and wait for message");
+  function fixToolbox() {
+    toolbox._toolPanels = oldPanels;
+  }
 
-    executeSoon(() => {
-      executeSoon(fixToolbox);
-      expectUncaughtException();
-      toolbox.getToolPanels();
-    });
+  info("generate exception and wait for message");
 
-    let [result] = yield waitForMessages({
-      webconsole: browserconsole,
-      messages: [{
-        text: "TypeError: this._toolPanels is not iterable",
-        category: CATEGORY_JS,
-        severity: SEVERITY_ERROR,
-      }],
-    });
+  executeSoon(() => {
+    expectUncaughtException();
+    executeSoon(fixToolbox);
+    toolbox.getToolPanels();
+  });
 
-    fixToolbox();
+  let msg = await waitFor(() => findMessage(bcHud,
+    "TypeError: this._toolPanels is not iterable"));
 
-    let msg = [...result.matched][0];
-    ok(msg, "message element found");
-    let locationNode = msg
-      .querySelector(".message .message-location > .frame-link");
-    ok(locationNode, "message location element found");
+  fixToolbox();
 
-    let url = locationNode.getAttribute("data-url");
-    info("location node url: " + url);
-    ok(url.indexOf("resource://") === 0, "error comes from a subscript");
+  ok(msg, `Message found: "TypeError: this._toolPanels is not iterable"`);
 
-    let viewSource = browserconsole.viewSource;
-    let URL = null;
-    let clickPromise = defer();
-    browserconsole.viewSourceInDebugger = (sourceURL) => {
-      info("browserconsole.viewSourceInDebugger() was invoked: " + sourceURL);
-      URL = sourceURL;
-      clickPromise.resolve(null);
-    };
+  let locationNode = msg.querySelector(".message-location .frame-link-source");
+  ok(locationNode, "Message location link element found");
 
-    msg.scrollIntoView();
-    EventUtils.synthesizeMouse(locationNode, 2, 2, {},
-                               browserconsole.iframeWindow);
+  let url = locationNode.href;
+  info("view-source url: " + url);
+  ok(url, "we have some source URL after the click");
+  ok(url.includes("toolbox.js"), "we have the expected view source URL");
+  ok(!url.includes("->"), "no -> in the URL given to view-source");
 
-    info("wait for click on locationNode");
-    yield clickPromise.promise;
+  let onTabOpen = BrowserTestUtils.waitForNewTab(gBrowser, null, true);
+  locationNode.click();
 
-    info("view-source url: " + URL);
-    ok(URL, "we have some source URL after the click");
-    isnot(URL.indexOf("toolbox.js"), -1,
-      "we have the expected view source URL");
-    is(URL.indexOf("->"), -1, "no -> in the URL given to view-source");
+  let newTab = await onTabOpen;
+  ok(true, "The view source tab was opened in response to clicking the link");
 
-    browserconsole.viewSourceInDebugger = viewSource;
-  }
-}
+  await BrowserTestUtils.removeTab(newTab);
+});
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -101,17 +101,17 @@
 #include "nsIStyleSheetService.h"
 #include "nsContentPermissionHelper.h"
 #include "nsCSSPseudoElements.h"            // for CSSPseudoElementType
 #include "nsNetUtil.h"
 #include "nsDocument.h"
 #include "HTMLImageElement.h"
 #include "HTMLCanvasElement.h"
 #include "mozilla/css/ImageLoader.h"
-#include "mozilla/layers/APZCTreeManager.h" // for layers::ZoomToRectBehavior
+#include "mozilla/layers/IAPZCTreeManager.h" // for layers::ZoomToRectBehavior
 #include "mozilla/dom/Promise.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/dom/TimeoutManager.h"
 #include "mozilla/PreloadedStyleSheet.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -295,16 +295,19 @@ GK_ATOM(count, "count")
 GK_ATOM(crop, "crop")
 GK_ATOM(crossorigin, "crossorigin")
 GK_ATOM(curpos, "curpos")
 GK_ATOM(current, "current")
 GK_ATOM(cutoutregion, "cutoutregion")
 GK_ATOM(cycler, "cycler")
 GK_ATOM(data, "data")
 GK_ATOM(datalist, "datalist")
+GK_ATOM(datal10nid, "data-l10n-id")
+GK_ATOM(datal10nargs, "data-l10n-args")
+GK_ATOM(datal10nattrs, "data-l10n-attrs")
 GK_ATOM(dataType, "data-type")
 GK_ATOM(dateTime, "date-time")
 GK_ATOM(datasources, "datasources")
 GK_ATOM(date, "date")
 GK_ATOM(datetime, "datetime")
 GK_ATOM(datetimebox, "datetimebox")
 GK_ATOM(dblclick, "dblclick")
 GK_ATOM(dd, "dd")
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -25,16 +25,19 @@
 #include "mozilla/Telemetry.h"
 #include "mozilla/TextEditor.h"
 #include "mozilla/TimeStamp.h"
 #ifdef MOZ_OLD_STYLE
 #include "mozilla/css/StyleRule.h"
 #endif
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Event.h"
+#include "mozilla/dom/L10nUtilsBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "nsAttrValueOrString.h"
 #include "nsBindingManager.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsContentList.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionParticipant.h"
@@ -3062,8 +3065,222 @@ nsINode::IsStyledByServo() const
 }
 #endif
 
 DocGroup*
 nsINode::GetDocGroup() const
 {
   return OwnerDoc()->GetDocGroup();
 }
+
+class LocalizationHandler : public PromiseNativeHandler
+{
+public:
+  LocalizationHandler() = default;
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(LocalizationHandler)
+
+  nsTArray<nsCOMPtr<Element>>& Elements() { return mElements; }
+
+  void SetReturnValuePromise(Promise* aReturnValuePromise)
+  {
+    mReturnValuePromise = aReturnValuePromise;
+  }
+
+  virtual void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    nsTArray<L10nValue> l10nData;
+    if (aValue.isObject()) {
+      JS::ForOfIterator iter(aCx);
+      if (!iter.init(aValue, JS::ForOfIterator::AllowNonIterable)) {
+        mReturnValuePromise->MaybeRejectWithUndefined();
+        return;
+      }
+      if (!iter.valueIsIterable()) {
+        mReturnValuePromise->MaybeRejectWithUndefined();
+        return;
+      }
+
+      JS::Rooted<JS::Value> temp(aCx);
+      while (true) {
+        bool done;
+        if (!iter.next(&temp, &done)) {
+          mReturnValuePromise->MaybeRejectWithUndefined();
+          return;
+        }
+
+        if (done) {
+          break;
+        }
+
+        L10nValue* slotPtr =
+          l10nData.AppendElement(mozilla::fallible);
+        if (!slotPtr) {
+          mReturnValuePromise->MaybeRejectWithUndefined();
+          return;
+        }
+
+        if (!slotPtr->Init(aCx, temp)) {
+          mReturnValuePromise->MaybeRejectWithUndefined();
+          return;
+        }
+      }
+    }
+
+    if (mElements.Length() != l10nData.Length()) {
+      mReturnValuePromise->MaybeRejectWithUndefined();
+      return;
+    }
+
+    JS::Rooted<JSObject*> untranslatedElements(aCx,
+      JS_NewArrayObject(aCx, mElements.Length()));
+    if (!untranslatedElements) {
+      mReturnValuePromise->MaybeRejectWithUndefined();
+      return;
+    }
+
+    ErrorResult rv;
+    for (size_t i = 0; i < l10nData.Length(); ++i) {
+      Element* elem = mElements[i];
+      nsString& content = l10nData[i].mValue;
+      if (!content.IsVoid()) {
+        elem->SetTextContent(content, rv);
+        if (NS_WARN_IF(rv.Failed())) {
+          mReturnValuePromise->MaybeRejectWithUndefined();
+          return;
+        }
+      }
+
+      Nullable<Sequence<AttributeNameValue>>& attributes =
+        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();
+            return;
+          }
+        }
+      }
+
+      if (content.IsVoid() && attributes.IsNull()) {
+        JS::Rooted<JS::Value> wrappedElem(aCx);
+        if (!ToJSValue(aCx, elem, &wrappedElem)) {
+          mReturnValuePromise->MaybeRejectWithUndefined();
+          return;
+        }
+
+        if (!JS_DefineElement(aCx, untranslatedElements, i, wrappedElem, JSPROP_ENUMERATE)) {
+          mReturnValuePromise->MaybeRejectWithUndefined();
+          return;
+        }
+      }
+    }
+    mReturnValuePromise->MaybeResolve(untranslatedElements);
+  }
+
+  virtual void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    mReturnValuePromise->MaybeRejectWithUndefined();
+  }
+
+private:
+  ~LocalizationHandler() = default;
+
+  nsTArray<nsCOMPtr<Element>> mElements;
+  RefPtr<Promise> mReturnValuePromise;
+};
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LocalizationHandler)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(LocalizationHandler)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(LocalizationHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(LocalizationHandler)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(LocalizationHandler)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mElements)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mReturnValuePromise)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(LocalizationHandler)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElements)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReturnValuePromise)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+
+already_AddRefed<Promise>
+nsINode::Localize(JSContext* aCx,
+                  mozilla::dom::L10nCallback& aCallback,
+                  mozilla::ErrorResult& aRv)
+{
+  Sequence<L10nElement> l10nElements;
+  SequenceRooter<L10nElement> rooter(aCx, &l10nElements);
+  RefPtr<LocalizationHandler> nativeHandler = new LocalizationHandler();
+  nsTArray<nsCOMPtr<Element>>& domElements = nativeHandler->Elements();
+  nsIContent* node = IsContent() ? AsContent() : GetFirstChild();
+  nsAutoString l10nId;
+  nsAutoString l10nArgs;
+  nsAutoString l10nAttrs;
+  nsAutoString type;
+  for (; node; node = node->GetNextNode(this)) {
+    if (!node->IsElement()) {
+      continue;
+    }
+
+    Element* domElement = node->AsElement();
+    if (!domElement->GetAttr(kNameSpaceID_None, nsGkAtoms::datal10nid, l10nId)) {
+      continue;
+    }
+
+    domElement->GetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, l10nArgs);
+    domElement->GetAttr(kNameSpaceID_None, nsGkAtoms::datal10nattrs, l10nAttrs);
+    L10nElement* element = l10nElements.AppendElement(fallible);
+    if (!element) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return nullptr;
+    }
+    domElements.AppendElement(domElement, fallible);
+
+    domElement->GetNamespaceURI(element->mNamespaceURI);
+    element->mLocalName = domElement->LocalName();
+    domElement->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type);
+    if (!type.IsEmpty()) {
+      element->mType = type;
+    }
+    element->mL10nId = l10nId;
+    if (!l10nAttrs.IsEmpty()) {
+      element->mL10nAttrs = l10nAttrs;
+    }
+    if (!l10nArgs.IsEmpty()) {
+      JS::Rooted<JS::Value> json(aCx);
+      if (!JS_ParseJSON(aCx, l10nArgs.get(), l10nArgs.Length(), &json) ||
+          !json.isObject()) {
+        aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+        return nullptr;
+      }
+      element->mL10nArgs = &json.toObject();
+    }
+  }
+
+  RefPtr<Promise> callbackResult = aCallback.Call(l10nElements, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  RefPtr<Promise> promise = Promise::Create(OwnerDoc()->GetParentObject(), aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  nativeHandler->SetReturnValuePromise(promise);
+  callbackResult->AppendNativeHandler(nativeHandler);
+
+  return promise.forget();
+}
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -78,18 +78,20 @@ class AccessibleNode;
 struct BoxQuadOptions;
 struct ConvertCoordinateOptions;
 class DocGroup;
 class DOMPoint;
 class DOMQuad;
 class DOMRectReadOnly;
 class Element;
 class EventHandlerNonNull;
+class L10nCallback;
 template<typename T> class Optional;
 class OwningNodeOrString;
+class Promise;
 template<typename> class Sequence;
 class Text;
 class TextOrElementOrDocument;
 struct DOMPointInit;
 struct GetRootNodeOptions;
 enum class CallerType : uint32_t;
 } // namespace dom
 } // namespace mozilla
@@ -1879,16 +1881,19 @@ public:
   void BindObject(nsISupports* aObject);
   // After calling UnbindObject nsINode object doesn't keep
   // aObject alive anymore.
   void UnbindObject(nsISupports* aObject);
 
   void GetBoundMutationObservers(nsTArray<RefPtr<nsDOMMutationObserver> >& aResult);
   void GenerateXPath(nsAString& aResult);
 
+  already_AddRefed<mozilla::dom::Promise>
+  Localize(JSContext* aCx, mozilla::dom::L10nCallback& aCallback, mozilla::ErrorResult& aRv);
+
   already_AddRefed<mozilla::dom::AccessibleNode> GetAccessibleNode();
 
   /**
    * Returns the length of this node, as specified at
    * <http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node-length>
    */
   uint32_t Length() const;
 
--- a/dom/base/test/chrome/chrome.ini
+++ b/dom/base/test/chrome/chrome.ini
@@ -63,16 +63,17 @@ support-files = ../file_bug357450.js
 [test_bug1346936.html]
 [test_cpows.xul]
 [test_getElementsWithGrid.html]
 [test_custom_element_content.xul]
 [test_custom_element_ep.xul]
 [test_domparsing.xul]
 [test_fileconstructor.xul]
 [test_nsITextInputProcessor.xul]
+[test_node_localize.xul]
 [test_permission_isHandlingUserInput.xul]
 support-files = ../dummy.html
 [test_range_getClientRectsAndTexts.html]
 [test_title.xul]
 support-files = file_title.xul
 [test_windowroot.xul]
 [test_swapFrameLoaders.xul]
 [test_bug1339722.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/test_node_localize.xul
@@ -0,0 +1,143 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1363862
+-->
+<window title="Node.localize - Bug 1363862"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+  xmlns:html="http://www.w3.org/1999/xhtml">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1363862"
+     target="_blank">Mozilla Bug 1363862</a>
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript"><![CDATA[
+
+  /** Test for Bug 1363862 **/
+
+  const translations = {
+    "key1": {
+      value: "Value 1",
+      attrs: null,
+    },
+    "key2": {
+      value: null,
+      attrs: [
+        {name: "label", value: "Value 2"},
+        {name: "accesskey", value: "K"},
+      ]
+    },
+    "key3": {
+      value: "Value 3",
+      attrs: [
+        {name: "accesskey", value: "V"},
+      ]
+    },
+    "key4": undefined,
+    "key5": {
+      value: null,
+      attrs: [
+        {name: "value", value: "Submit Value"},
+      ]
+    }
+  }
+
+  /**
+   * This function serves as an approximation of what Localization does.
+   */
+  async function mockFormatTranslations(l10nItems) {
+    testL10nItems(l10nItems);
+    return l10nItems.map(l10nItem => {
+      return translations[l10nItem.l10nId];
+    });
+  }
+
+
+  /**
+   * This function serves as an approximation of what DOMLocalization does.
+   */
+  async function translateFragment(frag) {
+    const untranslatedElements = await frag.localize(async l10nItems => {
+      const translations = await mockFormatTranslations(l10nItems);
+      return translations;
+    });
+    return untranslatedElements;
+  }
+
+
+  /**
+   * Test items returned from Node.localize to make sure they match what
+   * we would read using JS DOM.
+   */
+  function testL10nItems(l10nItems) {
+    for (l10nItem of l10nItems) {
+      const elem = document.querySelector(`[data-l10n-id=${l10nItem.l10nId}]`);
+      SimpleTest.isDeeply(
+        l10nItem.l10nArgs,
+        JSON.parse(elem.getAttribute("data-l10n-args") || null),
+        "l10nArgs should match data-l10n-args"
+      );
+      ok(l10nItem.l10nAttrs === (elem.getAttribute("data-l10n-attrs") || null),
+        "l10nAttrs should match data-l10n-attrs");
+      ok(l10nItem.localName === elem.localName,
+        "l10nItem.localeName should match elem.localName");
+      ok(l10nItem.namespaceURI === elem.namespaceURI,
+        "l10nItem.namespaceURI should match elem.namespaceURI");
+      ok(l10nItem.type === (elem.getAttribute("type") || null),
+        "l10nItem.type should match elem.type");
+    }
+  }
+
+  async function testLocalization() {
+    const container = document.getElementById("testContainer");
+
+    const untranslatedElements = await translateFragment(container);
+
+    // We will walk through all translations and check if they
+    // were correctly populated onto the DOM.
+    for (const [l10nId, translation] of Object.entries(translations)) {
+      const elem = document.querySelector(`[data-l10n-id=${l10nId}]`);
+
+      // If there is no translation then the element should be returned
+      // as part of the `untranslatedElements`.
+      if (translation === undefined) {
+        const i = Object.keys(translations).indexOf(l10nId);
+        ok(untranslatedElements[i] === elem);
+        continue;
+      }
+
+      if (translation.value !== null) {
+      ok(elem.textContent === translation.value,
+        "element's textContent should be populated with the translation value");
+      }
+
+      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();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  addLoadEvent(testLocalization);
+
+  ]]></script>
+  <box id="testContainer">
+    <label data-l10n-id="key1" />
+    <label data-l10n-id="key2" data-l10n-args='{"unreadCount": 5}' />
+    <label data-l10n-id="key3" />
+    <label data-l10n-id="key4" />
+    <html:input type="submit" data-l10n-id="key5" />
+  </box>
+</window>
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -619,16 +619,17 @@ DOMInterfaces = {
 
 'NetworkInformation': {
     'nativeType': 'mozilla::dom::network::Connection',
 },
 
 'Node': {
     'nativeType': 'nsINode',
     'concrete': False,
+    'implicitJSContext': [ 'localize' ],
 },
 
 'NodeIterator': {
     'wrapperCache': False,
 },
 
 'NodeList': {
     'nativeType': 'nsINodeList',
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -86,17 +86,16 @@
 #include "mozilla/Services.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/HTMLLabelElement.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/LookAndFeel.h"
 #include "GeckoProfiler.h"
 #include "Units.h"
-#include "mozilla/layers/APZCTreeManager.h"
 #include "nsIObjectLoadingContent.h"
 
 #ifdef XP_MACOSX
 #import <ApplicationServices/ApplicationServices.h>
 #endif
 
 namespace mozilla {
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -21,17 +21,16 @@
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/dom/indexedDB/PIndexedDBPermissionRequestChild.h"
 #include "mozilla/dom/PaymentRequestChild.h"
 #include "mozilla/dom/TelemetryScrollProbe.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/layers/APZChild.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
-#include "mozilla/layers/APZCTreeManager.h"
 #include "mozilla/layers/APZCTreeManagerChild.h"
 #include "mozilla/layers/APZEventState.h"
 #include "mozilla/layers/ContentProcessController.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/DoubleTapToZoom.h"
 #include "mozilla/layers/IAPZCTreeManager.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/InputAPZContext.h"
new file mode 100644
--- /dev/null
+++ b/dom/webidl/L10nUtils.webidl
@@ -0,0 +1,38 @@
+/* -*- Mode: IDL; 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/.
+ */
+
+/**
+ * The following dictionaries are for Mozilla use only. They allow startup
+ * localization runtime to work around the performance cost of Stylo having
+ * to resolve XBL bindings in order to localize DOM in JS.
+ *
+ * Instead, we use `Node.localize` method which handles scanning for localizable
+ * elements and applies the result translations without having to create
+ * JS reflections for them.
+ *
+ * For details on the implementation of the API, see `Node.webidl`.
+ */
+dictionary L10nElement {
+  required DOMString namespaceURI;
+  required DOMString localName;
+  required DOMString l10nId; // value of data-l10n-id
+  DOMString? type = null;
+  DOMString? l10nAttrs = null; // value of data-l10n-attrs
+  object? l10nArgs = null; // json value of data-l10n-args attribute
+};
+
+dictionary AttributeNameValue {
+  required DOMString name;
+  required DOMString value;
+};
+
+dictionary L10nValue {
+  DOMString? value = null;
+  sequence<AttributeNameValue>? attrs = null;
+};
+
+callback L10nCallback =
+  Promise<sequence<L10nValue>> (sequence<L10nElement> l10nElements);
--- a/dom/webidl/Node.webidl
+++ b/dom/webidl/Node.webidl
@@ -107,16 +107,117 @@ interface Node : EventTarget {
   readonly attribute Principal nodePrincipal;
   [ChromeOnly]
   readonly attribute URI? baseURIObject;
   [ChromeOnly]
   sequence<MutationObserver> getBoundMutationObservers();
   [ChromeOnly]
   DOMString generateXPath();
 
+  /**
+   * This method provides a fast-path for the Fluent localization system to
+   * bypass the slowdowns in performance during initial document translation.
+   * The slowdowns are specific to XBL+Stylo.
+   * To learn more, see bug 1441037.
+   *
+   * The API is designed to fit into the DOMLocalization flow with minimal
+   * overhead, which dictates much of its signature.
+   * It takes the following steps:
+   *
+   * 1) The API can be called at any point on any DOM element and it
+   *    synchronously scans the element subtree for all children with
+   *    `data-l10n-id` attribute set.
+   *
+   * 2) Next, the API collects all of the l10n attributes
+   *    (l10n-id, l10n-args and l10n-attrs), and passes them to the
+   *    callback function together with three `Element` properties:
+   *      `name` - name of the element as lowercase
+   *      `namespaceURI` - namespace URI
+   *      `type` - the type prop of the element (used for input sanitization)
+   *
+   * 3) The callback function is responsible for (asynchronously) collecting
+   *    the translations for all l10n id+args pairs, sanitizing them and then
+   *    return them back to this API.
+   *
+   * 4) The API takes the list of elements collected in step (1) and their
+   *    translations and applies all of the translation values onto
+   *    the elements.
+   *
+   * 5) The API returns a list with empty slots for all translated elements
+   *    and references to elements that could not be translated.
+   *
+   * 6) The JS handles the translations of remaining elements.
+   *
+   *
+   * Through the whole cycle, the API uses the same list of elements and
+   * corresponding translations. It means that after step (1), the element
+   * at index 1 will match the l10nData at index 1, translations at index 1
+   * and in the final return list, the element will be also stored at index 1
+   * or the slot will be empty if the translations was applied on the C++ side.
+   *
+   * Note: There are several reasons why the JS callback may pass undefined for
+   *       a given element including missing translation, or the need to
+   *       translate the element using DOM Overlays.
+   *
+   *
+   * Example of use from JS:
+   *
+   * async function translateFragment(frag) {
+   *   let untranslatedElements = await frag.localize(
+   *     async cb(l10nItems) => {                          // 1
+   *       let trans = await getTranslations(l10nItems);   // 2
+   *       return trans;
+   *     }
+   *   );
+   *
+   *   annotateMissingTranslations(untranslatedElements);  // 3
+   * }
+   *
+   * [1] l10nItems == [
+   *       {
+   *         l10nId: "key1",
+   *         l10nArgs: null,
+   *         l10nAttrs: null,
+   *         name: "button"
+   *         namespaceURI: "..."
+   *         type: null
+   *       },
+   *       {
+   *         l10nId: "key2",
+   *         l10nArgs: {unreadCount: 5},
+   *         l10nAttrs: null,
+   *         name: "label"
+   *         namespaceURI: "..."
+   *         type: null
+   *       },
+   *       {
+   *         l10nId: "key3",
+   *         l10nArgs: null,
+   *         l10nAttrs: "title",
+   *         name: "window"
+   *         namespaceURI: "..."
+   *         type: null
+   *       },
+   *     ]
+   * [2] trans == [
+   *       {value: "Key 1", attrs: {accesskey: "K"} },
+   *       undefined,
+   *       {value: null, attrs: {title: "Unread emails: 5"} },
+   *     ]
+   * [3] untranslatedElements == [
+   *       ,
+   *       <label>
+   *       ,
+   *     ]
+   *
+   * For exact dictionary structures, see `L10nUtils.webidl`.
+   */
+  [ChromeOnly, Throws]
+  Promise<void> localize(L10nCallback l10nCallback);
+
 #ifdef ACCESSIBILITY
   [Pref="accessibility.AOM.enabled"]
   readonly attribute AccessibleNode? accessibleNode;
 #endif
 };
 
 dictionary GetRootNodeOptions {
   boolean composed = false;
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -640,16 +640,17 @@ WEBIDL_FILES = [
     'IntlUtils.webidl',
     'IterableIterator.webidl',
     'KeyAlgorithm.webidl',
     'KeyboardEvent.webidl',
     'KeyEvent.webidl',
     'KeyframeAnimationOptions.webidl',
     'KeyframeEffect.webidl',
     'KeyIdsInitData.webidl',
+    'L10nUtils.webidl',
     'LegacyQueryInterface.webidl',
     'LinkStyle.webidl',
     'ListBoxObject.webidl',
     'LocalMediaStream.webidl',
     'Location.webidl',
     'MatchGlob.webidl',
     'MatchPattern.webidl',
     'MediaDeviceInfo.webidl',
--- a/gfx/ipc/GPUParent.cpp
+++ b/gfx/ipc/GPUParent.cpp
@@ -17,17 +17,17 @@
 #include "mozilla/dom/MemoryReportRequest.h"
 #include "mozilla/dom/VideoDecoderManagerChild.h"
 #include "mozilla/dom/VideoDecoderManagerParent.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/ipc/CrashReporterClient.h"
 #include "mozilla/ipc/ProcessChild.h"
 #include "mozilla/layers/APZThreadUtils.h"
-#include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/APZUtils.h"    // for apz::InitializeGlobalState
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorManagerParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/LayerTreeOwnerTracker.h"
 #include "mozilla/layers/UiCompositorControllerParent.h"
 #include "mozilla/layers/MemoryReportingMLGPU.h"
 #include "mozilla/layers/SharedSurfacesParent.h"
@@ -121,17 +121,17 @@ GPUParent::Init(base::ProcessId aParentP
   if (NS_FAILED(NS_InitMinimalXPCOM())) {
     return false;
   }
 
   CompositorThreadHolder::Start();
   // TODO: Bug 1406327, Start VRListenerThreadHolder when loading VR content.
   VRListenerThreadHolder::Start();
   APZThreadUtils::SetControllerThread(CompositorThreadHolder::Loop());
-  APZCTreeManager::InitializeGlobalState();
+  apz::InitializeGlobalState();
   LayerTreeOwnerTracker::Initialize();
   mozilla::ipc::SetThisProcessName("GPU Process");
 #ifdef XP_WIN
   wmf::MFStartup();
 #endif
   return true;
 }
 
--- a/gfx/ipc/GPUProcessManager.cpp
+++ b/gfx/ipc/GPUProcessManager.cpp
@@ -9,17 +9,16 @@
 #include "gfxPrefs.h"
 #include "GPUProcessHost.h"
 #include "GPUProcessListener.h"
 #include "mozilla/MemoryReportingProcess.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/gfx/gfxVars.h"
-#include "mozilla/layers/APZCTreeManager.h"
 #include "mozilla/layers/APZCTreeManagerChild.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorManagerChild.h"
 #include "mozilla/layers/CompositorManagerParent.h"
 #include "mozilla/layers/CompositorOptions.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/InProcessCompositorSession.h"
--- a/gfx/layers/apz/public/APZSampler.h
+++ b/gfx/layers/apz/public/APZSampler.h
@@ -4,18 +4,27 @@
  * 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_layers_APZSampler_h
 #define mozilla_layers_APZSampler_h
 
 #include "mozilla/layers/APZTestData.h"
 #include "mozilla/Maybe.h"
+#include "nsTArray.h"
 
 namespace mozilla {
+
+class TimeStamp;
+
+namespace wr {
+class TransactionBuilder;
+struct WrTransformProperty;
+} // namespace wr
+
 namespace layers {
 
 class APZCTreeManager;
 class FocusTarget;
 class Layer;
 class WebRenderScrollData;
 
 /**
@@ -43,18 +52,29 @@ public:
                             bool aIsFirstPaint,
                             uint64_t aOriginatingLayersId,
                             uint32_t aPaintSequenceNumber);
 
   void NotifyLayerTreeAdopted(uint64_t aLayersId,
                               const RefPtr<APZSampler>& aOldSampler);
   void NotifyLayerTreeRemoved(uint64_t aLayersId);
 
+  bool PushStateToWR(wr::TransactionBuilder& aTxn,
+                     const TimeStamp& aSampleTime,
+                     nsTArray<wr::WrTransformProperty>& aTransformArray);
+
   bool GetAPZTestData(uint64_t aLayersId, APZTestData* aOutData);
 
+  void SetTestAsyncScrollOffset(uint64_t aLayersId,
+                                const FrameMetrics::ViewID& aScrollId,
+                                const CSSPoint& aOffset);
+  void SetTestAsyncZoom(uint64_t aLayersId,
+                        const FrameMetrics::ViewID& aScrollId,
+                        const LayerToParentLayerScale& aZoom);
+
 protected:
   virtual ~APZSampler();
 
 private:
   RefPtr<APZCTreeManager> mApz;
 };
 
 } // namespace layers
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -19,17 +19,16 @@
 #include "mozilla/gfx/gfxVars.h"        // for gfxVars
 #include "mozilla/gfx/GPUParent.h"      // for GPUParent
 #include "mozilla/gfx/Logging.h"        // for gfx::TreeLog
 #include "mozilla/gfx/Point.h"          // for Point
 #include "mozilla/layers/APZThreadUtils.h"  // for AssertOnControllerThread, etc
 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
 #include "mozilla/layers/AsyncDragMetrics.h" // for AsyncDragMetrics
 #include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc
-#include "mozilla/layers/FocusState.h"  // for FocusState
 #include "mozilla/layers/LayerMetricsWrapper.h"
 #include "mozilla/layers/WebRenderScrollDataWrapper.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/mozalloc.h"           // for operator new
 #include "mozilla/TouchEvents.h"
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/EventStateManager.h"  // for WheelPrefs
 #include "mozilla/webrender/WebRenderAPI.h"
@@ -218,25 +217,16 @@ public:
   }
 
 private:
   FocusState& mFocusState;
   InputData& mEvent;
   bool mMayChangeFocus;
 };
 
-/*static*/ const ScreenMargin
-APZCTreeManager::CalculatePendingDisplayPort(
-  const FrameMetrics& aFrameMetrics,
-  const ParentLayerPoint& aVelocity)
-{
-  return AsyncPanZoomController::CalculatePendingDisplayPort(
-    aFrameMetrics, aVelocity);
-}
-
 APZCTreeManager::APZCTreeManager(uint64_t aRootLayersId)
     : mInputQueue(new InputQueue()),
       mRootLayersId(aRootLayersId),
       mTreeLock("APZCTreeLock"),
       mHitResultForInputBlock(CompositorHitTestInfo::eInvisibleToHitTest),
       mRetainedTouchIdentifier(-1),
       mInScrollbarTouchDrag(false),
       mApzcTreeLog("apzctree"),
@@ -253,23 +243,16 @@ APZCTreeManager::APZCTreeManager(uint64_
   mToolbarAnimator = new AndroidDynamicToolbarAnimator();
 #endif // (MOZ_WIDGET_ANDROID)
 }
 
 APZCTreeManager::~APZCTreeManager()
 {
 }
 
-/*static*/ void
-APZCTreeManager::InitializeGlobalState()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  AsyncPanZoomController::InitializeGlobalState();
-}
-
 void
 APZCTreeManager::NotifyLayerTreeAdopted(uint64_t aLayersId,
                                         const RefPtr<APZCTreeManager>& aOldApzcTreeManager)
 {
   APZThreadUtils::AssertOnSamplerThread();
 
   MOZ_ASSERT(aOldApzcTreeManager);
   aOldApzcTreeManager->mFocusState.RemoveFocusTarget(aLayersId);
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -4,31 +4,31 @@
  * 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_layers_APZCTreeManager_h
 #define mozilla_layers_APZCTreeManager_h
 
 #include <unordered_map>                          // for std::unordered_map
 
+#include "FocusState.h"                 // for FocusState
 #include "gfxPoint.h"                   // for gfxPoint
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT_HELPER2
 #include "mozilla/gfx/CompositorHitTestInfo.h"
 #include "mozilla/gfx/Logging.h"        // for gfx::TreeLog
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
 #include "mozilla/layers/APZTestData.h" // for APZTestData
-#include "mozilla/layers/FocusState.h"  // for FocusState
 #include "mozilla/layers/IAPZCTreeManager.h" // for IAPZCTreeManager
 #include "mozilla/layers/KeyboardMap.h" // for KeyboardMap
-#include "mozilla/layers/TouchCounter.h"// for TouchCounter
 #include "mozilla/RecursiveMutex.h"     // for RecursiveMutex
 #include "mozilla/RefPtr.h"             // for RefPtr
 #include "mozilla/TimeStamp.h"          // for mozilla::TimeStamp
 #include "mozilla/UniquePtr.h"          // for UniquePtr
 #include "nsCOMPtr.h"                   // for already_AddRefed
+#include "TouchCounter.h"               // for TouchCounter
 
 #if defined(MOZ_WIDGET_ANDROID)
 #include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
 #endif // defined(MOZ_WIDGET_ANDROID)
 
 namespace mozilla {
 class MultiTouchInput;
 
@@ -108,24 +108,16 @@ class APZCTreeManager : public IAPZCTree
   // UpdateHitTestingTree. All the state that we don't need to
   // push on the stack during recursion and pop on unwind is stored here.
   struct TreeBuildingState;
 
 public:
   explicit APZCTreeManager(uint64_t aRootLayersId);
 
   /**
-   * Initializes the global state used in AsyncPanZoomController.
-   * This is normally called when it is first needed in the constructor
-   * of APZCTreeManager, but can be called manually to force it to be
-   * initialized earlier.
-   */
-  static void InitializeGlobalState();
-
-  /**
    * Notifies this APZCTreeManager that the associated compositor is now
    * responsible for managing another layers id, which got moved over from
    * some other compositor. That other compositor's APZCTreeManager is also
    * provided. This allows APZCTreeManager to transfer any necessary state
    * from the old APZCTreeManager related to that layers id.
    * This function must be called on the sampler thread.
    */
   void NotifyLayerTreeAdopted(uint64_t aLayersId,
@@ -342,25 +334,16 @@ public:
   void ClearTree();
 
   /**
    * Tests if a screen point intersect an apz in the tree.
    */
   bool HitTestAPZC(const ScreenIntPoint& aPoint);
 
   /**
-   * See AsyncPanZoomController::CalculatePendingDisplayPort. This
-   * function simply delegates to that one, so that non-layers code
-   * never needs to include AsyncPanZoomController.h
-   */
-  static const ScreenMargin CalculatePendingDisplayPort(
-    const FrameMetrics& aFrameMetrics,
-    const ParentLayerPoint& aVelocity);
-
-  /**
    * Sets the dpi value used by all AsyncPanZoomControllers.
    * DPI defaults to 72 if not set using SetDPI() at any point.
    */
   void SetDPI(float aDpiValue) override { sDPI = aDpiValue; }
 
   /**
    * Returns the current dpi value in use.
    */
--- a/gfx/layers/apz/src/APZSampler.cpp
+++ b/gfx/layers/apz/src/APZSampler.cpp
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/APZSampler.h"
 
-#include "mozilla/layers/APZCTreeManager.h"
+#include "APZCTreeManager.h"
 #include "mozilla/layers/CompositorThread.h"
 
 namespace mozilla {
 namespace layers {
 
 APZSampler::APZSampler(const RefPtr<APZCTreeManager>& aApz)
   : mApz(aApz)
 {
@@ -73,17 +73,55 @@ APZSampler::NotifyLayerTreeAdopted(uint6
 void
 APZSampler::NotifyLayerTreeRemoved(uint64_t aLayersId)
 {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   mApz->NotifyLayerTreeRemoved(aLayersId);
 }
 
 bool
+APZSampler::PushStateToWR(wr::TransactionBuilder& aTxn,
+                          const TimeStamp& aSampleTime,
+                          nsTArray<wr::WrTransformProperty>& aTransformArray)
+{
+  // This function will be removed eventually since we'll have WR pull
+  // the transforms from APZ instead.
+  return mApz->PushStateToWR(aTxn, aSampleTime, aTransformArray);
+}
+
+bool
 APZSampler::GetAPZTestData(uint64_t aLayersId,
                            APZTestData* aOutData)
 {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   return mApz->GetAPZTestData(aLayersId, aOutData);
 }
 
+void
+APZSampler::SetTestAsyncScrollOffset(uint64_t aLayersId,
+                                     const FrameMetrics::ViewID& aScrollId,
+                                     const CSSPoint& aOffset)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  RefPtr<AsyncPanZoomController> apzc = mApz->GetTargetAPZC(aLayersId, aScrollId);
+  if (apzc) {
+    apzc->SetTestAsyncScrollOffset(aOffset);
+  } else {
+    NS_WARNING("Unable to find APZC in SetTestAsyncScrollOffset");
+  }
+}
+
+void
+APZSampler::SetTestAsyncZoom(uint64_t aLayersId,
+                             const FrameMetrics::ViewID& aScrollId,
+                             const LayerToParentLayerScale& aZoom)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  RefPtr<AsyncPanZoomController> apzc = mApz->GetTargetAPZC(aLayersId, aScrollId);
+  if (apzc) {
+    apzc->SetTestAsyncZoom(aZoom);
+  } else {
+    NS_WARNING("Unable to find APZC in SetTestAsyncZoom");
+  }
+}
+
 } // namespace layers
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/APZUtils.cpp
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/layers/APZUtils.h"
+
+#include "AsyncPanZoomController.h"
+
+namespace mozilla {
+namespace layers {
+namespace apz {
+
+/*static*/ void
+InitializeGlobalState()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  AsyncPanZoomController::InitializeGlobalState();
+}
+
+/*static*/ const ScreenMargin
+CalculatePendingDisplayPort(const FrameMetrics& aFrameMetrics,
+                            const ParentLayerPoint& aVelocity)
+{
+  return AsyncPanZoomController::CalculatePendingDisplayPort(
+      aFrameMetrics, aVelocity);
+}
+
+} // namespace apz
+} // namespace layers
+} // namespace mozilla
--- a/gfx/layers/apz/src/APZUtils.h
+++ b/gfx/layers/apz/src/APZUtils.h
@@ -3,16 +3,17 @@
 /* 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_layers_APZUtils_h
 #define mozilla_layers_APZUtils_h
 
 #include <stdint.h>                     // for uint32_t
+#include "FrameMetrics.h"
 #include "LayersTypes.h"
 #include "UnitTransforms.h"
 #include "mozilla/gfx/CompositorHitTestInfo.h"
 #include "mozilla/gfx/Point.h"
 #include "mozilla/EnumSet.h"
 #include "mozilla/FloatingPoint.h"
 
 namespace mozilla {
@@ -88,12 +89,32 @@ struct TargetConfirmationFlags {
                        !(aHitTestInfo & gfx::CompositorHitTestInfo::eDispatchToContent))
     , mRequiresTargetConfirmation(aHitTestInfo & gfx::CompositorHitTestInfo::eRequiresTargetConfirmation)
   {}
 
   bool mTargetConfirmed : 1;
   bool mRequiresTargetConfirmation : 1;
 };
 
+namespace apz {
+
+/**
+ * Initializes the global state used in AsyncPanZoomController.
+ * This is normally called when it is first needed in the constructor
+ * of APZCTreeManager, but can be called manually to force it to be
+ * initialized earlier.
+ */
+void InitializeGlobalState();
+
+/**
+ * See AsyncPanZoomController::CalculatePendingDisplayPort. This
+ * function simply delegates to that one, so that non-layers code
+ * never needs to include AsyncPanZoomController.h
+ */
+const ScreenMargin CalculatePendingDisplayPort(const FrameMetrics& aFrameMetrics,
+                                               const ParentLayerPoint& aVelocity);
+
+} // namespace apz
+
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_APZUtils_h
--- a/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
+++ b/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
@@ -2,23 +2,24 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
 
 #include <cmath>
+
+#include "APZCTreeManager.h"
 #include "FrameMetrics.h"
 #include "gfxPrefs.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Types.h"
-#include "mozilla/layers/APZCTreeManager.h"
 #include "mozilla/layers/APZThreadUtils.h"
 #include "mozilla/layers/AsyncCompositionManager.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorOGL.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/UiCompositorControllerMessageTypes.h"
 #include "mozilla/layers/UiCompositorControllerParent.h"
 #include "mozilla/MathAlgorithms.h"
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1,19 +1,23 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "AsyncPanZoomController.h"     // for AsyncPanZoomController, etc
+
 #include <math.h>                       // for fabsf, fabs, atan2
 #include <stdint.h>                     // for uint32_t, uint64_t
 #include <sys/types.h>                  // for int32_t
 #include <algorithm>                    // for max, min
-#include "AsyncPanZoomController.h"     // for AsyncPanZoomController, etc
+
+#include "APZCTreeManager.h"            // for APZCTreeManager
+#include "AsyncPanZoomAnimation.h"      // for AsyncPanZoomAnimation
 #include "AutoscrollAnimation.h"        // for AutoscrollAnimation
 #include "Axis.h"                       // for AxisX, AxisY, Axis, etc
 #include "CheckerboardEvent.h"          // for CheckerboardEvent
 #include "Compositor.h"                 // for Compositor
 #include "FrameMetrics.h"               // for FrameMetrics, etc
 #include "GenericFlingAnimation.h"      // for GenericFlingAnimation
 #include "GestureEventListener.h"       // for GestureEventListener
 #include "HitTestingTreeNode.h"         // for HitTestingTreeNode
@@ -45,17 +49,16 @@
 #include "mozilla/dom/CheckerboardReportService.h" // for CheckerboardEventStorage
              // note: CheckerboardReportService.h actually lives in gfx/layers/apz/util/
 #include "mozilla/dom/Touch.h"          // for Touch
 #include "mozilla/gfx/BasePoint.h"      // for BasePoint
 #include "mozilla/gfx/BaseRect.h"       // for BaseRect
 #include "mozilla/gfx/Point.h"          // for Point, RoundedToInt, etc
 #include "mozilla/gfx/Rect.h"           // for RoundedIn
 #include "mozilla/gfx/ScaleFactor.h"    // for ScaleFactor
-#include "mozilla/layers/APZCTreeManager.h"  // for ScrollableLayerGuid
 #include "mozilla/layers/APZThreadUtils.h"  // for AssertOnControllerThread, etc
 #include "mozilla/layers/AsyncCompositionManager.h"  // for ViewTransform
 #include "mozilla/layers/AxisPhysicsModel.h" // for AxisPhysicsModel
 #include "mozilla/layers/AxisPhysicsMSDModel.h" // for AxisPhysicsMSDModel
 #include "mozilla/layers/CompositorController.h" // for CompositorController
 #include "mozilla/layers/DirectionUtils.h"   // for GetAxis{Start,End,Length,Scale}
 #include "mozilla/layers/LayerTransactionParent.h" // for LayerTransactionParent
 #include "mozilla/layers/MetricsSharingController.h" // for MetricsSharingController
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -4,18 +4,16 @@
  * 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_layers_AsyncPanZoomController_h
 #define mozilla_layers_AsyncPanZoomController_h
 
 #include "CrossProcessMutex.h"
 #include "mozilla/layers/GeckoContentController.h"
-#include "mozilla/layers/APZCTreeManager.h"
-#include "mozilla/layers/AsyncPanZoomAnimation.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/RecursiveMutex.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Atomics.h"
 #include "InputData.h"
@@ -38,28 +36,31 @@ namespace ipc {
 
 class SharedMemoryBasic;
 
 } // namespace ipc
 
 namespace layers {
 
 class AsyncDragMetrics;
+class APZCTreeManager;
 struct ScrollableLayerGuid;
 class CompositorController;
 class MetricsSharingController;
 class GestureEventListener;
 struct AsyncTransform;
 class AsyncPanZoomAnimation;
 class AndroidFlingAnimation;
 class GenericFlingAnimation;
 class InputBlockState;
+struct FlingHandoffState;
 class TouchBlockState;
 class PanGestureBlockState;
 class OverscrollHandoffChain;
+struct OverscrollHandoffState;
 class StateChangeNotificationBlocker;
 class CheckerboardEvent;
 class OverscrollEffectBase;
 class WidgetOverscrollEffect;
 class GenericOverscrollEffect;
 class AndroidSpecificState;
 struct KeyboardScrollAction;
 
--- a/gfx/layers/apz/src/Axis.cpp
+++ b/gfx/layers/apz/src/Axis.cpp
@@ -1,19 +1,21 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Axis.h"
+
 #include <math.h>                       // for fabsf, pow, powf
 #include <algorithm>                    // for max
+
+#include "APZCTreeManager.h"            // for APZCTreeManager
 #include "AsyncPanZoomController.h"     // for AsyncPanZoomController
-#include "mozilla/layers/APZCTreeManager.h" // for APZCTreeManager
 #include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread
 #include "FrameMetrics.h"               // for FrameMetrics
 #include "mozilla/Attributes.h"         // for final
 #include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/gfx/Rect.h"           // for RoundedIn
 #include "mozilla/mozalloc.h"           // for operator new
 #include "mozilla/FloatingPoint.h"      // for FuzzyEqualsAdditive
--- a/gfx/layers/apz/src/FocusState.cpp
+++ b/gfx/layers/apz/src/FocusState.cpp
@@ -1,15 +1,15 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "mozilla/layers/FocusState.h"
+#include "FocusState.h"
 
 // #define FS_LOG(...) printf_stderr("FS: " __VA_ARGS__)
 #define FS_LOG(...)
 
 namespace mozilla {
 namespace layers {
 
 FocusState::FocusState()
--- a/gfx/layers/apz/src/InputBlockState.cpp
+++ b/gfx/layers/apz/src/InputBlockState.cpp
@@ -1,21 +1,23 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "InputBlockState.h"
+
+#include "APZCTreeManager.h"                // for APZCTreeManager::GetDPI
 #include "AsyncPanZoomController.h"         // for AsyncPanZoomController
 #include "ScrollAnimationPhysics.h"         // for kScrollSeriesTimeoutMs
 #include "gfxPrefs.h"                       // for gfxPrefs
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Telemetry.h"              // for Telemetry
-#include "mozilla/layers/APZCTreeManager.h" // for AllowedTouchBehavior
+#include "mozilla/layers/IAPZCTreeManager.h" // for AllowedTouchBehavior
 #include "OverscrollHandoffState.h"
 #include "QueuedInput.h"
 
 #define TBS_LOG(...)
 // #define TBS_LOG(...) printf_stderr("TBS: " __VA_ARGS__)
 
 namespace mozilla {
 namespace layers {
--- a/gfx/layers/apz/test/gtest/APZTestCommon.h
+++ b/gfx/layers/apz/test/gtest/APZTestCommon.h
@@ -14,21 +14,21 @@
 
 #include "gtest/gtest.h"
 #include "gmock/gmock.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
 #include "mozilla/layers/GeckoContentController.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
-#include "mozilla/layers/APZCTreeManager.h"
 #include "mozilla/layers/LayerMetricsWrapper.h"
 #include "mozilla/layers/APZThreadUtils.h"
 #include "mozilla/TypedEnumBits.h"
 #include "mozilla/UniquePtr.h"
+#include "apz/src/APZCTreeManager.h"
 #include "apz/src/AsyncPanZoomController.h"
 #include "apz/src/HitTestingTreeNode.h"
 #include "base/task.h"
 #include "Layers.h"
 #include "TestLayers.h"
 #include "UnitTransforms.h"
 #include "gfxPrefs.h"
 
--- a/gfx/layers/apz/test/mochitest/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest/mochitest.ini
@@ -79,9 +79,9 @@
   skip-if = (toolkit == 'android') || (toolkit == 'cocoa') # wheel events not supported on mobile, and synthesized wheel smooth-scrolling not supported on OS X
 [test_wheel_scroll.html]
   skip-if = (os == 'android') # wheel events not supported on mobile
 [test_wheel_transactions.html]
   skip-if = (toolkit == 'android') || webrender # wheel events not supported on mobile; bug 1429521 for webrender
 [test_group_overrides.html]
   skip-if = (toolkit == 'android') || webrender # wheel events not supported on mobile; bug 1429521 for webrender
 [test_group_hittest.html]
-  skip-if = (toolkit == 'android') # mouse events not supported on mobile
+  skip-if = (toolkit == 'android') || webrender # mouse events not supported on mobile; re-enable on webrender when bug 1391318 is fixed
--- a/gfx/layers/apz/util/TouchActionHelper.cpp
+++ b/gfx/layers/apz/util/TouchActionHelper.cpp
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "TouchActionHelper.h"
 
-#include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
 #include "nsContainerFrame.h"
 #include "nsIScrollableFrame.h"
 #include "nsLayoutUtils.h"
 
 namespace mozilla {
 namespace layers {
 
 void
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -34,16 +34,17 @@
 #include "nsRect.h"                     // for mozilla::gfx::IntRect
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl, etc
 #include "nsTArrayForwardDeclare.h"     // for InfallibleTArray
 #include "UnitTransforms.h"             // for TransformTo
 #include "gfxPrefs.h"
 #if defined(MOZ_WIDGET_ANDROID)
 # include <android/log.h>
+# include "apz/src/APZCTreeManager.h"
 # include "mozilla/layers/UiCompositorControllerParent.h"
 # include "mozilla/widget/AndroidCompositorWidget.h"
 #endif
 #include "GeckoProfiler.h"
 #include "FrameUniformityData.h"
 #include "TreeTraversal.h"              // for ForEachNode, BreadthFirstSearch
 #include "VsyncSource.h"
 
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -52,16 +52,17 @@
 #include "nsDebug.h"                    // for NS_WARNING, etc
 #include "nsISupportsImpl.h"            // for Layer::AddRef, etc
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsRect.h"                     // for mozilla::gfx::IntRect
 #include "nsRegion.h"                   // for nsIntRegion, etc
 #if defined(MOZ_WIDGET_ANDROID)
 #include <android/log.h>
 #include <android/native_window.h>
+#include "apz/src/APZCTreeManager.h"
 #include "mozilla/widget/AndroidCompositorWidget.h"
 #include "opengl/CompositorOGL.h"
 #include "GLConsts.h"
 #include "GLContextEGL.h"
 #include "GLContextProvider.h"
 #include "mozilla/Unused.h"
 #include "mozilla/widget/AndroidCompositorWidget.h"
 #include "ScopedGLHelpers.h"
--- a/gfx/layers/ipc/APZCTreeManagerParent.cpp
+++ b/gfx/layers/ipc/APZCTreeManagerParent.cpp
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/APZCTreeManagerParent.h"
 
-#include "mozilla/layers/APZCTreeManager.h"
+#include "apz/src/APZCTreeManager.h"
 #include "mozilla/layers/APZThreadUtils.h"
 
 namespace mozilla {
 namespace layers {
 
 APZCTreeManagerParent::APZCTreeManagerParent(uint64_t aLayersId, RefPtr<APZCTreeManager> aAPZCTreeManager)
   : mLayersId(aLayersId)
   , mTreeManager(aAPZCTreeManager)
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -1,19 +1,22 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/CompositorBridgeParent.h"
+
 #include <stdio.h>                      // for fprintf, stdout
 #include <stdint.h>                     // for uint64_t
 #include <map>                          // for _Rb_tree_iterator, etc
 #include <utility>                      // for pair
+
+#include "apz/src/APZCTreeManager.h"    // for APZCTreeManager
 #include "LayerTransactionParent.h"     // for LayerTransactionParent
 #include "RenderTrace.h"                // for RenderTraceLayers
 #include "base/message_loop.h"          // for MessageLoop
 #include "base/process.h"               // for ProcessId
 #include "base/task.h"                  // for CancelableTask, etc
 #include "base/thread.h"                // for Thread
 #include "gfxContext.h"                 // for gfxContext
 #include "gfxPlatform.h"                // for gfxPlatform
@@ -32,17 +35,16 @@
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/gfx/Rect.h"          // for IntSize
 #include "mozilla/gfx/gfxVars.h"        // for gfxVars
 #include "VRManager.h"                  // for VRManager
 #include "mozilla/ipc/Transport.h"      // for Transport
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/GPUParent.h"
 #include "mozilla/layers/AnimationHelper.h" // for CompositorAnimationStorage
-#include "mozilla/layers/APZCTreeManager.h"  // for APZCTreeManager
 #include "mozilla/layers/APZCTreeManagerParent.h"  // for APZCTreeManagerParent
 #include "mozilla/layers/APZSampler.h"  // for APZSampler
 #include "mozilla/layers/APZThreadUtils.h"  // for APZThreadUtils
 #include "mozilla/layers/AsyncCompositionManager.h"
 #include "mozilla/layers/BasicCompositor.h"  // for BasicCompositor
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorManagerParent.h" // for CompositorManagerParent
 #include "mozilla/layers/CompositorOGL.h"  // for CompositorOGL
@@ -1141,16 +1143,27 @@ CompositorBridgeParent::AllocPAPZCTreeMa
 
 bool
 CompositorBridgeParent::DeallocPAPZCTreeManagerParent(PAPZCTreeManagerParent* aActor)
 {
   delete aActor;
   return true;
 }
 
+void
+CompositorBridgeParent::AllocateAPZCTreeManagerParent(const MonitorAutoLock& aProofOfLayerTreeStateLock,
+                                                      const uint64_t& aLayersId,
+                                                      LayerTreeState& aState)
+{
+  MOZ_ASSERT(aState.mParent == this);
+  MOZ_ASSERT(mApzcTreeManager);
+  MOZ_ASSERT(!aState.mApzcTreeManagerParent);
+  aState.mApzcTreeManagerParent = new APZCTreeManagerParent(aLayersId, mApzcTreeManager);
+}
+
 PAPZParent*
 CompositorBridgeParent::AllocPAPZParent(const uint64_t& aLayersId)
 {
   // The main process should pass in 0 because we assume mRootLayerTreeID
   MOZ_ASSERT(aLayersId == 0);
 
   RemoteContentController* controller = new RemoteContentController();
 
@@ -1169,21 +1182,23 @@ CompositorBridgeParent::AllocPAPZParent(
 bool
 CompositorBridgeParent::DeallocPAPZParent(PAPZParent* aActor)
 {
   RemoteContentController* controller = static_cast<RemoteContentController*>(aActor);
   controller->Release();
   return true;
 }
 
+#if defined(MOZ_WIDGET_ANDROID)
 RefPtr<APZCTreeManager>
 CompositorBridgeParent::GetAPZCTreeManager()
 {
   return mApzcTreeManager;
 }
+#endif
 
 RefPtr<APZSampler>
 CompositorBridgeParent::GetAPZSampler()
 {
   return mApzSampler;
 }
 
 CompositorBridgeParent*
@@ -1363,16 +1378,40 @@ CompositorBridgeParent::GetAnimationStor
 mozilla::ipc::IPCResult
 CompositorBridgeParent::RecvGetFrameUniformity(FrameUniformityData* aOutData)
 {
   mCompositionManager->GetFrameUniformity(aOutData);
   return IPC_OK();
 }
 
 void
+CompositorBridgeParent::SetTestAsyncScrollOffset(
+    const uint64_t& aLayersId,
+    const FrameMetrics::ViewID& aScrollId,
+    const CSSPoint& aPoint)
+{
+  if (mApzSampler) {
+    uint64_t layersId = (aLayersId == 0 ? mRootLayerTreeID : aLayersId);
+    mApzSampler->SetTestAsyncScrollOffset(layersId, aScrollId, aPoint);
+  }
+}
+
+void
+CompositorBridgeParent::SetTestAsyncZoom(
+    const uint64_t& aLayersId,
+    const FrameMetrics::ViewID& aScrollId,
+    const LayerToParentLayerScale& aZoom)
+{
+  if (mApzSampler) {
+    uint64_t layersId = (aLayersId == 0 ? mRootLayerTreeID : aLayersId);
+    mApzSampler->SetTestAsyncZoom(layersId, aScrollId, aZoom);
+  }
+}
+
+void
 CompositorBridgeParent::FlushApzRepaints(const uint64_t& aLayersId)
 {
   MOZ_ASSERT(mApzcTreeManager);
   uint64_t layersId = aLayersId;
   if (layersId == 0) {
     // The request is coming from the parent-process layer tree, so we should
     // use the compositor's root layer tree id.
     layersId = mRootLayerTreeID;
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -25,17 +25,16 @@
 #include "mozilla/TimeStamp.h"          // for TimeStamp
 #include "mozilla/dom/ipc/IdType.h"
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/ipc/SharedMemory.h"
 #include "mozilla/layers/CompositorController.h"
 #include "mozilla/layers/CompositorOptions.h"
 #include "mozilla/layers/CompositorVsyncSchedulerOwner.h"
-#include "mozilla/layers/FocusState.h"
 #include "mozilla/layers/GeckoContentController.h"
 #include "mozilla/layers/ISurfaceAllocator.h" // for ShmemAllocator
 #include "mozilla/layers/LayersMessages.h"  // for TargetConfig
 #include "mozilla/layers/MetricsSharingController.h"
 #include "mozilla/layers/PCompositorBridgeParent.h"
 #include "mozilla/layers/APZTestData.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "mozilla/widget/CompositorWidget.h"
@@ -109,16 +108,22 @@ public:
 
   virtual void NotifyClearCachedResources(LayerTransactionParent* aLayerTree) { }
 
   virtual void ScheduleComposite(LayerTransactionParent* aLayerTree) { }
   virtual bool SetTestSampleTime(const uint64_t& aId,
                                  const TimeStamp& aTime) { return true; }
   virtual void LeaveTestMode(const uint64_t& aId) { }
   virtual void ApplyAsyncProperties(LayerTransactionParent* aLayerTree) = 0;
+  virtual void SetTestAsyncScrollOffset(const uint64_t& aLayersId,
+                                        const FrameMetrics::ViewID& aScrollId,
+                                        const CSSPoint& aPoint) = 0;
+  virtual void SetTestAsyncZoom(const uint64_t& aLayersId,
+                                const FrameMetrics::ViewID& aScrollId,
+                                const LayerToParentLayerScale& aZoom) = 0;
   virtual void FlushApzRepaints(const uint64_t& aLayersId) = 0;
   virtual void GetAPZTestData(const uint64_t& aLayersId,
                               APZTestData* aOutData) { }
   virtual void SetConfirmedTargetAPZC(const uint64_t& aLayersId,
                                       const uint64_t& aInputBlockId,
                                       const nsTArray<ScrollableLayerGuid>& aTargets) = 0;
   virtual void UpdatePaintTime(LayerTransactionParent* aLayerTree, const TimeDuration& aPaintTime) {}
 
@@ -243,16 +248,22 @@ public:
                            const TransactionInfo& aInfo,
                            bool aHitTestUpdate) override;
   void ScheduleComposite(LayerTransactionParent* aLayerTree) override;
   bool SetTestSampleTime(const uint64_t& aId,
                          const TimeStamp& aTime) override;
   void LeaveTestMode(const uint64_t& aId) override;
   void ApplyAsyncProperties(LayerTransactionParent* aLayerTree) override;
   CompositorAnimationStorage* GetAnimationStorage();
+  void SetTestAsyncScrollOffset(const uint64_t& aLayersId,
+                                const FrameMetrics::ViewID& aScrollId,
+                                const CSSPoint& aPoint) override;
+  void SetTestAsyncZoom(const uint64_t& aLayersId,
+                        const FrameMetrics::ViewID& aScrollId,
+                        const LayerToParentLayerScale& aZoom) override;
   void FlushApzRepaints(const uint64_t& aLayersId) override;
   void GetAPZTestData(const uint64_t& aLayersId,
                       APZTestData* aOutData) override;
   void SetConfirmedTargetAPZC(const uint64_t& aLayersId,
                               const uint64_t& aInputBlockId,
                               const nsTArray<ScrollableLayerGuid>& aTargets) override;
   AsyncCompositionManager* GetCompositionManager(LayerTransactionParent* aLayerTree) override { return mCompositionManager; }
 
@@ -438,20 +449,28 @@ public:
 
   widget::CompositorWidget* GetWidget() { return mWidget; }
 
   void ForceComposeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect = nullptr);
 
   PAPZCTreeManagerParent* AllocPAPZCTreeManagerParent(const uint64_t& aLayersId) override;
   bool DeallocPAPZCTreeManagerParent(PAPZCTreeManagerParent* aActor) override;
 
+  // Helper method so that we don't have to expose mApzcTreeManager to
+  // CrossProcessCompositorBridgeParent.
+  void AllocateAPZCTreeManagerParent(const MonitorAutoLock& aProofOfLayerTreeStateLock,
+                                     const uint64_t& aLayersId,
+                                     LayerTreeState& aLayerTreeStateToUpdate);
+
   PAPZParent* AllocPAPZParent(const uint64_t& aLayersId) override;
   bool DeallocPAPZParent(PAPZParent* aActor) override;
 
+#if defined(MOZ_WIDGET_ANDROID)
   RefPtr<APZCTreeManager> GetAPZCTreeManager();
+#endif
   RefPtr<APZSampler> GetAPZSampler();
 
   CompositorOptions GetOptions() const {
     return mOptions;
   }
 
   TimeDuration GetVsyncInterval() const {
     // the variable is called "rate" but really it's an interval
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
@@ -1,28 +1,29 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/CrossProcessCompositorBridgeParent.h"
+
 #include <stdint.h>                     // for uint64_t
+
 #include "LayerTransactionParent.h"     // for LayerTransactionParent
+#include "apz/src/APZCTreeManager.h"    // for APZCTreeManager
 #include "base/message_loop.h"          // for MessageLoop
 #include "base/task.h"                  // for CancelableTask, etc
 #include "base/thread.h"                // for Thread
 #ifdef XP_WIN
 #include "mozilla/gfx/DeviceManagerDx.h" // for DeviceManagerDx
 #endif
 #include "mozilla/ipc/Transport.h"      // for Transport
 #include "mozilla/layers/AnimationHelper.h" // for CompositorAnimationStorage
-#include "mozilla/layers/APZCTreeManager.h"  // for APZCTreeManager
 #include "mozilla/layers/APZCTreeManagerParent.h"  // for APZCTreeManagerParent
-#include "mozilla/layers/APZThreadUtils.h"  // for APZCTreeManager
 #include "mozilla/layers/AsyncCompositionManager.h"
 #include "mozilla/layers/CompositorOptions.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/LayerTreeOwnerTracker.h"
 #include "mozilla/layers/PLayerTransactionParent.h"
 #include "mozilla/layers/RemoteContentController.h"
 #include "mozilla/layers/WebRenderBridgeParent.h"
@@ -131,19 +132,17 @@ CrossProcessCompositorBridgeParent::Allo
   if (!state.mParent) {
     // Note: we immediately call ClearTree since otherwise the APZCTM will
     // retain a reference to itself, through the checkerboard observer.
     RefPtr<APZCTreeManager> temp = new APZCTreeManager(0);
     temp->ClearTree();
     return new APZCTreeManagerParent(aLayersId, temp);
   }
 
-  MOZ_ASSERT(!state.mApzcTreeManagerParent);
-  state.mApzcTreeManagerParent = new APZCTreeManagerParent(aLayersId, state.mParent->GetAPZCTreeManager());
-
+  state.mParent->AllocateAPZCTreeManagerParent(lock, aLayersId, state);
   return state.mApzcTreeManagerParent;
 }
 bool
 CrossProcessCompositorBridgeParent::DeallocPAPZCTreeManagerParent(PAPZCTreeManagerParent* aActor)
 {
   APZCTreeManagerParent* parent = static_cast<APZCTreeManagerParent*>(aActor);
 
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
@@ -462,16 +461,50 @@ CrossProcessCompositorBridgeParent::Appl
     return;
   }
 
   MOZ_ASSERT(state->mParent);
   state->mParent->ApplyAsyncProperties(aLayerTree);
 }
 
 void
+CrossProcessCompositorBridgeParent::SetTestAsyncScrollOffset(
+    const uint64_t& aLayersId,
+    const FrameMetrics::ViewID& aScrollId,
+    const CSSPoint& aPoint)
+{
+  MOZ_ASSERT(aLayersId != 0);
+  const CompositorBridgeParent::LayerTreeState* state =
+    CompositorBridgeParent::GetIndirectShadowTree(aLayersId);
+  if (!state) {
+    return;
+  }
+
+  MOZ_ASSERT(state->mParent);
+  state->mParent->SetTestAsyncScrollOffset(aLayersId, aScrollId, aPoint);
+}
+
+void
+CrossProcessCompositorBridgeParent::SetTestAsyncZoom(
+    const uint64_t& aLayersId,
+    const FrameMetrics::ViewID& aScrollId,
+    const LayerToParentLayerScale& aZoom)
+{
+  MOZ_ASSERT(aLayersId != 0);
+  const CompositorBridgeParent::LayerTreeState* state =
+    CompositorBridgeParent::GetIndirectShadowTree(aLayersId);
+  if (!state) {
+    return;
+  }
+
+  MOZ_ASSERT(state->mParent);
+  state->mParent->SetTestAsyncZoom(aLayersId, aScrollId, aZoom);
+}
+
+void
 CrossProcessCompositorBridgeParent::FlushApzRepaints(const uint64_t& aLayersId)
 {
   MOZ_ASSERT(aLayersId != 0);
   const CompositorBridgeParent::LayerTreeState* state =
     CompositorBridgeParent::GetIndirectShadowTree(aLayersId);
   if (!state) {
     return;
   }
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
@@ -90,16 +90,22 @@ public:
                            const TransactionInfo& aInfo,
                            bool aHitTestUpdate) override;
   void ScheduleComposite(LayerTransactionParent* aLayerTree) override;
   void NotifyClearCachedResources(LayerTransactionParent* aLayerTree) override;
   bool SetTestSampleTime(const uint64_t& aId,
                          const TimeStamp& aTime) override;
   void LeaveTestMode(const uint64_t& aId) override;
   void ApplyAsyncProperties(LayerTransactionParent* aLayerTree) override;
+  void SetTestAsyncScrollOffset(const uint64_t& aLayersId,
+                                const FrameMetrics::ViewID& aScrollId,
+                                const CSSPoint& aPoint) override;
+  void SetTestAsyncZoom(const uint64_t& aLayersId,
+                        const FrameMetrics::ViewID& aScrollId,
+                        const LayerToParentLayerScale& aZoom) override;
   void FlushApzRepaints(const uint64_t& aLayersId) override;
   void GetAPZTestData(const uint64_t& aLayersId,
                       APZTestData* aOutData) override;
   void SetConfirmedTargetAPZC(const uint64_t& aLayersId,
                               const uint64_t& aInputBlockId,
                               const nsTArray<ScrollableLayerGuid>& aTargets) override;
 
   AsyncCompositionManager* GetCompositionManager(LayerTransactionParent* aParent) override;
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "LayerTransactionParent.h"
 #include <vector>                       // for vector
-#include "apz/src/AsyncPanZoomController.h"
 #include "CompositableHost.h"           // for CompositableParent, Get, etc
 #include "ImageLayers.h"                // for ImageLayer
 #include "Layers.h"                     // for Layer, ContainerLayer, etc
 #include "CompositableTransactionParent.h"  // for EditReplyVector
 #include "CompositorBridgeParent.h"
 #include "gfxPrefs.h"
 #include "mozilla/gfx/BasePoint3D.h"    // for BasePoint3D
 #include "mozilla/layers/AnimationHelper.h" // for GetAnimatedPropValue
@@ -746,64 +745,37 @@ LayerTransactionParent::RecvGetAnimation
   if (transform) {
     *aTransform = *transform;
   } else {
     *aTransform = mozilla::void_t();
   }
   return IPC_OK();
 }
 
-static AsyncPanZoomController*
-GetAPZCForViewID(Layer* aLayer, FrameMetrics::ViewID aScrollID)
-{
-  AsyncPanZoomController* resultApzc = nullptr;
-  ForEachNode<ForwardIterator>(
-      aLayer,
-      [aScrollID, &resultApzc] (Layer* layer)
-      {
-        for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) {
-          if (layer->GetFrameMetrics(i).GetScrollId() == aScrollID) {
-            resultApzc = layer->GetAsyncPanZoomController(i);
-            return TraversalFlag::Abort;
-          }
-        }
-        return TraversalFlag::Continue;
-      });
-  return resultApzc;
-}
-
 mozilla::ipc::IPCResult
 LayerTransactionParent::RecvSetAsyncScrollOffset(const FrameMetrics::ViewID& aScrollID,
                                                  const float& aX, const float& aY)
 {
   if (mDestroyed || !mLayerManager || mLayerManager->IsDestroyed()) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  AsyncPanZoomController* controller = GetAPZCForViewID(mRoot, aScrollID);
-  if (!controller) {
-    return IPC_FAIL_NO_REASON(this);
-  }
-  controller->SetTestAsyncScrollOffset(CSSPoint(aX, aY));
+  mCompositorBridge->SetTestAsyncScrollOffset(GetId(), aScrollID, CSSPoint(aX, aY));
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 LayerTransactionParent::RecvSetAsyncZoom(const FrameMetrics::ViewID& aScrollID,
                                          const float& aValue)
 {
   if (mDestroyed || !mLayerManager || mLayerManager->IsDestroyed()) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  AsyncPanZoomController* controller = GetAPZCForViewID(mRoot, aScrollID);
-  if (!controller) {
-    return IPC_FAIL_NO_REASON(this);
-  }
-  controller->SetTestAsyncZoom(LayerToParentLayerScale(aValue));
+  mCompositorBridge->SetTestAsyncZoom(GetId(), aScrollID, LayerToParentLayerScale(aValue));
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 LayerTransactionParent::RecvFlushApzRepaints()
 {
   mCompositorBridge->FlushApzRepaints(GetId());
   return IPC_OK();
--- a/gfx/layers/ipc/SharedSurfacesChild.h
+++ b/gfx/layers/ipc/SharedSurfacesChild.h
@@ -21,16 +21,17 @@ class SourceSurfaceSharedData;
 
 namespace wr {
 class IpcResourceUpdateQueue;
 } // namespace wr
 
 namespace layers {
 
 class CompositorManagerChild;
+class ImageContainer;
 class WebRenderLayerManager;
 
 class SharedSurfacesChild final
 {
 public:
   /**
    * Request that the surface be mapped into the compositor thread's memory
    * space. This is useful for when the caller itself has no present need for
--- a/gfx/layers/ipc/UiCompositorControllerParent.cpp
+++ b/gfx/layers/ipc/UiCompositorControllerParent.cpp
@@ -1,15 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #include "UiCompositorControllerParent.h"
-#include "mozilla/layers/APZCTreeManager.h"
+
+#if defined(MOZ_WIDGET_ANDROID)
+#include "apz/src/APZCTreeManager.h"
+#endif
 #include "mozilla/layers/Compositor.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/gfx/Types.h"
 #include "mozilla/Move.h"
 #include "mozilla/Unused.h"
 
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -94,25 +94,21 @@ EXPORTS.mozilla.layers += [
     'AnimationInfo.h',
     'apz/public/APZSampler.h',
     'apz/public/CompositorController.h',
     'apz/public/GeckoContentController.h',
     'apz/public/IAPZCTreeManager.h',
     'apz/public/MetricsSharingController.h',
     # exporting things from apz/src is temporary until we extract a
     # proper interface for the code there
-    'apz/src/APZCTreeManager.h',
     'apz/src/APZUtils.h',
     'apz/src/AsyncDragMetrics.h',
-    'apz/src/AsyncPanZoomAnimation.h',
-    'apz/src/FocusState.h',
     'apz/src/FocusTarget.h',
     'apz/src/KeyboardMap.h',
     'apz/src/KeyboardScrollAction.h',
-    'apz/src/TouchCounter.h',
     'apz/testutil/APZTestData.h',
     'apz/util/ActiveElementManager.h',
     'apz/util/APZCCallbackHelper.h',
     'apz/util/APZEventState.h',
     'apz/util/APZThreadUtils.h',
     'apz/util/ChromeProcessController.h',
     'apz/util/ContentProcessController.h',
     'apz/util/DoubleTapToZoom.h',
@@ -294,16 +290,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'andr
     ]
 
 UNIFIED_SOURCES += [
     'AnimationHelper.cpp',
     'AnimationInfo.cpp',
     'apz/public/IAPZCTreeManager.cpp',
     'apz/src/APZCTreeManager.cpp',
     'apz/src/APZSampler.cpp',
+    'apz/src/APZUtils.cpp',
     'apz/src/AsyncPanZoomController.cpp',
     'apz/src/AutoscrollAnimation.cpp',
     'apz/src/Axis.cpp',
     'apz/src/CheckerboardEvent.cpp',
     'apz/src/DragTracker.cpp',
     'apz/src/FocusState.cpp',
     'apz/src/FocusTarget.cpp',
     'apz/src/GenericScrollAnimation.cpp',
--- a/gfx/layers/opengl/TextureClientOGL.h
+++ b/gfx/layers/opengl/TextureClientOGL.h
@@ -7,27 +7,34 @@
 #ifndef MOZILLA_GFX_TEXTURECLIENTOGL_H
 #define MOZILLA_GFX_TEXTURECLIENTOGL_H
 
 #include "GLContextTypes.h"             // for SharedTextureHandle, etc
 #include "GLImages.h"
 #include "gfxTypes.h"
 #include "mozilla/Attributes.h"         // for override
 #include "mozilla/gfx/Point.h"          // for IntSize
+#include "mozilla/gfx/Types.h"          // for SurfaceFormat
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor
 #include "mozilla/layers/TextureClient.h"  // for TextureClient, etc
 #include "AndroidSurfaceTexture.h"
 #include "AndroidNativeWindow.h"
 #ifdef MOZ_WIDGET_ANDROID
 #include "GeneratedJNIWrappers.h"
 #endif
 
 namespace mozilla {
 
+namespace gfx {
+
+class DrawTarget;
+
+} // namespace gfx
+
 namespace layers {
 
 #ifdef MOZ_WIDGET_ANDROID
 
 class AndroidSurfaceTextureData : public TextureData
 {
 public:
   static already_AddRefed<TextureClient>
@@ -55,53 +62,49 @@ public:
 protected:
   AndroidSurfaceTextureData(AndroidSurfaceTextureHandle aHandle, gfx::IntSize aSize, bool aContinuous);
 
   const AndroidSurfaceTextureHandle mHandle;
   const gfx::IntSize mSize;
   const bool mContinuous;
 };
 
-#endif // MOZ_WIDGET_ANDROID
-
-#ifdef MOZ_WIDGET_ANDROID
-
 class AndroidNativeWindowTextureData : public TextureData
 {
 public:
-  static AndroidNativeWindowTextureData* Create(gfx::IntSize aSize, SurfaceFormat aFormat);
+  static AndroidNativeWindowTextureData* Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat);
 
   virtual void FillInfo(TextureData::Info& aInfo) const override;
 
   virtual bool Serialize(SurfaceDescriptor& aOutDescriptor) override;
 
   virtual bool Lock(OpenMode) override;
   virtual void Unlock() override;
 
   virtual void Forget(LayersIPCChannel*) override;
   virtual void Deallocate(LayersIPCChannel*) override {}
 
-  virtual already_AddRefed<DrawTarget> BorrowDrawTarget() override;
+  virtual already_AddRefed<gfx::DrawTarget> BorrowDrawTarget() override;
 
   virtual void OnForwardedToHost() override;
 
 protected:
   AndroidNativeWindowTextureData(java::GeckoSurface::Param aSurface,
                                  gfx::IntSize aSize,
-                                 SurfaceFormat aFormat);
+                                 gfx::SurfaceFormat aFormat);
 
 private:
   java::GeckoSurface::GlobalRef mSurface;
   ANativeWindow* mNativeWindow;
   ANativeWindow_Buffer mBuffer;
   // Keeps track of whether the underlying NativeWindow is actually locked.
   bool mIsLocked;
 
   const gfx::IntSize mSize;
-  const SurfaceFormat mFormat;
+  const gfx::SurfaceFormat mFormat;
 };
 
 #endif // MOZ_WIDGET_ANDROID
 
 } // namespace layers
 } // namespace mozilla
 
 #endif
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -1,27 +1,26 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/WebRenderBridgeParent.h"
 
-#include "apz/src/AsyncPanZoomController.h"
 #include "CompositableHost.h"
 #include "gfxEnv.h"
 #include "gfxPrefs.h"
 #include "gfxEnv.h"
 #include "GeckoProfiler.h"
 #include "GLContext.h"
 #include "GLContextProvider.h"
 #include "mozilla/Range.h"
 #include "mozilla/layers/AnimationHelper.h"
-#include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/APZSampler.h"
 #include "mozilla/layers/Compositor.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/CompositorVsyncScheduler.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/ImageDataSerializer.h"
 #include "mozilla/layers/IpcResourceUpdateQueue.h"
 #include "mozilla/layers/SharedSurfacesParent.h"
@@ -510,45 +509,45 @@ WebRenderBridgeParent::UpdateAPZ(bool aU
   if (!cbp) {
     return;
   }
   uint64_t rootLayersId = cbp->RootLayerTreeId();
   RefPtr<WebRenderBridgeParent> rootWrbp = cbp->GetWebRenderBridgeParent();
   if (!rootWrbp) {
     return;
   }
-  if (RefPtr<APZCTreeManager> apzc = cbp->GetAPZCTreeManager()) {
-    apzc->UpdateFocusState(rootLayersId, GetLayersId(),
-                           mScrollData.GetFocusTarget());
+  if (RefPtr<APZSampler> apz = cbp->GetAPZSampler()) {
+    apz->UpdateFocusState(rootLayersId, GetLayersId(),
+                          mScrollData.GetFocusTarget());
     if (aUpdateHitTestingTree) {
-      apzc->UpdateHitTestingTree(rootLayersId, rootWrbp->GetScrollData(),
+      apz->UpdateHitTestingTree(rootLayersId, rootWrbp->GetScrollData(),
           mScrollData.IsFirstPaint(), GetLayersId(),
           mScrollData.GetPaintSequenceNumber());
     }
   }
 }
 
 bool
 WebRenderBridgeParent::PushAPZStateToWR(wr::TransactionBuilder& aTxn,
                                         nsTArray<wr::WrTransformProperty>& aTransformArray)
 {
   CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
   if (!cbp) {
     return false;
   }
-  if (RefPtr<APZCTreeManager> apzc = cbp->GetAPZCTreeManager()) {
+  if (RefPtr<APZSampler> apz = cbp->GetAPZSampler()) {
     TimeStamp animationTime = cbp->GetTestingTimeStamp().valueOr(
         mCompositorScheduler->GetLastComposeTime());
     TimeDuration frameInterval = cbp->GetVsyncInterval();
     // As with the non-webrender codepath in AsyncCompositionManager, we want to
     // use the timestamp for the next vsync when advancing animations.
     if (frameInterval != TimeDuration::Forever()) {
       animationTime += frameInterval;
     }
-    return apzc->PushStateToWR(aTxn, animationTime, aTransformArray);
+    return apz->PushStateToWR(aTxn, animationTime, aTransformArray);
   }
   return false;
 }
 
 const WebRenderScrollData&
 WebRenderBridgeParent::GetScrollData() const
 {
   MOZ_ASSERT(mozilla::layers::CompositorThreadHolder::IsInCompositorThread());
@@ -1011,28 +1010,16 @@ mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvCapture()
 {
   if (!mDestroyed) {
     mApi->Capture();
   }
   return IPC_OK();
 }
 
-already_AddRefed<AsyncPanZoomController>
-WebRenderBridgeParent::GetTargetAPZC(const FrameMetrics::ViewID& aScrollId)
-{
-  RefPtr<AsyncPanZoomController> apzc;
-  if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
-    if (RefPtr<APZCTreeManager> apzctm = cbp->GetAPZCTreeManager()) {
-      apzc = apzctm->GetTargetAPZC(GetLayersId(), aScrollId);
-    }
-  }
-  return apzc.forget();
-}
-
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvSetConfirmedTargetAPZC(const uint64_t& aBlockId,
                                                   nsTArray<ScrollableLayerGuid>&& aTargets)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
   mCompositorBridge->SetConfirmedTargetAPZC(GetLayersId(), aBlockId, aTargets);
@@ -1100,36 +1087,28 @@ WebRenderBridgeParent::RecvGetAnimationT
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvSetAsyncScrollOffset(const FrameMetrics::ViewID& aScrollId,
                                                 const float& aX,
                                                 const float& aY)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
-  RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aScrollId);
-  if (!apzc) {
-    return IPC_FAIL_NO_REASON(this);
-  }
-  apzc->SetTestAsyncScrollOffset(CSSPoint(aX, aY));
+  mCompositorBridge->SetTestAsyncScrollOffset(GetLayersId(), aScrollId, CSSPoint(aX, aY));
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvSetAsyncZoom(const FrameMetrics::ViewID& aScrollId,
                                         const float& aZoom)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
-  RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aScrollId);
-  if (!apzc) {
-    return IPC_FAIL_NO_REASON(this);
-  }
-  apzc->SetTestAsyncZoom(LayerToParentLayerScale(aZoom));
+  mCompositorBridge->SetTestAsyncZoom(GetLayersId(), aScrollId, LayerToParentLayerScale(aZoom));
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvFlushApzRepaints()
 {
   if (mDestroyed) {
     return IPC_OK();
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -223,20 +223,16 @@ private:
 
   // Have APZ push the async scroll state to WR. Returns true if an APZ
   // animation is in effect and we need to schedule another composition.
   // If scrollbars need their transforms updated, the provided aTransformArray
   // is populated with the property update details.
   bool PushAPZStateToWR(wr::TransactionBuilder& aTxn,
                         nsTArray<wr::WrTransformProperty>& aTransformArray);
 
-  // Helper method to get an APZC reference from a scroll id. Uses the layers
-  // id of this bridge, and may return null if the APZC wasn't found.
-  already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const FrameMetrics::ViewID& aId);
-
   uint32_t GetNextWrEpoch();
 
 private:
   struct PendingTransactionId {
     PendingTransactionId(wr::Epoch aEpoch, uint64_t aId, const TimeStamp& aTxnStartTime, const TimeStamp& aFwdTime)
       : mEpoch(aEpoch)
       , mId(aId)
       , mTxnStartTime(aTxnStartTime)
--- a/intl/l10n/DOMLocalization.jsm
+++ b/intl/l10n/DOMLocalization.jsm
@@ -102,19 +102,19 @@ function overlayElement(targetElement, t
   // translation.
   for (const attr of Array.from(targetElement.attributes)) {
     if (isAttrNameLocalizable(attr.name, targetElement, explicitlyAllowed)) {
       targetElement.removeAttribute(attr.name);
     }
   }
 
   if (translation.attrs) {
-    for (const [name, val] of translation.attrs) {
+    for (const {name, value} of translation.attrs) {
       if (isAttrNameLocalizable(name, targetElement, explicitlyAllowed)) {
-        targetElement.setAttribute(name, val);
+        targetElement.setAttribute(name, value);
       }
     }
   }
 }
 
 /**
  * Sanitize `translationFragment` using `sourceElement` to add functional
  * HTML attributes to children.  `sourceElement` will have all its child nodes
@@ -299,16 +299,57 @@ function shiftNamedElement(element, loca
     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.
+ *
+ * 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.attrs) {
+    const explicitlyAllowed = l10nItem.l10nAttrs === null ? null :
+      l10nItem.l10nAttrs.split(",").map(i => i.trim());
+    for (const [j, {name}] of translation.attrs.entries()) {
+      if (!isAttrNameLocalizable(name, l10nItem, explicitlyAllowed)) {
+        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.
@@ -463,17 +504,17 @@ class DOMLocalization extends Localizati
   /**
    * Translate all roots associated with this `DOMLocalization`.
    *
    * @returns {Promise}
    */
   translateRoots() {
     const roots = Array.from(this.roots);
     return Promise.all(
-      roots.map(root => this.translateElements(this.getTranslatables(root)))
+      roots.map(root => this.translateFragment(root))
     );
   }
 
   /**
    * Pauses the `MutationObserver`.
    *
    * @private
    */
@@ -528,31 +569,83 @@ class DOMLocalization extends Localizati
           this.translateElements(Array.from(this.pendingElements));
           this.pendingElements.clear();
           this.pendingrAF = null;
         });
       }
     }
   }
 
-
   /**
    * Translate a DOM element or fragment asynchronously using this
    * `DOMLocalization` object.
    *
    * Manually trigger the translation (or re-translation) of a DOM fragment.
    * Use the `data-l10n-id` and `data-l10n-args` attributes to mark up the DOM
    * with information about which translations to use.
    *
    * Returns a `Promise` that gets resolved once the translation is complete.
    *
    * @param   {DOMFragment} frag - Element or DocumentFragment to be translated
    * @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) {
+              overlayElement(untranslatedElements[i], overlayTranslations[i]);
+            }
+          }
+          this.resumeObserving();
+        })
+        .catch(() => this.resumeObserving());
+    }
     return this.translateElements(this.getTranslatables(frag));
   }
 
   /**
    * Translate a list of DOM elements asynchronously using this
    * `DOMLocalization` object.
    *
    * Manually trigger the translation (or re-translation) of a list of elements.
@@ -580,17 +673,19 @@ class DOMLocalization extends Localizati
    * @param {Array<Element>} elements
    * @param {Array<Object>}  translations
    * @private
    */
   applyTranslations(elements, translations) {
     this.pauseObserving();
 
     for (let i = 0; i < elements.length; i++) {
-      overlayElement(elements[i], translations[i]);
+      if (translations[i] !== undefined) {
+        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
@@ -19,16 +19,17 @@
 /* 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", {});
 
 /*
  * CachedIterable caches the elements yielded by an iterable.
  *
  * It can be used to iterate over an iterable many times without depleting the
  * iterable.
  */
 class CachedIterable {
@@ -86,34 +87,16 @@ class CachedIterable {
     const { seen, iterator } = this;
     if (seen.length === 0 || seen[seen.length - 1].done === false) {
       seen.push(iterator.next());
     }
   }
 }
 
 /**
- * Specialized version of an Error used to indicate errors that are result
- * of a problem during the localization process.
- *
- * We use them to identify the class of errors the require a fallback
- * mechanism to be triggered vs errors that should be reported, but
- * do not prevent the message from being used.
- *
- * An example of an L10nError is a missing entry.
- */
-class L10nError extends Error {
-  constructor(message) {
-    super();
-    this.name = "L10nError";
-    this.message = message;
-  }
-}
-
- /**
  * 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.
  */
@@ -151,27 +134,36 @@ class Localization {
    *
    * @param   {Array<Array>}          keys    - Translation keys to format.
    * @param   {Function}              method  - Formatting function.
    * @returns {Promise<Array<string|Object>>}
    * @private
    */
   async formatWithFallback(keys, method) {
     const translations = [];
+
     for await (let ctx of this.ctxs) {
       // This can operate on synchronous and asynchronous
       // contexts coming from the iterator.
       if (typeof ctx.then === "function") {
         ctx = await ctx;
       }
-      const errors = keysFromContext(method, ctx, keys, translations);
-      if (!errors) {
+      const missingIds = keysFromContext(method, ctx, keys, translations);
+
+      if (missingIds.size === 0) {
         break;
       }
+
+      if (AppConstants.NIGHTLY_BUILD) {
+        const locale = ctx.locales[0];
+        const ids = Array.from(missingIds).join(", ");
+        console.warn(`Missing translations in ${locale}: ${ids}`);
+      }
     }
+
     return translations;
   }
 
   /**
    * Format translations into {value, attrs} objects.
    *
    * The fallback logic is the same as in `formatValues` but the argument type
    * is stricter (an array of arrays) and it returns {value, attrs} objects
@@ -307,21 +299,16 @@ Localization.prototype.QueryInterface = 
  * @param   {string}         id
  * @param   {Object}         args
  * @returns {string}
  * @private
  */
 function valueFromContext(ctx, errors, id, args) {
   const msg = ctx.getMessage(id);
 
-  if (msg === undefined) {
-    errors.push(new L10nError(`Unknown entity: ${id}`));
-    return id;
-  }
-
   return ctx.format(msg, args, errors);
 }
 
 /**
  * Format all public values of a message into a { value, attrs } object.
  *
  * This function is passed as a method to `keysFromContext` and resolve
  * a single L10n Entity using provided `MessageContext`.
@@ -340,95 +327,81 @@ function valueFromContext(ctx, errors, i
  * @param   {String}         id
  * @param   {Object}         args
  * @returns {Object}
  * @private
  */
 function messageFromContext(ctx, errors, id, args) {
   const msg = ctx.getMessage(id);
 
-  if (msg === undefined) {
-    errors.push(new L10nError(`Unknown message: ${id}`));
-    return { value: id, attrs: null };
-  }
-
   const formatted = {
     value: ctx.format(msg, args, errors),
     attrs: null,
   };
 
   if (msg.attrs) {
     formatted.attrs = [];
-    for (const attrName in msg.attrs) {
-      const formattedAttr = ctx.format(msg.attrs[attrName], args, errors);
-      if (formattedAttr !== null) {
-        formatted.attrs.push([
-          attrName,
-          formattedAttr
-        ]);
+    for (const name in msg.attrs) {
+      const value = ctx.format(msg.attrs[name], args, errors);
+      if (value !== null) {
+        formatted.attrs.push({ name, value });
       }
     }
   }
 
   return formatted;
 }
 
 /**
  * This function is an inner function for `Localization.formatWithFallback`.
  *
  * It takes a `MessageContext`, list of l10n-ids and a method to be used for
- * key resolution (either `valueFromContext` or `entityFromContext`) and
+ * key resolution (either `valueFromContext` or `messageFromContext`) and
  * optionally a value returned from `keysFromContext` executed against
  * another `MessageContext`.
  *
  * The idea here is that if the previous `MessageContext` did not resolve
  * all keys, we're calling this function with the next context to resolve
  * the remaining ones.
  *
  * In the function, we loop over `keys` and check if we have the `prev`
  * passed and if it has an error entry for the position we're in.
  *
  * If it doesn't, it means that we have a good translation for this key and
  * we return it. If it does, we'll try to resolve the key using the passed
  * `MessageContext`.
  *
- * In the end, we fill the translations array, and return if we
- * encountered at least one error.
+ * In the end, we fill the translations array, and return the Set with
+ * missing ids.
  *
  * See `Localization.formatWithFallback` for more info on how this is used.
  *
  * @param {Function}       method
  * @param {MessageContext} ctx
  * @param {Array<string>}  keys
  * @param {{Array<{value: string, attrs: Object}>}} translations
  *
- * @returns {Boolean}
+ * @returns {Set<string>}
  * @private
  */
 function keysFromContext(method, ctx, keys, translations) {
   const messageErrors = [];
-  let hasErrors = false;
+  const missingIds = new Set();
 
   keys.forEach((key, i) => {
     if (translations[i] !== undefined) {
       return;
     }
 
-    messageErrors.length = 0;
-    const translation = method(ctx, messageErrors, key[0], key[1]);
-
-    if (messageErrors.length === 0 ||
-        !messageErrors.some(e => e instanceof L10nError)) {
-      translations[i] = translation;
+    if (ctx.hasMessage(key[0])) {
+      messageErrors.length = 0;
+      translations[i] = method(ctx, messageErrors, key[0], key[1]);
+      // XXX: Report resolver errors
     } else {
-      hasErrors = true;
-    }
-
-    if (messageErrors.length) {
-      messageErrors.forEach(error => console.warn(error));
+      missingIds.add(key[0]);
     }
   });
 
-  return hasErrors;
+  return missingIds;
 }
 
 this.Localization = Localization;
 var EXPORTED_SYMBOLS = ["Localization"];
--- a/intl/l10n/test/chrome.ini
+++ b/intl/l10n/test/chrome.ini
@@ -1,12 +1,16 @@
+[dom/test_domloc_attr_sanitized.html]
 [dom/test_domloc_getAttributes.html]
 [dom/test_domloc_setAttributes.html]
 [dom/test_domloc_translateElements.html]
 [dom/test_domloc_translateFragment.html]
 [dom/test_domloc_connectRoot.html]
 [dom/test_domloc_disconnectRoot.html]
+[dom/test_domloc_repeated_l10nid.html]
 [dom/test_domloc_translateRoots.html]
 [dom/test_domloc_mutations.html]
 [dom/test_domloc_overlay.html]
 [dom/test_domloc_overlay_repeated.html]
+[dom/test_domloc_overlay_missing_all.html]
 [dom/test_domloc_overlay_missing_children.html]
+[dom/test_domloc_overlay_sanitized.html]
 [dom/test_domloc.xul]
new file mode 100644
--- /dev/null
+++ b/intl/l10n/test/dom/test_domloc_attr_sanitized.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test DOMLocalization's attr sanitization functionality</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  <script type="application/javascript">
+  "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(`
+key1 = Value for Key 1
+
+key2 = Value for <a>Key 2<a/>.
+    `);
+    yield mc;
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  addLoadEvent(async () => {
+    const domLoc = new DOMLocalization(
+      window,
+      [],
+      mockGenerateMessages
+    );
+
+    await domLoc.translateFragment(document.body);
+
+    const elem1 = document.querySelector("#elem1");
+    const elem2 = document.querySelector("#elem2");
+
+    ok(elem1.textContent.includes("Value for"));
+    // This is a limitation of us using Node.localize API
+    // Documenting it here to make sure we notice when we fix it
+    is(elem1.getAttribute("title"), "Old Translation");
+
+    ok(elem2.textContent.includes("Value for"));
+    ok(!elem2.hasAttribute("title"));
+
+    SimpleTest.finish();
+  });
+  </script>
+</head>
+<body>
+  <p id="elem1" title="Old Translation" data-l10n-id="key1"></p>
+  <p id="elem2" title="Old Translation" data-l10n-id="key2"></p>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/intl/l10n/test/dom/test_domloc_overlay_missing_all.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test DOMLocalization's DOMOverlay functionality</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  <script type="application/javascript">
+  "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);
+    // No translations!
+    yield mc;
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  addLoadEvent(async () => {
+
+    const domLoc = new DOMLocalization(
+      window,
+      [],
+      mockGenerateMessages
+    );
+
+    await domLoc.translateFragment(document.body);
+
+    // we just care that it doesn't throw here.
+    ok(1);
+
+    SimpleTest.finish();
+  });
+  </script>
+</head>
+<body>
+  <p data-l10n-id="title">
+    <a href="http://www.mozilla.org"></a>
+    <a href="http://www.firefox.com"></a>
+    <a href="http://www.w3.org"></a>
+  </p>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/intl/l10n/test/dom/test_domloc_overlay_sanitized.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test DOMLocalization's DOMOverlay functionality</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  <script type="application/javascript">
+  "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(`
+key1 =
+    .href = https://www.hacked.com
+
+key2 =
+    .href = https://pl.wikipedia.org
+`);
+    yield mc;
+  }
+
+  async function test() {
+    const domLoc = new DOMLocalization(
+      window,
+      [],
+      mockGenerateMessages
+    );
+
+    await domLoc.translateFragment(document.body);
+
+    const key1Elem = document.querySelector("[data-l10n-id=key1]");
+    const key2Elem = document.querySelector("[data-l10n-id=key2]");
+
+
+    is(key1Elem.hasAttribute("href"), false, "href translation should not be allowed");
+    is(key2Elem.getAttribute("href"), "https://pl.wikipedia.org",
+      "href translation should be allowed");
+
+    SimpleTest.finish();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  addLoadEvent(test);
+  </script>
+</head>
+<body>
+  <a data-l10n-id="key1"></a>
+  <a data-l10n-id="key2" data-l10n-attrs="href"></a>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/intl/l10n/test/dom/test_domloc_repeated_l10nid.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test DOMLocalization's matching l10nIds functionality</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  <script type="application/javascript">
+  "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(`
+key1 = Translation For Key 1
+
+key2 = Visit <a>this link<a/>.
+    `);
+    yield mc;
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  addLoadEvent(async () => {
+    const domLoc = new DOMLocalization(
+      window,
+      [],
+      mockGenerateMessages
+    );
+
+    await domLoc.translateFragment(document.body);
+
+    ok(document.querySelector("#elem1").textContent.includes("Key 1"));
+    ok(document.querySelector("#elem2").textContent.includes("Key 1"));
+
+    const elem3 = document.querySelector("#elem3");
+    const elem4 = document.querySelector("#elem4");
+
+    ok(elem3.textContent.includes("Visit"));
+    is(elem3.querySelector("a").getAttribute("href"), "http://www.mozilla.org");
+
+    ok(elem4.textContent.includes("Visit"));
+    is(elem4.querySelector("a").getAttribute("href"), "http://www.firefox.com");
+
+    SimpleTest.finish();
+  });
+  </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"></a>
+  </p>
+
+  <p id="elem4" data-l10n-id="key2">
+    <a href="http://www.firefox.com"></a>
+  </p>
+</body>
+</html>
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -3742,17 +3742,17 @@ PresShell::ScheduleViewManagerFlush(Pain
 
   nsPresContext* presContext = GetPresContext();
   if (presContext) {
     presContext->RefreshDriver()->ScheduleViewManagerFlush();
   }
   SetNeedLayoutFlush();
 }
 
-bool
+static bool
 FlushLayoutRecursive(nsIDocument* aDocument,
                      void* aData = nullptr)
 {
   MOZ_ASSERT(!aData);
   nsCOMPtr<nsIDocument> kungFuDeathGrip(aDocument);
   aDocument->EnumerateSubDocuments(FlushLayoutRecursive, nullptr);
   aDocument->FlushPendingNotifications(FlushType::Layout);
   return true;
@@ -6735,17 +6735,18 @@ PresShell::RecordMouseLocation(WidgetGUI
     printf("[ps=%p]got mouse exit for %p\n",
            this, aEvent->mWidget);
     printf("[ps=%p]clearing mouse location\n",
            this);
 #endif
   }
 }
 
-nsIFrame* GetNearestFrameContainingPresShell(nsIPresShell* aPresShell)
+static nsIFrame*
+GetNearestFrameContainingPresShell(nsIPresShell* aPresShell)
 {
   nsView* view = aPresShell->GetViewManager()->GetRootView();
   while (view && !view->GetFrame()) {
     view = view->GetParent();
   }
 
   nsIFrame* frame = nullptr;
   if (view) {
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -845,17 +845,17 @@ GetFrameForChildrenOnlyTransformHint(nsI
   MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer),
              "Children-only transforms only expected on SVG frames");
   return aFrame;
 }
 
 // Returns true if this function managed to successfully move a frame, and
 // false if it could not process the position change, and a reflow should
 // be performed instead.
-bool
+static bool
 RecomputePosition(nsIFrame* aFrame)
 {
   // Don't process position changes on table frames, since we already handle
   // the dynamic position change on the table wrapper frame, and the
   // reflow-based fallback code path also ignores positions on inner table
   // frames.
   if (aFrame->IsTableFrame()) {
     return true;
--- a/layout/base/ZoomConstraintsClient.cpp
+++ b/layout/base/ZoomConstraintsClient.cpp
@@ -171,17 +171,17 @@ ZoomConstraintsClient::Observe(nsISuppor
 
 void
 ZoomConstraintsClient::ScreenSizeChanged()
 {
   ZCC_LOG("Got a screen-size change notification in %p\n", this);
   RefreshZoomConstraints();
 }
 
-mozilla::layers::ZoomConstraints
+static mozilla::layers::ZoomConstraints
 ComputeZoomConstraintsFromViewportInfo(const nsViewportInfo& aViewportInfo)
 {
   mozilla::layers::ZoomConstraints constraints;
   constraints.mAllowZoom = aViewportInfo.IsZoomAllowed() && gfxPrefs::APZAllowZooming();
   constraints.mAllowDoubleTapZoom = constraints.mAllowZoom;
   if (constraints.mAllowZoom) {
     constraints.mMinZoom.scale = aViewportInfo.GetMinZoom().scale;
     constraints.mMaxZoom.scale = aViewportInfo.GetMaxZoom().scale;
--- a/layout/base/gtest/TestAccessibleCaretEventHub.cpp
+++ b/layout/base/gtest/TestAccessibleCaretEventHub.cpp
@@ -86,18 +86,19 @@ public:
 
   MockAccessibleCaretManager* GetMockAccessibleCaretManager()
   {
     return static_cast<MockAccessibleCaretManager*>(mManager.get());
   }
 };
 
 // Print the name of the state for debugging.
-::std::ostream& operator<<(::std::ostream& aOstream,
-                           const MockAccessibleCaretEventHub::State* aState)
+static ::std::ostream&
+operator<<(::std::ostream& aOstream,
+           const MockAccessibleCaretEventHub::State* aState)
 {
   return aOstream << aState->Name();
 }
 
 class AccessibleCaretEventHubTester : public ::testing::Test
 {
 public:
   AccessibleCaretEventHubTester()
--- a/layout/base/nsLayoutDebugger.cpp
+++ b/layout/base/nsLayoutDebugger.cpp
@@ -87,17 +87,19 @@ NS_IMETHODIMP
 nsLayoutDebugger::GetShowEventTargetFrameBorder(bool* aResult)
 {
   *aResult = nsFrame::GetShowEventTargetFrameBorder();
   return NS_OK;
 }
 
 #endif
 
-std::ostream& operator<<(std::ostream& os, const nsPrintfCString& rhs) {
+static std::ostream&
+operator<<(std::ostream& os, const nsPrintfCString& rhs)
+{
   os << rhs.get();
   return os;
 }
 
 static void
 PrintDisplayListTo(nsDisplayListBuilder* aBuilder, const nsDisplayList& aList,
                    std::stringstream& aStream, uint32_t aIndent, bool aDumpHtml);
 
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -106,17 +106,17 @@
 #include "TiledLayerBuffer.h" // For TILEDLAYERBUFFER_TILE_SIZE
 #include "ClientLayerManager.h"
 #include "nsRefreshDriver.h"
 #include "nsIContentViewer.h"
 #include "LayersLogging.h"
 #include "mozilla/Preferences.h"
 #include "nsFrameSelection.h"
 #include "FrameLayerBuilder.h"
-#include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/APZUtils.h"    // for apz::CalculatePendingDisplayPort
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/RuleNodeCacheConditions.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/StyleSetHandle.h"
 #include "mozilla/StyleSetHandleInlines.h"
 #include "mozilla/WheelHandlingHelper.h" // for WheelHandlingUtils
@@ -828,17 +828,17 @@ nsLayoutUtils::FindContentFor(ViewID aId
 
   if (exists) {
     return content;
   } else {
     return nullptr;
   }
 }
 
-nsIFrame*
+static nsIFrame*
 GetScrollFrameFromContent(nsIContent* aContent)
 {
   nsIFrame* frame = aContent->GetPrimaryFrame();
   if (aContent->OwnerDoc()->GetRootElement() == aContent) {
     nsIPresShell* presShell = frame ? frame->PresShell() : nullptr;
     if (!presShell) {
       presShell = aContent->OwnerDoc()->GetShell();
     }
@@ -1311,17 +1311,17 @@ GetDisplayPortImpl(nsIContent* aContent,
       }
     }
   }
 
   *aResult = result;
   return true;
 }
 
-void
+static void
 TranslateFromScrollPortToScrollFrame(nsIContent* aContent, nsRect* aRect)
 {
   MOZ_ASSERT(aRect);
   nsIFrame* frame = GetScrollFrameFromContent(aContent);
   nsIScrollableFrame* scrollableFrame = frame ? frame->GetScrollTargetFrame() : nullptr;
   if (scrollableFrame) {
     *aRect += scrollableFrame->GetScrollPortRect().TopLeft();
   }
@@ -3416,17 +3416,17 @@ bool
 nsLayoutUtils::CalculateAndSetDisplayPortMargins(nsIScrollableFrame* aScrollFrame,
                                                  RepaintMode aRepaintMode) {
   nsIFrame* frame = do_QueryFrame(aScrollFrame);
   MOZ_ASSERT(frame);
   nsIContent* content = frame->GetContent();
   MOZ_ASSERT(content);
 
   FrameMetrics metrics = CalculateBasicFrameMetrics(aScrollFrame);
-  ScreenMargin displayportMargins = APZCTreeManager::CalculatePendingDisplayPort(
+  ScreenMargin displayportMargins = apz::CalculatePendingDisplayPort(
       metrics, ParentLayerPoint(0.0f, 0.0f));
   nsIPresShell* presShell = frame->PresContext()->GetPresShell();
   return nsLayoutUtils::SetDisplayPortMargins(
       content, presShell, displayportMargins, 0, aRepaintMode);
 }
 
 bool
 nsLayoutUtils::MaybeCreateDisplayPort(nsDisplayListBuilder& aBuilder,
--- a/layout/build/nsContentDLF.cpp
+++ b/layout/build/nsContentDLF.cpp
@@ -112,17 +112,17 @@ nsContentDLF::nsContentDLF()
 
 nsContentDLF::~nsContentDLF()
 {
 }
 
 NS_IMPL_ISUPPORTS(nsContentDLF,
                   nsIDocumentLoaderFactory)
 
-bool
+static bool
 MayUseXULXBL(nsIChannel* aChannel)
 {
   nsIScriptSecurityManager *securityManager =
     nsContentUtils::GetSecurityManager();
   if (!securityManager) {
     return false;
   }
 
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -260,18 +260,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(Noti
 NS_GENERIC_FACTORY_CONSTRUCTOR(PushNotifier)
 
 //-----------------------------------------------------------------------------
 
 static bool gInitialized = false;
 
 // Perform our one-time intialization for this module
 
-// static
-nsresult
+static nsresult
 Initialize()
 {
   if (gInitialized) {
     MOZ_CRASH("Recursive layout module initialization");
     return NS_ERROR_FAILURE;
   }
   if (XRE_GetProcessType() == GeckoProcessType_GPU) {
     // We mark the layout module as being available in the GPU process so that
--- a/layout/generic/ReflowInput.cpp
+++ b/layout/generic/ReflowInput.cpp
@@ -1974,17 +1974,17 @@ ReflowInput::InitAbsoluteConstraints(nsP
   ComputedISize() = computedSize.ConvertTo(wm, cbwm).ISize(wm);
 
   SetComputedLogicalOffsets(offsets.ConvertTo(wm, cbwm));
   SetComputedLogicalMargin(margin.ConvertTo(wm, cbwm));
 }
 
 // This will not be converted to abstract coordinates because it's only
 // used in CalcQuirkContainingBlockHeight
-nscoord
+static nscoord
 GetBlockMarginBorderPadding(const ReflowInput* aReflowInput)
 {
   nscoord result = 0;
   if (!aReflowInput) return result;
 
   // zero auto margins
   nsMargin margin = aReflowInput->ComputedPhysicalMargin();
   if (NS_AUTOMARGIN == margin.top)
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -1067,17 +1067,17 @@ BuildStrutInfoFromCollapsedItems(const F
         aStruts.AppendElement(StrutInfo(itemIdxInContainer,
                                         line->GetLineCrossSize()));
       }
       itemIdxInContainer++;
     }
   }
 }
 
-uint8_t
+static uint8_t
 SimplifyAlignOrJustifyContentForOneItem(uint16_t aAlignmentVal,
                                         bool aIsAlign)
 {
   // Mask away any explicit fallback, to get the main (non-fallback) part of
   // the specified value:
   uint16_t specified = aAlignmentVal & NS_STYLE_ALIGN_ALL_BITS;
 
   // XXX strip off <overflow-position> bits until we implement it (bug 1311892)
@@ -2315,17 +2315,17 @@ nsFlexContainerFrame::GetLogicalBaseline
 }
 
 // Helper for BuildDisplayList, to implement this special-case for flex items
 // from the spec:
 //    Flex items paint exactly the same as block-level elements in the
 //    normal flow, except that 'z-index' values other than 'auto' create
 //    a stacking context even if 'position' is 'static'.
 // http://www.w3.org/TR/2012/CR-css3-flexbox-20120918/#painting
-uint32_t
+static uint32_t
 GetDisplayFlagsForFlexItem(nsIFrame* aFrame)
 {
   MOZ_ASSERT(aFrame->IsFlexItem(), "Should only be called on flex items");
 
   const nsStylePosition* pos = aFrame->StylePosition();
   if (pos->mZIndex.GetUnit() == eStyleUnit_Integer) {
     return nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT;
   }
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -5059,18 +5059,18 @@ static FrameTarget GetSelectionClosestFr
       if (nsSVGUtils::IsInSVGTextSubtree(closest.mFrame))
         return FrameTarget(closest.mFrame, false, false);
       return GetSelectionClosestFrameForChild(closest.mFrame, aPoint, aFlags);
     }
   }
   return FrameTarget(aFrame, false, false);
 }
 
-nsIFrame::ContentOffsets OffsetsForSingleFrame(nsIFrame* aFrame,
-                                               const nsPoint& aPoint)
+static nsIFrame::ContentOffsets
+OffsetsForSingleFrame(nsIFrame* aFrame, const nsPoint& aPoint)
 {
   nsIFrame::ContentOffsets offsets;
   FrameContentRange range = GetRangeForFrame(aFrame);
   offsets.content = range.content;
   // If there are continuations (meaning it's not one rectangle), this is the
   // best this function can do
   if (aFrame->GetNextContinuation() || aFrame->GetPrevContinuation()) {
     offsets.offset = range.start;
@@ -6985,17 +6985,17 @@ static void InvalidateRenderingObservers
 
   if (!aFrameChanged) {
     return;
   }
 
   aFrame->MarkNeedsDisplayItemRebuild();
 }
 
-void
+static void
 SchedulePaintInternal(nsIFrame* aDisplayRoot, nsIFrame* aFrame,
                       nsIFrame::PaintType aType = nsIFrame::PAINT_DEFAULT)
 {
   MOZ_ASSERT(aDisplayRoot == nsLayoutUtils::GetDisplayRootFrame(aFrame));
   nsPresContext* pres = aDisplayRoot->PresContext()->GetRootPresContext();
 
   // No need to schedule a paint for an external document since they aren't
   // painted directly.
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -866,17 +866,17 @@ GetBrowserRoot(nsIContent* aContent)
 // child depends on the offset to the scroll frame, which changes as we scroll.
 // This perspective transform can cause the element to move relative to the
 // scrolled inner frame, which would cause the scrollable length changes during
 // scrolling if we didn't account for it. Since we don't want scrollHeight/Width
 // and the size of scrollbar thumbs to change during scrolling, we compute the
 // scrollable overflow by determining the scroll position at which the child
 // becomes completely visible within the scrollport rather than using the union
 // of the overflow areas at their current position.
-void
+static void
 GetScrollableOverflowForPerspective(nsIFrame* aScrolledFrame,
                                     nsIFrame* aCurrentFrame,
                                     const nsRect aScrollPort,
                                     nsPoint aOffset,
                                     nsRect& aScrolledFrameOverflowArea)
 {
   // Iterate over all children except pop-ups.
   FrameChildListIDs skip = nsIFrame::kSelectPopupList | nsIFrame::kPopupList;
@@ -2526,17 +2526,17 @@ bool ScrollFrameHelper::IsAlwaysActive()
 
   // If we're overflow:hidden, then start as inactive until
   // we get scrolled manually.
   ScrollbarStyles styles = GetScrollbarStylesFromFrame();
   return (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN &&
           styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN);
 }
 
-/*static*/ void
+static void
 RemoveDisplayPortCallback(nsITimer* aTimer, void* aClosure)
 {
   ScrollFrameHelper* helper = static_cast<ScrollFrameHelper*>(aClosure);
 
   // This function only ever gets called from the expiry timer, so it must
   // be non-null here. Set it to null here so that we don't keep resetting
   // it unnecessarily in MarkRecentlyScrolled().
   MOZ_ASSERT(helper->mDisplayPortExpiryTimer);
@@ -6310,17 +6310,17 @@ nsIScrollableFrame::GetPerceivedScrollin
   }
   return directions;
 }
 
 /**
  * Collect the scroll-snap-coordinates of frames in the subtree rooted at
  * |aFrame|, relative to |aScrolledFrame|, into |aOutCoords|.
  */
-void
+static void
 CollectScrollSnapCoordinates(nsIFrame* aFrame, nsIFrame* aScrolledFrame,
                              nsTArray<nsPoint>& aOutCoords)
 {
   nsIFrame::ChildListIterator childLists(aFrame);
   for (; !childLists.IsDone(); childLists.Next()) {
     nsFrameList::Enumerator childFrames(childLists.CurrentList());
     for (; !childFrames.AtEnd(); childFrames.Next()) {
       nsIFrame* f = childFrames.get();
@@ -6351,17 +6351,17 @@ CollectScrollSnapCoordinates(nsIFrame* a
         }
       }
 
       CollectScrollSnapCoordinates(f, aScrolledFrame, aOutCoords);
     }
   }
 }
 
-layers::ScrollSnapInfo
+static layers::ScrollSnapInfo
 ComputeScrollSnapInfo(const ScrollFrameHelper& aScrollFrame)
 {
   ScrollSnapInfo result;
 
   ScrollbarStyles styles = aScrollFrame.GetScrollbarStylesFromFrame();
 
   if (styles.mScrollSnapTypeY == NS_STYLE_SCROLL_SNAP_TYPE_NONE &&
       styles.mScrollSnapTypeX == NS_STYLE_SCROLL_SNAP_TYPE_NONE) {
--- a/layout/generic/nsSimplePageSequenceFrame.cpp
+++ b/layout/generic/nsSimplePageSequenceFrame.cpp
@@ -435,18 +435,19 @@ nsSimplePageSequenceFrame::StartPrint(ns
   // Begin printing of the document
   nsresult rv = NS_OK;
 
   mPageNum = 1;
 
   return rv;
 }
 
-void
-GetPrintCanvasElementsInFrame(nsIFrame* aFrame, nsTArray<RefPtr<HTMLCanvasElement> >* aArr)
+static void
+GetPrintCanvasElementsInFrame(nsIFrame* aFrame,
+                              nsTArray<RefPtr<HTMLCanvasElement> >* aArr)
 {
   if (!aFrame) {
     return;
   }
   for (nsIFrame::ChildListIterator childLists(aFrame);
     !childLists.IsDone(); childLists.Next()) {
 
     nsFrameList children = childLists.CurrentList();
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -5534,17 +5534,17 @@ struct EmphasisMarkInfo
 {
   RefPtr<gfxTextRun> textRun;
   gfxFloat advance;
   gfxFloat baselineOffset;
 };
 
 NS_DECLARE_FRAME_PROPERTY_DELETABLE(EmphasisMarkProperty, EmphasisMarkInfo)
 
-already_AddRefed<gfxTextRun>
+static already_AddRefed<gfxTextRun>
 GenerateTextRunForEmphasisMarks(nsTextFrame* aFrame,
                                 nsFontMetrics* aFontMetrics,
                                 nsStyleContext* aStyleContext,
                                 const nsStyleText* aStyleText)
 {
   const nsString& emphasisString = aStyleText->mTextEmphasisStyleString;
   RefPtr<DrawTarget> dt = CreateReferenceDrawTarget(aFrame);
   auto appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -10,18 +10,16 @@
 #include "gfxPrefs.h"
 #include "mozilla/BrowserElementParent.h"
 #include "mozilla/EventForwards.h"  // for Modifiers
 #include "mozilla/ViewportFrame.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/TabParent.h"
-#include "mozilla/layers/APZCTreeManager.h"
-#include "mozilla/layers/APZThreadUtils.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/LayerTransactionParent.h"
 #include "nsContentUtils.h"
 #include "nsFocusManager.h"
 #include "nsFrameLoader.h"
 #include "nsIObserver.h"
 #include "nsStyleStructInlines.h"
 #include "nsSubDocumentFrame.h"
@@ -71,17 +69,17 @@ GetContentRectLayerOffset(nsIFrame* aCon
 // widget, and hence in non-retained mode.
 inline static bool
 IsTempLayerManager(LayerManager* aManager)
 {
   return (mozilla::layers::LayersBackend::LAYERS_BASIC == aManager->GetBackendType() &&
           !static_cast<BasicLayerManager*>(aManager)->IsRetained());
 }
 
-already_AddRefed<LayerManager>
+static already_AddRefed<LayerManager>
 GetLayerManager(nsFrameLoader* aFrameLoader)
 {
   if (nsIContent* content = aFrameLoader->GetOwnerContent()) {
     RefPtr<LayerManager> lm = nsContentUtils::LayerManagerForContent(content);
     if (lm) {
       return lm.forget();
     }
   }
--- a/layout/mathml/nsMathMLmactionFrame.cpp
+++ b/layout/mathml/nsMathMLmactionFrame.cpp
@@ -241,17 +241,17 @@ nsMathMLmactionFrame::AttributeChanged(i
 // ################################################################
 
 NS_IMPL_ISUPPORTS(nsMathMLmactionFrame::MouseListener,
                   nsIDOMEventListener)
 
 
 // helper to show a msg on the status bar
 // curled from nsPluginFrame.cpp ...
-void
+static void
 ShowStatus(nsPresContext* aPresContext, nsString& aStatusMsg)
 {
   nsCOMPtr<nsIDocShellTreeItem> docShellItem(aPresContext->GetDocShell());
   if (docShellItem) {
     nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
     docShellItem->GetTreeOwner(getter_AddRefs(treeOwner));
     if (treeOwner) {
       nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(treeOwner));
--- a/layout/painting/DisplayItemClip.cpp
+++ b/layout/painting/DisplayItemClip.cpp
@@ -180,18 +180,19 @@ DisplayItemClip::ApproximateIntersectInw
     nsRegion rgn = nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, r);
     r = rgn.GetLargestRectangle();
   }
   return r;
 }
 
 // Test if (aXPoint, aYPoint) is in the ellipse with center (aXCenter, aYCenter)
 // and radii aXRadius, aYRadius.
-bool IsInsideEllipse(nscoord aXRadius, nscoord aXCenter, nscoord aXPoint,
-                     nscoord aYRadius, nscoord aYCenter, nscoord aYPoint)
+static bool
+IsInsideEllipse(nscoord aXRadius, nscoord aXCenter, nscoord aXPoint,
+                nscoord aYRadius, nscoord aYCenter, nscoord aYPoint)
 {
   float scaledX = float(aXPoint - aXCenter) / float(aXRadius);
   float scaledY = float(aYPoint - aYCenter) / float(aYRadius);
   return scaledX * scaledX + scaledY * scaledY < 1.0f;
 }
 
 bool
 DisplayItemClip::IsRectClippedByRoundedCorner(const nsRect& aRect) const
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -1833,17 +1833,18 @@ private:
 
   bool mTextureClientLocked;
   gfx::IntSize mSize;
   LayerManager* mLayerManager;
   RefPtr<gfx::DrawTarget> mDrawTarget;
   RefPtr<TextureClient> mTextureClient;
 };
 
-PaintedDisplayItemLayerUserData* GetPaintedDisplayItemLayerUserData(Layer* aLayer)
+static PaintedDisplayItemLayerUserData*
+GetPaintedDisplayItemLayerUserData(Layer* aLayer)
 {
   return static_cast<PaintedDisplayItemLayerUserData*>(
     aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
 }
 
 /* static */ void
 FrameLayerBuilder::Shutdown()
 {
@@ -1886,41 +1887,43 @@ FrameLayerBuilder::GetDisplayItemData(ns
     if (item->mDisplayItemKey == aKey &&
         item->mLayer->Manager() == mRetainingManager) {
       return item;
     }
   }
   return nullptr;
 }
 
-nsACString&
+#ifdef MOZ_DUMP_PAINTING
+static nsACString&
 AppendToString(nsACString& s, const nsIntRect& r,
                const char* pfx="", const char* sfx="")
 {
   s += pfx;
   s += nsPrintfCString(
     "(x=%d, y=%d, w=%d, h=%d)",
     r.x, r.y, r.width, r.height);
   return s += sfx;
 }
 
-nsACString&
+static nsACString&
 AppendToString(nsACString& s, const nsIntRegion& r,
                const char* pfx="", const char* sfx="")
 {
   s += pfx;
 
   s += "< ";
   for (auto iter = r.RectIter(); !iter.Done(); iter.Next()) {
     AppendToString(s, iter.Get()) += "; ";
   }
   s += ">";
 
   return s += sfx;
 }
+#endif // MOZ_DUMP_PAINTING
 
 /**
  * Invalidate aRegion in aLayer. aLayer is in the coordinate system
  * *after* aTranslation has been applied, so we need to
  * apply the inverse of that transform before calling InvalidateRegion.
  */
 static void
 InvalidatePostTransformRegion(PaintedLayer* aLayer, const nsIntRegion& aRegion,
@@ -4033,27 +4036,27 @@ GetASRForPerspective(const ActiveScrolle
     nsIFrame* scrolledFrame = asr->mScrollableFrame->GetScrolledFrame();
     if (nsLayoutUtils::IsAncestorFrameCrossDoc(scrolledFrame, aPerspectiveFrame)) {
       return asr;
     }
   }
   return nullptr;
 }
 
-CSSMaskLayerUserData*
+static CSSMaskLayerUserData*
 GetCSSMaskLayerUserData(Layer* aMaskLayer)
 {
   if (!aMaskLayer) {
     return nullptr;
   }
 
   return static_cast<CSSMaskLayerUserData*>(aMaskLayer->GetUserData(&gCSSMaskLayerUserData));
 }
 
-void
+static void
 SetCSSMaskLayerUserData(Layer* aMaskLayer)
 {
   MOZ_ASSERT(aMaskLayer);
 
   aMaskLayer->SetUserData(&gCSSMaskLayerUserData,
                           new CSSMaskLayerUserData());
 }
 
@@ -5127,18 +5130,19 @@ FindOpaqueRegionEntry(nsTArray<OpaqueReg
     if (d->mAnimatedGeometryRoot == aAnimatedGeometryRoot &&
         d->mASR == aASR) {
       return d;
     }
   }
   return nullptr;
 }
 
-const ActiveScrolledRoot*
-FindDirectChildASR(const ActiveScrolledRoot* aParent, const ActiveScrolledRoot* aDescendant)
+static const ActiveScrolledRoot*
+FindDirectChildASR(const ActiveScrolledRoot* aParent,
+                   const ActiveScrolledRoot* aDescendant)
 {
   MOZ_ASSERT(aDescendant, "can't start at the root when looking for a child");
   MOZ_ASSERT(ActiveScrolledRoot::IsAncestor(aParent, aDescendant));
   const ActiveScrolledRoot* directChild = aDescendant;
   while (directChild->mParent != aParent) {
     directChild = directChild->mParent;
     MOZ_RELEASE_ASSERT(directChild, "this must not be null");
   }
@@ -6356,27 +6360,27 @@ ContainerState::SetupMaskLayer(Layer *aL
   if (!maskLayer) {
     return 0;
   }
 
   aLayer->SetMaskLayer(maskLayer);
   return aRoundedRectClipCount;
 }
 
-MaskLayerUserData*
+static MaskLayerUserData*
 GetMaskLayerUserData(Layer* aMaskLayer)
 {
   if (!aMaskLayer) {
     return nullptr;
   }
 
   return static_cast<MaskLayerUserData*>(aMaskLayer->GetUserData(&gMaskLayerUserData));
 }
 
-void
+static void
 SetMaskLayerUserData(Layer* aMaskLayer)
 {
   MOZ_ASSERT(aMaskLayer);
 
   aMaskLayer->SetUserData(&gMaskLayerUserData,
                           new MaskLayerUserData());
 }
 
--- a/layout/painting/RetainedDisplayListBuilder.cpp
+++ b/layout/painting/RetainedDisplayListBuilder.cpp
@@ -35,17 +35,18 @@
  * ordering in the DAG, since they need to intersect to have an ordering and
  * we would have built both in the new list if they intersected. Given that, we
  * can align items that appear in both lists, and any items that appear between
  * matched items can be inserted into the merged list in any order.
  */
 
 using namespace mozilla;
 
-void MarkFramesWithItemsAndImagesModified(nsDisplayList* aList)
+static void
+MarkFramesWithItemsAndImagesModified(nsDisplayList* aList)
 {
   for (nsDisplayItem* i = aList->GetBottom(); i != nullptr; i = i->GetAbove()) {
     if (!i->HasDeletedFrame() && i->CanBeReused() && !i->Frame()->IsFrameModified()) {
       // If we have existing cached geometry for this item, then check that for
       // whether we need to invalidate for a sync decode. If we don't, then
       // use the item's flags.
       DisplayItemData* data = FrameLayerBuilder::GetOldDataFor(i);
       bool invalidate = false;
@@ -64,17 +65,18 @@ void MarkFramesWithItemsAndImagesModifie
       }
     }
     if (i->GetChildren()) {
       MarkFramesWithItemsAndImagesModified(i->GetChildren());
     }
   }
 }
 
-bool IsAnyAncestorModified(nsIFrame* aFrame)
+static bool
+IsAnyAncestorModified(nsIFrame* aFrame)
 {
   nsIFrame* f = aFrame;
   while (f) {
     if (f->IsFrameModified()) {
       return true;
     }
     f = nsLayoutUtils::GetCrossDocParentFrame(f);
   }
@@ -141,17 +143,18 @@ RetainedDisplayListBuilder::PreProcessDi
 
     saved.AppendToTop(i);
   }
   aList->AppendToTop(&saved);
   aList->RestoreState();
   return modified;
 }
 
-bool IsSameItem(nsDisplayItem* aFirst, nsDisplayItem* aSecond)
+static bool
+IsSameItem(nsDisplayItem* aFirst, nsDisplayItem* aSecond)
 {
   if (!aFirst || !aSecond) {
     return aFirst == aSecond;
   }
   return aFirst->Frame() == aSecond->Frame() &&
          aFirst->GetPerFrameKey() == aSecond->GetPerFrameKey();
 }
 
@@ -304,18 +307,19 @@ RetainedDisplayListBuilder::IncrementSub
   MOZ_ASSERT(subDocFrame);
 
   nsIPresShell* presShell = subDocFrame->GetSubdocumentPresShellForPainting(0);
   MOZ_ASSERT(presShell);
 
   mBuilder.IncrementPresShellPaintCount(presShell);
 }
 
-void UpdateASR(nsDisplayItem* aItem,
-               Maybe<const ActiveScrolledRoot*>& aContainerASR)
+static void
+UpdateASR(nsDisplayItem* aItem,
+          Maybe<const ActiveScrolledRoot*>& aContainerASR)
 {
   if (!aContainerASR) {
     return;
   }
 
   nsDisplayWrapList* wrapList = aItem->AsDisplayWrapList();
   if (!wrapList) {
     aItem->SetActiveScrolledRoot(aContainerASR.value());
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -782,17 +782,17 @@ nsCSSRendering::CreateWebRenderCommandsF
   }
 
   bir->CreateWebRenderCommands(aItem, aForFrame, aBuilder, aResources, aSc,
                                aManager, aDisplayListBuilder);
 
   return true;
 }
 
-nsCSSBorderRenderer
+static nsCSSBorderRenderer
 ConstructBorderRenderer(nsPresContext* aPresContext,
                         nsStyleContext* aStyleContext,
                         DrawTarget* aDrawTarget,
                         nsIFrame* aForFrame,
                         const nsRect& aDirtyRect,
                         const nsRect& aBorderArea,
                         const nsStyleBorder& aStyleBorder,
                         Sides aSkipSides,
--- a/layout/painting/nsCSSRenderingBorders.cpp
+++ b/layout/painting/nsCSSRenderingBorders.cpp
@@ -2613,17 +2613,18 @@ nsCSSBorderRenderer::AllBordersSolid()
       continue;
     }
     return false;
   }
 
   return true;
 }
 
-bool IsVisible(int aStyle)
+static bool
+IsVisible(int aStyle)
 {
   if (aStyle != NS_STYLE_BORDER_STYLE_NONE &&
       aStyle != NS_STYLE_BORDER_STYLE_HIDDEN) {
         return true;
   }
   return false;
 }
 
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -2893,17 +2893,18 @@ struct FramesWithDepth
     return this == &aOther;
   }
 
   float mDepth;
   nsTArray<nsIFrame*> mFrames;
 };
 
 // Sort the frames by depth and then moves all the contained frames to the destination
-void FlushFramesArray(nsTArray<FramesWithDepth>& aSource, nsTArray<nsIFrame*>* aDest)
+static void
+FlushFramesArray(nsTArray<FramesWithDepth>& aSource, nsTArray<nsIFrame*>* aDest)
 {
   if (aSource.IsEmpty()) {
     return;
   }
   aSource.Sort();
   uint32_t length = aSource.Length();
   for (uint32_t i = 0; i < length; i++) {
     aDest->AppendElements(Move(aSource[i].mFrames));
@@ -3602,17 +3603,17 @@ static nsIFrame* GetBackgroundStyleConte
       return nullptr;
     }
 
     f = aFrame;
   }
   return f;
 }
 
-/* static */ void
+static void
 SetBackgroundClipRegion(DisplayListClipState::AutoSaveRestore& aClipState,
                         nsIFrame* aFrame, const nsPoint& aToReferenceFrame,
                         const nsStyleImageLayers::Layer& aLayer,
                         const nsRect& aBackgroundRect,
                         bool aWillPaintBorder)
 {
   nsCSSRendering::ImageLayerClipState clip;
   nsCSSRendering::GetImageLayerClip(aLayer, aFrame, *aFrame->StyleBorder(),
--- a/layout/style/ImageLoader.cpp
+++ b/layout/style/ImageLoader.cpp
@@ -312,17 +312,18 @@ ImageLoader::GetPresContext()
 static bool
 IsRenderNoImages(uint32_t aDisplayItemKey)
 {
   DisplayItemType type = GetDisplayItemTypeFromKey(aDisplayItemKey);
   uint8_t flags = GetDisplayItemFlagsForType(type);
   return flags & TYPE_RENDERS_NO_IMAGES;
 }
 
-void InvalidateImages(nsIFrame* aFrame)
+static void
+InvalidateImages(nsIFrame* aFrame)
 {
   bool invalidateFrame = false;
   const SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData();
   for (uint32_t i = 0; i < array.Length(); i++) {
     DisplayItemData* data = DisplayItemData::AssertDisplayItemData(array.ElementAt(i));
     uint32_t displayItemKey = data->GetDisplayItemKey();
     if (displayItemKey != 0 && !IsRenderNoImages(displayItemKey)) {
       if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -436,17 +436,17 @@ Gecko_UnsetDirtyStyleAttr(RawGeckoElemen
     // XXX This can happen when nodes are adopted from a Gecko-style-backend
     //     document into a Servo-style-backend document.  See bug 1330051.
     NS_WARNING("stylo: requesting a Gecko declaration block?");
     return;
   }
   decl->UnsetDirty();
 }
 
-const RawServoDeclarationBlockStrong*
+static const RawServoDeclarationBlockStrong*
 AsRefRawStrong(const RefPtr<RawServoDeclarationBlock>& aDecl)
 {
   static_assert(sizeof(RefPtr<RawServoDeclarationBlock>) ==
                 sizeof(RawServoDeclarationBlockStrong),
                 "RefPtr should just be a pointer");
   return reinterpret_cast<const RawServoDeclarationBlockStrong*>(&aDecl);
 }
 
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -459,17 +459,17 @@ CalcValueToCSSValue(const PixelCalcValue
     arr->Item(0).SetArrayValue(arr2, eCSSUnit_Calc_Plus);
     arr2->Item(0).SetFloatValue(aCalc.mLength, eCSSUnit_Pixel);
     arr2->Item(1).SetPercentValue(aCalc.mPercent);
   }
 
   aValue.SetArrayValue(arr, eCSSUnit_Calc);
 }
 
-double
+static double
 CalcPositionSquareDistance(const nsCSSValue& aPos1,
                            const nsCSSValue& aPos2)
 {
   NS_ASSERTION(aPos1.GetUnit() == eCSSUnit_Array &&
                aPos2.GetUnit() == eCSSUnit_Array,
                "Expected two arrays");
 
   PixelCalcValue calcVal[4];
@@ -515,17 +515,17 @@ CalcBackgroundCoord(const nsCSSValue& aC
   nsCSSValue::Array* array = aCoord.GetArrayValue();
   MOZ_ASSERT(array->Count() == 2 &&
              array->Item(0).GetUnit() == eCSSUnit_Null &&
              array->Item(1).GetUnit() != eCSSUnit_Null,
              "Invalid position value");
   return ExtractCalcValue(array->Item(1));
 }
 
-double
+static double
 CalcPositionCoordSquareDistance(const nsCSSValue& aPos1,
                                 const nsCSSValue& aPos2)
 {
   PixelCalcValue calcVal1 = CalcBackgroundCoord(aPos1);
   PixelCalcValue calcVal2 = CalcBackgroundCoord(aPos2);
 
   float difflen = calcVal2.mLength - calcVal1.mLength;
   float diffpct = calcVal2.mPercent - calcVal1.mPercent;
@@ -2026,33 +2026,33 @@ AddWeightedColorsAndClamp(double aCoeff1
   // But unpremultiplication in AddWeightedColors() does not work well
   // for such cases, so we use another function named DiluteColor() which
   // has a similar logic to AddWeightedColors().
   return aCoeff2 == 0.0
     ? DiluteColor(aValue1, aCoeff1)
     : AddWeightedColors(aCoeff1, aValue1, aCoeff2, aValue2).ToColor();
 }
 
-void
+static void
 AppendToCSSValueList(UniquePtr<nsCSSValueList>& aHead,
                      UniquePtr<nsCSSValueList>&& aValueToAppend,
                      nsCSSValueList** aTail)
 {
   MOZ_ASSERT(!aHead == !*aTail,
              "Can't have head w/o tail, & vice versa");
 
   if (!aHead) {
     aHead = Move(aValueToAppend);
     *aTail = aHead.get();
   } else {
     (*aTail) = (*aTail)->mNext = aValueToAppend.release();
   }
 }
 
-void
+static void
 AppendToCSSValuePairList(UniquePtr<nsCSSValuePairList>& aHead,
                          UniquePtr<nsCSSValuePairList>&& aValueToAppend,
                          nsCSSValuePairList** aTail)
 {
   MOZ_ASSERT(!aHead == !*aTail,
              "Can't have head w/o tail, & vice versa");
 
   if (!aHead) {
@@ -3414,17 +3414,17 @@ StyleAnimationValue::Accumulate(nsCSSPro
                             1.0, result,
                             aCount, aA,
                             result);
       break;
   }
   return result;
 }
 
-already_AddRefed<css::StyleRule>
+static already_AddRefed<css::StyleRule>
 BuildStyleRule(nsCSSPropertyID aProperty,
                dom::Element* aTargetElement,
                const nsAString& aSpecifiedValue,
                bool aUseSVGMode)
 {
   // Set up an empty CSS Declaration
   RefPtr<css::Declaration> declaration(new css::Declaration());
   declaration->InitializeEmpty();
@@ -3449,17 +3449,17 @@ BuildStyleRule(nsCSSPropertyID aProperty
   }
 
   RefPtr<css::StyleRule> rule = new css::StyleRule(nullptr,
                                                      declaration,
                                                      0, 0);
   return rule.forget();
 }
 
-already_AddRefed<css::StyleRule>
+static already_AddRefed<css::StyleRule>
 BuildStyleRule(nsCSSPropertyID aProperty,
                dom::Element* aTargetElement,
                const nsCSSValue& aSpecifiedValue,
                bool aUseSVGMode)
 {
   MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
              "Should be a longhand property");
 
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -156,17 +156,17 @@ enum {
 // The names are in kCSSRawProperties.
 static nsCSSPropertyID gAliases[eCSSAliasCount != 0 ? eCSSAliasCount : 1] = {
 #define CSS_PROP_ALIAS(aliasname_, aliasid_, propid_, aliasmethod_, pref_)  \
   eCSSProperty_##propid_ ,
 #include "nsCSSPropAliasList.h"
 #undef CSS_PROP_ALIAS
 };
 
-nsStaticCaseInsensitiveNameTable*
+static nsStaticCaseInsensitiveNameTable*
 CreateStaticTable(const char* const aRawTable[], int32_t aLength)
 {
   auto table = new nsStaticCaseInsensitiveNameTable(aRawTable, aLength);
 #ifdef DEBUG
   // Partially verify the entries.
   for (int32_t index = 0; index < aLength; ++index) {
     nsAutoCString temp(aRawTable[index]);
     MOZ_ASSERT(-1 == temp.FindChar('_'),
--- a/layout/style/nsMediaFeatures.cpp
+++ b/layout/style/nsMediaFeatures.cpp
@@ -32,32 +32,32 @@ using namespace mozilla;
 static nsTArray<RefPtr<nsAtom>>* sSystemMetrics = nullptr;
 
 #ifdef XP_WIN
 // Cached theme identifier for the moz-windows-theme media query.
 static uint8_t sWinThemeId = LookAndFeel::eWindowsTheme_Generic;
 #endif
 
 static const nsCSSProps::KTableEntry kOrientationKeywords[] = {
-  { eCSSKeyword_portrait,                 NS_STYLE_ORIENTATION_PORTRAIT },
-  { eCSSKeyword_landscape,                NS_STYLE_ORIENTATION_LANDSCAPE },
+  { eCSSKeyword_portrait,                 StyleOrientation::Portrait },
+  { eCSSKeyword_landscape,                StyleOrientation::Landscape },
   { eCSSKeyword_UNKNOWN,                  -1 }
 };
 
 static const nsCSSProps::KTableEntry kScanKeywords[] = {
-  { eCSSKeyword_progressive,              NS_STYLE_SCAN_PROGRESSIVE },
-  { eCSSKeyword_interlace,                NS_STYLE_SCAN_INTERLACE },
+  { eCSSKeyword_progressive,              StyleScan::Progressive },
+  { eCSSKeyword_interlace,                StyleScan::Interlace },
   { eCSSKeyword_UNKNOWN,                  -1 }
 };
 
 static const nsCSSProps::KTableEntry kDisplayModeKeywords[] = {
-  { eCSSKeyword_browser,                 NS_STYLE_DISPLAY_MODE_BROWSER },
-  { eCSSKeyword_minimal_ui,              NS_STYLE_DISPLAY_MODE_MINIMAL_UI },
-  { eCSSKeyword_standalone,              NS_STYLE_DISPLAY_MODE_STANDALONE },
-  { eCSSKeyword_fullscreen,              NS_STYLE_DISPLAY_MODE_FULLSCREEN },
+  { eCSSKeyword_browser,                 StyleDisplayMode::Browser },
+  { eCSSKeyword_minimal_ui,              StyleDisplayMode::MinimalUi },
+  { eCSSKeyword_standalone,              StyleDisplayMode::Standalone },
+  { eCSSKeyword_fullscreen,              StyleDisplayMode::Fullscreen },
   { eCSSKeyword_UNKNOWN,                 -1 }
 };
 
 #ifdef XP_WIN
 struct WindowsThemeName {
   LookAndFeel::WindowsTheme mId;
   nsStaticAtom** mName;
 };
@@ -184,30 +184,30 @@ GetDeviceHeight(nsIDocument* aDocument, 
 }
 
 static void
 GetOrientation(nsIDocument* aDocument, const nsMediaFeature*,
                nsCSSValue& aResult)
 {
   nsSize size = GetSize(aDocument);
   // Per spec, square viewports should be 'portrait'
-  int32_t orientation = size.width > size.height
-    ?  NS_STYLE_ORIENTATION_LANDSCAPE : NS_STYLE_ORIENTATION_PORTRAIT;
-  aResult.SetIntValue(orientation, eCSSUnit_Enumerated);
+  auto orientation = size.width > size.height
+    ? StyleOrientation::Landscape : StyleOrientation::Portrait;
+  aResult.SetEnumValue(orientation);
 }
 
 static void
 GetDeviceOrientation(nsIDocument* aDocument, const nsMediaFeature*,
                      nsCSSValue& aResult)
 {
   nsSize size = GetDeviceSize(aDocument);
   // Per spec, square viewports should be 'portrait'
-  int32_t orientation = size.width > size.height
-    ? NS_STYLE_ORIENTATION_LANDSCAPE : NS_STYLE_ORIENTATION_PORTRAIT;
-  aResult.SetIntValue(orientation, eCSSUnit_Enumerated);
+  auto orientation = size.width > size.height
+    ? StyleOrientation::Landscape : StyleOrientation::Portrait;
+  aResult.SetEnumValue(orientation);
 }
 
 static void
 GetIsResourceDocument(nsIDocument* aDocument, const nsMediaFeature*,
                       nsCSSValue& aResult)
 {
   aResult.SetIntValue(aDocument->IsResourceDoc() ? 1 : 0, eCSSUnit_Integer);
 }
@@ -346,38 +346,37 @@ GetDisplayMode(nsIDocument* aDocument, c
 {
   nsIDocument* rootDocument = TopDocument(aDocument);
 
   nsCOMPtr<nsISupports> container = rootDocument->GetContainer();
   if (nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container)) {
     nsCOMPtr<nsIWidget> mainWidget;
     baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
     if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Fullscreen) {
-      aResult.SetIntValue(NS_STYLE_DISPLAY_MODE_FULLSCREEN, eCSSUnit_Enumerated);
+      aResult.SetEnumValue(StyleDisplayMode::Fullscreen);
       return;
     }
   }
 
-  static_assert(nsIDocShell::DISPLAY_MODE_BROWSER == NS_STYLE_DISPLAY_MODE_BROWSER &&
-                nsIDocShell::DISPLAY_MODE_MINIMAL_UI == NS_STYLE_DISPLAY_MODE_MINIMAL_UI &&
-                nsIDocShell::DISPLAY_MODE_STANDALONE == NS_STYLE_DISPLAY_MODE_STANDALONE &&
-                nsIDocShell::DISPLAY_MODE_FULLSCREEN == NS_STYLE_DISPLAY_MODE_FULLSCREEN,
+  static_assert(nsIDocShell::DISPLAY_MODE_BROWSER == static_cast<int32_t>(StyleDisplayMode::Browser) &&
+                nsIDocShell::DISPLAY_MODE_MINIMAL_UI == static_cast<int32_t>(StyleDisplayMode::MinimalUi) &&
+                nsIDocShell::DISPLAY_MODE_STANDALONE == static_cast<int32_t>(StyleDisplayMode::Standalone) &&
+                nsIDocShell::DISPLAY_MODE_FULLSCREEN == static_cast<int32_t>(StyleDisplayMode::Fullscreen),
                 "nsIDocShell display modes must mach nsStyleConsts.h");
 
-  uint32_t displayMode = NS_STYLE_DISPLAY_MODE_BROWSER;
+  uint32_t displayMode = nsIDocShell::DISPLAY_MODE_BROWSER;
   if (nsIDocShell* docShell = rootDocument->GetDocShell()) {
     docShell->GetDisplayMode(&displayMode);
   }
 
-  aResult.SetIntValue(displayMode, eCSSUnit_Enumerated);
+  aResult.SetEnumValue(static_cast<StyleDisplayMode>(displayMode));
 }
 
 static void
-GetGrid(nsIDocument* aDocument, const nsMediaFeature*,
-        nsCSSValue& aResult)
+GetGrid(nsIDocument* aDocument, const nsMediaFeature*, nsCSSValue& aResult)
 {
   // Gecko doesn't support grid devices (e.g., ttys), so the 'grid'
   // feature is always 0.
   aResult.SetIntValue(0, eCSSUnit_Integer);
 }
 
 static void
 GetDevicePixelRatio(nsIDocument* aDocument, const nsMediaFeature*,
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -1201,24 +1201,30 @@ enum class StyleOverscrollBehavior : uin
 #define NS_STYLE_SCROLL_SNAP_TYPE_MANDATORY         1
 #define NS_STYLE_SCROLL_SNAP_TYPE_PROXIMITY         2
 
 /*****************************************************************************
  * Constants for media features.                                             *
  *****************************************************************************/
 
 // orientation
-#define NS_STYLE_ORIENTATION_PORTRAIT           0
-#define NS_STYLE_ORIENTATION_LANDSCAPE          1
+enum class StyleOrientation : uint8_t {
+  Portrait = 0,
+  Landscape,
+};
 
 // scan
-#define NS_STYLE_SCAN_PROGRESSIVE               0
-#define NS_STYLE_SCAN_INTERLACE                 1
+enum class StyleScan : uint8_t {
+  Progressive = 0,
+  Interlace,
+};
 
 // display-mode
-#define NS_STYLE_DISPLAY_MODE_BROWSER           0
-#define NS_STYLE_DISPLAY_MODE_MINIMAL_UI        1
-#define NS_STYLE_DISPLAY_MODE_STANDALONE        2
-#define NS_STYLE_DISPLAY_MODE_FULLSCREEN        3
+enum class StyleDisplayMode : uint8_t {
+  Browser = 0,
+  MinimalUi,
+  Standalone,
+  Fullscreen,
+};
 
 } // namespace mozilla
 
 #endif /* nsStyleConsts_h___ */
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -650,17 +650,17 @@ struct EffectOffsets {
   // The offset between the reference frame and the bounding box of the
   // target frame in app unit.
   nsPoint  offsetToUserSpace;
   // The offset between the reference frame and the bounding box of the
   // target frame in device unit.
   gfxPoint offsetToUserSpaceInDevPx;
 };
 
-EffectOffsets
+static EffectOffsets
 ComputeEffectOffset(nsIFrame* aFrame, const PaintFramesParams& aParams)
 {
   EffectOffsets result;
 
   result.offsetToBoundingBox =
     aParams.builder->ToReferenceFrame(aFrame) -
     nsSVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
   if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
--- a/layout/tables/nsTableRowFrame.cpp
+++ b/layout/tables/nsTableRowFrame.cpp
@@ -303,17 +303,17 @@ nsTableRowFrame::GetUsedBorder() const
 }
 
 /* virtual */ nsMargin
 nsTableRowFrame::GetUsedPadding() const
 {
   return nsMargin(0,0,0,0);
 }
 
-nscoord
+static nscoord
 GetBSizeOfRowsSpannedBelowFirst(nsTableCellFrame& aTableCellFrame,
                                 nsTableFrame&     aTableFrame,
                                 const WritingMode aWM)
 {
   nscoord bsize = 0;
   int32_t rowSpan = aTableFrame.GetEffectiveRowSpan(aTableCellFrame);
   // add in bsize of rows spanned beyond the 1st one
   nsIFrame* nextRow = aTableCellFrame.GetParent()->GetNextSibling();
@@ -686,17 +686,17 @@ CalcAvailISize(nsTableFrame&     aTableF
     if (spanX > 0 &&
         aTableFrame.ColumnHasCellSpacingBefore(colIndex + spanX)) {
       cellAvailISize += aTableFrame.GetColSpacing(colIndex + spanX - 1);
     }
   }
   return cellAvailISize;
 }
 
-nscoord
+static nscoord
 GetSpaceBetween(int32_t       aPrevColIndex,
                 int32_t       aColIndex,
                 int32_t       aColSpan,
                 nsTableFrame& aTableFrame,
                 bool          aCheckVisibility)
 {
   nscoord space = 0;
   int32_t colIdx;
--- a/layout/xul/nsBoxFrame.cpp
+++ b/layout/xul/nsBoxFrame.cpp
@@ -1922,17 +1922,17 @@ nsBoxFrame::AppendDirectlyOwnedAnonBoxes
 {
   if (GetStateBits() & NS_STATE_BOX_WRAPS_KIDS_IN_BLOCK) {
     aResult.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild()));
   }
 }
 
 // Helper less-than-or-equal function, used in CheckBoxOrder() as a
 // template-parameter for the sorting functions.
-bool
+static bool
 IsBoxOrdinalLEQ(nsIFrame* aFrame1,
                 nsIFrame* aFrame2)
 {
   // If we've got a placeholder frame, use its out-of-flow frame's ordinal val.
   nsIFrame* aRealFrame1 = nsPlaceholderFrame::GetRealFrameFor(aFrame1);
   nsIFrame* aRealFrame2 = nsPlaceholderFrame::GetRealFrameFor(aFrame2);
   return aRealFrame1->GetXULOrdinal() <= aRealFrame2->GetXULOrdinal();
 }
--- a/layout/xul/nsImageBoxFrame.cpp
+++ b/layout/xul/nsImageBoxFrame.cpp
@@ -101,18 +101,17 @@ nsImageBoxFrameEvent::Run()
 
 // Fire off an event that'll asynchronously call the image elements
 // onload handler once handled. This is needed since the image library
 // can't decide if it wants to call its observer methods
 // synchronously or asynchronously. If an image is loaded from the
 // cache the notifications come back synchronously, but if the image
 // is loaded from the network the notifications come back
 // asynchronously.
-
-void
+static void
 FireImageDOMEvent(nsIContent* aContent, EventMessage aMessage)
 {
   NS_ASSERTION(aMessage == eLoad || aMessage == eLoadError,
                "invalid message");
 
   nsCOMPtr<nsIRunnable> event = new nsImageBoxFrameEvent(aContent, aMessage);
   nsresult rv = aContent->OwnerDoc()->Dispatch(TaskCategory::Other,
                                                event.forget());
--- a/layout/xul/nsSliderFrame.cpp
+++ b/layout/xul/nsSliderFrame.cpp
@@ -1042,24 +1042,24 @@ public:
   }
 
 private:
   RefPtr<nsIPresShell> mPresShell;
   RefPtr<nsIWidget> mWidget;
   AsyncDragMetrics mDragMetrics;
 };
 
-bool
+static bool
 UsesSVGEffects(nsIFrame* aFrame)
 {
   return aFrame->StyleEffects()->HasFilters()
       || nsSVGIntegrationUtils::UsingMaskOrClipPathForFrame(aFrame);
 }
 
-bool
+static bool
 ScrollFrameWillBuildScrollInfoLayer(nsIFrame* aScrollFrame)
 {
   nsIFrame* current = aScrollFrame;
   while (current) {
     if (UsesSVGEffects(current)) {
       return true;
     }
     current = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(current);
--- a/media/libcubeb/cubeb-pulse-rs/README_MOZILLA
+++ b/media/libcubeb/cubeb-pulse-rs/README_MOZILLA
@@ -1,8 +1,8 @@
 The source from this directory was copied from the cubeb-pulse-rs
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
 The cubeb-pulse-rs git repository is: https://github.com/djg/cubeb-pulse-rs.git
 
-The git commit ID used was f58dc34c5af519352aed4b4cd79bb34060e59c65 (2018-02-23 11:16:40 +1000)
+The git commit ID used was 22cdde3e573303649a77e48a19f7ca2c4d308047 (2018-03-06 10:39:46 +1000)
--- a/media/libcubeb/cubeb-pulse-rs/pulse-rs/src/context.rs
+++ b/media/libcubeb/cubeb-pulse-rs/pulse-rs/src/context.rs
@@ -184,27 +184,30 @@ impl Context {
 
             result
         }
 
         unsafe { ffi::pa_context_rttime_new(self.raw_mut(), usec, Some(wrapped::<CB>), userdata) }
     }
 
     pub fn get_server_info<CB>(&self, _: CB, userdata: *mut c_void) -> Result<Operation>
-        where CB: Fn(&Context, &ServerInfo, *mut c_void)
+        where CB: Fn(&Context, Option<&ServerInfo>, *mut c_void)
     {
         debug_assert_eq!(::std::mem::size_of::<CB>(), 0);
 
         // See: A note about `wrapped` functions
         unsafe extern "C" fn wrapped<F>(c: *mut ffi::pa_context, i: *const ffi::pa_server_info, userdata: *mut c_void)
-            where F: Fn(&Context, &ServerInfo, *mut c_void)
+            where F: Fn(&Context, Option<&ServerInfo>, *mut c_void)
         {
             use std::mem::{forget, uninitialized};
-            debug_assert_ne!(i, ptr::null_mut());
-            let info = &*i;
+            let info = if i.is_null() {
+                None
+            } else {
+                Some(&*i)
+            };
             let ctx = context::from_raw_ptr(c);
             let result = uninitialized::<F>()(&ctx, info, userdata);
             forget(ctx);
 
             result
         }
 
         op_or_err!(self,
--- a/media/libcubeb/cubeb-pulse-rs/src/backend/context.rs
+++ b/media/libcubeb/cubeb-pulse-rs/src/backend/context.rs
@@ -113,17 +113,17 @@ impl PulseContext {
             error: true,
             version_0_9_8: false,
             version_2_0_0: false,
             devids: RefCell::new(Intern::new()),
         }))
     }
 
     fn new(name: Option<&CStr>) -> Result<Box<Self>> {
-        fn server_info_cb(context: &pulse::Context, info: &pulse::ServerInfo, u: *mut c_void) {
+        fn server_info_cb(context: &pulse::Context, info: Option<&pulse::ServerInfo>, u: *mut c_void) {
             fn sink_info_cb(
                 _: &pulse::Context,
                 i: *const pulse::SinkInfo,
                 eol: i32,
                 u: *mut c_void,
             ) {
                 let ctx = unsafe { &mut *(u as *mut PulseContext) };
                 if eol == 0 {
@@ -133,21 +133,27 @@ impl PulseContext {
                         sample_spec: info.sample_spec,
                         channel_map: info.channel_map,
                         flags: flags,
                     });
                 }
                 ctx.mainloop.signal();
             }
 
-            let _ = context.get_sink_info_by_name(
-                try_cstr_from(info.default_sink_name),
-                sink_info_cb,
-                u,
-            );
+            if let Some(info) = info {
+                let _ = context.get_sink_info_by_name(
+                    try_cstr_from(info.default_sink_name),
+                    sink_info_cb,
+                    u,
+                );
+            } else {
+                // If info is None, then an error occured.
+                let ctx = unsafe { &mut *(u as *mut PulseContext) };
+                ctx.mainloop.signal();
+            }
         }
 
         let name = name.map(|s| s.to_owned());
         let mut ctx = try!(PulseContext::_new(name));
 
         if ctx.mainloop.start().is_err() {
             ctx.destroy();
             return Err(Error::error());
@@ -344,27 +350,29 @@ impl ContextOps for PulseContext {
                 latency_hi: 0,
             };
 
             list_data.devinfo.push(devinfo);
         }
 
         fn default_device_names(
             _: &pulse::Context,
-            info: &pulse::ServerInfo,
+            info: Option<&pulse::ServerInfo>,
             user_data: *mut c_void,
         ) {
             let list_data = unsafe { &mut *(user_data as *mut PulseDevListData) };
 
-            list_data.default_sink_name = super::try_cstr_from(info.default_sink_name)
-                .map(|s| s.to_owned())
-                .unwrap_or_default();
-            list_data.default_source_name = super::try_cstr_from(info.default_source_name)
-                .map(|s| s.to_owned())
-                .unwrap_or_default();
+            if let Some(info) = info {
+                list_data.default_sink_name = super::try_cstr_from(info.default_sink_name)
+                    .map(|s| s.to_owned())
+                    .unwrap_or_default();
+                list_data.default_source_name = super::try_cstr_from(info.default_source_name)
+                    .map(|s| s.to_owned())
+                    .unwrap_or_default();
+            }
 
             (*list_data.context).mainloop.signal();
         }
 
         let mut user_data = PulseDevListData::new(self);
 
         if let Some(ref context) = self.context {
             self.mainloop.lock();
--- a/mfbt/NotNull.h
+++ b/mfbt/NotNull.h
@@ -150,27 +150,57 @@ template <typename T>
 NotNull<T>
 WrapNotNull(const T aBasePtr)
 {
   NotNull<T> notNull(aBasePtr);
   MOZ_RELEASE_ASSERT(aBasePtr);
   return notNull;
 }
 
+namespace detail {
+
+// Extract the pointed-to type from a pointer type (be it raw or smart).
+// The default implementation uses the dereferencing operator of the pointer
+// type to find what it's pointing to.
+template<typename Pointer>
+struct PointedTo
+{
+  // Remove the reference that dereferencing operators may return.
+  using Type = typename RemoveReference<decltype(*DeclVal<Pointer>())>::Type;
+  using NonConstType = typename RemoveConst<Type>::Type;
+};
+
+// Specializations for raw pointers.
+// This is especially required because VS 2017 15.6 (March 2018) started
+// rejecting the above `decltype(*DeclVal<Pointer>())` trick for raw pointers.
+// See bug 1443367.
+template<typename T>
+struct PointedTo<T*>
+{
+  using Type = T;
+  using NonConstType = T;
+};
+
+template<typename T>
+struct PointedTo<const T*>
+{
+  using Type = const T;
+  using NonConstType = T;
+};
+
+} // namespace detail
+
 // Allocate an object with infallible new, and wrap its pointer in NotNull.
 // |MakeNotNull<Ptr<Ob>>(args...)| will run |new Ob(args...)|
 // and return NotNull<Ptr<Ob>>.
 template<typename T, typename... Args>
 NotNull<T>
 MakeNotNull(Args&&... aArgs)
 {
-  // Extract the pointee type from what T's dereferencing operator returns
-  // (which could be a reference to a const type).
-  using Pointee = typename mozilla::RemoveConst<
-    typename mozilla::RemoveReference<decltype(*DeclVal<T>())>::Type>::Type;
+  using Pointee = typename detail::PointedTo<T>::NonConstType;
   static_assert(!IsArray<Pointee>::value,
                 "MakeNotNull cannot construct an array");
   return NotNull<T>(new Pointee(Forward<Args>(aArgs)...));
 }
 
 // Compare two NotNulls.
 template <typename T, typename U>
 inline bool
--- a/servo/components/canvas/webgl_thread.rs
+++ b/servo/components/canvas/webgl_thread.rs
@@ -753,16 +753,18 @@ impl WebGLImpl {
             WebGLCommand::GetVertexAttrib(index, pname, chan) =>
                 Self::vertex_attrib(ctx.gl(), index, pname, chan),
             WebGLCommand::GetVertexAttribOffset(index, pname, chan) =>
                 Self::vertex_attrib_offset(ctx.gl(), index, pname, chan),
             WebGLCommand::GetBufferParameter(target, param_id, chan) =>
                 Self::buffer_parameter(ctx.gl(), target, param_id, chan),
             WebGLCommand::GetParameter(param_id, chan) =>
                 Self::parameter(ctx.gl(), param_id, chan),
+            WebGLCommand::GetTexParameter(target, pname, chan) =>
+                Self::get_tex_parameter(ctx.gl(), target, pname, chan),
             WebGLCommand::GetProgramParameter(program_id, param_id, chan) =>
                 Self::program_parameter(ctx.gl(), program_id, param_id, chan),
             WebGLCommand::GetShaderParameter(shader_id, param_id, chan) =>
                 Self::shader_parameter(ctx.gl(), shader_id, param_id, chan),
             WebGLCommand::GetShaderPrecisionFormat(shader_type, precision_type, chan) =>
                 Self::shader_precision_format(ctx.gl(), shader_type, precision_type, chan),
             WebGLCommand::GetExtensions(chan) =>
                 Self::get_extensions(ctx.gl(), chan),
@@ -1056,16 +1058,37 @@ impl WebGLImpl {
 
             // Invalid parameters
             _ => Err(WebGLError::InvalidEnum)
         };
 
         chan.send(result).unwrap();
     }
 
+    fn get_tex_parameter(gl: &gl::Gl,
+                       target: u32,
+                       pname: u32,
+                       chan: WebGLSender<WebGLResult<WebGLParameter>> ) {
+        let result = match pname {
+            gl::TEXTURE_MAG_FILTER |
+            gl::TEXTURE_MIN_FILTER |
+            gl::TEXTURE_WRAP_S |
+            gl::TEXTURE_WRAP_T => {
+                let parameter = gl.get_tex_parameter_iv(target, pname);
+                if parameter == 0 {
+                    Ok(WebGLParameter::Invalid)
+                } else {
+                    Ok(WebGLParameter::Int(parameter))
+                }
+            }
+            _ => Err(WebGLError::InvalidEnum)
+        };
+        chan.send(result).unwrap();
+    }
+
     fn finish(gl: &gl::Gl, chan: WebGLSender<()>) {
         gl.finish();
         chan.send(()).unwrap();
     }
 
     fn vertex_attrib(gl: &gl::Gl,
                      index: u32,
                      pname: u32,
--- a/servo/components/canvas_traits/webgl.rs
+++ b/servo/components/canvas_traits/webgl.rs
@@ -202,16 +202,17 @@ pub enum WebGLCommand {
     DrawArrays(u32, i32, i32),
     DrawElements(u32, i32, u32, i64),
     EnableVertexAttribArray(u32),
     FramebufferRenderbuffer(u32, u32, u32, Option<WebGLRenderbufferId>),
     FramebufferTexture2D(u32, u32, u32, Option<WebGLTextureId>, i32),
     GetBufferParameter(u32, u32, WebGLSender<WebGLResult<WebGLParameter>>),
     GetExtensions(WebGLSender<String>),
     GetParameter(u32, WebGLSender<WebGLResult<WebGLParameter>>),
+    GetTexParameter(u32, u32, WebGLSender<WebGLResult<WebGLParameter>>),
     GetProgramParameter(WebGLProgramId, u32, WebGLSender<WebGLResult<WebGLParameter>>),
     GetShaderParameter(WebGLShaderId, u32, WebGLSender<WebGLResult<WebGLParameter>>),
     GetShaderPrecisionFormat(u32, u32, WebGLSender<WebGLResult<(i32, i32, i32)>>),
     GetActiveAttrib(WebGLProgramId, u32, WebGLSender<WebGLResult<(i32, u32, String)>>),
     GetActiveUniform(WebGLProgramId, u32, WebGLSender<WebGLResult<(i32, u32, String)>>),
     GetAttribLocation(WebGLProgramId, String, WebGLSender<Option<i32>>),
     GetUniformLocation(WebGLProgramId, String, WebGLSender<Option<i32>>),
     GetVertexAttrib(u32, u32, WebGLSender<WebGLResult<WebGLParameter>>),
@@ -472,16 +473,17 @@ impl fmt::Debug for WebGLCommand {
             DrawArrays(..) => "DrawArrays",
             DrawElements(..) => "DrawElements",
             EnableVertexAttribArray(..) => "EnableVertexAttribArray",
             FramebufferRenderbuffer(..) => "FramebufferRenderbuffer",
             FramebufferTexture2D(..) => "FramebufferTexture2D",
             GetBufferParameter(..) => "GetBufferParameter",
             GetExtensions(..) => "GetExtensions",
             GetParameter(..) => "GetParameter",
+            GetTexParameter(..) => "GetTexParameter",
             GetProgramParameter(..) => "GetProgramParameter",
             GetShaderParameter(..) => "GetShaderParameter",
             GetShaderPrecisionFormat(..) => "GetShaderPrecisionFormat",
             GetActiveAttrib(..) => "GetActiveAttrib",
             GetActiveUniform(..) => "GetActiveUniform",
             GetAttribLocation(..) => "GetAttribLocation",
             GetUniformLocation(..) => "GetUniformLocation",
             GetShaderInfoLog(..) => "GetShaderInfoLog",
--- a/servo/components/script/dom/webgl2renderingcontext.rs
+++ b/servo/components/script/dom/webgl2renderingcontext.rs
@@ -110,16 +110,22 @@ impl WebGL2RenderingContextMethods for W
     }
 
     #[allow(unsafe_code)]
     /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
     unsafe fn GetParameter(&self, cx: *mut JSContext, parameter: u32) -> JSVal {
         self.base.GetParameter(cx, parameter)
     }
 
+    #[allow(unsafe_code)]
+    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
+    unsafe fn GetTexParameter(&self, cx: *mut JSContext, target: u32, pname: u32) -> JSVal {
+        self.base.GetTexParameter(cx, target, pname)
+    }
+
     /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
     fn GetError(&self) -> u32 {
         self.base.GetError()
     }
 
     /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.2
     fn GetContextAttributes(&self) -> Option<WebGLContextAttributes> {
         self.base.GetContextAttributes()
--- a/servo/components/script/dom/webglrenderingcontext.rs
+++ b/servo/components/script/dom/webglrenderingcontext.rs
@@ -1331,16 +1331,47 @@ impl WebGLRenderingContextMethods for We
                 rooted!(in(cx) let mut rval = UndefinedValue());
                 val.to_jsval(cx, rval.handle_mut());
                 rval.get()
             }
             WebGLParameter::Invalid => NullValue(),
         }
     }
 
+    #[allow(unsafe_code)]
+    // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
+    unsafe fn GetTexParameter(&self, _cx: *mut JSContext, target: u32, pname: u32) -> JSVal {
+        let texture = match target {
+            constants::TEXTURE_2D |
+            constants::TEXTURE_CUBE_MAP => self.bound_texture(target),
+            _ => {
+                self.webgl_error(InvalidEnum);
+                return NullValue();
+            }
+        };
+        if texture.is_some() {
+            let (sender, receiver) = webgl_channel().unwrap();
+            self.send_command(WebGLCommand::GetTexParameter(target, pname, sender));
+            match handle_potential_webgl_error!(self, receiver.recv().unwrap(), WebGLParameter::Invalid) {
+                WebGLParameter::Int(val) => Int32Value(val),
+                WebGLParameter::Bool(_) => panic!("Texture parameter should not be bool"),
+                WebGLParameter::Float(_) => panic!("Texture parameter should not be float"),
+                WebGLParameter::FloatArray(_) => panic!("Texture parameter should not be float array"),
+                WebGLParameter::String(_) => panic!("Texture parameter should not be string"),
+                WebGLParameter::Invalid => {
+                    self.webgl_error(InvalidEnum);
+                    NullValue()
+                }
+            }
+        } else {
+            self.webgl_error(InvalidOperation);
+            NullValue()
+        }
+    }
+
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
     fn GetError(&self) -> u32 {
         let error_code = if let Some(error) = self.last_error.get() {
             match error {
                 WebGLError::InvalidEnum => constants::INVALID_ENUM,
                 WebGLError::InvalidFramebufferOperation => constants::INVALID_FRAMEBUFFER_OPERATION,
                 WebGLError::InvalidValue => constants::INVALID_VALUE,
                 WebGLError::InvalidOperation => constants::INVALID_OPERATION,
--- a/servo/components/script/dom/webidls/WebGLRenderingContext.webidl
+++ b/servo/components/script/dom/webidls/WebGLRenderingContext.webidl
@@ -595,17 +595,17 @@ interface WebGLRenderingContextBase
     DOMString? getProgramInfoLog(WebGLProgram? program);
     //any getRenderbufferParameter(GLenum target, GLenum pname);
     any getShaderParameter(WebGLShader? shader, GLenum pname);
     WebGLShaderPrecisionFormat? getShaderPrecisionFormat(GLenum shadertype, GLenum precisiontype);
     DOMString? getShaderInfoLog(WebGLShader? shader);
 
     DOMString? getShaderSource(WebGLShader? shader);
 
-    //any getTexParameter(GLenum target, GLenum pname);
+    any getTexParameter(GLenum target, GLenum pname);
 
     //any getUniform(WebGLProgram? program, WebGLUniformLocation? location);
 
     WebGLUniformLocation? getUniformLocation(WebGLProgram? program, DOMString name);
 
     any getVertexAttrib(GLuint index, GLenum pname);
 
     [WebGLHandlesContextLoss] GLsizeiptr getVertexAttribOffset(GLuint index, GLenum pname);
--- a/servo/components/style/font_face.rs
+++ b/servo/components/style/font_face.rs
@@ -43,34 +43,26 @@ pub enum Source {
 impl OneOrMoreSeparated for Source {
     type S = Comma;
 }
 
 /// A `UrlSource` represents a font-face source that has been specified with a
 /// `url()` function.
 ///
 /// <https://drafts.csswg.org/css-fonts/#src-desc>
-#[derive(Clone, Debug, Eq, PartialEq)]
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
+#[derive(Clone, Debug, Eq, PartialEq, ToCss)]
 pub struct UrlSource {
     /// The specified url.
     pub url: SpecifiedUrl,
     /// The format hints specified with the `format()` function.
+    #[css(skip)]
     pub format_hints: Vec<String>,
 }
 
-impl ToCss for UrlSource {
-    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
-    where
-        W: Write,
-    {
-        self.url.to_css(dest)
-    }
-}
-
 /// A font-display value for a @font-face rule.
 /// The font-display descriptor determines how a font face is displayed based
 /// on whether and when it is downloaded and ready to use.
 #[allow(missing_docs)]
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq)]
 #[derive(ToComputedValue, ToCss)]
 pub enum FontDisplay {
--- a/servo/components/style/gecko/url.rs
+++ b/servo/components/style/gecko/url.rs
@@ -7,34 +7,36 @@
 use gecko_bindings::structs::{ServoBundledURI, URLExtraData};
 use gecko_bindings::structs::mozilla::css::URLValueData;
 use gecko_bindings::structs::root::{nsStyleImageRequest, RustString};
 use gecko_bindings::structs::root::mozilla::css::ImageValue;
 use gecko_bindings::sugar::refptr::RefPtr;
 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
 use parser::ParserContext;
 use servo_arc::{Arc, RawOffsetArc};
-use std::fmt::{self, Write};
 use std::mem;
-use style_traits::{CssWriter, ToCss, ParseError};
+use style_traits::ParseError;
 
 /// A specified url() value for gecko. Gecko does not eagerly resolve SpecifiedUrls.
-#[derive(Clone, Debug, PartialEq)]
+#[css(function = "url")]
+#[derive(Clone, Debug, PartialEq, ToCss)]
 pub struct SpecifiedUrl {
     /// The URL in unresolved string form.
     ///
     /// Refcounted since cloning this should be cheap and data: uris can be
     /// really large.
     serialization: Arc<String>,
 
     /// The URL extra data.
+    #[css(skip)]
     pub extra_data: RefPtr<URLExtraData>,
 
     /// Cache ImageValue, if any, so that we can reuse it while rematching a
     /// a property with this specified url value.
+    #[css(skip)]
     pub image_value: Option<RefPtr<ImageValue>>,
 }
 trivial_to_computed_value!(SpecifiedUrl);
 
 impl SpecifiedUrl {
     /// Try to parse a URL from a string value that is a valid CSS token for a
     /// URL.
     ///
@@ -128,27 +130,16 @@ impl SpecifiedUrl {
                 // We do not expect Gecko_ImageValue_Create returns null.
                 debug_assert!(!ptr.is_null());
                 Some(RefPtr::from_addrefed(ptr))
             }
         }
     }
 }
 
-impl ToCss for SpecifiedUrl {
-    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
-    where
-        W: Write,
-    {
-        dest.write_str("url(")?;
-        self.serialization.to_css(dest)?;
-        dest.write_str(")")
-    }
-}
-
 impl MallocSizeOf for SpecifiedUrl {
     fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
         use gecko_bindings::bindings::Gecko_ImageValue_SizeOfIncludingThis;
 
         let mut n = 0;
 
         // XXX: measure `serialization` once bug 1397971 lands
 
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -1702,69 +1702,45 @@ impl PropertyId {
             Some(id) => id,
         };
         id.allowed_in(context)
     }
 }
 
 /// A declaration using a CSS-wide keyword.
 #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
-#[derive(Clone, PartialEq)]
+#[derive(Clone, PartialEq, ToCss)]
 pub struct WideKeywordDeclaration {
+    #[css(skip)]
     id: LonghandId,
     keyword: CSSWideKeyword,
 }
 
-impl ToCss for WideKeywordDeclaration {
-    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
-    where
-        W: fmt::Write,
-    {
-        self.keyword.to_css(dest)
-    }
-}
-
 /// An unparsed declaration that contains `var()` functions.
 #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
-#[derive(Clone, PartialEq)]
+#[derive(Clone, PartialEq, ToCss)]
 pub struct VariableDeclaration {
+    #[css(skip)]
     id: LonghandId,
     #[cfg_attr(feature = "gecko", ignore_malloc_size_of = "XXX: how to handle this?")]
     value: Arc<UnparsedValue>,
 }
 
-impl ToCss for VariableDeclaration {
-    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
-    where
-        W: fmt::Write,
-    {
-        self.value.to_css(dest)
-    }
-}
-
 /// A custom property declaration with the property name and the declared value.
 #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
-#[derive(Clone, PartialEq)]
+#[derive(Clone, PartialEq, ToCss)]
 pub struct CustomDeclaration {
     /// The name of the custom property.
+    #[css(skip)]
     pub name: ::custom_properties::Name,
     /// The value of the custom property.
     #[cfg_attr(feature = "gecko", ignore_malloc_size_of = "XXX: how to handle this?")]
     pub value: DeclaredValueOwned<Arc<::custom_properties::SpecifiedValue>>,
 }
 
-impl ToCss for CustomDeclaration {
-    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
-    where
-        W: fmt::Write,
-    {
-        self.value.to_css(dest)
-    }
-}
-
 impl fmt::Debug for PropertyDeclaration {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         self.id().to_css(&mut CssWriter::new(f))?;
         f.write_str(": ")?;
 
         // Because PropertyDeclaration::to_css requires CssStringWriter, we can't write
         // it directly to f, and need to allocate an intermediate string. This is
         // fine for debug-only code.
--- a/servo/components/style/values/computed/align.rs
+++ b/servo/components/style/values/computed/align.rs
@@ -1,44 +1,52 @@
 /* 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/. */
 
 //! Values for CSS Box Alignment properties
 //!
 //! https://drafts.csswg.org/css-align/
 
-use std::fmt;
-use style_traits::{CssWriter, ToCss};
 use values::computed::{Context, ToComputedValue};
 use values::specified;
 
 pub use super::specified::{AlignContent, JustifyContent, AlignItems, SelfAlignment};
 pub use super::specified::{AlignSelf, JustifySelf};
 
 /// The computed value for the `justify-items` property.
 ///
 /// Need to carry around both the specified and computed value to handle the
-/// special legacy keyword. Sigh.
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+/// special legacy keyword without destroying style sharing.
+///
+/// In particular, `justify-items` is a reset property, so we ought to be able
+/// to share its computed representation across elements as long as they match
+/// the same rules. Except that it's not true if the specified value for
+/// `justify-items` is `auto` and the computed value of the parent has the
+/// `legacy` modifier.
+///
+/// So instead of computing `auto` "normally" looking at get_parent_position(),
+/// marking it as uncacheable, we carry the specified value around and handle
+/// the special case in `StyleAdjuster` instead, only when the result of the
+/// computation would vary.
+///
+/// Note that we also need to special-case this property in matching.rs, in
+/// order to properly handle changes to the legacy keyword... This all kinda
+/// sucks :(.
+///
+/// See the discussion in https://bugzil.la/1384542.
+#[derive(Clone, Copy, Debug, Eq, PartialEq, ToCss)]
 pub struct JustifyItems {
     /// The specified value for the property. Can contain `auto`.
+    #[css(skip)]
     pub specified: specified::JustifyItems,
     /// The computed value for the property. Cannot contain `auto`.
     pub computed: specified::JustifyItems,
 }
 
-impl ToCss for JustifyItems {
-    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
-        where W: fmt::Write,
-    {
-        self.computed.to_css(dest)
-    }
-}
-
 impl JustifyItems {
     /// Returns the `auto` value.
     pub fn auto() -> Self {
         Self {
             specified: specified::JustifyItems::auto(),
             computed: specified::JustifyItems::normal(),
         }
     }
--- a/servo/components/style/values/computed/font.rs
+++ b/servo/components/style/values/computed/font.rs
@@ -35,23 +35,24 @@ pub use values::specified::font::{XTextZ
 /// valid: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
 ///
 /// However, system fonts may provide other values. Pango
 /// may provide 350, 380, and 1000 (on top of the existing values), for example.
 #[derive(Clone, ComputeSquaredDistance, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToCss)]
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 pub struct FontWeight(pub u16);
 
-#[derive(Animate, ComputeSquaredDistance, MallocSizeOf, ToAnimatedZero)]
-#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf)]
+#[derive(PartialEq, ToAnimatedZero, ToCss)]
 /// The computed value of font-size
 pub struct FontSize {
     /// The size.
     pub size: NonNegativeLength,
     /// If derived from a keyword, the keyword and additional transformations applied to it
+    #[css(skip)]
     pub keyword_info: Option<KeywordInfo>,
 }
 
 /// Additional information for computed keyword-derived font sizes.
 pub type KeywordInfo = GenericKeywordInfo<NonNegativeLength>;
 
 impl FontWeight {
     /// Value for normal
@@ -154,22 +155,16 @@ impl FontSize {
         context.builder.mutate_font().set_font_size(computed);
         #[cfg(feature = "gecko")] {
             let device = context.builder.device;
             context.builder.mutate_font().fixup_font_min_size(device);
         }
     }
 }
 
-impl ToCss for FontSize {
-    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
-        self.size.to_css(dest)
-    }
-}
-
 /// XXXManishearth it might be better to
 /// animate this as computed, however this complicates
 /// clamping and might not be the right thing to do.
 /// We should figure it out.
 impl ToAnimatedValue for FontSize {
     type AnimatedValue = NonNegativeLength;
 
     #[inline]
--- a/servo/components/style/values/generics/font.rs
+++ b/servo/components/style/values/generics/font.rs
@@ -157,24 +157,26 @@ impl Parse for FontTag {
         }
 
         let mut raw = Cursor::new(tag.as_bytes());
         Ok(FontTag(raw.read_u32::<BigEndian>().unwrap()))
     }
 }
 
 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf)]
-#[derive(PartialEq, ToAnimatedValue, ToAnimatedZero)]
+#[derive(PartialEq, ToAnimatedValue, ToAnimatedZero, ToCss)]
 /// Additional information for keyword-derived font sizes.
 pub struct KeywordInfo<Length> {
     /// The keyword used
     pub kw: KeywordSize,
     /// A factor to be multiplied by the computed size of the keyword
+    #[css(skip)]
     pub factor: f32,
     /// An additional Au offset to add to the kw*factor in the case of calcs
+    #[css(skip)]
     pub offset: Length,
 }
 
 impl<L> KeywordInfo<L>
 where
     Au: Into<L>,
 {
     /// KeywordInfo value for font-size: medium
--- a/servo/components/style/values/specified/font.rs
+++ b/servo/components/style/values/specified/font.rs
@@ -113,17 +113,17 @@ impl ToComputedValue for FontWeight {
     }
 
     #[inline]
     fn from_computed_value(computed: &computed::FontWeight) -> Self {
         FontWeight::Weight(*computed)
     }
 }
 
-#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
 /// A specified font-size value
 pub enum FontSize {
     /// A length; e.g. 10px.
     Length(LengthOrPercentage),
     /// A keyword value, along with a ratio and absolute offset.
     /// The ratio in any specified keyword value
     /// will be 1 (with offset 0), but we cascade keywordness even
     /// after font-relative (percent and em) values
@@ -137,31 +137,16 @@ pub enum FontSize {
     /// font-size: smaller
     Smaller,
     /// font-size: larger
     Larger,
     /// Derived from a specified system font.
     System(SystemFont)
 }
 
-impl ToCss for FontSize {
-    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
-    where
-        W: Write,
-    {
-        match *self {
-            FontSize::Length(ref lop) => lop.to_css(dest),
-            FontSize::Keyword(info) => info.kw.to_css(dest),
-            FontSize::Smaller => dest.write_str("smaller"),
-            FontSize::Larger => dest.write_str("larger"),
-            FontSize::System(sys) => sys.to_css(dest),
-        }
-    }
-}
-
 impl From<LengthOrPercentage> for FontSize {
     fn from(other: LengthOrPercentage) -> Self {
         FontSize::Length(other)
     }
 }
 
 /// Specifies a prioritized list of font family names or generic family names.
 #[derive(Clone, Debug, Eq, Hash, PartialEq, ToCss)]
@@ -2010,39 +1995,30 @@ impl Parse for VariationValue<Number> {
     ) -> Result<Self, ParseError<'i>> {
         let tag = FontTag::parse(context, input)?;
         let value = Number::parse(context, input)?;
         Ok(Self { tag, value })
     }
 }
 
 
-#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss)]
 /// text-zoom. Enable if true, disable if false
-pub struct XTextZoom(pub bool);
+pub struct XTextZoom(#[css(skip)] pub bool);
 
 impl Parse for XTextZoom {
     fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result<XTextZoom, ParseError<'i>> {
         debug_assert!(false, "Should be set directly by presentation attributes only.");
         Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
     }
 }
 
-impl ToCss for XTextZoom {
-    fn to_css<W>(&self, _: &mut CssWriter<W>) -> fmt::Result
-    where
-        W: Write,
-    {
-        Ok(())
-    }
-}
-
-#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss)]
 /// Internal property that reflects the lang attribute
-pub struct XLang(pub Atom);
+pub struct XLang(#[css(skip)] pub Atom);
 
 impl XLang {
     #[inline]
     /// Get default value for `-x-lang`
     pub fn get_initial_value() -> XLang {
         XLang(atom!(""))
     }
 }
@@ -2052,25 +2028,16 @@ impl Parse for XLang {
         _: &ParserContext,
         input: &mut Parser<'i, 't>
     ) -> Result<XLang, ParseError<'i>> {
         debug_assert!(false, "Should be set directly by presentation attributes only.");
         Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
     }
 }
 
-impl ToCss for XLang {
-    fn to_css<W>(&self, _: &mut CssWriter<W>) -> fmt::Result
-    where
-        W: Write,
-    {
-        Ok(())
-    }
-}
-
 #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
 #[derive(Clone, Copy, Debug, PartialEq, ToCss)]
 /// Specifies the minimum font size allowed due to changes in scriptlevel.
 /// Ref: https://wiki.mozilla.org/MathML:mstyle
 pub struct MozScriptMinSize(pub NoCalcLength);
 
 impl MozScriptMinSize {
     #[inline]
--- a/servo/components/style/values/specified/table.rs
+++ b/servo/components/style/values/specified/table.rs
@@ -1,27 +1,20 @@
 /* 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/. */
 
 //! Specified types for table properties.
 
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
-use std::fmt;
-use style_traits::{CssWriter, ToCss, StyleParseErrorKind, ParseError};
+use style_traits::{StyleParseErrorKind, ParseError};
 
-#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss)]
 /// span. for `<col span>` pres attr
-pub struct XSpan(pub i32);
+pub struct XSpan(#[css(skip)] pub i32);
 
 impl Parse for XSpan {
     // never parse it, only set via presentation attribute
     fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result<XSpan, ParseError<'i>> {
         Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
     }
 }
-
-impl ToCss for XSpan {
-    fn to_css<W>(&self, _: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
-        Ok(())
-    }
-}
--- a/servo/components/style_derive/to_css.rs
+++ b/servo/components/style_derive/to_css.rs
@@ -48,16 +48,19 @@ pub fn derive(input: syn::DeriveInput) -
 
                     for item in #binding.iter() {
                         writer.item(&item)?;
                     }
                 };
             } else {
                 for binding in bindings {
                     let attrs = cg::parse_field_attrs::<CssFieldAttrs>(&binding.ast());
+                    if attrs.skip {
+                        continue;
+                    }
                     if !attrs.ignore_bound {
                         where_clause.add_trait_bound(&binding.ast().ty);
                     }
                     expr = quote! {
                         #expr
                         writer.item(#binding)?;
                     };
                 }
@@ -147,9 +150,10 @@ pub struct CssVariantAttrs {
     pub keyword: Option<String>,
     pub aliases: Option<String>,
 }
 
 #[darling(attributes(css), default)]
 #[derive(Default, FromField)]
 struct CssFieldAttrs {
     ignore_bound: bool,
+    skip: bool,
 }
--- a/servo/components/style_traits/values.rs
+++ b/servo/components/style_traits/values.rs
@@ -21,20 +21,22 @@ use std::fmt::{self, Write};
 /// * unit variants whose name starts with "Moz" or "Webkit" are prepended
 ///   with a "-";
 /// * if `#[css(comma)]` is found on a variant, its fields are separated by
 ///   commas, otherwise, by spaces;
 /// * if `#[css(function)]` is found on a variant, the variant name gets
 ///   serialised like unit variants and its fields are surrounded by parentheses;
 /// * if `#[css(iterable)]` is found on a function variant, that variant needs
 ///   to have a single member, and that member needs to be iterable. The
-///   iterable will be serialized as the arguments for the function.
+///   iterable will be serialized as the arguments for the function;
 /// * if `#[css(dimension)]` is found on a variant, that variant needs
 ///   to have a single member. The variant would be serialized as a CSS
-///   dimension token, like: <member><identifier>.
+///   dimension token, like: <member><identifier>;
+/// * if `#[css(skip)]` is found on a field, the `ToCss` call for that field
+///   is skipped;
 /// * finally, one can put `#[css(derive_debug)]` on the whole type, to
 ///   implement `Debug` by a single call to `ToCss::to_css`.
 pub trait ToCss {
     /// Serialize `self` in CSS syntax, writing to `dest`.
     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write;
 
     /// Serialize `self` in CSS syntax and return a string.
     ///
--- a/taskcluster/ci/release-snap/kind.yml
+++ b/taskcluster/ci/release-snap/kind.yml
@@ -15,16 +15,17 @@ kind-dependencies:
 
 job-defaults:
    description: Generates snap image
    run-on-projects: []  # to make sure this never runs as part of CI
    shipping-phase: promote
    scopes:
       by-project:
          mozilla-beta: ["secrets:get:project/releng/snapcraft/firefox/edge"]
+         mozilla-release: ["secrets:get:project/releng/snapcraft/firefox/candidate"]
          default: []
    treeherder:
       platform: linux64/opt
       kind: build
       tier: 3
    worker-type: aws-provisioner-v1/gecko-{level}-b-linux
    worker:
       implementation: docker-worker
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -36,16 +36,17 @@ const {
   NoSuchFrameError,
   NoSuchWindowError,
   SessionNotCreatedError,
   UnknownError,
   UnsupportedOperationError,
   WebDriverError,
 } = ChromeUtils.import("chrome://marionette/content/error.js", {});
 ChromeUtils.import("chrome://marionette/content/evaluate.js");
+const {pprint} = ChromeUtils.import("chrome://marionette/content/format.js", {});
 ChromeUtils.import("chrome://marionette/content/interaction.js");
 ChromeUtils.import("chrome://marionette/content/l10n.js");
 ChromeUtils.import("chrome://marionette/content/legacyaction.js");
 ChromeUtils.import("chrome://marionette/content/modal.js");
 ChromeUtils.import("chrome://marionette/content/proxy.js");
 ChromeUtils.import("chrome://marionette/content/reftest.js");
 ChromeUtils.import("chrome://marionette/content/session.js");
 const {
@@ -832,17 +833,17 @@ GeckoDriver.prototype.getContext = funct
  * causing any change it makes on the global state of the document to have
  * lasting side-effects.
  *
  * @param {string} script
  *     Script to evaluate as a function body.
  * @param {Array.<(string|boolean|number|object|WebElement)>} args
  *     Arguments exposed to the script in <code>arguments</code>.
  *     The array items must be serialisable to the WebDriver protocol.
- * @param {number} scriptTimeout
+ * @param {number=} scriptTimeout
  *     Duration in milliseconds of when to interrupt and abort the
  *     script evaluation.
  * @param {string=} sandbox
  *     Name of the sandbox to evaluate the script in.  The sandbox is
  *     cached for later re-use on the same Window object if
  *     <var>newSandbox</var> is false.  If he parameter is undefined,
  *     the script is evaluated in a mutable sandbox.  If the parameter
  *     is "system", it will be evaluted in a sandbox with elevated system
@@ -867,31 +868,28 @@ GeckoDriver.prototype.getContext = funct
  *
  * @throws {ScriptTimeoutError}
  *     If the script was interrupted due to reaching the
  *     <var>scriptTimeout</var> or default timeout.
  * @throws {JavaScriptError}
  *     If an {@link Error} was thrown whilst evaluating the script.
  */
 GeckoDriver.prototype.executeScript = async function(cmd, resp) {
-  assert.open(this.getCurrentWindow());
-
-  let {script, args, scriptTimeout} = cmd.parameters;
-  scriptTimeout = scriptTimeout || this.timeouts.script;
-
+  let {script, args} = cmd.parameters;
   let opts = {
+    script: cmd.parameters.script,
+    args: cmd.parameters.args,
+    timeout: cmd.parameters.scriptTimeout,
     sandboxName: cmd.parameters.sandbox,
-    newSandbox: !!(typeof cmd.parameters.newSandbox == "undefined") ||
-        cmd.parameters.newSandbox,
+    newSandbox: cmd.parameters.newSandbox,
     file: cmd.parameters.filename,
     line: cmd.parameters.line,
     debug: cmd.parameters.debug_script,
   };
-
-  resp.body.value = await this.execute_(script, args, scriptTimeout, opts);
+  resp.body.value = await this.execute_(script, args, opts);
 };
 
 /**
  * Executes a JavaScript function in the context of the current browsing
  * context, if in content space, or in chrome space otherwise, and returns
  * the object passed to the callback.
  *
  * The callback is always the last argument to the <var>arguments</var>
@@ -944,54 +942,88 @@ GeckoDriver.prototype.executeScript = as
  *
  * @throws {ScriptTimeoutError}
  *     If the script was interrupted due to reaching the
  *     <var>scriptTimeout</var> or default timeout.
  * @throws {JavaScriptError}
  *     If an Error was thrown whilst evaluating the script.
  */
 GeckoDriver.prototype.executeAsyncScript = async function(cmd, resp) {
-  assert.open(this.getCurrentWindow());
-
-  let {script, args, scriptTimeout} = cmd.parameters;
-  scriptTimeout = scriptTimeout || this.timeouts.script;
-
+  let {script, args} = cmd.parameters;
   let opts = {
+    script: cmd.parameters.script,
+    args: cmd.parameters.args,
+    timeout: cmd.parameters.scriptTimeout,
     sandboxName: cmd.parameters.sandbox,
-    newSandbox: !!(typeof cmd.parameters.newSandbox == "undefined") ||
-        cmd.parameters.newSandbox,
+    newSandbox: cmd.parameters.newSandbox,
     file: cmd.parameters.filename,
     line: cmd.parameters.line,
     debug: cmd.parameters.debug_script,
     async: true,
   };
-
-  resp.body.value = await this.execute_(script, args, scriptTimeout, opts);
+  resp.body.value = await this.execute_(script, args, opts);
 };
 
 GeckoDriver.prototype.execute_ = async function(
-    script, args, timeout, opts = {}) {
+    script,
+    args = [],
+    {
+      timeout = null,
+      sandboxName = null,
+      newSandbox = false,
+      file = "",
+      line = 0,
+      debug = false,
+      async = false,
+    } = {}) {
+
+  if (typeof timeout == "undefined" || timeout === null) {
+    timeout = this.timeouts.script;
+  }
+
+  assert.open(this.getCurrentWindow());
+
+  assert.string(script, pprint`Expected "script" to be a string: ${script}`);
+  assert.array(args, pprint`Expected script args to be an array: ${args}`);
+  assert.positiveInteger(timeout, pprint`Expected script timeout to be a positive integer: ${timeout}`);
+  if (sandboxName !== null) {
+    assert.string(sandboxName, pprint`Expected sandbox name to be a string: ${sandboxName}`);
+  }
+  assert.boolean(newSandbox, pprint`Expected newSandbox to be boolean: ${newSandbox}`);
+  assert.string(file, pprint`Expected file to be a string: ${file}`);
+  assert.number(line, pprint`Expected line to be a number: ${line}`);
+  assert.boolean(debug, pprint`Expected debug_script to be boolean: ${debug}`);
+
+  let opts = {
+    timeout,
+    sandboxName,
+    newSandbox,
+    file,
+    line,
+    debug,
+    async,
+  };
+
   let res, els;
 
   switch (this.context) {
     case Context.Content:
       // evaluate in content with lasting side-effects
-      if (!opts.sandboxName) {
-        res = await this.listener.execute(script, args, timeout, opts);
+      if (!sandboxName) {
+        res = await this.listener.execute(script, args, opts);
 
       // evaluate in content with sandbox
       } else {
-        res = await this.listener.executeInSandbox(
-            script, args, timeout, opts);
+        res = await this.listener.executeInSandbox(script, args, opts);
       }
 
       break;
 
     case Context.Chrome:
-      let sb = this.sandboxes.get(opts.sandboxName, opts.newSandbox);
+      let sb = this.sandboxes.get(sandboxName, newSandbox);
       opts.timeout = timeout;
       let wargs = evaluate.fromJSON(args, this.curBrowser.seenEls, sb.window);
       res = await evaluate.sandbox(sb, script, wargs, opts);
       els = this.curBrowser.seenEls;
       break;
 
     default:
       throw new TypeError(`Unknown context: ${this.context}`);
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -663,24 +663,22 @@ function sendOk(uuid) {
  *     Error to notify chrome of.
  * @param {UUID} uuid
  *     Unique identifier of the request.
  */
 function sendError(err, uuid) {
   sendToServer(uuid, err);
 }
 
-async function execute(script, args, timeout, opts) {
-  opts.timeout = timeout;
+async function execute(script, args, opts) {
   let sb = sandbox.createMutable(curContainer.frame);
   return evaluate.sandbox(sb, script, args, opts);
 }
 
-async function executeInSandbox(script, args, timeout, opts) {
-  opts.timeout = timeout;
+async function executeInSandbox(script, args, opts) {
   let sb = sandboxes.get(opts.sandboxName, opts.newSandbox);
   return evaluate.sandbox(sb, script, args, opts);
 }
 
 function emitTouchEvent(type, touch) {
   logger.info(`Emitting Touch event of type ${type} ` +
       `to element with id: ${touch.target.id} ` +
       `and tag name: ${touch.target.tagName} ` +
--- a/testing/mozharness/scripts/web_platform_tests.py
+++ b/testing/mozharness/scripts/web_platform_tests.py
@@ -237,16 +237,17 @@ class WebPlatformTest(TestingMixin, Merc
                     cmd.append("--%s=%s" % (opt.replace("_", "-"), val))
 
         if "wdspec" in test_types:
             geckodriver_path = self._query_geckodriver()
             if not geckodriver_path or not os.path.isfile(geckodriver_path):
                 self.fatal("Unable to find geckodriver binary "
                            "in common test package: %s" % str(geckodriver_path))
             cmd.append("--webdriver-binary=%s" % geckodriver_path)
+            cmd.append("--webdriver-arg=-vv")  # enable trace logs
 
         options = list(c.get("options", []))
 
         str_format_values = {
             'binary_path': self.binary_path,
             'test_path': dirs["abs_wpttest_dir"],
             'test_install_path': dirs["abs_test_install_dir"],
             'abs_app_dir': abs_app_dir,
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -385663,17 +385663,17 @@
    "f8af4287f3b0f6925a2a6c5c75b3788e24de1680",
    "support"
   ],
   "./config.default.json": [
    "403d365196f6fe2c631d27fe6042e3114d204016",
    "support"
   ],
   "./lint.whitelist": [
-   "fc1e09fcc92c3f54ed75fe93681c5ff0a53d25a4",
+   "2292c83ea0bd1432a7ba43e86a6b9f9fa8836e23",
    "support"
   ],
   "./serve.py": [
    "0efa39b1f26f86d73f2fce4f9e46003d62057b41",
    "support"
   ],
   "./server-side.md": [
    "c51b17fbac2a2e3121dc74f7badbd2873ce92f61",
@@ -593087,17 +593087,17 @@
    "5a31a3917a5157516c10951a3b3d5ffb43b992d9",
    "support"
   ],
   "webdriver/tests/support/asserts.py": [
    "1b839404daaca1d059cba98377edb91691ef7e82",
    "support"
   ],
   "webdriver/tests/support/fixtures.py": [
-   "b9b62366cd60ae7167ad2d0efdf3790ae2e780a4",
+   "ff5b6ca84a42c849b018b512af6987dc9e317cc5",
    "support"
   ],
   "webdriver/tests/support/http_request.py": [
    "cb40c781fea2280b98135522def5e6a116d7b946",
    "support"
   ],
   "webdriver/tests/support/inline.py": [
    "ffabd6a12d6e7928176fa00702214e0c8e0a25d7",
--- a/testing/web-platform/meta/webdriver/tests/actions/key.py.ini
+++ b/testing/web-platform/meta/webdriver/tests/actions/key.py.ini
@@ -1,3 +1,3 @@
 [key.py]
   disabled:
-    if os == "linux": https://bugzilla.mozilla.org/show_bug.cgi?id=1407383
+    if (os == "linux") and (bits == 32): https://bugzilla.mozilla.org/show_bug.cgi?id=1377805
--- a/testing/web-platform/tests/lint.whitelist
+++ b/testing/web-platform/tests/lint.whitelist
@@ -105,19 +105,20 @@ CR AT EOL: html/form-elements/the-textar
 CR AT EOL: html/input/the-placeholder-attribute/multiline-cr.html
 CR AT EOL: html/input/the-placeholder-attribute/multiline-crlf.html
 CR AT EOL: html/semantics/embedded-content/the-canvas-element/size.attributes.parse.whitespace.html
 CR AT EOL: webvtt/parsing/file-parsing/tests/support/newlines.vtt
 
 # Intentional use of tabs
 INDENT TABS: html/semantics/embedded-content/the-canvas-element/size.attributes.parse.whitespace.html
 
-# Test generation files containing print statements
+# Intentional use of print statements
 PRINT STATEMENT: dom/nodes/Document-createElement-namespace-tests/generate.py
 PRINT STATEMENT: encrypted-media/polyfill/make-polyfill-tests.py
+PRINT STATEMENT: webdriver/tests/support/fixtures.py
 
 # semi-legitimate use of console.*
 CONSOLE: console/*
 CONSOLE: resources/check-layout-th.js
 CONSOLE: resources/chromium/*
 CONSOLE: resources/idlharness.js
 CONSOLE: streams/resources/test-utils.js
 CONSOLE: service-workers/service-worker/resources/navigation-redirect-other-origin.html
--- a/testing/web-platform/tests/tools/webdriver/webdriver/client.py
+++ b/testing/web-platform/tests/tools/webdriver/webdriver/client.py
@@ -1,20 +1,16 @@
 import urlparse
 
 import error
 import protocol
 import transport
 
 from six import string_types
 
-from mozlog import get_default_logger
-
-logger = get_default_logger()
-
 
 def command(func):
     def inner(self, *args, **kwargs):
         if hasattr(self, "session"):
             session = self.session
         else:
             session = self
 
--- a/testing/web-platform/tests/webdriver/tests/support/fixtures.py
+++ b/testing/web-platform/tests/webdriver/tests/support/fixtures.py
@@ -1,33 +1,30 @@
+from __future__ import print_function
+
 import json
 import os
 import urlparse
 import re
 
 import webdriver
-import mozlog
 
-from tests.support.asserts import assert_error
 from tests.support.http_request import HTTPRequest
 from tests.support.wait import wait
-from tests.support import merge_dictionaries
 
 default_host = "http://127.0.0.1"
 default_port = "4444"
 
-logger = mozlog.get_default_logger()
-
 
 def ignore_exceptions(f):
     def inner(*args, **kwargs):
         try:
             return f(*args, **kwargs)
         except webdriver.error.WebDriverException as e:
-            logger.warning("Ignored exception %s" % e)
+            print("Ignored exception %s" % e, file=sys.stderr)
     inner.__name__ = f.__name__
     return inner
 
 
 @ignore_exceptions
 def _ensure_valid_window(session):
     """If current window is not open anymore, ensure to have a valid
     one selected.
--- a/toolkit/components/extensions/.eslintrc.js
+++ b/toolkit/components/extensions/.eslintrc.js
@@ -88,17 +88,17 @@ module.exports = {
     // much benefit.
     "no-multi-spaces": "error",
 
     // No expressions where a statement is expected
     "no-unused-expressions": "error",
 
     // No declaring variables that are never used
     "no-unused-vars": ["error", {
-      "args": "none", "vars": "all", "varsIgnorePattern": "^EXPORTED_SYMBOLS$"
+      "args": "none", "vars": "all"
     }],
 
     // No using variables before defined
     "no-use-before-define": "error",
 
     // Never use spaces before function parentheses
     "space-before-function-paren": ["error", {"anonymous": "never", "named": "never"}],
 
--- a/toolkit/components/payments/.eslintrc.js
+++ b/toolkit/components/payments/.eslintrc.js
@@ -36,18 +36,17 @@ module.exports = {
     "no-multiple-empty-lines": ["error", {
       max: 2,
     }],
     "no-proto": "error",
     "no-throw-literal": "error",
     "no-unused-expressions": "error",
     "no-unused-vars": ["error", {
       args: "none",
-      vars: "all",
-      varsIgnorePattern: "^EXPORTED_SYMBOLS$",
+      vars: "all"
     }],
     "no-use-before-define": ["error", {
       functions: false,
     }],
     "padded-blocks": ["error", "never"],
     radix: "error",
     "semi-spacing": ["error", {"before": false, "after": true}],
     "space-in-parens": ["error", "never"],
--- a/toolkit/components/payments/test/browser/head.js
+++ b/toolkit/components/payments/test/browser/head.js
@@ -1,15 +1,14 @@
 "use strict";
 
 /* eslint
   "no-unused-vars": ["error", {
     vars: "local",
     args: "none",
-    varsIgnorePattern: "^(EXPORTED_SYMBOLS)$",
   }],
 */
 
 
 const BLANK_PAGE_PATH = "/browser/toolkit/components/payments/test/browser/blank_page.html";
 const BLANK_PAGE_URL = "https://example.com" + BLANK_PAGE_PATH;
 
 const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
--- a/toolkit/components/places/PlacesTransactions.jsm
+++ b/toolkit/components/places/PlacesTransactions.jsm
@@ -901,19 +901,19 @@ DefineTransaction.verifyInput = function
 };
 
 // Update the documentation at the top of this module if you add or
 // remove properties.
 DefineTransaction.defineInputProps(["url", "feedUrl", "siteUrl"],
                                    DefineTransaction.urlValidate, null);
 DefineTransaction.defineInputProps(["guid", "parentGuid", "newParentGuid"],
                                    DefineTransaction.guidValidate);
-DefineTransaction.defineInputProps(["title"],
+DefineTransaction.defineInputProps(["title", "postData"],
                                    DefineTransaction.strOrNullValidate, null);
-DefineTransaction.defineInputProps(["keyword", "oldKeyword", "postData", "tag",
+DefineTransaction.defineInputProps(["keyword", "oldKeyword", "tag",
                                     "excludingAnnotation"],
                                    DefineTransaction.strValidate, "");
 DefineTransaction.defineInputProps(["index", "newIndex"],
                                    DefineTransaction.indexValidate,
                                    PlacesUtils.bookmarks.DEFAULT_INDEX);
 DefineTransaction.defineInputProps(["annotation"],
                                    DefineTransaction.annotationObjectValidate);
 DefineTransaction.defineInputProps(["child"],
--- a/toolkit/components/places/tests/unit/test_async_transactions.js
+++ b/toolkit/components/places/tests/unit/test_async_transactions.js
@@ -1156,16 +1156,60 @@ add_task(async function test_edit_keywor
   ensureKeywordChange();
   await PT.undo();
   ensureItemsRemoved(bm_info);
 
   await PT.clearTransactionsHistory();
   ensureUndoState();
 });
 
+add_task(async function test_edit_keyword_null_postData() {
+  let bm_info = { parentGuid: rootGuid,
+                  url: NetUtil.newURI("http://test.edit.keyword") };
+  const KEYWORD = "test_keyword";
+  bm_info.guid = await PT.NewBookmark(bm_info).transact();
+  function ensureKeywordChange(aCurrentKeyword = "") {
+    ensureItemsChanged({ guid: bm_info.guid,
+                         property: "keyword",
+                         newValue: aCurrentKeyword });
+  }
+
+  bm_info.guid = await PT.NewBookmark(bm_info).transact();
+
+  observer.reset();
+  await PT.EditKeyword({ guid: bm_info.guid, keyword: KEYWORD, postData: null }).transact();
+  ensureKeywordChange(KEYWORD);
+  let entry = await PlacesUtils.keywords.fetch(KEYWORD);
+  Assert.equal(entry.url.href, bm_info.url.spec);
+  Assert.equal(entry.postData, null);
+
+  observer.reset();
+  await PT.undo();
+  ensureKeywordChange();
+  entry = await PlacesUtils.keywords.fetch(KEYWORD);
+  Assert.equal(entry, null);
+
+  observer.reset();
+  await PT.redo();
+  ensureKeywordChange(KEYWORD);
+  entry = await PlacesUtils.keywords.fetch(KEYWORD);
+  Assert.equal(entry.url.href, bm_info.url.spec);
+  Assert.equal(entry.postData, null);
+
+  // Cleanup
+  observer.reset();
+  await PT.undo();
+  ensureKeywordChange();
+  await PT.undo();
+  ensureItemsRemoved(bm_info);
+
+  await PT.clearTransactionsHistory();
+  ensureUndoState();
+});
+
 add_task(async function test_edit_specific_keyword() {
   let bm_info = { parentGuid: rootGuid,
                   url: NetUtil.newURI("http://test.edit.keyword") };
   bm_info.guid = await PT.NewBookmark(bm_info).transact();
   function ensureKeywordChange(aCurrentKeyword = "", aPreviousKeyword = "") {
     ensureItemsChanged({ guid: bm_info.guid,
                          property: "keyword",
                          newValue: aCurrentKeyword
--- a/toolkit/components/reader/.eslintrc.js
+++ b/toolkit/components/reader/.eslintrc.js
@@ -1,11 +1,11 @@
 "use strict";
 
 module.exports = {
   "rules": {
     "indent-legacy": ["error", 2, { "SwitchCase": 1 }],
     "new-parens": "error",
     "no-inner-declarations": "error",
     "no-shadow": "error",
-    "no-unused-vars": ["error", {"vars": "all", "varsIgnorePattern": "^EXPORTED_SYMBOLS$", "args": "none"}],
+    "no-unused-vars": ["error", {"vars": "all", "args": "none"}],
   },
 }
--- a/toolkit/components/satchel/.eslintrc.js
+++ b/toolkit/components/satchel/.eslintrc.js
@@ -38,17 +38,16 @@ module.exports = {
       max: 2,
     }],
     "no-proto": "error",
     "no-throw-literal": "error",
     "no-unused-expressions": "error",
     "no-unused-vars": ["error", {
       args: "none",
       vars: "all",
-      varsIgnorePattern: "^EXPORTED_SYMBOLS$",
     }],
     "no-use-before-define": ["error", {
       functions: false,
     }],
     "padded-blocks": ["error", "never"],
     radix: "error",
     semi: ["error", "always"],
     "semi-spacing": ["error", {"before": false, "after": true}],
--- a/toolkit/components/satchel/test/satchel_common.js
+++ b/toolkit/components/satchel/test/satchel_common.js
@@ -1,17 +1,16 @@
 /* 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/. */
 
 /* eslint
   "no-unused-vars": ["error", {
     vars: "local",
     args: "none",
-    varsIgnorePattern: "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$",
   }],
 */
 
 /* import-globals-from ../../../../testing/mochitest/tests/SimpleTest/SimpleTest.js */
 
 var gPopupShownExpected = false;
 var gPopupShownListener;
 var gLastAutoCompleteResults;
--- a/toolkit/components/satchel/test/unit/head_satchel.js
+++ b/toolkit/components/satchel/test/unit/head_satchel.js
@@ -1,17 +1,16 @@
 /* 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/. */
 
 /* eslint
   "no-unused-vars": ["error", {
     vars: "local",
     args: "none",
-    varsIgnorePattern: "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$",
   }],
 */
 
 const CURRENT_SCHEMA = 4;
 const PR_HOURS = 60 * 60 * 1000000;
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
--- a/toolkit/mozapps/extensions/.eslintrc.js
+++ b/toolkit/mozapps/extensions/.eslintrc.js
@@ -5,12 +5,11 @@ module.exports = {
     // Warn about cyclomatic complexity in functions.
     // XXX Bug 1326071 - This should be reduced down - probably to 20 or to
     // be removed & synced with the mozilla/recommended value.
     "complexity": ["error", {"max": 68}],
 
     "no-unused-vars": ["error", {
       "args": "none",
       "vars": "all",
-      "varsIgnorePattern": "^EXPORTED_SYMBOLS$"
     }],
   }
 };
--- a/toolkit/mozapps/extensions/test/browser/.eslintrc.js
+++ b/toolkit/mozapps/extensions/test/browser/.eslintrc.js
@@ -1,11 +1,11 @@
 "use strict";
 
 module.exports = {
   "extends": [
     "plugin:mozilla/browser-test"
   ],
 
   "rules": {
-    "no-unused-vars": ["error", {"args": "none", "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS|end_test)$"}],
+    "no-unused-vars": ["error", {"args": "none", "varsIgnorePattern": "^end_test$"}],
   }
 };
--- a/toolkit/mozapps/extensions/test/xpcshell/.eslintrc.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/.eslintrc.js
@@ -1,10 +1,10 @@
 "use strict";
 
 module.exports = {
   "extends": [
     "plugin:mozilla/xpcshell-test"
   ],
   "rules": {
-    "no-unused-vars": ["error", {"args": "none", "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS|end_test)$"}],
+    "no-unused-vars": ["error", {"args": "none", "varsIgnorePattern": "^end_test$"}],
   }
 };
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js
@@ -73,18 +73,17 @@ module.exports = {
       "browser": false,
       "mozilla/jsm": true
     },
     "files": "**/*.jsm",
     "rules": {
       "mozilla/mark-exported-symbols-as-used": "error",
       "no-unused-vars": ["error", {
         "args": "none",
-        "vars": "all",
-        "varsIgnorePattern": "^EXPORTED_SYMBOLS$"
+        "vars": "all"
       }]
     }
   }],
 
   "parserOptions": {
     "ecmaVersion": 9
   },
 
@@ -350,18 +349,17 @@ module.exports = {
     // No unsanitized use of innerHTML=, document.write() etc.
     // cf. https://github.com/mozilla/eslint-plugin-no-unsanitized#rule-details
     "no-unsanitized/method": "error",
     "no-unsanitized/property": "error",
 
     // No declaring variables that are never used
     "no-unused-vars": ["error", {
       "args": "none",
-      "vars": "local",
-      "varsIgnorePattern": "^EXPORTED_SYMBOLS$"
+      "vars": "local"
     }],
 
     // No using variables before defined
     // "no-use-before-define": ["error", "nofunc"],
 
     // Disallow unnecessary .call() and .apply()
     "no-useless-call": "error",
 
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-exported-symbols-as-used.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-exported-symbols-as-used.js
@@ -16,16 +16,18 @@ function markArrayElementsAsUsed(context
       message: "Unexpected assignment of non-Array to EXPORTED_SYMBOLS"
     });
     return;
   }
 
   for (let element of expression.elements) {
     context.markVariableAsUsed(element.value);
   }
+  // Also mark EXPORTED_SYMBOLS as used.
+  context.markVariableAsUsed("EXPORTED_SYMBOLS");
 }
 
 // -----------------------------------------------------------------------------
 // Rule Definition
 // -----------------------------------------------------------------------------
 
 module.exports = function(context) {
   // Ignore assignments not in the global scope, e.g. where special module
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -129,18 +129,16 @@ using namespace mozilla::widget;
 #include "nsShmImage.h"
 
 #include "nsIDOMWheelEvent.h"
 
 #include "NativeKeyBindings.h"
 
 #include <dlfcn.h>
 
-#include "mozilla/layers/APZCTreeManager.h"
-
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::widget;
 using namespace mozilla::layers;
 using mozilla::gl::GLContext;
 
 // Don't put more than this many rects in the dirty region, just fluff
 // out to the bounding-box if there are more
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -198,17 +198,16 @@
 #define SM_CONVERTIBLESLATEMODE 0x2003
 #endif
 
 #if !defined(WM_DPICHANGED)
 #define WM_DPICHANGED 0x02E0
 #endif
 
 #include "mozilla/gfx/DeviceManagerDx.h"
-#include "mozilla/layers/APZCTreeManager.h"
 #include "mozilla/layers/InputAPZContext.h"
 #include "mozilla/layers/KnowsCompositor.h"
 #include "InputData.h"
 
 #include "mozilla/Telemetry.h"
 #include "mozilla/plugins/PluginProcessParent.h"
 #include "mozilla/webrender/WebRenderAPI.h"