Merge m-c to inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 10 Nov 2017 16:14:16 -0500
changeset 444648 abc17e0eea7749de3c486daf4cb978448e29e449
parent 444647 a1691d09605c78e3f62bc07c33b5d3278df70758 (current diff)
parent 444587 092ccc6335a529133a8412a8f1bf1cdda872c6a9 (diff)
child 444649 8e62f2933a3995e2503bf15346f3af3a3e50f914
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone58.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 m-c to inbound. a=merge
browser/themes/shared/notification-icons/plugin-blocked.svg
browser/themes/shared/notification-icons/plugin.svg
config/stl-headers
config/system-headers
ipc/chromium/src/base/platform_file_posix.cc
ipc/chromium/src/base/platform_file_win.cc
ipc/chromium/src/base/thread_local_storage_posix.cc
ipc/chromium/src/base/thread_local_storage_win.cc
toolkit/themes/shared/plugins/contentPluginActivate.png
toolkit/themes/shared/plugins/contentPluginClose.png
--- a/browser/app/Makefile.in
+++ b/browser/app/Makefile.in
@@ -48,21 +48,16 @@ ifdef COMPILE_ENVIRONMENT
 libs::
 	cp -p $(MOZ_APP_NAME)$(BIN_SUFFIX) $(DIST)/bin/$(MOZ_APP_NAME)-bin$(BIN_SUFFIX)
 endif
 
 GARBAGE += $(addprefix $(FINAL_TARGET)/defaults/pref/, firefox.js)
 
 endif
 
-# channel-prefs.js is handled separate from other prefs due to bug 756325
-libs:: $(srcdir)/profile/channel-prefs.js
-	$(NSINSTALL) -D $(DIST)/bin/defaults/pref
-	$(call py_action,preprocessor,-Fsubstitution $(PREF_PPFLAGS) $(ACDEFINES) $^ -o $(DIST)/bin/defaults/pref/channel-prefs.js)
-
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 
 MAC_APP_NAME = $(MOZ_APP_DISPLAYNAME)
 
 ifdef MOZ_DEBUG
 MAC_APP_NAME := $(MAC_APP_NAME)Debug
 endif
 
--- a/browser/app/moz.build
+++ b/browser/app/moz.build
@@ -31,28 +31,25 @@ with Files("profile/channel-prefs.js"):
 with Files("profile/firefox.js"):
     BUG_COMPONENT = ("Firefox", "General")
 
 
 DIRS += ['profile/extensions']
 
 GeckoProgram(CONFIG['MOZ_APP_NAME'])
 
-JS_PREFERENCE_PP_FILES += [
-    'profile/firefox.js',
-]
-
 SOURCES += [
     'nsBrowserApp.cpp',
 ]
 
-FINAL_TARGET_FILES += ['blocklist.xml']
-FINAL_TARGET_FILES.defaults += ['permissions']
+# Neither channel-prefs.js nor firefox.exe want to end up in dist/bin/browser.
+DIST_SUBDIR = ""
 
-DEFINES['APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+# channel-prefs.js is handled separate from other prefs due to bug 756325
+JS_PREFERENCE_PP_FILES += ['profile/channel-prefs.js']
 
 LOCAL_INCLUDES += [
     '!/build',
     '/toolkit/xre',
     '/xpcom/base',
     '/xpcom/build',
 ]
 
@@ -69,19 +66,16 @@ if CONFIG['_MSC_VER']:
     # Always enter a Windows program through wmain, whether or not we're
     # a console application.
     WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup']
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     RCINCLUDE = 'splash.rc'
     DEFINES['MOZ_PHOENIX'] = True
 
-for cdm in CONFIG['MOZ_EME_MODULES']:
-    DEFINES['MOZ_%s_EME' % cdm.upper()] = True
-
 if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_ARCH'] == 'WINNT':
     # For sandbox includes and the include dependencies those have
     LOCAL_INCLUDES += [
         '/security/sandbox/chromium',
         '/security/sandbox/chromium-shim',
     ]
 
     USE_LIBS += [
@@ -107,19 +101,16 @@ if CONFIG['OS_ARCH'] == 'WINNT' and not 
 DisableStlWrapping()
 
 if CONFIG['MOZ_LINKER']:
     OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
 
 if CONFIG['HAVE_CLOCK_MONOTONIC']:
     OS_LIBS += CONFIG['REALTIME_LIBS']
 
-if CONFIG['MOZ_GPSD']:
-    DEFINES['MOZ_GPSD'] = True
-
 if CONFIG['MOZ_LINUX_32_SSE2_STARTUP_ERROR']:
     DEFINES['MOZ_LINUX_32_SSE2_STARTUP_ERROR'] = True
     COMPILE_FLAGS['OS_CXXFLAGS'] = [
         f for f in COMPILE_FLAGS.get('OS_CXXFLAGS', [])
         if not f.startswith('-march=') and f not in ('-msse', '-msse2', '-mfpmath=sse')
     ] + [
         '-mno-sse', '-mno-sse2', '-mfpmath=387',
     ]
--- a/browser/app/profile/channel-prefs.js
+++ b/browser/app/profile/channel-prefs.js
@@ -1,5 +1,6 @@
 /* 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/. */
 
+#filter substitution
 pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@");
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -43,34 +43,16 @@ var BrowserPageActions = {
     return this.mainViewBodyNode = this.mainViewNode.querySelector(".panel-subview-body");
   },
 
   /**
    * Inits.  Call to init.
    */
   init() {
     this.placeAllActions();
-
-    // Add a click listener to #page-action-buttons for blocking clicks on
-    // disabled actions in the urlbar.  Normally we'd do this by setting
-    // `pointer-events: none` in the CSS, but that also blocks context menu
-    // events, and we want the context menu even on disabled actions so that
-    // they can be removed from the urlbar.
-    this.mainButtonNode.parentNode.addEventListener("click", event => {
-      if (event.button == 2) {
-        // Let context-clicks be handled normally.
-        return;
-      }
-      let node = event.originalTarget;
-      let action = this.actionForNode(node);
-      if (action && action.getDisabled(window)) {
-        event.preventDefault();
-        event.stopPropagation();
-      }
-    }, true);
   },
 
   /**
    * Places all registered actions.
    */
   placeAllActions() {
     // Place actions in the panel.  Notify of onBeforePlacedInWindow too.
     for (let action of PageActions.actions) {
@@ -81,17 +63,17 @@ var BrowserPageActions = {
     // Place actions in the urlbar.  Do this in reverse order.  The reason is
     // subtle.  If there were no urlbar nodes already in markup (like the
     // bookmark star button), then doing this in forward order would be fine.
     // Forward order means that the insert-before relationship is always broken:
     // there's never a next-sibling node before which to insert a new node, so
     // node.insertBefore() is always passed null, and nodes are always appended.
     // That will break the position of nodes that should be inserted before
     // nodes that are in markup, which in turn can break other nodes.
-    let actionsInUrlbar = PageActions.actionsInUrlbar;
+    let actionsInUrlbar = PageActions.actionsInUrlbar(window);
     for (let i = actionsInUrlbar.length - 1; i >= 0; i--) {
       let action = actionsInUrlbar[i];
       this.placeActionInUrlbar(action);
     }
   },
 
   /**
    * Adds or removes as necessary DOM nodes for the given action.
@@ -107,31 +89,29 @@ var BrowserPageActions = {
 
   /**
    * Adds or removes as necessary DOM nodes for the action in the panel.
    *
    * @param  action (PageActions.Action, required)
    *         The action to place.
    */
   placeActionInPanel(action) {
-    let insertBeforeID = PageActions.nextActionIDInPanel(action);
     let id = this.panelButtonNodeIDForActionID(action.id);
     let node = document.getElementById(id);
     if (!node) {
       let panelViewNode;
       [node, panelViewNode] = this._makePanelButtonNodeForAction(action);
       node.id = id;
-      let insertBeforeNode = null;
-      if (insertBeforeID) {
-        let insertBeforeNodeID =
-          this.panelButtonNodeIDForActionID(insertBeforeID);
-        insertBeforeNode = document.getElementById(insertBeforeNodeID);
-      }
+      let insertBeforeID = PageActions.nextActionIDInPanel(action);
+      let insertBeforeNode =
+        insertBeforeID ? this.panelButtonNodeForActionID(insertBeforeID) :
+        null;
       this.mainViewBodyNode.insertBefore(node, insertBeforeNode);
       this.updateAction(action);
+      this._updateActionDisabledInPanel(action);
       action.onPlacedInPanel(node);
       if (panelViewNode) {
         action.subview.onPlaced(panelViewNode);
       }
     }
   },
 
   _makePanelButtonNodeForAction(action) {
@@ -351,21 +331,20 @@ var BrowserPageActions = {
 
   /**
    * Adds or removes as necessary a DOM node for the given action in the urlbar.
    *
    * @param  action (PageActions.Action, required)
    *         The action to place.
    */
   placeActionInUrlbar(action) {
-    let insertBeforeID = PageActions.nextActionIDInUrlbar(action);
     let id = this.urlbarButtonNodeIDForActionID(action.id);
     let node = document.getElementById(id);
 
-    if (!action.shownInUrlbar) {
+    if (!action.shouldShowInUrlbar(window)) {
       if (node) {
         if (action.__urlbarNodeInMarkup) {
           node.hidden = true;
         } else {
           node.remove();
         }
       }
       return;
@@ -377,49 +356,42 @@ var BrowserPageActions = {
       node.hidden = false;
     } else if (!node) {
       newlyPlaced = true;
       node = this._makeUrlbarButtonNode(action);
       node.id = id;
     }
 
     if (newlyPlaced) {
-      let parentNode = this.mainButtonNode.parentNode;
-      let insertBeforeNode = null;
-      if (insertBeforeID) {
-        let insertBeforeNodeID =
-          this.urlbarButtonNodeIDForActionID(insertBeforeID);
-        insertBeforeNode = document.getElementById(insertBeforeNodeID);
-      }
-      parentNode.insertBefore(node, insertBeforeNode);
+      let insertBeforeID = PageActions.nextActionIDInUrlbar(window, action);
+      let insertBeforeNode =
+        insertBeforeID ? this.urlbarButtonNodeForActionID(insertBeforeID) :
+        null;
+      this.mainButtonNode.parentNode.insertBefore(node, insertBeforeNode);
       this.updateAction(action);
       action.onPlacedInUrlbar(node);
 
       // urlbar buttons should always have tooltips, so if the node doesn't have
       // one, then as a last resort use the label of the corresponding panel
       // button.  Why not set tooltiptext to action.title when the node is
       // created?  Because the consumer may set a title dynamically.
       if (!node.hasAttribute("tooltiptext")) {
-        let panelNodeID = this.panelButtonNodeIDForActionID(action.id);
-        let panelNode = document.getElementById(panelNodeID);
+        let panelNode = this.panelButtonNodeForActionID(action.id);
         if (panelNode) {
           node.setAttribute("tooltiptext", panelNode.getAttribute("label"));
         }
       }
     }
   },
 
   _makeUrlbarButtonNode(action) {
     let buttonNode = document.createElement("image");
     buttonNode.classList.add("urlbar-icon", "urlbar-page-action");
     buttonNode.setAttribute("actionid", action.id);
     buttonNode.setAttribute("role", "button");
-    buttonNode.addEventListener("contextmenu", event => {
-      BrowserPageActions.onContextMenu(event);
-    });
     if (action.nodeAttributes) {
       for (let name in action.nodeAttributes) {
         buttonNode.setAttribute(name, action.nodeAttributes[name]);
       }
     }
     buttonNode.addEventListener("click", event => {
       this.doCommandForAction(action, event, buttonNode);
     });
@@ -434,18 +406,17 @@ var BrowserPageActions = {
    */
   removeAction(action) {
     this._removeActionFromPanel(action);
     this._removeActionFromUrlbar(action);
     action.onRemovedFromWindow(window);
   },
 
   _removeActionFromPanel(action) {
-    let id = this.panelButtonNodeIDForActionID(action.id);
-    let node = document.getElementById(id);
+    let node = this.panelButtonNodeForActionID(action.id);
     if (node) {
       node.remove();
     }
     if (action.subview) {
       let panelViewNodeID = this._panelViewNodeIDForActionID(action.id, false);
       let panelViewNode = document.getElementById(panelViewNodeID);
       if (panelViewNode) {
         panelViewNode.remove();
@@ -461,127 +432,120 @@ var BrowserPageActions = {
       );
       if (separator) {
         separator.remove();
       }
     }
   },
 
   _removeActionFromUrlbar(action) {
-    let id = this.urlbarButtonNodeIDForActionID(action.id);
-    let node = document.getElementById(id);
+    let node = this.urlbarButtonNodeForActionID(action.id);
     if (node) {
       node.remove();
     }
   },
 
   /**
    * Updates the DOM nodes of an action to reflect either a changed property or
    * all properties.
    *
    * @param  action (PageActions.Action, required)
    *         The action to update.
-   * @param  nameToUpdate (string, optional)
-   *         The property's name.  If not given, then DOM nodes will be updated
-   *         to reflect the current values of all properties.
+   * @param  propertyName (string, optional)
+   *         The name of the property to update.  If not given, then DOM nodes
+   *         will be updated to reflect the current values of all properties.
    */
-  updateAction(action, nameToUpdate = null) {
-    let names = nameToUpdate ? [nameToUpdate] : [
-      "disabled",
+  updateAction(action, propertyName = null) {
+    let propertyNames = propertyName ? [propertyName] : [
       "iconURL",
       "title",
       "tooltip",
     ];
-    for (let name of names) {
+    for (let name of propertyNames) {
       let upper = name[0].toUpperCase() + name.substr(1);
       this[`_updateAction${upper}`](action);
     }
   },
 
   _updateActionDisabled(action) {
-    let nodeIDs = [
-      this.panelButtonNodeIDForActionID(action.id),
-      this.urlbarButtonNodeIDForActionID(action.id),
-    ];
-    for (let nodeID of nodeIDs) {
-      let node = document.getElementById(nodeID);
-      if (node) {
-        if (action.getDisabled(window)) {
-          node.setAttribute("disabled", "true");
-        } else {
-          node.removeAttribute("disabled");
-        }
+    this._updateActionDisabledInPanel(action);
+    this.placeActionInUrlbar(action);
+  },
+
+  _updateActionDisabledInPanel(action) {
+    let panelButton = this.panelButtonNodeForActionID(action.id);
+    if (panelButton) {
+      if (action.getDisabled(window)) {
+        panelButton.setAttribute("disabled", "true");
+      } else {
+        panelButton.removeAttribute("disabled");
       }
     }
   },
 
   _updateActionIconURL(action) {
-    let nodeIDs = [
-      this.panelButtonNodeIDForActionID(action.id),
-      this.urlbarButtonNodeIDForActionID(action.id),
-    ];
-    for (let nodeID of nodeIDs) {
-      let node = document.getElementById(nodeID);
-      if (node) {
-        for (let size of [16, 32]) {
-          let url = action.iconURLForSize(size, window);
-          let prop = `--pageAction-image-${size}px`;
-          if (url) {
-            node.style.setProperty(prop, `url("${url}")`);
-          } else {
-            node.style.removeProperty(prop);
-          }
+    let nodes = [
+      this.panelButtonNodeForActionID(action.id),
+      this.urlbarButtonNodeForActionID(action.id),
+    ].filter(n => !!n);
+    for (let node of nodes) {
+      for (let size of [16, 32]) {
+        let url = action.iconURLForSize(size, window);
+        let prop = `--pageAction-image-${size}px`;
+        if (url) {
+          node.style.setProperty(prop, `url("${url}")`);
+        } else {
+          node.style.removeProperty(prop);
         }
       }
     }
   },
 
   _updateActionTitle(action) {
     let title = action.getTitle(window);
     if (!title) {
       // `title` is a required action property, but the bookmark action's is an
       // empty string since its actual title is set via
       // BookmarkingUI.updateBookmarkPageMenuItem().  The purpose of this early
       // return is to ignore that empty title.
       return;
     }
-    let attrNamesByNodeIDFnName = {
-      panelButtonNodeIDForActionID: "label",
-      urlbarButtonNodeIDForActionID: "aria-label",
+    let attrNamesByNodeFnName = {
+      panelButtonNodeForActionID: "label",
+      urlbarButtonNodeForActionID: "aria-label",
     };
-    for (let [fnName, attrName] of Object.entries(attrNamesByNodeIDFnName)) {
-      let nodeID = this[fnName](action.id);
-      let node = document.getElementById(nodeID);
+    for (let [fnName, attrName] of Object.entries(attrNamesByNodeFnName)) {
+      let node = this[fnName](action.id);
       if (node) {
         node.setAttribute(attrName, title);
       }
     }
     // tooltiptext falls back to the title, so update it, too.
     this._updateActionTooltip(action);
   },
 
   _updateActionTooltip(action) {
-    let node = document.getElementById(
-      this.urlbarButtonNodeIDForActionID(action.id)
-    );
+    let node = this.urlbarButtonNodeForActionID(action.id);
     if (node) {
       let tooltip = action.getTooltip(window) || action.getTitle(window);
       node.setAttribute("tooltiptext", tooltip);
     }
   },
 
   doCommandForAction(action, event, buttonNode) {
     if (event && event.type == "click" && event.button != 0) {
       return;
     }
     PageActions.logTelemetry("used", action, buttonNode);
     // If we're in the panel, open a subview inside the panel:
     // Note that we can't use this.panelNode.contains(buttonNode) here
-    // because of XBL boundaries breaking ELement.contains.
-    if (action.subview && buttonNode && buttonNode.closest("panel") == this.panelNode) {
+    // because of XBL boundaries breaking Element.contains.
+    if (action.subview &&
+        buttonNode &&
+        buttonNode.closest("panel") == this.panelNode) {
       let panelViewNodeID = this._panelViewNodeIDForActionID(action.id, false);
       let panelViewNode = document.getElementById(panelViewNodeID);
       action.subview.onShowing(panelViewNode);
       this.multiViewNode.showSubView(panelViewNode, buttonNode);
       return;
     }
     // Otherwise, hide the main popup in case it was open:
     this.panelNode.hidePopup();
@@ -623,27 +587,49 @@ var BrowserPageActions = {
         actionID = this._actionIDForNodeID(n.id);
         action = PageActions.actionForID(actionID);
       }
     }
     return action;
   },
 
   /**
+   * The given action's top-level button in the main panel.
+   *
+   * @param  actionID (string, required)
+   *         The action ID.
+   * @return (DOM node) The action's button in the main panel.
+   */
+  panelButtonNodeForActionID(actionID) {
+    return document.getElementById(this.panelButtonNodeIDForActionID(actionID));
+  },
+
+  /**
    * The ID of the given action's top-level button in the main panel.
    *
    * @param  actionID (string, required)
    *         The action ID.
    * @return (string) The ID of the action's button in the main panel.
    */
   panelButtonNodeIDForActionID(actionID) {
     return `pageAction-panel-${actionID}`;
   },
 
   /**
+   * The given action's button in the urlbar.
+   *
+   * @param  actionID (string, required)
+   *         The action ID.
+   * @return (DOM node) The action's urlbar button node.
+   */
+  urlbarButtonNodeForActionID(actionID) {
+    return document.getElementById(this.urlbarButtonNodeIDForActionID(actionID));
+  },
+
+  /**
    * The ID of the given action's button in the urlbar.
    *
    * @param  actionID (string, required)
    *         The action ID.
    * @return (string) The ID of the action's urlbar button node.
    */
   urlbarButtonNodeIDForActionID(actionID) {
     let action = PageActions.actionForID(actionID);
@@ -717,86 +703,95 @@ var BrowserPageActions = {
    * Show the page action panel
    *
    * @param  event (DOM event, optional)
    *         The event that triggers showing the panel. (such as a mouse click,
    *         if the user clicked something to open the panel)
    */
   showPanel(event = null) {
     for (let action of PageActions.actions) {
-      let buttonNodeID = this.panelButtonNodeIDForActionID(action.id);
-      let buttonNode = document.getElementById(buttonNodeID);
+      let buttonNode = this.panelButtonNodeForActionID(action.id);
       action.onShowingInPanel(buttonNode);
     }
 
     this.panelNode.hidden = false;
     this.panelNode.addEventListener("popuphiding", () => {
       this.mainButtonNode.removeAttribute("open");
     }, {once: true});
     this.mainButtonNode.setAttribute("open", "true");
     this.panelNode.openPopup(this.mainButtonNode, {
       position: "bottomcenter topright",
       triggerEvent: event,
     });
   },
 
   /**
-   * Call this on the contextmenu event.  Note that this is called before
-   * onContextMenuShowing.
-   *
-   * @param  event (DOM event, required)
-   *         The contextmenu event.
-   */
-  onContextMenu(event) {
-    let node = event.originalTarget;
-    this._contextAction = this.actionForNode(node);
-    // Don't show the menu if there's no action where the user clicked!
-    if (!this._contextAction) {
-      event.preventDefault();
-    }
-  },
-
-  /**
    * Call this on the context menu's popupshowing event.
    *
    * @param  event (DOM event, required)
    *         The popupshowing event.
    * @param  popup (DOM node, required)
    *         The context menu popup DOM node.
    */
   onContextMenuShowing(event, popup) {
     if (event.target != popup) {
       return;
     }
-    // Right now there's only one item in the context menu, to toggle the
-    // context action's shown-in-urlbar state.  Update it now.
-    let toggleItem = popup.firstChild;
-    let toggleItemLabel = null;
-    if (this._contextAction) {
-      toggleItem.disabled = false;
-      if (this._contextAction.shownInUrlbar) {
-        toggleItemLabel = toggleItem.getAttribute("remove-label");
-      }
+
+    this._contextAction = this.actionForNode(popup.triggerNode);
+    if (!this._contextAction) {
+      event.preventDefault();
+      return;
     }
-    if (!toggleItemLabel) {
-      toggleItemLabel = toggleItem.getAttribute("add-label");
+
+    let state;
+    if (this._contextAction._isBuiltIn) {
+      state =
+        this._contextAction.pinnedToUrlbar ?
+        "builtInPinned" :
+        "builtInUnpinned";
+    } else {
+      state =
+        this._contextAction.pinnedToUrlbar ?
+        "extensionPinned" :
+        "extensionUnpinned";
     }
-    toggleItem.label = toggleItemLabel;
+    popup.setAttribute("state", state);
   },
 
   /**
-   * Call this from the context menu's toggle menu item.
+   * Call this from the menu item in the context menu that toggles pinning.
    */
-  toggleShownInUrlbarForContextAction() {
+  togglePinningForContextAction() {
     if (!this._contextAction) {
       return;
     }
-    let telemetryType = this._contextAction.shownInUrlbar ? "removed" : "added";
-    PageActions.logTelemetry(telemetryType, this._contextAction);
-    this._contextAction.shownInUrlbar = !this._contextAction.shownInUrlbar;
+    let action = this._contextAction;
+    this._contextAction = null;
+
+    let telemetryType = action.pinnedToUrlbar ? "removed" : "added";
+    PageActions.logTelemetry(telemetryType, action);
+
+    action.pinnedToUrlbar = !action.pinnedToUrlbar;
+  },
+
+  /**
+   * Call this from the menu item in the context menu that opens about:addons.
+   */
+  openAboutAddonsForContextAction() {
+    if (!this._contextAction) {
+      return;
+    }
+    let action = this._contextAction;
+    this._contextAction = null;
+
+    PageActions.logTelemetry("managed", action);
+
+    let viewID = "addons://detail/" + encodeURIComponent(action.extensionID);
+    window.BrowserOpenAddonsMgr(viewID);
   },
 
   _contextAction: null,
 
   /**
    * Titles for a few of the built-in actions are defined in DTD, but the
    * actions are created in JS.  So what we do is for each title, set an
    * attribute in markup on the main page action panel whose value is the DTD
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1411,16 +1411,27 @@ toolbarpaletteitem[place="palette"][hidd
   .pageAction-panel-button > .toolbarbutton-icon {
     list-style-image: var(--pageAction-image-32px, inherit);
   }
   .urlbar-page-action {
     list-style-image: var(--pageAction-image-32px, inherit);
   }
 }
 
+/* Page action context menu */
+#pageActionContextMenu > .pageActionContextMenuItem {
+  visibility: collapse;
+}
+#pageActionContextMenu[state=builtInPinned] > .pageActionContextMenuItem.builtInPinned,
+#pageActionContextMenu[state=builtInUnpinned] > .pageActionContextMenuItem.builtInUnpinned,
+#pageActionContextMenu[state=extensionPinned] > .pageActionContextMenuItem.extensionPinned,
+#pageActionContextMenu[state=extensionUnpinned] > .pageActionContextMenuItem.extensionUnpinned {
+  visibility: visible;
+}
+
 /* WebExtension Sidebars */
 #sidebar-box[sidebarcommand$="-sidebar-action"] > #sidebar-header > #sidebar-switcher-target > #sidebar-icon {
   list-style-image: var(--webextension-menuitem-image, inherit);
   -moz-context-properties: fill;
   fill: currentColor;
   width: 16px;
   height: 16px;
 }
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -417,17 +417,16 @@
            emailLink-title="&emailPageCmd.label;"
            sendToDevice-title="&pageAction.sendTabToDevice.label;"
            sendToDevice-notReadyTitle="&sendToDevice.syncNotReady.label;">
       <photonpanelmultiview id="pageActionPanelMultiView"
                             mainViewId="pageActionPanelMainView"
                             viewCacheId="appMenu-viewCache">
         <panelview id="pageActionPanelMainView"
                    context="pageActionContextMenu"
-                   oncontextmenu="BrowserPageActions.onContextMenu(event);"
                    class="PanelUI-subView">
           <vbox class="panel-subview-body"/>
         </panelview>
       </photonpanelmultiview>
     </panel>
     <panel id="pageActionFeedback"
            role="alert"
            type="arrow"
@@ -441,21 +440,32 @@
       <hbox id="pageActionFeedbackAnimatableBox">
         <image id="pageActionFeedbackAnimatableImage"/>
       </hbox>
       <label id="pageActionFeedbackMessage"/>
     </panel>
 
     <menupopup id="pageActionContextMenu"
                onpopupshowing="BrowserPageActions.onContextMenuShowing(event, this);">
-      <menuitem id="pageActionContextMenu-toggleUrlbar"
-                add-label="&pageAction.addToUrlbar.label;"
-                remove-label="&pageAction.removeFromUrlbar.label;"
+      <menuitem class="pageActionContextMenuItem builtInUnpinned"
                 label="&pageAction.addToUrlbar.label;"
-                oncommand="BrowserPageActions.toggleShownInUrlbarForContextAction();"/>
+                oncommand="BrowserPageActions.togglePinningForContextAction();"/>
+      <menuitem class="pageActionContextMenuItem builtInPinned"
+                label="&pageAction.removeFromUrlbar.label;"
+                oncommand="BrowserPageActions.togglePinningForContextAction();"/>
+      <menuitem class="pageActionContextMenuItem extensionUnpinned"
+                label="&pageAction.allowInUrlbar.label;"
+                oncommand="BrowserPageActions.togglePinningForContextAction();"/>
+      <menuitem class="pageActionContextMenuItem extensionPinned"
+                label="&pageAction.disallowInUrlbar.label;"
+                oncommand="BrowserPageActions.togglePinningForContextAction();"/>
+      <menuseparator class="pageActionContextMenuItem extensionPinned extensionUnpinned"/>
+      <menuitem class="pageActionContextMenuItem extensionPinned extensionUnpinned"
+                label="&pageAction.manageExtension.label;"
+                oncommand="BrowserPageActions.openAboutAddonsForContextAction();"/>
     </menupopup>
 
     <!-- Bookmarks and history tooltip -->
     <tooltip id="bhTooltip"/>
 
     <tooltip id="tabbrowser-tab-tooltip" onpopupshowing="gBrowser.createTooltip(event);"/>
 
     <tooltip id="back-button-tooltip">
@@ -854,19 +864,17 @@
                   <label id="identity-icon-label" class="plain" flex="1"/>
                   <label id="identity-icon-country-label" class="plain"/>
                 </hbox>
               </box>
               <box id="urlbar-display-box" align="center">
                 <label id="switchtab" class="urlbar-display urlbar-display-switchtab" value="&urlbar.switchToTab.label;"/>
                 <label id="extension" class="urlbar-display urlbar-display-extension" value="&urlbar.extension.label;"/>
               </box>
-              <hbox id="page-action-buttons"
-                    context="pageActionContextMenu"
-                    oncontextmenu="BrowserPageActions.onContextMenu(event);">
+              <hbox id="page-action-buttons" context="pageActionContextMenu">
                 <hbox id="userContext-icons" hidden="true">
                   <label id="userContext-label"/>
                   <image id="userContext-indicator"/>
                 </hbox>
                 <image id="reader-mode-button"
                        class="urlbar-icon urlbar-page-action"
                        role="button"
                        hidden="true"
--- a/browser/base/content/pageinfo/pageInfo.xul
+++ b/browser/base/content/pageinfo/pageInfo.xul
@@ -284,16 +284,17 @@
                  value="&permPlugins;" control="pluginsRadioGroup"/>
           <hbox id="permPluginTemplate" role="group" aria-labelledby="permPluginsLabel" align="baseline">
             <label class="permPluginTemplateLabel"/>
             <spacer flex="1"/>
             <radiogroup class="permPluginTemplateRadioGroup" orient="horizontal" command="cmd_pluginsToggle">
               <radio class="permPluginTemplateRadioDefault" label="&permUseDefault;"/>
               <radio class="permPluginTemplateRadioAsk" label="&permAskAlways;"/>
               <radio class="permPluginTemplateRadioAllow" label="&permAllow;"/>
+              <radio class="permPluginTemplateRadioHide" label="&permHide;"/>
               <radio class="permPluginTemplateRadioBlock" label="&permBlock;"/>
             </radiogroup>
           </hbox>
         </vbox>
       </vbox>
       <hbox pack="end">
         <button command="cmd_help" label="&helpButton.label;" dlgtype="help"/>
       </hbox>
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -178,16 +178,18 @@ function fillInPluginPermissionTemplate(
   let permPluginTemplate = document.getElementById("permPluginTemplate").cloneNode(true);
   permPluginTemplate.setAttribute("permString", aPermissionString);
   let attrs = [
     [ ".permPluginTemplateLabel", "value", aPluginName ],
     [ ".permPluginTemplateRadioGroup", "id", aPermissionString + "RadioGroup" ],
     [ ".permPluginTemplateRadioDefault", "id", aPermissionString + "#0" ],
     [ ".permPluginTemplateRadioAsk", "id", aPermissionString + "#3" ],
     [ ".permPluginTemplateRadioAllow", "id", aPermissionString + "#1" ],
+    // #8 comes from Ci.nsIObjectLoadingContent.PLUGIN_PERMISSION_PROMPT_ACTION_QUIET
+    [ ".permPluginTemplateRadioHide", "id", aPermissionString + "#8"],
     [ ".permPluginTemplateRadioBlock", "id", aPermissionString + "#2" ]
   ];
 
   for (let attr of attrs) {
     permPluginTemplate.querySelector(attr[0]).setAttribute(attr[1], attr[2]);
   }
 
   return permPluginTemplate;
--- a/browser/base/content/test/plugins/browser.ini
+++ b/browser/base/content/test/plugins/browser.ini
@@ -21,16 +21,17 @@ support-files =
   plugin_bug797677.html
   plugin_bug820497.html
   plugin_clickToPlayAllow.html
   plugin_clickToPlayDeny.html
   plugin_favorfallback.html
   plugin_hidden_to_visible.html
   plugin_iframe.html
   plugin_outsideScrollArea.html
+  plugin_overlay_styles.html
   plugin_overlayed.html
   plugin_positioned.html
   plugin_simple_blank.swf
   plugin_shouldShowOverlay.html
   plugin_small.html
   plugin_small_2.html
   plugin_syncRemoved.html
   plugin_test.html
@@ -66,16 +67,17 @@ tags = blocklist
 [browser_CTP_iframe.js]
 tags = blocklist
 [browser_CTP_nonplugins.js]
 tags = blocklist
 [browser_CTP_notificationBar.js]
 tags = blocklist
 [browser_CTP_outsideScrollArea.js]
 tags = blocklist
+[browser_CTP_overlay_styles.js]
 [browser_CTP_remove_navigate.js]
 tags = blocklist
 [browser_CTP_resize.js]
 tags = blocklist
 [browser_CTP_shouldShowOverlay.js]
 [browser_CTP_zoom.js]
 tags = blocklist
 [browser_blocking.js]
--- a/browser/base/content/test/plugins/browser_CTP_notificationBar.js
+++ b/browser/base/content/test/plugins/browser_CTP_notificationBar.js
@@ -77,18 +77,18 @@ add_task(async function() {
 
   let pluginInfo = await promiseForPluginInfo("test");
   ok(!pluginInfo.activated, "Test 1a, plugin should not be activated");
 
   await ContentTask.spawn(gTestBrowser, null, async function() {
     let doc = content.document;
     let plugin = doc.getElementById("test");
     let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-    Assert.ok(!overlay || !overlay.classList.contains("visible"),
-      "Test 3b, overlay should be hidden.");
+    Assert.ok(!overlay || overlay.getAttribute("sizing") == "blank",
+      "Test 3b, overlay should be blank.");
   });
 });
 
 add_task(async function() {
   await promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_positioned.html");
 
   // Work around for delayed PluginBindingAttached
   await promiseUpdatePluginBindings(gTestBrowser);
@@ -104,18 +104,18 @@ add_task(async function() {
     Assert.equal(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
       "Test 4b, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
   });
 
   await ContentTask.spawn(gTestBrowser, null, async function() {
     let doc = content.document;
     let plugin = doc.getElementById("test");
     let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-    Assert.ok(!overlay || !overlay.classList.contains("visible"),
-      "Test 4b, overlay should be hidden.");
+    Assert.ok(!overlay || overlay.getAttribute("sizing") == "blank",
+      "Test 4b, overlay should be blank.");
   });
 });
 
 // Test that the notification bar is getting dismissed when directly activating plugins
 // via the doorhanger.
 
 add_task(async function() {
   await promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_small.html");
--- a/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js
+++ b/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js
@@ -53,17 +53,17 @@ add_task(async function() {
 
   await promisePopupNotification("click-to-play-plugins");
 
   await ContentTask.spawn(gTestBrowser, {}, async function() {
     let plugin = content.document.getElementById("test");
     let doc = content.document;
     let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
     Assert.ok(overlay && overlay.classList.contains("visible") &&
-              !overlay.classList.contains("minimal"),
+              overlay.getAttribute("sizing") != "blank",
               "Test 2, overlay should be visible.");
   });
 });
 
 add_task(async function() {
   await promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_outsideScrollArea.html");
 
   await ContentTask.spawn(gTestBrowser, {}, async function() {
@@ -83,17 +83,17 @@ add_task(async function() {
 
   await promisePopupNotification("click-to-play-plugins");
 
   await ContentTask.spawn(gTestBrowser, null, async function() {
     let plugin = content.document.getElementById("test");
     let doc = content.document;
     let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
     Assert.ok(overlay && overlay.classList.contains("visible") &&
-              !overlay.classList.contains("minimal"),
+              overlay.getAttribute("sizing") != "blank",
               "Test 3, overlay should be visible.");
   });
 });
 
 add_task(async function() {
   await promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_outsideScrollArea.html");
 
   await ContentTask.spawn(gTestBrowser, {}, async function() {
@@ -111,12 +111,12 @@ add_task(async function() {
   // Work around for delayed PluginBindingAttached
   await promiseUpdatePluginBindings(gTestBrowser);
 
   await promisePopupNotification("click-to-play-plugins");
   await ContentTask.spawn(gTestBrowser, null, async function() {
     let plugin = content.document.getElementById("test");
     let doc = content.document;
     let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-    Assert.ok(!overlay || !overlay.classList.contains("visible"),
-      "Test 4, overlay should be hidden.");
+    Assert.ok(!overlay || overlay.getAttribute("sizing") == "blank",
+      "Test 4, overlay should be blank.");
   });
 });
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_overlay_styles.js
@@ -0,0 +1,93 @@
+/* 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/. */
+
+"use strict";
+
+/* This test ensures that the click-to-play "Activate Plugin" overlay
+ * is shown in the right style (which is dependent on its size).
+ */
+
+const rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+var gTestBrowser = null;
+
+const gTestcases = {
+
+  // 10x10
+  testcase1: {
+    sizing: "blank",
+    notext: null,
+  },
+
+  // 40x40
+  testcase2: {
+    sizing: "tiny",
+    notext: "notext",
+  },
+
+  // 100x70
+  testcase3: {
+    sizing: "reduced",
+    notext: "notext",
+  },
+
+  // 200x200
+  testcase4: {
+    sizing: null,
+    notext: "notext",
+  },
+
+  // 300x300
+  testcase5: {
+    sizing: null,
+    notext: null,
+  },
+}
+
+
+add_task(async function() {
+  registerCleanupFunction(function() {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    gBrowser.removeCurrentTab();
+    gTestBrowser = null;
+  });
+});
+
+add_task(async function() {
+  gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+  gTestBrowser = gBrowser.selectedBrowser;
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(!popupNotification, "Sanity check, should not have a click-to-play notification");
+
+  await promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_overlay_styles.html");
+
+  // Work around for delayed PluginBindingAttached
+  await promiseUpdatePluginBindings(gTestBrowser);
+
+  await ContentTask.spawn(gTestBrowser, gTestcases, async function(testcases) {
+    let doc = content.document;
+
+    for (let testcaseId of Object.keys(testcases)) {
+      let plugin = doc.querySelector(`#${testcaseId} > object`);
+      let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+      Assert.ok(overlay, `overlay exists in ${testcaseId}`);
+
+      let expectations = testcases[testcaseId];
+
+      Assert.ok(overlay.classList.contains("visible") == true,
+                `The expected visibility is correct in ${testcaseId}`);
+
+      Assert.ok(overlay.getAttribute("sizing") == expectations.sizing,
+                `The expected sizing is correct in ${testcaseId}`);
+
+      Assert.ok(overlay.getAttribute("notext") == expectations.notext,
+                `The expected notext is correct in ${testcaseId}`);
+    }
+  });
+});
--- a/browser/base/content/test/plugins/browser_CTP_resize.js
+++ b/browser/base/content/test/plugins/browser_CTP_resize.js
@@ -41,36 +41,36 @@ add_task(async function() {
 add_task(async function() {
   let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(popupNotification, "Test 2, Should have a click-to-play notification");
 
   await ContentTask.spawn(gTestBrowser, null, async function() {
     let doc = content.document;
     let plugin = doc.getElementById("test");
     let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-    Assert.ok(!overlay || overlay.classList.contains("minimal"),
-      "Test 2, overlay should be hidden.");
+    Assert.ok(!overlay || overlay.getAttribute("sizing") == "blank",
+      "Test 2, overlay should be blank.");
   });
 });
 
 add_task(async function() {
   await ContentTask.spawn(gTestBrowser, {}, async function() {
     let plugin = content.document.getElementById("test");
     plugin.style.width = "300px";
   });
 
   // Work around for delayed PluginBindingAttached
   await promiseUpdatePluginBindings(gTestBrowser);
 
   await ContentTask.spawn(gTestBrowser, null, async function() {
     let doc = content.document;
     let plugin = doc.getElementById("test");
     let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-    Assert.ok(!overlay || overlay.classList.contains("minimal"),
-      "Test 3, overlay should be hidden.");
+    Assert.ok(!overlay || overlay.getAttribute("sizing") == "blank",
+      "Test 3, overlay should be blank.");
   });
 });
 
 
 add_task(async function() {
   await ContentTask.spawn(gTestBrowser, {}, async function() {
     let plugin = content.document.getElementById("test");
     plugin.style.height = "300px";
@@ -79,17 +79,17 @@ add_task(async function() {
   await ContentTask.spawn(gTestBrowser, {}, async function() {
     content.document.getElementById("test").clientTop;
   });
 
   await ContentTask.spawn(gTestBrowser, null, async function() {
     let doc = content.document;
     let plugin = doc.getElementById("test");
     let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-    Assert.ok(overlay && overlay.classList.contains("visible"),
+    Assert.ok(overlay && overlay.getAttribute("sizing") != "blank",
       "Test 4, overlay should be visible.");
   });
 });
 
 add_task(async function() {
   await ContentTask.spawn(gTestBrowser, {}, async function() {
     let plugin = content.document.getElementById("test");
     plugin.style.width = "10px";
@@ -99,18 +99,18 @@ add_task(async function() {
   await ContentTask.spawn(gTestBrowser, {}, async function() {
     content.document.getElementById("test").clientTop;
   });
 
   await ContentTask.spawn(gTestBrowser, null, async function() {
     let doc = content.document;
     let plugin = doc.getElementById("test");
     let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-    Assert.ok(!overlay || overlay.classList.contains("minimal"),
-      "Test 5, overlay should be hidden.");
+    Assert.ok(!overlay || overlay.getAttribute("sizing") == "blank",
+      "Test 5, overlay should be blank.");
   });
 });
 
 add_task(async function() {
   await ContentTask.spawn(gTestBrowser, {}, async function() {
     let plugin = content.document.getElementById("test");
     plugin.style.height = "300px";
     plugin.style.width = "300px";
@@ -119,12 +119,12 @@ add_task(async function() {
   await ContentTask.spawn(gTestBrowser, {}, async function() {
     content.document.getElementById("test").clientTop;
   });
 
   await ContentTask.spawn(gTestBrowser, null, async function() {
     let doc = content.document;
     let plugin = doc.getElementById("test");
     let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-    Assert.ok(overlay && overlay.classList.contains("visible"),
+    Assert.ok(overlay && overlay.getAttribute("sizing") != "blank",
       "Test 6, overlay should be visible.");
   });
 });
--- a/browser/base/content/test/plugins/browser_CTP_shouldShowOverlay.js
+++ b/browser/base/content/test/plugins/browser_CTP_shouldShowOverlay.js
@@ -2,16 +2,22 @@
  * 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/. */
 
 "use strict";
 
 /* This test ensures that the click-to-play "Activate Plugin" overlay
  * is shown when expected.
  * All testcases are in the plugin_shouldShowOverlay.html file.
+ *
+ * Note: Technically, the overlay is *always* shown. When this test was
+ * originally written, the meaning of "shown" was "shown with the contents",
+ * as opposed to "shown as blank". The behavior hasn't changed, but the naming
+ * has: now, a "shown as blank" overlay no longer receives a ".hidden" class.
+ * It receives a sizing="blank" attribute.
  */
 
 var rootDir = getRootDirectory(gTestPath);
 const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 
 var gTestBrowser = null;
 
 add_task(async function() {
@@ -44,13 +50,13 @@ add_task(async function() {
     for (let testcase of testcases) {
       let plugin = testcase.querySelector("object");
       Assert.ok(plugin, `plugin exists in ${testcase.id}`);
 
       let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
       Assert.ok(overlay, `overlay exists in ${testcase.id}`);
 
       let expectedVisibility = (testcase.getAttribute("shouldshow") == "true");
-      Assert.ok(overlay.classList.contains("visible") == expectedVisibility,
+      Assert.ok((overlay.getAttribute("sizing") != "blank") == expectedVisibility,
                 `The expected visibility is correct in ${testcase.id}`);
     }
   })
 });
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_overlay_styles.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+
+  <div id="testcase1" class="testcase">
+    <object width="10" height="10" type="application/x-test"></object>
+  </div>
+
+  <div id="testcase2" class="testcase">
+    <object width="40" height="40" type="application/x-test"></object>
+  </div>
+
+  <div id="testcase3" class="testcase">
+    <object width="100" height="70" type="application/x-test"></object>
+  </div>
+
+  <div id="testcase4" class="testcase">
+    <object width="200" height="200" type="application/x-test"></object>
+  </div>
+
+  <div id="testcase5" class="testcase">
+    <object width="300" height="300" type="application/x-test"></object>
+  </div>
+
+</body>
+</html>
--- a/browser/base/content/test/plugins/plugin_test.html
+++ b/browser/base/content/test/plugins/plugin_test.html
@@ -1,9 +1,9 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta charset="utf-8">
 </head>
 <body>
-<embed id="test" style="width: 200px; height: 200px" type="application/x-test">
+<embed id="test" style="width: 300px; height: 300px" type="application/x-test">
 </body>
 </html>
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -117,19 +117,16 @@ var whitelist = [
 
   // browser/extensions/pdfjs/content/web/viewer.js#7450
   {file: "resource://pdf.js/web/debugger.js"},
 
   // These are used in content processes. They are actually referenced.
   {file: "resource://shield-recipe-client-content/shield-content-frame.js"},
   {file: "resource://shield-recipe-client-content/shield-content-process.js"},
 
-  // New L10n API that is not yet used in production
-  {file: "chrome://global/content/l10n.js"},
-
   // Starting from here, files in the whitelist are bugs that need fixing.
   // Bug 1339424 (wontfix?)
   {file: "chrome://browser/locale/taskbar.properties",
    platforms: ["linux", "macosx"]},
   // Bug 1316187
   {file: "chrome://global/content/customizeToolbar.xul"},
   // Bug 1343837
   {file: "chrome://global/content/findUtils.js"},
--- a/browser/base/content/test/urlbar/browser_page_action_menu.js
+++ b/browser/base/content/test/urlbar/browser_page_action_menu.js
@@ -152,18 +152,18 @@ add_task(async function copyURLFromPanel
 
 add_task(async function copyURLFromURLBar() {
   // Open an actionable page so that the main page action button appears.  (It
   // does not appear on about:blank for example.)
   let url = "http://example.com/";
   await BrowserTestUtils.withNewTab(url, async () => {
     // Add action to URL bar.
     let action = PageActions._builtInActions.find(a => a.id == "copyURL");
-    action.shownInUrlbar = true;
-    registerCleanupFunction(() => action.shownInUrlbar = false);
+    action.pinnedToUrlbar = true;
+    registerCleanupFunction(() => action.pinnedToUrlbar = false);
 
     let copyURLButton =
       document.getElementById("pageAction-urlbar-copyURL");
     let feedbackShownPromise = promisePanelShown("pageActionFeedback");
     EventUtils.synthesizeMouseAtCenter(copyURLButton, {});
 
     await feedbackShownPromise;
     let panel = document.getElementById("pageActionFeedback");
@@ -539,17 +539,17 @@ add_task(async function sendToDevice_inU
 
     let cleanUp = () => {
       sandbox.restore();
     };
     registerCleanupFunction(cleanUp);
 
     // Add Send to Device to the urlbar.
     let action = PageActions.actionForID("sendToDevice");
-    action.shownInUrlbar = true;
+    action.pinnedToUrlbar = true;
 
     // Click it to open its panel.
     let urlbarButton = document.getElementById(
       BrowserPageActions.urlbarButtonNodeIDForActionID(action.id)
     );
     Assert.ok(!urlbarButton.disabled);
     let panelPromise =
       promisePanelShown(BrowserPageActions._activatedActionPanelID);
@@ -617,17 +617,17 @@ add_task(async function sendToDevice_inU
     Assert.equal(
       BrowserPageActionFeedback.panelNode.anchorNode.id,
       urlbarButton.id
     );
     info("Waiting for the Sent! notification panel to close");
     await promisePanelHidden(BrowserPageActionFeedback.panelNode.id);
 
     // Remove Send to Device from the urlbar.
-    action.shownInUrlbar = false;
+    action.pinnedToUrlbar = false;
 
     cleanUp();
   });
 });
 
 add_task(async function contextMenu() {
   // Open an actionable page so that the main page action button appears.
   let url = "http://example.com/";
@@ -637,24 +637,24 @@ add_task(async function contextMenu() {
     let bookmarkButton = document.getElementById("pageAction-panel-bookmark");
     let contextMenuPromise = promisePanelShown("pageActionContextMenu");
     EventUtils.synthesizeMouseAtCenter(bookmarkButton, {
       type: "contextmenu",
       button: 2,
     });
     await contextMenuPromise;
 
-    // The context menu should show "Remove from Address Bar".  Click it.
-    let contextMenuNode = document.getElementById("pageActionContextMenu");
-    Assert.equal(contextMenuNode.childNodes.length, 1,
+    // The context menu should show the "remove" item.  Click it.
+    let menuItems = collectContextMenuItems();
+    Assert.equal(menuItems.length, 1,
                  "Context menu has one child");
-    Assert.equal(contextMenuNode.childNodes[0].label, "Remove from Address Bar",
+    Assert.equal(menuItems[0].label, "Remove from Address Bar",
                  "Context menu is in the 'remove' state");
     contextMenuPromise = promisePanelHidden("pageActionContextMenu");
-    EventUtils.synthesizeMouseAtCenter(contextMenuNode.childNodes[0], {});
+    EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
     await contextMenuPromise;
 
     // The action should be removed from the urlbar.  In this case, the bookmark
     // star, the node in the urlbar should be hidden.
     let starButtonBox = document.getElementById("star-button-box");
     await BrowserTestUtils.waitForCondition(() => {
       return starButtonBox.hidden;
     }, "Waiting for star button to become hidden");
@@ -663,67 +663,71 @@ add_task(async function contextMenu() {
     // panel remains open.)
     contextMenuPromise = promisePanelShown("pageActionContextMenu");
     EventUtils.synthesizeMouseAtCenter(bookmarkButton, {
       type: "contextmenu",
       button: 2,
     });
     await contextMenuPromise;
 
-    // The context menu should show "Add to Address Bar".  Click it.
-    Assert.equal(contextMenuNode.childNodes.length, 1,
+    // The context menu should show the "add" item.  Click it.
+    menuItems = collectContextMenuItems();
+    Assert.equal(menuItems.length, 1,
                  "Context menu has one child");
-    Assert.equal(contextMenuNode.childNodes[0].label, "Add to Address Bar",
+    Assert.equal(menuItems[0].label, "Add to Address Bar",
                  "Context menu is in the 'add' state");
     contextMenuPromise = promisePanelHidden("pageActionContextMenu");
-    EventUtils.synthesizeMouseAtCenter(contextMenuNode.childNodes[0], {});
+    EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
     await contextMenuPromise;
 
     // The action should be added to the urlbar.
     await BrowserTestUtils.waitForCondition(() => {
       return !starButtonBox.hidden;
     }, "Waiting for star button to become unhidden");
 
     // Open the context menu on the bookmark star in the urlbar.
     contextMenuPromise = promisePanelShown("pageActionContextMenu");
     EventUtils.synthesizeMouseAtCenter(starButtonBox, {
       type: "contextmenu",
       button: 2,
     });
     await contextMenuPromise;
 
-    // The context menu should show "Remove from Address Bar".  Click it.
-    Assert.equal(contextMenuNode.childNodes.length, 1,
+    // The context menu should show the "remove" item.  Click it.
+    menuItems = collectContextMenuItems();
+    Assert.equal(menuItems.length, 1,
                  "Context menu has one child");
-    Assert.equal(contextMenuNode.childNodes[0].label, "Remove from Address Bar",
+    Assert.equal(menuItems[0].label, "Remove from Address Bar",
                  "Context menu is in the 'remove' state");
     contextMenuPromise = promisePanelHidden("pageActionContextMenu");
-    EventUtils.synthesizeMouseAtCenter(contextMenuNode.childNodes[0], {});
+    EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
     await contextMenuPromise;
 
     // The action should be removed from the urlbar.
     await BrowserTestUtils.waitForCondition(() => {
       return starButtonBox.hidden;
     }, "Waiting for star button to become hidden");
 
     // Finally, add the bookmark star back to the urlbar so that other tests
     // that rely on it are OK.
     await promisePageActionPanelOpen();
     contextMenuPromise = promisePanelShown("pageActionContextMenu");
     EventUtils.synthesizeMouseAtCenter(bookmarkButton, {
       type: "contextmenu",
       button: 2,
     });
     await contextMenuPromise;
-    Assert.equal(contextMenuNode.childNodes.length, 1,
+
+    menuItems = collectContextMenuItems();
+    Assert.equal(menuItems.length, 1,
                  "Context menu has one child");
-    Assert.equal(contextMenuNode.childNodes[0].label, "Add to Address Bar",
+    Assert.equal(menuItems[0].label, "Add to Address Bar",
                  "Context menu is in the 'add' state");
     contextMenuPromise = promisePanelHidden("pageActionContextMenu");
-    EventUtils.synthesizeMouseAtCenter(contextMenuNode.childNodes[0], {});
+    EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
     await contextMenuPromise;
     await BrowserTestUtils.waitForCondition(() => {
       return !starButtonBox.hidden;
     }, "Waiting for star button to become unhidden");
   });
 
   // urlbar tests that run after this one can break if the mouse is left over
   // the area where the urlbar popup appears, which seems to happen due to the
@@ -770,8 +774,15 @@ function checkSendToDeviceItems(expected
         if (name == "label") {
           attrVal = attrVal.normalize("NFKC"); // There's a bug with …
         }
         Assert.equal(attrVal, expected.attrs[name]);
       }
     }
   }
 }
+
+function collectContextMenuItems() {
+  let contextMenu = document.getElementById("pageActionContextMenu");
+  return Array.filter(contextMenu.childNodes, node => {
+    return window.getComputedStyle(node).visibility == "visible";
+  });
+}
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -61,19 +61,20 @@ this.pageAction = class extends Extensio
 
     this.defaults.icon = await StartupCache.get(
       extension, ["pageAction", "default_icon"],
       () => IconDetails.normalize({path: options.default_icon}, extension));
 
     if (!this.browserPageAction) {
       this.browserPageAction = PageActions.addAction(new PageActions.Action({
         id: widgetId,
+        extensionID: extension.id,
         title: this.defaults.title,
         iconURL: this.getIconData(this.defaults.icon),
-        shownInUrlbar: true,
+        pinnedToUrlbar: true,
         disabled: true,
         onCommand: (event, buttonNode) => {
           this.handleClick(event.target.ownerGlobal);
         },
         onBeforePlacedInWindow: browserWindow => {
           if (this.extension.hasPermission("menus") ||
               this.extension.hasPermission("contextMenus")) {
             browserWindow.document.addEventListener("popupshowing", this);
--- a/browser/components/extensions/test/xpcshell/test_ext_pageAction_shutdown.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_pageAction_shutdown.js
@@ -19,17 +19,17 @@ createAppInfo("xpcshell@tests.mozilla.or
 // This is copied and pasted from ExtensionPopups.jsm.  It's used as the
 // PageActions action ID.  See ext-pageAction.js.
 function makeWidgetId(id) {
   id = id.toLowerCase();
   // FIXME: This allows for collisions.
   return id.replace(/[^a-z0-9_-]/g, "_");
 }
 
-// Tests that the shownInUrlbar property of the PageActions.Action object
+// Tests that the pinnedToUrlbar property of the PageActions.Action object
 // backing the extension's page action persists across app restarts.
 add_task(async function testAppShutdown() {
   let extensionData = {
     useAddonManager: "permanent",
     manifest: {
       page_action: {
         default_title: "test_ext_pageAction_shutdown.js",
         browser_style: false,
@@ -39,41 +39,41 @@ add_task(async function testAppShutdown(
 
   // Simulate starting up the app.
   PageActions.init();
   await promiseStartupManager();
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   await extension.startup();
 
-  // Get the PageAction.Action object.  Its shownInUrlbar should have been
+  // Get the PageAction.Action object.  Its pinnedToUrlbar should have been
   // initialized to true in ext-pageAction.js, when it's created.
   let actionID = makeWidgetId(extension.id);
   let action = PageActions.actionForID(actionID);
-  Assert.equal(action.shownInUrlbar, true);
+  Assert.equal(action.pinnedToUrlbar, true);
 
   // Simulate restarting the app without first unloading the extension.
   await promiseShutdownManager();
   PageActions._reset();
   await promiseStartupManager();
   await extension.awaitStartup();
 
-  // Get the action.  Its shownInUrlbar should remain true.
+  // Get the action.  Its pinnedToUrlbar should remain true.
   action = PageActions.actionForID(actionID);
-  Assert.equal(action.shownInUrlbar, true);
+  Assert.equal(action.pinnedToUrlbar, true);
 
-  // Now set its shownInUrlbar to false.
-  action.shownInUrlbar = false;
+  // Now set its pinnedToUrlbar to false.
+  action.pinnedToUrlbar = false;
 
   // Simulate restarting the app again without first unloading the extension.
   await promiseShutdownManager();
   PageActions._reset();
   await promiseStartupManager();
   await extension.awaitStartup();
 
-  // Get the action.  Its shownInUrlbar should remain false.
+  // Get the action.  Its pinnedToUrlbar should remain false.
   action = PageActions.actionForID(actionID);
-  Assert.equal(action.shownInUrlbar, false);
+  Assert.equal(action.pinnedToUrlbar, false);
 
   // Now unload the extension and quit the app.
   await extension.unload();
   await promiseShutdownManager();
 });
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -1181,22 +1181,38 @@ var gMainPane = {
     }
   },
 
   buildContentProcessCountMenuList() {
     if (Services.appinfo.browserTabsRemoteAutostart) {
       let processCountPref = document.getElementById("dom.ipc.processCount");
       let defaultProcessCount = processCountPref.defaultValue;
       let bundlePreferences = document.getElementById("bundlePreferences");
-      let label = bundlePreferences.getFormattedString("defaultContentProcessCount",
-        [defaultProcessCount]);
+
       let contentProcessCount =
         document.querySelector(`#contentProcessCount > menupopup >
                                 menuitem[value="${defaultProcessCount}"]`);
-      contentProcessCount.label = label;
+
+      // New localization API experiment (October 2017).
+      // See bug 1402061 for details.
+      //
+      // The `intl.l10n.fluent.disabled` pref can be used
+      // to opt-out of the experiment in case of any
+      // unforseen problems. The legacy API will then
+      // be used.
+      if (Services.prefs.getBoolPref("intl.l10n.fluent.disabled", false)) {
+        let label = bundlePreferences.getFormattedString("defaultContentProcessCount",
+          [defaultProcessCount]);
+        contentProcessCount.label = label;
+      } else {
+        document.l10n.setAttributes(
+          contentProcessCount,
+          "default-content-process-count",
+          { num: defaultProcessCount });
+      }
 
       document.getElementById("limitContentProcess").disabled = false;
       document.getElementById("contentProcessCount").disabled = false;
       document.getElementById("contentProcessCountEnabledDescription").hidden = false;
       document.getElementById("contentProcessCountDisabledDescription").hidden = true;
     } else {
       document.getElementById("limitContentProcess").disabled = true;
       document.getElementById("contentProcessCount").disabled = true;
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -1,14 +1,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/.
 
 <!-- General panel -->
 
+<link rel="localization" href="browser/preferences/main.ftl"/>
+<script type="text/javascript" src="chrome://global/content/l10n.js"></script>
+
 <script type="application/javascript"
         src="chrome://browser/content/preferences/in-content/main.js"/>
 
 #ifdef MOZ_UPDATER
   <script type="application/javascript" src="chrome://browser/content/aboutDialog-appUpdater.js"/>
 #endif
 
 <script type="application/javascript"
--- a/browser/components/preferences/in-content/preferences.xul
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -171,17 +171,19 @@
           <label class="category-name" flex="1">&paneSync1.title;</label>
         </richlistitem>
       </richlistbox>
 
       <spacer flex="1"/>
 
       <hbox class="help-button" pack="center">
         <label class="text-link">
-          <image class="help-icon"/><label class="help-label" flex="1">&helpButton2.label;</label>
+          <hbox align="center">
+            <image class="help-icon"/><label class="help-label" flex="1">&helpButton2.label;</label>
+          </hbox>
         </label>
       </hbox>
     </vbox>
 
     <keyset>
       <key key="&focusSearch1.key;" modifiers="accel" id="focusSearch1" oncommand="gSearchResultsPane.searchInput.focus();"/>
     </keyset>
 
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -80,8 +80,9 @@ skip-if = e10s
 support-files =
   subdialog.xul
   subdialog2.xul
 [browser_telemetry.js]
 # Skip this test on Android as FHR and Telemetry are separate systems there.
 skip-if = !healthreport || !telemetry || (os == 'linux' && debug) || (os == 'android')
 [browser_containers_name_input.js]
 run-if = nightly_build # Containers is enabled only on Nightly
+[browser_fluent.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_fluent.js
@@ -0,0 +1,48 @@
+/* eslint-disable mozilla/no-cpows-in-tests */
+
+function whenMainPaneLoadedFinished() {
+  return new Promise(function(resolve, reject) {
+    const topic = "main-pane-loaded";
+    Services.obs.addObserver(function observer(aSubject) {
+      Services.obs.removeObserver(observer, topic);
+      resolve();
+    }, topic);
+  });
+}
+
+// Temporary test for an experimental new localization API.
+// See bug 1402069 for details.
+add_task(async function() {
+  // The string is used only when `browserTabsRemoteAutostart` is true
+  if (!Services.appinfo.browserTabsRemoteAutostart) {
+    ok(true, "fake test to avoid harness complaining");
+    return;
+  }
+
+  await Promise.all([
+    openPreferencesViaOpenPreferencesAPI("general", {leaveOpen: true}),
+    whenMainPaneLoadedFinished(),
+  ]);
+
+  let doc = gBrowser.contentDocument;
+  await doc.l10n.ready;
+
+  let processCountPref = doc.getElementById("dom.ipc.processCount");
+  let defaultProcessCount = processCountPref.defaultValue;
+
+  let [ msg ] = await doc.l10n.formatMessages([
+    ["default-content-process-count", { num: defaultProcessCount }]
+  ]);
+
+  let elem = doc.querySelector(
+    `#contentProcessCount > menupopup > menuitem[value="${defaultProcessCount}"]`);
+
+  Assert.deepEqual(msg, {
+    value: null,
+    attrs: [
+      ["label", elem.getAttribute("label")]
+    ]
+  });
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
--- a/browser/components/uitour/test/browser_UITour_availableTargets.js
+++ b/browser/components/uitour/test/browser_UITour_availableTargets.js
@@ -131,27 +131,27 @@ async function assertTargetNode(targetNa
   let target = await UITour.getTarget(window, targetName);
   is(target.node.id, expectedNodeId, "UITour should get the right target node");
 }
 
 var pageActionsHelper = {
   setActionsUrlbarState(inUrlbar) {
     this._originalStates = [];
     PageActions._actionsByID.forEach(action => {
-      this._originalStates.push([ action, action.shownInUrlbar ]);
-      action.shownInUrlbar = inUrlbar;
+      this._originalStates.push([ action, action.pinnedToUrlbar ]);
+      action.pinnedToUrlbar = inUrlbar;
     });
   },
 
   restoreActionsUrlbarState() {
     if (!this._originalStates) {
       return;
     }
     for (let [ action, originalState] of this._originalStates) {
-      action.shownInUrlbar = originalState;
+      action.pinnedToUrlbar = originalState;
     }
     this._originalStates = null;
   }
 };
 
 function ensureScreenshotsEnabled() {
   SpecialPowers.pushPrefEnv({ set: [
     [ "extensions.screenshots.disabled", false ]
--- a/browser/extensions/formautofill/FormAutofillDoorhanger.jsm
+++ b/browser/extensions/formautofill/FormAutofillDoorhanger.jsm
@@ -41,17 +41,17 @@ const CONTENT = {
     message: formatStringFromName("saveAddressesMessage", [brandShortName], 1),
     anchor: {
       id: "autofill-address-notification-icon",
       URL: "chrome://formautofill/content/formfill-anchor.svg",
       tooltiptext: GetStringFromName("openAutofillMessagePanel"),
     },
     mainAction: {
       label: GetStringFromName(changeAutofillOptsKey),
-      accessKey: "C",
+      accessKey: GetStringFromName("changeAutofillOptionsAccessKey"),
       callbackState: "open-pref",
       disableHighlight: true,
     },
     options: {
       persistWhileVisible: true,
       popupIconURL: "chrome://formautofill/content/icon-address-save.svg",
       checkbox: {
         get checked() {
@@ -77,22 +77,22 @@ const CONTENT = {
     linkMessage: GetStringFromName(autofillOptsKey),
     anchor: {
       id: "autofill-address-notification-icon",
       URL: "chrome://formautofill/content/formfill-anchor.svg",
       tooltiptext: GetStringFromName("openAutofillMessagePanel"),
     },
     mainAction: {
       label: GetStringFromName("updateAddressLabel"),
-      accessKey: "U",
+      accessKey: GetStringFromName("updateAddressAccessKey"),
       callbackState: "update",
     },
     secondaryActions: [{
       label: GetStringFromName("createAddressLabel"),
-      accessKey: "C",
+      accessKey: GetStringFromName("createAddressAccessKey"),
       callbackState: "create",
     }],
     options: {
       persistWhileVisible: true,
       popupIconURL: "chrome://formautofill/content/icon-address-update.svg",
       hideClose: true,
     },
   },
@@ -102,26 +102,26 @@ const CONTENT = {
     linkMessage: GetStringFromName(autofillSecurityOptionsKey),
     anchor: {
       id: "autofill-credit-card-notification-icon",
       URL: "chrome://formautofill/content/formfill-anchor.svg",
       tooltiptext: GetStringFromName("openAutofillMessagePanel"),
     },
     mainAction: {
       label: GetStringFromName("saveCreditCardLabel"),
-      accessKey: "S",
+      accessKey: GetStringFromName("saveCreditCardAccessKey"),
       callbackState: "save",
     },
     secondaryActions: [{
       label: GetStringFromName("cancelCreditCardLabel"),
-      accessKey: "D",
+      accessKey: GetStringFromName("cancelCreditCardAccessKey"),
       callbackState: "cancel",
     }, {
       label: GetStringFromName("neverSaveCreditCardLabel"),
-      accessKey: "N",
+      accessKey: GetStringFromName("neverSaveCreditCardAccessKey"),
       callbackState: "disable",
     }],
     options: {
       persistWhileVisible: true,
       popupIconURL: "chrome://formautofill/content/icon-credit-card.svg",
       hideClose: true,
       checkbox: {
         get checked() {
@@ -155,22 +155,22 @@ const CONTENT = {
     linkMessage: GetStringFromName(autofillOptsKey),
     anchor: {
       id: "autofill-credit-card-notification-icon",
       URL: "chrome://formautofill/content/formfill-anchor.svg",
       tooltiptext: GetStringFromName("openAutofillMessagePanel"),
     },
     mainAction: {
       label: GetStringFromName("updateCreditCardLabel"),
-      accessKey: "U",
+      accessKey: GetStringFromName("updateCreditCardAccessKey"),
       callbackState: "update",
     },
     secondaryActions: [{
       label: GetStringFromName("createCreditCardLabel"),
-      accessKey: "C",
+      accessKey: GetStringFromName("createCreditCardAccessKey"),
       callbackState: "create",
     }],
     options: {
       persistWhileVisible: true,
       popupIconURL: "chrome://formautofill/content/icon-credit-card.svg",
       hideClose: true,
     },
   },
--- a/browser/extensions/formautofill/locales/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locales/en-US/formautofill.properties
@@ -12,39 +12,47 @@ autofillOptionsLinkOSX = Form Autofill P
 # LOCALIZATION NOTE (autofillSecurityOptionsLink, autofillSecurityOptionsLinkOSX): These strings are used
 # in the doorhanger for saving credit card info. The link leads users to Form Autofill browser preferences.
 autofillSecurityOptionsLink = Form Autofill & Security Options
 autofillSecurityOptionsLinkOSX = Form Autofill & Security Preferences
 # LOCALIZATION NOTE (changeAutofillOptions, changeAutofillOptionsOSX): These strings are used on the doorhanger
 # that notifies users that addresses are saved. The button leads users to Form Autofill browser preferences.
 changeAutofillOptions = Change Form Autofill Options
 changeAutofillOptionsOSX = Change Form Autofill Preferences
+changeAutofillOptionsAccessKey = C
 # LOCALIZATION NOTE (addressesSyncCheckbox): If Sync is enabled, this checkbox is displayed on the doorhanger
 # shown when saving addresses.
 addressesSyncCheckbox = Share addresses with synced devices
 # LOCALIZATION NOTE (creditCardsSyncCheckbox): If Sync is enabled and credit card sync is available,
 # this checkbox is displayed on the doorhanger shown when saving credit card.
 creditCardsSyncCheckbox = Share credit cards with synced devices
 # LOCALIZATION NOTE (updateAddressMessage, createAddressLabel, updateAddressLabel): Used on the doorhanger
 # when an address change is detected.
 updateAddressMessage = Would you like to update your address with this new information?
 createAddressLabel = Create New Address
+createAddressAccessKey = C
 updateAddressLabel = Update Address
+updateAddressAccessKey = U
 # LOCALIZATION NOTE (saveCreditCardMessage, saveCreditCardLabel, cancelCreditCardLabel, neverSaveCreditCardLabel):
 # Used on the doorhanger when users submit payment with credit card.
 # LOCALIZATION NOTE (saveCreditCardMessage): %S is brandShortName.
 saveCreditCardMessage = Would you like %S to save this credit card? (Security code will not be saved)
 saveCreditCardLabel = Save Credit Card
+saveCreditCardAccessKey = S
 cancelCreditCardLabel = Don’t Save
+cancelCreditCardAccessKey = D
 neverSaveCreditCardLabel = Never Save Credit Cards
+neverSaveCreditCardAccessKey = N
 # LOCALIZATION NOTE (updateCreditCardMessage, createCreditCardLabel, updateCreditCardLabel): Used on the doorhanger
 # when an credit card change is detected.
 updateCreditCardMessage = Would you like to update your credit card with this new information?
 createCreditCardLabel = Create New Credit Card
+createCreditCardAccessKey = C
 updateCreditCardLabel = Update Credit Card
+updateCreditCardAccessKey = U
 # LOCALIZATION NOTE (openAutofillMessagePanel): Tooltip label for Form Autofill doorhanger icon on address bar.
 openAutofillMessagePanel = Open Form Autofill message panel
 
 # LOCALIZATION NOTE (autocompleteFooterOption, autocompleteFooterOptionOSX): Used as a label for the button,
 # displayed at the bottom of the drop down suggestion, to open Form Autofill browser preferences.
 autocompleteFooterOption = Form Autofill Options
 autocompleteFooterOptionOSX = Form Autofill Preferences
 # LOCALIZATION NOTE (autocompleteFooterOptionShort, autocompleteFooterOptionOSXShort): Used as a label for the button,
--- a/browser/extensions/pocket/bootstrap.js
+++ b/browser/extensions/pocket/bootstrap.js
@@ -89,17 +89,17 @@ var PocketPageAction = {
 
   init() {
     let id = "pocket";
     this.pageAction = PageActions.actionForID(id);
     if (!this.pageAction) {
       this.pageAction = PageActions.addAction(new PageActions.Action({
         id,
         title: gPocketBundle.GetStringFromName("saveToPocketCmd.label"),
-        shownInUrlbar: true,
+        pinnedToUrlbar: true,
         wantsIframe: true,
         urlbarIDOverride: "pocket-button-box",
         anchorIDOverride: "pocket-button",
         _insertBeforeActionID: PageActions.ACTION_ID_BOOKMARK_SEPARATOR,
         _urlbarNodeInMarkup: true,
         onBeforePlacedInWindow(window) {
           let doc = window.document;
 
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -41,16 +41,17 @@
 @APPNAME@/Contents/PkgInfo
 @RESPATH@/firefox.icns
 @RESPATH@/document.icns
 @RESPATH@/@LPROJ_ROOT@.lproj/*
 #endif
 
 [@AB_CD@]
 @RESPATH@/dictionaries/*
+@RESPATH@/browser/localization/*
 #if defined(XP_WIN) || defined(XP_LINUX)
 @RESPATH@/fonts/*
 #endif
 @RESPATH@/hyphenation/*
 @RESPATH@/browser/@PREF_DIR@/firefox-l10n.js
 #ifdef HAVE_MAKENSISU
 @BINPATH@/uninstall/helper.exe
 #endif
--- a/browser/locales/all-locales
+++ b/browser/locales/all-locales
@@ -91,11 +91,12 @@ ta
 te
 th
 tl
 tr
 uk
 ur
 uz
 vi
+wo
 xh
 zh-CN
 zh-TW
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/browser/preferences/main.ftl
@@ -0,0 +1,4 @@
+// Variables:
+//   $num - default value of the `dom.ipc.processCount` pref.
+default-content-process-count
+    .label = { $num } (default)
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -956,16 +956,19 @@ you can use these alternative items. Oth
 <!ENTITY updateRestart.acceptButton.accesskey "R">
 <!ENTITY updateRestart.cancelButton.label "Not Now">
 <!ENTITY updateRestart.cancelButton.accesskey "N">
 <!ENTITY updateRestart.panelUI.label2 "Restart to update &brandShorterName;">
 
 <!ENTITY pageActionButton.tooltip "Page actions">
 <!ENTITY pageAction.addToUrlbar.label "Add to Address Bar">
 <!ENTITY pageAction.removeFromUrlbar.label "Remove from Address Bar">
+<!ENTITY pageAction.allowInUrlbar.label "Show in Address Bar">
+<!ENTITY pageAction.disallowInUrlbar.label "Don’t Show in Address Bar">
+<!ENTITY pageAction.manageExtension.label "Manage Extension…">
 
 <!ENTITY pageAction.sendTabToDevice.label "Send Tab to Device">
 <!ENTITY sendToDevice.syncNotReady.label "Syncing Devices…">
 
 <!ENTITY libraryButton.tooltip "View history, saved bookmarks, and more">
 
 <!-- LOCALIZATION NOTE: (accessibilityIndicator.tooltip): This is used to
      display a tooltip for accessibility indicator in toolbar/tabbar. It is also
--- a/browser/locales/en-US/chrome/browser/pageInfo.dtd
+++ b/browser/locales/en-US/chrome/browser/pageInfo.dtd
@@ -50,16 +50,17 @@
 <!ENTITY  feedSubscribe.accesskey "u">
 
 <!ENTITY  permTab               "Permissions">
 <!ENTITY  permTab.accesskey     "P">
 <!ENTITY  permUseDefault        "Use Default">
 <!ENTITY  permAskAlways         "Always ask">
 <!ENTITY  permAllow             "Allow">
 <!ENTITY  permAllowSession      "Allow for Session">
+<!ENTITY  permHide              "Hide">
 <!ENTITY  permBlock             "Block">
 <!ENTITY  permissionsFor        "Permissions for:">
 <!ENTITY  permPlugins           "Activate Plugins">
 
 <!ENTITY  securityTab           "Security">
 <!ENTITY  securityTab.accesskey "S">
 <!ENTITY  securityView.certView "View Certificate">
 <!ENTITY  securityView.accesskey "V">
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -2,16 +2,19 @@
 # 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/.
 
 # Note: This file should only contain locale entries. All
 # override and resource entries should go to browser/base/jar.mn to avoid
 # having to create the same entry for each locale.
 
+[localization] @AB_CD@.jar:
+  browser                                          (%browser/**/*.ftl)
+
 @AB_CD@.jar:
 % locale browser @AB_CD@ %locale/browser/
 *   locale/browser/bookmarks.html                  (generic/profile/bookmarks.html.in)
     locale/browser/aboutDialog.dtd                 (%chrome/browser/aboutDialog.dtd)
     locale/browser/aboutPrivateBrowsing.dtd        (%chrome/browser/aboutPrivateBrowsing.dtd)
     locale/browser/aboutPrivateBrowsing.properties (%chrome/browser/aboutPrivateBrowsing.properties)
     locale/browser/aboutRobots.dtd                 (%chrome/browser/aboutRobots.dtd)
     locale/browser/aboutHome.dtd                   (%chrome/browser/aboutHome.dtd)
--- a/browser/locales/l10n.toml
+++ b/browser/locales/l10n.toml
@@ -98,16 +98,17 @@ locales = [
     "te",
     "th",
     "tl",
     "tr",
     "uk",
     "ur",
     "uz",
     "vi",
+    "wo",
     "xh",
     "zh-CN",
     "zh-TW",
     ]
 
 [env]
     l = "{l10n_base}/{locale}/"
 
--- a/browser/modules/PageActions.jsm
+++ b/browser/modules/PageActions.jsm
@@ -114,25 +114,30 @@ this.PageActions = {
   /**
    * The list of non-built-in actions.  Not live.  (array of Action objects)
    */
   get nonBuiltInActions() {
     return this._nonBuiltInActions.slice();
   },
 
   /**
-   * The list of actions in the urlbar, sorted in the order in which they should
-   * appear there.  Not live.  (array of Action objects)
+   * The list of actions currently in the urlbar, sorted in the order in which
+   * they appear.  Not live.
+   *
+   * @param  browserWindow (DOM window, required)
+   *         This window's actions will be returned.
+   * @return (array of PageAction.Action objects) The actions currently in the
+   *         given window's urlbar.
    */
-  get actionsInUrlbar() {
+  actionsInUrlbar(browserWindow) {
     // Remember that IDs in idsInUrlbar may belong to actions that aren't
     // currently registered.
     return this._persistedActions.idsInUrlbar.reduce((actions, id) => {
       let action = this.actionForID(id);
-      if (action) {
+      if (action && action.shouldShowInUrlbar(browserWindow)) {
         actions.push(action);
       }
       return actions;
     }, []);
   },
 
   /**
    * Gets an action.
@@ -223,40 +228,39 @@ this.PageActions = {
       // Keep this list sorted by title.
       let index = BinarySearch.insertionIndexOf((a1, a2) => {
         return a1.getTitle().localeCompare(a2.getTitle());
       }, this._nonBuiltInActions, action);
       this._nonBuiltInActions.splice(index, 0, action);
     }
 
     if (this._persistedActions.ids.includes(action.id)) {
-      // The action has been seen before.  Override its shownInUrlbar value
+      // The action has been seen before.  Override its pinnedToUrlbar value
       // with the persisted value.  Set the private version of that property
-      // so that onActionToggledShownInUrlbar isn't called, which happens when
+      // so that onActionToggledPinnedToUrlbar isn't called, which happens when
       // the public version is set.
-      action._shownInUrlbar =
+      action._pinnedToUrlbar =
         this._persistedActions.idsInUrlbar.includes(action.id);
     } else {
       // The action is new.  Store it in the persisted actions.
       this._persistedActions.ids.push(action.id);
-      this._updateIDsInUrlbarForAction(action);
+      this._updateIDsPinnedToUrlbarForAction(action);
     }
   },
 
-  _updateIDsInUrlbarForAction(action) {
+  _updateIDsPinnedToUrlbarForAction(action) {
     let index = this._persistedActions.idsInUrlbar.indexOf(action.id);
-    if (action.shownInUrlbar) {
+    if (action.pinnedToUrlbar) {
       if (index < 0) {
-        let nextID = this.nextActionIDInUrlbar(action.id);
-        let nextIndex =
-          nextID ? this._persistedActions.idsInUrlbar.indexOf(nextID) : -1;
-        if (nextIndex < 0) {
-          nextIndex = this._persistedActions.idsInUrlbar.length;
+        index = action.id == ACTION_ID_BOOKMARK ? -1 :
+                this._persistedActions.idsInUrlbar.indexOf(ACTION_ID_BOOKMARK);
+        if (index < 0) {
+          index = this._persistedActions.idsInUrlbar.length;
         }
-        this._persistedActions.idsInUrlbar.splice(nextIndex, 0, action.id);
+        this._persistedActions.idsInUrlbar.splice(index, 0, action.id);
       }
     } else if (index >= 0) {
       this._persistedActions.idsInUrlbar.splice(index, 1);
     }
     this._storePersistedActions();
   },
 
   // These keep track of currently registered actions.
@@ -268,23 +272,23 @@ this.PageActions = {
    * Returns the ID of the action before which the given action should be
    * inserted in the urlbar.
    *
    * @param  action (Action object, required)
    *         The action you're inserting.
    * @return The ID of the reference action, or null if your action should be
    *         appended.
    */
-  nextActionIDInUrlbar(action) {
+  nextActionIDInUrlbar(browserWindow, action) {
     // Actions in the urlbar are always inserted before the bookmark action,
     // which always comes last if it's present.
     if (action.id == ACTION_ID_BOOKMARK) {
       return null;
     }
-    let id = this._nextActionID(action, this.actionsInUrlbar);
+    let id = this._nextActionID(action, this.actionsInUrlbar(browserWindow));
     return id || ACTION_ID_BOOKMARK;
   },
 
   /**
    * Returns the ID of the action before which the given action should be
    * inserted in the panel.
    *
    * @param  action (Action object, required)
@@ -349,44 +353,42 @@ this.PageActions = {
     }
 
     for (let bpa of allBrowserPageActions()) {
       bpa.removeAction(action);
     }
   },
 
   /**
-   * Call this when an action's shownInUrlbar property changes.
+   * Call this when an action's pinnedToUrlbar property changes.
    *
    * @param  action (Action object, required)
-   *         The action whose shownInUrlbar property changed.
+   *         The action whose pinnedToUrlbar property changed.
    */
-  onActionToggledShownInUrlbar(action) {
+  onActionToggledPinnedToUrlbar(action) {
     if (!this.actionForID(action.id)) {
       // This may be called before the action has been added.
       return;
     }
-    this._updateIDsInUrlbarForAction(action);
+    this._updateIDsPinnedToUrlbarForAction(action);
     for (let bpa of allBrowserPageActions()) {
       bpa.placeActionInUrlbar(action);
     }
   },
 
   logTelemetry(type, action, node = null) {
-    const kAllowedLabels = ["pocket", "screenshots", "webcompat"].concat(
-      gBuiltInActions.filter(a => !a.__isSeparator).map(a => a.id)
-    );
-
     if (type == "used") {
-      type = (node && node.closest("#urlbar-container")) ? "urlbar_used" : "panel_used";
+      type =
+        node && node.closest("#urlbar-container") ? "urlbar_used" :
+        "panel_used";
     }
     let histogramID = "FX_PAGE_ACTION_" + type.toUpperCase();
     try {
       let histogram = Services.telemetry.getHistogramById(histogramID);
-      if (kAllowedLabels.includes(action.labelForHistogram)) {
+      if (action._isBuiltIn) {
         histogram.add(action.labelForHistogram);
       } else {
         histogram.add("other");
       }
     } catch (ex) {
       Cu.reportError(ex);
     }
   },
@@ -487,16 +489,18 @@ this.PageActions = {
  * @param title (string, required)
  *        The action's title.
  * @param anchorIDOverride (string, optional)
  *        Pass a string to override the node to which the action's activated-
  *        action panel is anchored.
  * @param disabled (bool, optional)
  *        Pass true to cause the action to be disabled initially in all browser
  *        windows.  False by default.
+ * @param extensionID (string, optional)
+ *        If the action lives in an extension, pass its ID.
  * @param iconURL (string or object, optional)
  *        The URL string of the action's icon.  Usually you want to specify an
  *        icon in CSS, but this option is useful if that would be a pain for
  *        some reason.  You can also pass an object that maps pixel sizes to
  *        URLs, like { 16: url16, 32: url32 }.  The best size for the user's
  *        screen will be used.
  * @param nodeAttributes (object, optional)
  *        An object of name-value pairs.  Each pair will be added as an
@@ -544,19 +548,19 @@ this.PageActions = {
  * @param onRemovedFromWindow (function, optional)
  *        Called after the action is removed from a browser window:
  *        onRemovedFromWindow(browserWindow)
  *        * browserWindow: The browser window that the action was removed from.
  * @param onShowingInPanel (function, optional)
  *        Called when a browser window's page action panel is showing:
  *        onShowingInPanel(buttonNode)
  *        * buttonNode: The action's node in the page action panel.
- * @param shownInUrlbar (bool, optional)
- *        Pass true to show the action in the urlbar, false otherwise.  False by
- *        default.
+ * @param pinnedToUrlbar (bool, optional)
+ *        Pass true to pin the action to the urlbar.  An action is shown in the
+ *        urlbar if it's pinned and not disabled.  False by default.
  * @param subview (object, optional)
  *        An options object suitable for passing to the Subview constructor, if
  *        you'd like the action to have a subview.  See the subview constructor
  *        for info on this object's properties.
  * @param tooltip (string, optional)
  *        The action's button tooltip text.
  * @param urlbarIDOverride (string, optional)
  *        Usually the ID of the action's button in the urlbar will be generated
@@ -567,30 +571,31 @@ this.PageActions = {
  *        clicked.
  */
 function Action(options) {
   setProperties(this, options, {
     id: true,
     title: !options._isSeparator,
     anchorIDOverride: false,
     disabled: false,
+    extensionID: false,
     iconURL: false,
     labelForHistogram: false,
     nodeAttributes: false,
     onBeforePlacedInWindow: false,
     onCommand: false,
     onIframeHiding: false,
     onIframeHidden: false,
     onIframeShown: false,
     onLocationChange: false,
     onPlacedInPanel: false,
     onPlacedInUrlbar: false,
     onRemovedFromWindow: false,
     onShowingInPanel: false,
-    shownInUrlbar: false,
+    pinnedToUrlbar: false,
     subview: false,
     tooltip: false,
     urlbarIDOverride: false,
     wantsIframe: false,
 
     // private
 
     // (string, optional)
@@ -613,42 +618,50 @@ function Action(options) {
   });
   if (this._subview) {
     this._subview = new Subview(options.subview);
   }
 }
 
 Action.prototype = {
   /**
+   * The ID of the action's parent extension (string, nullable)
+   */
+  get extensionID() {
+    return this._extensionID;
+  },
+
+  /**
    * The action's ID (string, nonnull)
    */
   get id() {
     return this._id;
   },
 
   /**
    * Attribute name => value mapping to set on nodes created for this action
    * (object, nullable)
    */
   get nodeAttributes() {
     return this._nodeAttributes;
   },
 
   /**
-   * True if the action is shown in the urlbar (bool, nonnull)
+   * True if the action is pinned to the urlbar.  The action is shown in the
+   * urlbar if it's pinned and not disabled.  (bool, nonnull)
    */
-  get shownInUrlbar() {
-    return this._shownInUrlbar || false;
+  get pinnedToUrlbar() {
+    return this._pinnedToUrlbar || false;
   },
-  set shownInUrlbar(shown) {
-    if (this.shownInUrlbar != shown) {
-      this._shownInUrlbar = shown;
-      PageActions.onActionToggledShownInUrlbar(this);
+  set pinnedToUrlbar(shown) {
+    if (this.pinnedToUrlbar != shown) {
+      this._pinnedToUrlbar = shown;
+      PageActions.onActionToggledPinnedToUrlbar(this);
     }
-    return this.shownInUrlbar;
+    return this.pinnedToUrlbar;
   },
 
   /**
    * The action's disabled state (bool, nonnull)
    */
   getDisabled(browserWindow = null) {
     return !!this._getProperty("disabled", browserWindow);
   },
@@ -967,17 +980,38 @@ Action.prototype = {
    *
    * PageActions will remember the action's urlbar placement, if any, after this
    * method is called until app shutdown.  If the action is not added again
    * before shutdown, then PageActions will discard the placement, and the next
    * time the action is added, its placement will be reset.
    */
   remove() {
     PageActions.onActionRemoved(this);
-  }
+  },
+
+  /**
+   * Returns whether the action should be shown in a given window's urlbar.
+   *
+   * @param  browserWindow (DOM window, required)
+   *         The window.
+   * @return True if the action should be shown and false otherwise.  The action
+   *         should be shown if it's both pinned and not disabled.
+   */
+  shouldShowInUrlbar(browserWindow) {
+    return this.pinnedToUrlbar && !this.getDisabled(browserWindow);
+  },
+
+  get _isBuiltIn() {
+    let builtInIDs = [
+      "pocket",
+      "screenshots",
+      "webcompat-reporter-button",
+    ].concat(gBuiltInActions.filter(a => !a.__isSeparator).map(a => a.id));
+    return builtInIDs.includes(this.id);
+  },
 };
 
 this.PageActions.Action = Action;
 
 
 /**
  * A Subview represents a PanelUI panelview that your actions can show.
  * `options` is a required object with the following properties.
@@ -1141,17 +1175,17 @@ var gBuiltInActions = [
   // bookmark
   {
     id: ACTION_ID_BOOKMARK,
     urlbarIDOverride: "star-button-box",
     _urlbarNodeInMarkup: true,
     // The title is set in browser-pageActions.js by calling
     // BookmarkingUI.updateBookmarkPageMenuItem().
     title: "",
-    shownInUrlbar: true,
+    pinnedToUrlbar: true,
     nodeAttributes: {
       observes: "bookmarkThisPageBroadcaster",
     },
     onShowingInPanel(buttonNode) {
       browserPageActions(buttonNode).bookmark.onShowingInPanel(buttonNode);
     },
     onCommand(event, buttonNode) {
       browserPageActions(buttonNode).bookmark.onCommand(event, buttonNode);
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -25,19 +25,24 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 this.PluginContent = function(global) {
   this.init(global);
 };
 
 const FLASH_MIME_TYPE = "application/x-shockwave-flash";
 const REPLACEMENT_STYLE_SHEET = Services.io.newURI("chrome://pluginproblem/content/pluginReplaceBinding.css");
 
-const OVERLAY_DISPLAY_HIDDEN = 0;
-const OVERLAY_DISPLAY_VISIBLE = 1;
-const OVERLAY_DISPLAY_MINIMAL = 2;
+const OVERLAY_DISPLAY = {
+  HIDDEN: 0, // The overlay will be transparent
+  BLANK: 1, // The overlay will be just a grey box
+  TINY: 2, // The overlay with a 16x16 plugin icon
+  REDUCED: 3, // The overlay with a 32x32 plugin icon
+  NOTEXT: 4, // The overlay with a 48x48 plugin icon and the close button
+  FULL: 5, // The full overlay: 48x48 plugin icon, close button and label
+};
 
 PluginContent.prototype = {
   init(global) {
     this.global = global;
     // Need to hold onto the content window or else it'll get destroyed
     this.content = this.global.content;
     // Cache of plugin actions for the current page.
     this.pluginData = new Map();
@@ -284,54 +289,90 @@ PluginContent.prototype = {
              blocklistState,
            };
   },
 
   /**
    * Update the visibility of the plugin overlay.
    */
   setVisibility(plugin, overlay, overlayDisplayState) {
-    overlay.classList.toggle("visible", overlayDisplayState != OVERLAY_DISPLAY_HIDDEN);
-    overlay.classList.toggle("minimal", overlayDisplayState == OVERLAY_DISPLAY_MINIMAL)
-    if (overlayDisplayState == OVERLAY_DISPLAY_VISIBLE) {
+    overlay.classList.toggle("visible", overlayDisplayState != OVERLAY_DISPLAY.HIDDEN);
+    if (overlayDisplayState != OVERLAY_DISPLAY.HIDDEN) {
       overlay.removeAttribute("dismissed");
     }
   },
 
   /**
-   * Check whether the plugin should be visible on the page. A plugin should
-   * not be visible if the overlay is too big, or if any other page content
-   * overlays it.
+   * Adjust the style in which the overlay will be displayed. It might be adjusted
+   * based on its size, or if there's some other element covering all corners of
+   * the overlay.
    *
-   * This function will handle showing or hiding the overlay.
-   * @returns true if the plugin is invisible.
+   * This function will handle adjusting the style of the overlay, but will
+   * not handle hiding it. That is done by setVisibility with the return value
+   * from this function.
+   *
+   * @returns A value from OVERLAY_DISPLAY.
    */
-  computeOverlayDisplayState(plugin, overlay) {
+  computeAndAdjustOverlayDisplay(plugin, overlay) {
     let fallbackType = plugin.pluginFallbackType;
     if (plugin.pluginFallbackTypeOverride !== undefined) {
       fallbackType = plugin.pluginFallbackTypeOverride;
     }
     if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY_QUIET) {
-      return OVERLAY_DISPLAY_HIDDEN;
+      return OVERLAY_DISPLAY.HIDDEN;
     }
 
     // If the overlay size is 0, we haven't done layout yet. Presume that
     // plugins are visible until we know otherwise.
     if (overlay.scrollWidth == 0) {
-      return OVERLAY_DISPLAY_VISIBLE;
+      return OVERLAY_DISPLAY.FULL;
     }
 
+    let overlayDisplay = OVERLAY_DISPLAY.FULL;
+
     // Is the <object>'s size too small to hold what we want to show?
     let pluginRect = plugin.getBoundingClientRect();
+    let pluginWidth = Math.ceil(pluginRect.width);
+    let pluginHeight = Math.ceil(pluginRect.height);
+
+    // We must set the attributes while here inside this function in order
+    // for a possible re-style to occur, which will make the scrollWidth/Height
+    // checks below correct. Otherwise, we would be requesting e.g. a TINY
+    // overlay here, but the default styling would be used, and that would make
+    // it overflow, causing it to change to BLANK instead of remaining as TINY.
+
+    if (pluginWidth <= 32 || pluginHeight <= 32) {
+      overlay.setAttribute("sizing", "blank");
+      overlayDisplay = OVERLAY_DISPLAY.BLANK;
+    } else if (pluginWidth <= 80 || pluginHeight <= 60) {
+      overlayDisplay = OVERLAY_DISPLAY.TINY;
+      overlay.setAttribute("sizing", "tiny");
+      overlay.setAttribute("notext", "notext");
+    } else if (pluginWidth <= 120 || pluginHeight <= 80) {
+      overlayDisplay = OVERLAY_DISPLAY.REDUCED;
+      overlay.setAttribute("sizing", "reduced");
+      overlay.setAttribute("notext", "notext");
+    } else if (pluginWidth <= 240 || pluginHeight <= 160) {
+      overlayDisplay = OVERLAY_DISPLAY.NOTEXT;
+      overlay.removeAttribute("sizing");
+      overlay.setAttribute("notext", "notext");
+    } else {
+      overlayDisplay = OVERLAY_DISPLAY.FULL;
+      overlay.removeAttribute("sizing");
+      overlay.removeAttribute("notext");
+    }
+
+
     // XXX bug 446693. The text-shadow on the submitted-report text at
     //     the bottom causes scrollHeight to be larger than it should be.
-    let overflows = (overlay.scrollWidth > Math.ceil(pluginRect.width)) ||
-                    (overlay.scrollHeight - 5 > Math.ceil(pluginRect.height));
+    let overflows = (overlay.scrollWidth > pluginWidth) ||
+                    (overlay.scrollHeight - 5 > pluginHeight);
     if (overflows) {
-      return OVERLAY_DISPLAY_MINIMAL;
+      overlay.setAttribute("sizing", "blank");
+      return OVERLAY_DISPLAY.BLANK;
     }
 
     // Is the plugin covered up by other content so that it is not clickable?
     // Floating point can confuse .elementFromPoint, so inset just a bit
     let left = pluginRect.left + 2;
     let right = pluginRect.right - 2;
     let top = pluginRect.top + 2;
     let bottom = pluginRect.bottom - 2;
@@ -348,21 +389,22 @@ PluginContent.prototype = {
                            .getInterface(Ci.nsIDOMWindowUtils);
 
     for (let [x, y] of points) {
       if (x < 0 || y < 0) {
         continue;
       }
       let el = cwu.elementFromPoint(x, y, true, true);
       if (el === plugin) {
-        return OVERLAY_DISPLAY_VISIBLE;
+        return overlayDisplay;
       }
     }
 
-    return OVERLAY_DISPLAY_HIDDEN;
+    overlay.setAttribute("sizing", "blank");
+    return OVERLAY_DISPLAY.BLANK;
   },
 
   addLinkClickCallback(linkNode, callbackName /* callbackArgs...*/) {
     // XXX just doing (callback)(arg) was giving a same-origin error. bug?
     let self = this;
     let callbackArgs = Array.prototype.slice.call(arguments).slice(2);
     linkNode.addEventListener("click",
                               function(evt) {
@@ -471,17 +513,17 @@ PluginContent.prototype = {
       }
     }
 
     let plugin = event.target;
 
     if (eventType == "PluginPlaceholderReplaced") {
       plugin.removeAttribute("href");
       let overlay = this.getPluginUI(plugin, "main");
-      this.setVisibility(plugin, overlay, OVERLAY_DISPLAY_VISIBLE);
+      this.setVisibility(plugin, overlay, OVERLAY_DISPLAY.FULL);
       let inIDOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
                           .getService(Ci.inIDOMUtils);
       // Add psuedo class so our styling will take effect
       inIDOMUtils.addPseudoClassLock(plugin, "-moz-handler-clicktoplay");
       overlay.addEventListener("click", this, true);
       return;
     }
 
@@ -556,20 +598,20 @@ PluginContent.prototype = {
         break;
     }
 
     // Show the in-content UI if it's not too big. The crashed plugin handler already did this.
     let overlay = this.getPluginUI(plugin, "main");
     if (eventType != "PluginCrashed") {
       if (overlay != null) {
         this.setVisibility(plugin, overlay,
-                           this.computeOverlayDisplayState(plugin, overlay));
+                           this.computeAndAdjustOverlayDisplay(plugin, overlay));
         let resizeListener = () => {
           this.setVisibility(plugin, overlay,
-            this.computeOverlayDisplayState(plugin, overlay));
+            this.computeAndAdjustOverlayDisplay(plugin, overlay));
           this.updateNotificationUI();
         };
         plugin.addEventListener("overflow", resizeListener);
         plugin.addEventListener("underflow", resizeListener);
       }
     }
 
     let closeIcon = this.getPluginUI(plugin, "closeIcon");
@@ -898,19 +940,19 @@ PluginContent.prototype = {
           fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE &&
           fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) {
         continue;
       }
       let overlay = this.getPluginUI(plugin, "main");
       if (!overlay) {
         continue;
       }
-      let overlayDisplayState = this.computeOverlayDisplayState(plugin, overlay);
+      let overlayDisplayState = this.computeAndAdjustOverlayDisplay(plugin, overlay);
       this.setVisibility(plugin, overlay, overlayDisplayState);
-      if (overlayDisplayState == OVERLAY_DISPLAY_VISIBLE) {
+      if (overlayDisplayState > OVERLAY_DISPLAY.BLANK) {
         actions.delete(info.permissionString);
         if (actions.size == 0) {
           break;
         }
       }
     }
 
     // If there are any items remaining in `actions` now, they are hidden
@@ -1084,31 +1126,31 @@ PluginContent.prototype = {
     this.addLinkClickCallback(helpIcon, "openHelpPage");
 
     let crashText = this.getPluginUI(plugin, "crashedText");
     crashText.textContent = message;
 
     let link = this.getPluginUI(plugin, "reloadLink");
     this.addLinkClickCallback(link, "reloadPage");
 
-    let overlayDisplayState = this.computeOverlayDisplayState(plugin, overlay);
+    let overlayDisplayState = this.computeAndAdjustOverlayDisplay(plugin, overlay);
 
     // Is the <object>'s size too small to hold what we want to show?
-    if (overlayDisplayState != OVERLAY_DISPLAY_VISIBLE) {
+    if (overlayDisplayState != OVERLAY_DISPLAY.FULL) {
       // First try hiding the crash report submission UI.
       statusDiv.removeAttribute("status");
 
-      overlayDisplayState = this.computeOverlayDisplayState(plugin, overlay);
+      overlayDisplayState = this.computeAndAdjustOverlayDisplay(plugin, overlay);
     }
     this.setVisibility(plugin, overlay, overlayDisplayState);
 
     let doc = plugin.ownerDocument;
     let runID = plugin.runID;
 
-    if (overlayDisplayState == OVERLAY_DISPLAY_VISIBLE) {
+    if (overlayDisplayState == OVERLAY_DISPLAY.FULL) {
       // If a previous plugin on the page was too small and resulted in adding a
       // notification bar, then remove it because this plugin instance it big
       // enough to serve as in-content notification.
       this.hideNotificationBar("plugin-crashed");
       doc.mozNoPluginCrashedNotification = true;
 
       // Notify others that the crash reporter UI is now ready.
       // Currently, this event is only used by tests.
--- a/browser/modules/test/browser/browser_PageActions.js
+++ b/browser/modules/test/browser/browser_PageActions.js
@@ -1,9 +1,8 @@
-/* eslint-disable mozilla/no-arbitrary-setTimeout */
 "use strict";
 
 // This is a test for PageActions.jsm, specifically the generalized parts that
 // add and remove page actions and toggle them in the urlbar.  This does not
 // test the built-in page actions; browser_page_action_menu.js does that.
 
 // Initialization.  Must run first.
 add_task(async function init() {
@@ -72,20 +71,24 @@ add_task(async function simple() {
       Assert.ok(buttonNode, "buttonNode should be non-null: " + buttonNode);
       Assert.equal(buttonNode.id, panelButtonID, "buttonNode.id");
     },
   }));
 
   Assert.equal(action.getIconURL(), iconURL, "iconURL");
   Assert.equal(action.id, id, "id");
   Assert.deepEqual(action.nodeAttributes, nodeAttributes, "nodeAttributes");
-  Assert.equal(action.shownInUrlbar, false, "shownInUrlbar");
+  Assert.equal(action.pinnedToUrlbar, false, "pinnedToUrlbar");
   Assert.equal(action.subview, null, "subview");
+  Assert.equal(action.getDisabled(), false, "disabled");
+  Assert.equal(action.getDisabled(window), false, "disabled in window");
   Assert.equal(action.getTitle(), title, "title");
+  Assert.equal(action.getTitle(window), title, "title in window");
   Assert.equal(action.getTooltip(), tooltip, "tooltip");
+  Assert.equal(action.getTooltip(window), tooltip, "tooltip in window");
   Assert.equal(action.urlbarIDOverride, null, "urlbarIDOverride");
   Assert.equal(action.wantsIframe, false, "wantsIframe");
 
   Assert.ok(!("__insertBeforeActionID" in action), "__insertBeforeActionID");
   Assert.ok(!("__isSeparator" in action), "__isSeparator");
   Assert.ok(!("__urlbarNodeInMarkup" in action), "__urlbarNodeInMarkup");
 
   Assert.equal(onPlacedInPanelCallCount, 1,
@@ -146,17 +149,17 @@ add_task(async function simple() {
   Assert.equal(onShowingInPanelCallCount, 1,
                "onShowingInPanelCallCount should be inc'ed");
   onCommandExpectedButtonID = panelButtonID;
   EventUtils.synthesizeMouseAtCenter(panelButtonNode, {});
   await promisePageActionPanelHidden();
   Assert.equal(onCommandCallCount, 1, "onCommandCallCount should be inc'ed");
 
   // Show the action's button in the urlbar.
-  action.shownInUrlbar = true;
+  action.pinnedToUrlbar = true;
   Assert.equal(onPlacedInUrlbarCallCount, 1,
                "onPlacedInUrlbarCallCount should be inc'ed");
   urlbarButtonNode = document.getElementById(urlbarButtonID);
   Assert.notEqual(urlbarButtonNode, null, "urlbarButtonNode");
   for (let name in action.nodeAttributes) {
     Assert.ok(urlbarButtonNode.hasAttribute(name), name,
               "Has attribute: " + name);
     Assert.equal(urlbarButtonNode.getAttribute(name),
@@ -167,29 +170,45 @@ add_task(async function simple() {
   // The button should have been inserted before the bookmark star.
   Assert.notEqual(urlbarButtonNode.nextSibling, null, "Should be a next node");
   Assert.equal(
     urlbarButtonNode.nextSibling.id,
     PageActions.actionForID(PageActions.ACTION_ID_BOOKMARK).urlbarIDOverride,
     "Next node should be the bookmark star"
   );
 
+  // Disable the action.  The button in the urlbar should be removed, and the
+  // button in the panel should be disabled.
+  action.setDisabled(true);
+  urlbarButtonNode = document.getElementById(urlbarButtonID);
+  Assert.equal(urlbarButtonNode, null, "urlbar button should be removed");
+  Assert.equal(panelButtonNode.disabled, true,
+               "panel button should be disabled");
+
+  // Enable the action.  The button in the urlbar should be added back, and the
+  // button in the panel should be enabled.
+  action.setDisabled(false);
+  urlbarButtonNode = document.getElementById(urlbarButtonID);
+  Assert.notEqual(urlbarButtonNode, null, "urlbar button should be added back");
+  Assert.equal(panelButtonNode.disabled, false,
+               "panel button should not be disabled");
+
   // Click the urlbar button.
   onCommandExpectedButtonID = urlbarButtonID;
   EventUtils.synthesizeMouseAtCenter(urlbarButtonNode, {});
   Assert.equal(onCommandCallCount, 2, "onCommandCallCount should be inc'ed");
 
   // Set a new title.
   let newTitle = title + " new title";
   action.setTitle(newTitle);
   Assert.equal(action.getTitle(), newTitle, "New title");
   Assert.equal(panelButtonNode.getAttribute("label"), action.getTitle(),
                "New label");
 
-  // Now that shownInUrlbar has been toggled, make sure that it sticks across
+  // Now that pinnedToUrlbar has been toggled, make sure that it sticks across
   // app restarts.  Simulate that by "unregistering" the action (not by removing
   // it, which is more permanent) and then registering it again.
 
   // unregister
   PageActions._actionsByID.delete(action.id);
   let index = PageActions._nonBuiltInActions.findIndex(a => a.id == action.id);
   Assert.ok(index >= 0, "Action should be in _nonBuiltInActions to begin with");
   PageActions._nonBuiltInActions.splice(index, 1);
@@ -197,20 +216,20 @@ add_task(async function simple() {
   // register again
   PageActions._registerAction(action);
 
   // check relevant properties
   Assert.ok(PageActions._persistedActions.ids.includes(action.id),
             "PageActions should have 'seen' the action");
   Assert.ok(PageActions._persistedActions.idsInUrlbar.includes(action.id),
             "idsInUrlbar should still include the action");
-  Assert.ok(action.shownInUrlbar,
-            "shownInUrlbar should still be true");
-  Assert.ok(action._shownInUrlbar,
-            "_shownInUrlbar should still be true, for good measure");
+  Assert.ok(action.pinnedToUrlbar,
+            "pinnedToUrlbar should still be true");
+  Assert.ok(action._pinnedToUrlbar,
+            "_pinnedToUrlbar should still be true, for good measure");
 
   // Remove the action.
   action.remove();
   panelButtonNode = document.getElementById(panelButtonID);
   Assert.equal(panelButtonNode, null, "panelButtonNode");
   urlbarButtonNode = document.getElementById(urlbarButtonID);
   Assert.equal(urlbarButtonNode, null, "urlbarButtonNode");
 
@@ -296,17 +315,17 @@ add_task(async function withSubview() {
         break;
       }
     }
   };
 
   let action = PageActions.addAction(new PageActions.Action({
     iconURL: "chrome://browser/skin/mail.svg",
     id,
-    shownInUrlbar: true,
+    pinnedToUrlbar: true,
     subview,
     title: "Test subview",
     onCommand(event, buttonNode) {
       onActionCommandCallCount++;
     },
     onPlacedInPanel(buttonNode) {
       onActionPlacedInPanelCallCount++;
       Assert.ok(buttonNode, "buttonNode should be non-null: " + buttonNode);
@@ -439,17 +458,17 @@ add_task(async function withIframe() {
   let onIframeShownCount = 0;
 
   let panelButtonID = BrowserPageActions.panelButtonNodeIDForActionID(id);
   let urlbarButtonID = BrowserPageActions.urlbarButtonNodeIDForActionID(id);
 
   let action = PageActions.addAction(new PageActions.Action({
     iconURL: "chrome://browser/skin/mail.svg",
     id,
-    shownInUrlbar: true,
+    pinnedToUrlbar: true,
     title: "Test iframe",
     wantsIframe: true,
     onCommand(event, buttonNode) {
       onCommandCallCount++;
     },
     onIframeShown(iframeNode, panelNode) {
       onIframeShownCount++;
       Assert.ok(iframeNode, "iframeNode should be non-null: " + iframeNode);
@@ -525,17 +544,17 @@ add_task(async function withIframe() {
   // action's urlbar button.
   aaPanel = document.getElementById(BrowserPageActions._activatedActionPanelID);
   Assert.notEqual(aaPanel, null, "aaPanel");
   Assert.equal(aaPanel.anchorNode.id, urlbarButtonID, "aaPanel.anchorNode.id");
   EventUtils.synthesizeMouseAtCenter(urlbarButtonNode, {});
   await promisePanelHidden(BrowserPageActions._activatedActionPanelID);
 
   // Hide the action's button in the urlbar.
-  action.shownInUrlbar = false;
+  action.pinnedToUrlbar = false;
   urlbarButtonNode = document.getElementById(urlbarButtonID);
   Assert.equal(urlbarButtonNode, null, "urlbarButtonNode");
 
   // Open the panel, click the action's button.
   await promisePageActionPanelOpen();
   EventUtils.synthesizeMouseAtCenter(panelButtonNode, {});
   await promisePanelShown(BrowserPageActions._activatedActionPanelID);
   Assert.equal(onCommandCallCount, 0, "onCommandCallCount should remain 0");
@@ -825,32 +844,32 @@ add_task(async function nonBuiltFirst() 
 
 // Makes sure that urlbar nodes appear in the correct order in a new window.
 add_task(async function urlbarOrderNewWindow() {
   // Make some new actions.
   let actions = [0, 1, 2].map(i => {
     return PageActions.addAction(new PageActions.Action({
       id: `test-urlbarOrderNewWindow-${i}`,
       title: `Test urlbarOrderNewWindow ${i}`,
-      shownInUrlbar: true,
+      pinnedToUrlbar: true,
     }));
   });
 
   // Make sure PageActions knows they're inserted before the bookmark action in
   // the urlbar.
   Assert.deepEqual(
     PageActions._persistedActions.idsInUrlbar.slice(
       PageActions._persistedActions.idsInUrlbar.length - (actions.length + 1)
     ),
     actions.map(a => a.id).concat([PageActions.ACTION_ID_BOOKMARK]),
     "PageActions._persistedActions.idsInUrlbar has new actions inserted"
   );
   Assert.deepEqual(
-    PageActions.actionsInUrlbar.slice(
-      PageActions.actionsInUrlbar.length - (actions.length + 1)
+    PageActions.actionsInUrlbar(window).slice(
+      PageActions.actionsInUrlbar(window).length - (actions.length + 1)
     ).map(a => a.id),
     actions.map(a => a.id).concat([PageActions.ACTION_ID_BOOKMARK]),
     "PageActions.actionsInUrlbar has new actions inserted"
   );
 
   // Reach into _persistedActions to move the new actions to the front of the
   // urlbar, same as if the user moved them.  That way we can test that insert-
   // before IDs are correctly non-null when the urlbar nodes are inserted in the
@@ -929,35 +948,35 @@ add_task(async function migrate1() {
     JSON.stringify(persisted)
   );
 
   // Migrate.
   PageActions._loadPersistedActions();
 
   Assert.equal(PageActions._persistedActions.version, 1, "Correct version");
 
-  // Need to set copyURL's _shownInUrlbar.  It won't be set since it's false by
+  // Need to set copyURL's _pinnedToUrlbar.  It won't be set since it's false by
   // default and we reached directly into persisted storage above.
-  PageActions.actionForID("copyURL")._shownInUrlbar = true;
+  PageActions.actionForID("copyURL")._pinnedToUrlbar = true;
 
   // expected order
   let orderedIDs = [
     "pocket",
     "copyURL",
     PageActions.ACTION_ID_BOOKMARK,
   ];
 
   // Check the ordering.
   Assert.deepEqual(
     PageActions._persistedActions.idsInUrlbar,
     orderedIDs,
     "PageActions._persistedActions.idsInUrlbar has right order"
   );
   Assert.deepEqual(
-    PageActions.actionsInUrlbar.map(a => a.id),
+    PageActions.actionsInUrlbar(window).map(a => a.id),
     orderedIDs,
     "PageActions.actionsInUrlbar has right order"
   );
 
   // Open a new window.
   let win = await BrowserTestUtils.openNewBrowserWindow();
   await BrowserTestUtils.openNewForegroundTab({
     gBrowser: win.gBrowser,
@@ -977,31 +996,33 @@ add_task(async function migrate1() {
     actualUrlbarNodeIDs,
     orderedIDs.map(id => win.BrowserPageActions.urlbarButtonNodeIDForActionID(id)),
     "Expected actions in new window's urlbar"
   );
 
   // Done, clean up.
   await BrowserTestUtils.closeWindow(win);
   Services.prefs.clearUserPref(PageActions.PREF_PERSISTED_ACTIONS);
-  PageActions.actionForID("copyURL").shownInUrlbar = false;
+  PageActions.actionForID("copyURL").pinnedToUrlbar = false;
 });
 
 
 // Opens a new browser window and makes sure per-window state works right.
 add_task(async function perWindowState() {
   // Add a test action.
   let title = "Test perWindowState";
   let action = PageActions.addAction(new PageActions.Action({
     iconURL: "chrome://browser/skin/mail.svg",
     id: "test-perWindowState",
-    shownInUrlbar: true,
+    pinnedToUrlbar: true,
     title,
   }));
 
+  let actionsInUrlbar = PageActions.actionsInUrlbar(window);
+
   // Open a new browser window and load an actionable page so that the action
   // shows up in it.
   let newWindow = await BrowserTestUtils.openNewBrowserWindow();
   await BrowserTestUtils.openNewForegroundTab({
     gBrowser: newWindow.gBrowser,
     url: "http://example.com/",
   });
 
@@ -1038,53 +1059,100 @@ add_task(async function perWindowState()
   // same in the old window.
   let panelButtonNode1 = document.getElementById(panelButtonID);
   Assert.equal(panelButtonNode1.getAttribute("label"), newGlobalTitle,
                "Panel button label in old window");
   let panelButtonNode2 = newWindow.document.getElementById(panelButtonID);
   Assert.equal(panelButtonNode2.getAttribute("label"), newPerWinTitle,
                "Panel button label in new window");
 
+  // Disable the action in the new window.
+  action.setDisabled(true, newWindow);
+  Assert.equal(action.getDisabled(), false,
+               "Disabled: global should remain false");
+  Assert.equal(action.getDisabled(window), false,
+               "Disabled: old window should remain false");
+  Assert.equal(action.getDisabled(newWindow), true,
+               "Disabled: new window should be true");
+
+  // Check PageActions.actionsInUrlbar for each window.
+  Assert.deepEqual(
+    PageActions.actionsInUrlbar(window).map(a => a.id),
+    actionsInUrlbar.map(a => a.id),
+    "PageActions.actionsInUrlbar: old window should have all actions in urlbar"
+  );
+  Assert.deepEqual(
+    PageActions.actionsInUrlbar(newWindow).map(a => a.id),
+    actionsInUrlbar.map(a => a.id).filter(id => id != action.id),
+    "PageActions.actionsInUrlbar: new window should have all actions in urlbar except the test action"
+  );
+
+  // Check the urlbar nodes for the old window.
+  let actualUrlbarNodeIDs = [];
+  for (let node = BrowserPageActions.mainButtonNode.nextSibling;
+       node;
+       node = node.nextSibling) {
+    actualUrlbarNodeIDs.push(node.id);
+  }
+  Assert.deepEqual(
+    actualUrlbarNodeIDs,
+    actionsInUrlbar.map(a => BrowserPageActions.urlbarButtonNodeIDForActionID(a.id)),
+    "Old window should have all nodes in urlbar"
+  );
+
+  // Check the urlbar nodes for the new window.
+  actualUrlbarNodeIDs = [];
+  for (let node = newWindow.BrowserPageActions.mainButtonNode.nextSibling;
+       node;
+       node = node.nextSibling) {
+    actualUrlbarNodeIDs.push(node.id);
+  }
+  Assert.deepEqual(
+    actualUrlbarNodeIDs,
+    actionsInUrlbar.filter(a => a.id != action.id).map(a => BrowserPageActions.urlbarButtonNodeIDForActionID(a.id)),
+    "New window should have all nodes in urlbar except for the test action's"
+  );
+
   // Done, clean up.
   await BrowserTestUtils.closeWindow(newWindow);
   action.remove();
 });
 
 
 // Adds an action, changes its placement in the urlbar to something non-default,
 // removes the action, and then adds it back.  Since the action was removed and
 // re-added without restarting the app (or more accurately without calling
 // PageActions._purgeUnregisteredPersistedActions), the action should remain in
 // persisted state and retain its last placement in the urlbar.
 add_task(async function removeRetainState() {
   // Get the list of actions initially in the urlbar.
-  let initialActionsInUrlbar = PageActions.actionsInUrlbar;
+  let initialActionsInUrlbar = PageActions.actionsInUrlbar(window);
   Assert.ok(initialActionsInUrlbar.length > 0,
             "This test expects there to be at least one action in the urlbar initially (like the bookmark star)");
 
   // Add a test action.
   let id = "test-removeRetainState";
   let testAction = PageActions.addAction(new PageActions.Action({
     id,
     title: "Test removeRetainState",
   }));
 
   // Show its button in the urlbar.
-  testAction.shownInUrlbar = true;
+  testAction.pinnedToUrlbar = true;
 
-  // "Move" the test action to the front of the urlbar by toggling shownInUrlbar
-  // for all the other actions in the urlbar.
+  // "Move" the test action to the front of the urlbar by toggling
+  // pinnedToUrlbar for all the other actions in the urlbar.
   for (let action of initialActionsInUrlbar) {
-    action.shownInUrlbar = false;
-    action.shownInUrlbar = true;
+    action.pinnedToUrlbar = false;
+    action.pinnedToUrlbar = true;
   }
 
   // Check the actions in PageActions.actionsInUrlbar.
   Assert.deepEqual(
-    PageActions.actionsInUrlbar.map(a => a.id),
+    PageActions.actionsInUrlbar(window).map(a => a.id),
     [testAction].concat(initialActionsInUrlbar).map(a => a.id),
     "PageActions.actionsInUrlbar should be in expected order: testAction followed by all initial actions"
   );
 
   // Check the nodes in the urlbar.
   let actualUrlbarNodeIDs = [];
   for (let node = BrowserPageActions.mainButtonNode.nextSibling;
        node;
@@ -1097,17 +1165,17 @@ add_task(async function removeRetainStat
     "urlbar nodes should be in expected order: testAction followed by all initial actions"
   );
 
   // Remove the test action.
   testAction.remove();
 
   // Check the actions in PageActions.actionsInUrlbar.
   Assert.deepEqual(
-    PageActions.actionsInUrlbar.map(a => a.id),
+    PageActions.actionsInUrlbar(window).map(a => a.id),
     initialActionsInUrlbar.map(a => a.id),
     "PageActions.actionsInUrlbar should be in expected order after removing test action: all initial actions"
   );
 
   // Check the nodes in the urlbar.
   actualUrlbarNodeIDs = [];
   for (let node = BrowserPageActions.mainButtonNode.nextSibling;
        node;
@@ -1122,21 +1190,21 @@ add_task(async function removeRetainStat
 
   // Add the test action again.
   testAction = PageActions.addAction(new PageActions.Action({
     id,
     title: "Test removeRetainState",
   }));
 
   // Show its button in the urlbar again.
-  testAction.shownInUrlbar = true;
+  testAction.pinnedToUrlbar = true;
 
   // Check the actions in PageActions.actionsInUrlbar.
   Assert.deepEqual(
-    PageActions.actionsInUrlbar.map(a => a.id),
+    PageActions.actionsInUrlbar(window).map(a => a.id),
     [testAction].concat(initialActionsInUrlbar).map(a => a.id),
     "PageActions.actionsInUrlbar should be in expected order after re-adding test action: testAction followed by all initial actions"
   );
 
   // Check the nodes in the urlbar.
   actualUrlbarNodeIDs = [];
   for (let node = BrowserPageActions.mainButtonNode.nextSibling;
        node;
@@ -1149,16 +1217,210 @@ add_task(async function removeRetainStat
     "urlbar nodes should be in expected order after re-adding test action: testAction followed by all initial actions"
   );
 
   // Done, clean up.
   testAction.remove();
 });
 
 
+// Opens the context menu on a non-built-in action.  (The context menu for
+// built-in actions is tested in browser_page_action_menu.js.)
+add_task(async function contextMenu() {
+  // Add a test action.
+  let action = PageActions.addAction(new PageActions.Action({
+    id: "test-contextMenu",
+    title: "Test contextMenu",
+    pinnedToUrlbar: true,
+  }));
+
+  // Open the panel and then open the context menu on the action's item.
+  await promisePageActionPanelOpen();
+  let panelButton = BrowserPageActions.panelButtonNodeForActionID(action.id);
+  let contextMenuPromise = promisePanelShown("pageActionContextMenu");
+  EventUtils.synthesizeMouseAtCenter(panelButton, {
+    type: "contextmenu",
+    button: 2,
+  });
+  await contextMenuPromise;
+
+  // The context menu should show the "don't show" item and the "manage" item.
+  // Click the "don't show" item.
+  let menuItems = collectContextMenuItems();
+  Assert.equal(menuItems.length, 3,
+               "Context menu has 3 children");
+  Assert.equal(menuItems[0].label, "Don\u2019t Show in Address Bar",
+               "Context menu is in the 'don't show' state");
+  Assert.equal(menuItems[1].localName, "menuseparator",
+               "menuseparator is present");
+  Assert.equal(menuItems[2].label, "Manage Extension\u2026",
+               "'Manage' item is present");
+  contextMenuPromise = promisePanelHidden("pageActionContextMenu");
+  EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
+  await contextMenuPromise;
+
+  // The action should be removed from the urlbar.
+  await BrowserTestUtils.waitForCondition(() => {
+    return !BrowserPageActions.urlbarButtonNodeForActionID(action.id);
+  }, "Waiting for urlbar button to be removed");
+
+  // Open the context menu again on the action's button in the panel.  (The
+  // panel should still be open.)
+  contextMenuPromise = promisePanelShown("pageActionContextMenu");
+  EventUtils.synthesizeMouseAtCenter(panelButton, {
+    type: "contextmenu",
+    button: 2,
+  });
+  await contextMenuPromise;
+
+  // The context menu should show the "show" item and the "manage" item.  Click
+  // the "show" item.
+  menuItems = collectContextMenuItems();
+  Assert.equal(menuItems.length, 3,
+               "Context menu has 3 children");
+  Assert.equal(menuItems[0].label, "Show in Address Bar",
+               "Context menu is in the 'show' state");
+  Assert.equal(menuItems[1].localName, "menuseparator",
+               "menuseparator is present");
+  Assert.equal(menuItems[2].label, "Manage Extension\u2026",
+               "'Manage' item is present");
+  contextMenuPromise = promisePanelHidden("pageActionContextMenu");
+  EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
+  await contextMenuPromise;
+
+  // The action should be added back to the urlbar.
+  await BrowserTestUtils.waitForCondition(() => {
+    return BrowserPageActions.urlbarButtonNodeForActionID(action.id);
+  }, "Waiting for urlbar button to be added back");
+
+  // Open the context menu again on the action's button in the panel.  (The
+  // panel should still be open.)
+  contextMenuPromise = promisePanelShown("pageActionContextMenu");
+  EventUtils.synthesizeMouseAtCenter(panelButton, {
+    type: "contextmenu",
+    button: 2,
+  });
+  await contextMenuPromise;
+
+  // The context menu should show the "don't show" item and the "manage" item.
+  // Click the "manage" item.  about:addons should open.
+  menuItems = collectContextMenuItems();
+  Assert.equal(menuItems.length, 3,
+               "Context menu has 3 children");
+  Assert.equal(menuItems[0].label, "Don\u2019t Show in Address Bar",
+               "Context menu is in the 'don't show' state");
+  Assert.equal(menuItems[1].localName, "menuseparator",
+               "menuseparator is present");
+  Assert.equal(menuItems[2].label, "Manage Extension\u2026",
+               "'Manage' item is present");
+  contextMenuPromise = promisePanelHidden("pageActionContextMenu");
+  let aboutAddonsPromise =
+    BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
+  EventUtils.synthesizeMouseAtCenter(menuItems[2], {});
+  let values = await Promise.all([aboutAddonsPromise, contextMenuPromise]);
+  let aboutAddonsTab = values[0];
+  await BrowserTestUtils.removeTab(aboutAddonsTab);
+
+  // Open the context menu on the action's urlbar button.
+  let urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID(action.id);
+  contextMenuPromise = promisePanelShown("pageActionContextMenu");
+  EventUtils.synthesizeMouseAtCenter(urlbarButton, {
+    type: "contextmenu",
+    button: 2,
+  });
+  await contextMenuPromise;
+
+  // The context menu should show the "don't show" item and the "manage" item.
+  // Click the "don't show" item.
+  menuItems = collectContextMenuItems();
+  Assert.equal(menuItems.length, 3,
+               "Context menu has 3 children");
+  Assert.equal(menuItems[0].label, "Don\u2019t Show in Address Bar",
+               "Context menu is in the 'don't show' state");
+  Assert.equal(menuItems[1].localName, "menuseparator",
+               "menuseparator is present");
+  Assert.equal(menuItems[2].label, "Manage Extension\u2026",
+               "'Manage' item is present");
+  contextMenuPromise = promisePanelHidden("pageActionContextMenu");
+  EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
+  await contextMenuPromise;
+
+  // The action should be removed from the urlbar.
+  await BrowserTestUtils.waitForCondition(() => {
+    return !BrowserPageActions.urlbarButtonNodeForActionID(action.id);
+  }, "Waiting for urlbar button to be removed");
+
+  // Open the panel and then open the context menu on the action's item.
+  await promisePageActionPanelOpen();
+  contextMenuPromise = promisePanelShown("pageActionContextMenu");
+  EventUtils.synthesizeMouseAtCenter(panelButton, {
+    type: "contextmenu",
+    button: 2,
+  });
+  await contextMenuPromise;
+
+  // The context menu should show the "show" item and the "manage" item.  Click
+  // the "show" item.
+  menuItems = collectContextMenuItems();
+  Assert.equal(menuItems.length, 3,
+               "Context menu has 3 children");
+  Assert.equal(menuItems[0].label, "Show in Address Bar",
+               "Context menu is in the 'show' state");
+  Assert.equal(menuItems[1].localName, "menuseparator",
+               "menuseparator is present");
+  Assert.equal(menuItems[2].label, "Manage Extension\u2026",
+               "'Manage' item is present");
+  contextMenuPromise = promisePanelHidden("pageActionContextMenu");
+  EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
+  await contextMenuPromise;
+
+  // The action should be added back to the urlbar.
+  await BrowserTestUtils.waitForCondition(() => {
+    return BrowserPageActions.urlbarButtonNodeForActionID(action.id);
+  }, "Waiting for urlbar button to be added back");
+
+  // Open the context menu on the action's urlbar button.
+  urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID(action.id);
+  contextMenuPromise = promisePanelShown("pageActionContextMenu");
+  EventUtils.synthesizeMouseAtCenter(urlbarButton, {
+    type: "contextmenu",
+    button: 2,
+  });
+  await contextMenuPromise;
+
+  // The context menu should show the "don't show" item and the "manage" item.
+  // Click the "manage" item.  about:addons should open.
+  menuItems = collectContextMenuItems();
+  Assert.equal(menuItems.length, 3,
+               "Context menu has 3 children");
+  Assert.equal(menuItems[0].label, "Don\u2019t Show in Address Bar",
+               "Context menu is in the 'don't show' state");
+  Assert.equal(menuItems[1].localName, "menuseparator",
+               "menuseparator is present");
+  Assert.equal(menuItems[2].label, "Manage Extension\u2026",
+               "'Manage' item is present");
+  contextMenuPromise = promisePanelHidden("pageActionContextMenu");
+  aboutAddonsPromise =
+    BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
+  EventUtils.synthesizeMouseAtCenter(menuItems[2], {});
+  values = await Promise.all([aboutAddonsPromise, contextMenuPromise]);
+  aboutAddonsTab = values[0];
+  await BrowserTestUtils.removeTab(aboutAddonsTab);
+
+  // Done, clean up.
+  action.remove();
+
+  // urlbar tests that run after this one can break if the mouse is left over
+  // the area where the urlbar popup appears, which seems to happen due to the
+  // above synthesized mouse events.  Move it over the urlbar.
+  EventUtils.synthesizeMouseAtCenter(gURLBar, { type: "mousemove" });
+  gURLBar.focus();
+});
+
+
 function promisePageActionPanelOpen() {
   let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
                   .getInterface(Ci.nsIDOMWindowUtils);
   return BrowserTestUtils.waitForCondition(() => {
     // Wait for the main page action button to become visible.  It's hidden for
     // some URIs, so depending on when this is called, it may not yet be quite
     // visible.  It's up to the caller to make sure it will be visible.
     info("Waiting for main page action button to have non-0 size");
@@ -1230,8 +1492,15 @@ function promisePageActionViewChildrenVi
       let bounds = dwu.getBoundsWithoutFlushing(childNode);
       if (bounds.width > 0 && bounds.height > 0) {
         return true;
       }
     }
     return false;
   });
 }
+
+function collectContextMenuItems() {
+  let contextMenu = document.getElementById("pageActionContextMenu");
+  return Array.filter(contextMenu.childNodes, node => {
+    return window.getComputedStyle(node).visibility == "visible";
+  });
+}
--- a/browser/moz.build
+++ b/browser/moz.build
@@ -35,16 +35,34 @@ export('DIST_SUBDIR')
 
 if CONFIG['MOZ_ARTIFACT_BUILDS']:
     # Ensure a pre-built interfaces.xpt installed to the objdir by the artifact
     # code is included by the top-level chrome.manifest.
     EXTRA_COMPONENTS += [
         '../build/prebuilt-interfaces.manifest',
     ]
 
+
+# These defines are read in firefox.js
+DEFINES['APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+
+for cdm in CONFIG['MOZ_EME_MODULES']:
+    DEFINES['MOZ_%s_EME' % cdm.upper()] = True
+
+if CONFIG['MOZ_GPSD']:
+    DEFINES['MOZ_GPSD'] = True
+
+# These files are specified in this moz.build to pick up DIST_SUBDIR as set in
+# this directory, which is un-set in browser/app.
+JS_PREFERENCE_PP_FILES += [
+    'app/profile/firefox.js',
+]
+FINAL_TARGET_FILES += ['app/blocklist.xml']
+FINAL_TARGET_FILES.defaults += ['app/permissions']
+
 with Files("**"):
     BUG_COMPONENT = ("Firefox", "General")
 
 with Files("Makefile.in"):
     BUG_COMPONENT = ("Core", "Build Config")
 
 with Files("*.mk"):
     BUG_COMPONENT = ("Core", "Build Config")
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -659,16 +659,17 @@ button > hbox > label {
 }
 
 .help-icon:hover {
   fill: currentColor !important;
 }
 
 .help-label {
   margin: 0 4px;
+  line-height: 22px;
   -moz-user-select: none;
 }
 
 @media (max-width: 830px) {
   .help-button > .text-link {
     -moz-box-flex: 0;
     width: 36px;
     height: 36px;
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -63,18 +63,16 @@
   skin/classic/browser/notification-icons/login-detailed.svg                (../shared/notification-icons/login-detailed.svg)
   skin/classic/browser/notification-icons/login.svg                         (../shared/notification-icons/login.svg)
   skin/classic/browser/notification-icons/microphone-blocked.svg            (../shared/notification-icons/microphone-blocked.svg)
   skin/classic/browser/notification-icons/microphone-detailed.svg           (../shared/notification-icons/microphone-detailed.svg)
   skin/classic/browser/notification-icons/microphone.svg                    (../shared/notification-icons/microphone.svg)
   skin/classic/browser/notification-icons/persistent-storage-blocked.svg    (../shared/notification-icons/persistent-storage-blocked.svg)
   skin/classic/browser/notification-icons/persistent-storage.svg            (../shared/notification-icons/persistent-storage.svg)
   skin/classic/browser/notification-icons/plugin-badge.svg                  (../shared/notification-icons/plugin-badge.svg)
-  skin/classic/browser/notification-icons/plugin-blocked.svg                (../shared/notification-icons/plugin-blocked.svg)
-  skin/classic/browser/notification-icons/plugin.svg                        (../shared/notification-icons/plugin.svg)
   skin/classic/browser/notification-icons/popup.svg                         (../shared/notification-icons/popup.svg)
   skin/classic/browser/notification-icons/popup-subitem.svg                 (../shared/notification-icons/popup-subitem.svg)
   skin/classic/browser/notification-icons/screen-blocked.svg                (../shared/notification-icons/screen-blocked.svg)
   skin/classic/browser/notification-icons/screen.svg                        (../shared/notification-icons/screen.svg)
   skin/classic/browser/notification-icons/update.svg                        (../shared/notification-icons/update.svg)
 
   skin/classic/browser/tracking-protection-16.svg              (../shared/identity-block/tracking-protection-16.svg)
   skin/classic/browser/newtab/close.png                        (../shared/newtab/close.png)
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -218,22 +218,22 @@ html|*#webRTC-previewVideo {
 .popup-notification-icon[popupid*="offline-app-requested"],
 .popup-notification-icon[popupid="offline-app-usage"] {
   list-style-image: url(chrome://global/skin/icons/question-64.png);
 }
 
 /* PLUGINS */
 
 .plugin-icon {
-  list-style-image: url(chrome://browser/skin/notification-icons/plugin.svg);
+  list-style-image: url(chrome://mozapps/skin/plugins/plugin.svg);
   transition: fill 1.5s;
 }
 
 .plugin-blocked-icon {
-  list-style-image: url(chrome://browser/skin/notification-icons/plugin-blocked.svg);
+  list-style-image: url(chrome://mozapps/skin/plugins/plugin-blocked.svg);
   transition: fill 1.5s;
 }
 
 #plugin-icon-badge {
   list-style-image: url(chrome://browser/skin/notification-icons/plugin-badge.svg);
   opacity: 0;
   transition: opacity 1.5s;
 }
@@ -255,17 +255,17 @@ html|*#webRTC-previewVideo {
     opacity: 0;
   }
   to {
     opacity: 1;
   }
 }
 
 .plugin-blocked > .plugin-icon {
-  list-style-image: url(chrome://browser/skin/notification-icons/plugin-blocked.svg);
+  list-style-image: url(chrome://mozapps/skin/plugins/plugin-blocked.svg);
   fill: #d92215 !important;
 }
 
 .plugin-blocked > #plugin-icon-badge {
   visibility: collapse;
 }
 
 #notification-popup-box[hidden] {
--- a/browser/themes/shared/plugin-doorhanger.inc.css
+++ b/browser/themes/shared/plugin-doorhanger.inc.css
@@ -1,14 +1,14 @@
 .messageImage[value="plugin-hidden"] {
-  list-style-image: url(chrome://browser/skin/notification-icons/plugin.svg);
+  list-style-image: url(chrome://mozapps/skin/plugins/plugin.svg);
 }
 
 /* Keep any changes to this style in sync with pluginProblem.css */
 notification.pluginVulnerable {
   background-color: rgb(72,72,72);
   background-image: url(chrome://mozapps/skin/plugins/contentPluginStripe.png);
   color: white;
 }
 
 notification.pluginVulnerable .messageImage {
-  list-style-image: url(chrome://browser/skin/notification-icons/plugin-blocked.svg);
+  list-style-image: url(chrome://mozapps/skin/plugins/plugin-blocked.svg);
 }
--- a/build/moz.build
+++ b/build/moz.build
@@ -10,16 +10,20 @@ with Files('**'):
 # This cannot be named "build" because of bug 922191.
 SPHINX_TREES['buildsystem'] = 'docs'
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['win32']
 else:
     DIRS += ['unix']
 
+CRAMTEST_MANIFESTS += [
+    'tests/cram/cram.ini',
+]
+
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     DIRS += ['annotationProcessors']
 
 DEFINES['ACCEPTED_MAR_CHANNEL_IDS'] = CONFIG['ACCEPTED_MAR_CHANNEL_IDS']
 
 if CONFIG['MOZ_BUILD_APP'] == 'browser':
     PYTHON_UNITTEST_MANIFESTS += [
         'compare-mozconfig/python.ini',
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -1,17 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # PGO
 # ==============================================================
-option(env='MOZ_PGO', help='Build with profile guided optimizations')
+js_option(env='MOZ_PGO', help='Build with profile guided optimizations')
 
 set_config('MOZ_PGO', depends('MOZ_PGO')(lambda x: bool(x)))
 add_old_configure_assignment('MOZ_PGO', depends('MOZ_PGO')(lambda x: bool(x)))
 
 # Code optimization
 # ==============================================================
 
 js_option('--enable-optimize',
new file mode 100644
--- /dev/null
+++ b/build/tests/cram/cram.ini
@@ -0,0 +1,1 @@
+[test_configure_help.t]
new file mode 100644
--- /dev/null
+++ b/build/tests/cram/test_configure_help.t
@@ -0,0 +1,14 @@
+configure --help works
+
+  $ cd $TESTDIR/../../..
+
+  $ touch $TMP/mozconfig
+  $ export MOZCONFIG=$TMP/mozconfig
+  $ ./configure --help 2>& 1 | head -n 7
+  Adding configure options from */tmp/mozconfig (glob)
+  checking for vcs source checkout... hg
+  checking for vcs source checkout... hg
+  Usage: configure.py [options]
+  
+  Options: [defaults in brackets after descriptions]
+    --help                    print this message
--- a/caps/tests/mochitest/test_bug292789.html
+++ b/caps/tests/mochitest/test_bug292789.html
@@ -51,18 +51,18 @@ function testScriptSrc(aCallback) {
       // trigger the callback
       if (aCallback)
         aCallback();
     }
 }
 
 /** <img src=""> tests **/
 var img_global = "chrome://global/skin/media/error.png";
-var img_mozapps = "chrome://mozapps/skin/plugins/contentPluginClose.png";
-var res_mozapps = "resource://gre/chrome/toolkit/skin/classic/mozapps/plugins/contentPluginClose.png";
+var img_mozapps = "chrome://mozapps/skin/plugins/contentPluginCrashed.png";
+var res_mozapps = "resource://gre/chrome/toolkit/skin/classic/mozapps/plugins/contentPluginCrashed.png";
 
 var imgTests = [[img_global, "success"],
                 [img_mozapps, "fail"],
                 [res_mozapps, "success"]];
 
 var curImgTest = 0;
 
 function runImgTest() {
--- a/client.mk
+++ b/client.mk
@@ -114,16 +114,21 @@ CONFIGURES += $(TOPSRCDIR)/js/src/config
 OBJDIR_TARGETS = install export libs clean realclean distclean upload sdk installer package package-compare stage-package source-package l10n-check automation/build
 
 #######################################################################
 # Rules
 
 # The default rule is build
 build::
 
+ifndef MACH
+$(error client.mk must be used via `mach`. Try running \
+`./mach $(firstword $(MAKECMDGOALS) $(.DEFAULT_GOAL))`)
+endif
+
 # Include baseconfig.mk for its $(MAKE) validation.
 include $(TOPSRCDIR)/config/baseconfig.mk
 
 # Define mkdir
 include $(TOPSRCDIR)/config/makefiles/makeutils.mk
 include $(TOPSRCDIR)/config/makefiles/autotargets.mk
 
 # For now, only output "export" lines and lines containing UPLOAD_EXTRA_FILES
--- a/config/Makefile.in
+++ b/config/Makefile.in
@@ -27,63 +27,16 @@ NSINSTALL_EXECUTABLES := nsinstall$(HOST
 NSINSTALL_DEST := $(DIST)/bin
 NSINSTALL_TARGET := host
 INSTALL_TARGETS += NSINSTALL
 endif
 endif
 
 include $(topsrcdir)/config/rules.mk
 
-ifdef WRAP_SYSTEM_INCLUDES
-export-preqs = \
-  $(call mkdir_deps,system_wrappers) \
-  $(NULL)
-
-export:: $(export-preqs)
-	$(PYTHON) -m mozbuild.action.preprocessor $(DEFINES) $(ACDEFINES) \
-		-DMOZ_TREE_CAIRO=$(MOZ_TREE_CAIRO) \
-		-DMOZ_TREE_PIXMAN=$(MOZ_TREE_PIXMAN) \
-		-DMOZ_SYSTEM_HUNSPELL=$(MOZ_SYSTEM_HUNSPELL) \
-		-DMOZ_SYSTEM_BZ2=$(MOZ_SYSTEM_BZ2) \
-		-DMOZ_SYSTEM_ZLIB=$(MOZ_SYSTEM_ZLIB) \
-		-DMOZ_SYSTEM_PNG=$(MOZ_SYSTEM_PNG) \
-		-DMOZ_SYSTEM_JPEG=$(MOZ_SYSTEM_JPEG) \
-		-DMOZ_SYSTEM_LIBEVENT=$(MOZ_SYSTEM_LIBEVENT) \
-		-DMOZ_SYSTEM_LIBVPX=$(MOZ_SYSTEM_LIBVPX) \
-		-DMOZ_SYSTEM_ICU=$(MOZ_SYSTEM_ICU) \
-		$(srcdir)/system-headers $(srcdir)/stl-headers | $(PERL) $(topsrcdir)/nsprpub/config/make-system-wrappers.pl system_wrappers
-	$(INSTALL) system_wrappers $(DIST)
-
-GARBAGE_DIRS += system_wrappers
-endif
-
-ifdef WRAP_STL_INCLUDES
-ifdef GNU_CXX
-stl_compiler = gcc
-else
-ifdef _MSC_VER
-stl_compiler = msvc
-endif
-endif
-endif
-
-ifdef stl_compiler
-STL_WRAPPERS_SENTINEL = $(DIST)/stl_wrappers/sentinel
-
-$(STL_WRAPPERS_SENTINEL): $(srcdir)/make-stl-wrappers.py $(srcdir)/$(stl_compiler)-stl-wrapper.template.h $(srcdir)/stl-headers $(GLOBAL_DEPS)
-	$(PYTHON) $(srcdir)/make-stl-wrappers.py stl_wrappers $(stl_compiler) $(srcdir)/$(stl_compiler)-stl-wrapper.template.h $(srcdir)/stl-headers
-	$(PYTHON) $(srcdir)/nsinstall.py -t stl_wrappers $(DIST)
-	touch $(STL_WRAPPERS_SENTINEL)
-
-export:: $(STL_WRAPPERS_SENTINEL)
-
-GARBAGE += $(STL_WRAPPERS_SENTINEL)
-GARBAGE_DIRS += stl_wrappers
-endif
-
 GARBAGE += \
   $(FINAL_LINK_COMPS) $(FINAL_LINK_LIBS) $(FINAL_LINK_COMP_NAMES) $(srcdir)/*.pyc *.pyc
 
 FORCE:
 
 ifndef JS_STANDALONE
 check-preqs += check-jar-mn
 endif
--- a/config/make-stl-wrappers.py
+++ b/config/make-stl-wrappers.py
@@ -17,36 +17,18 @@ def header_path(header, compiler):
         # we use include_next on gcc
         return header
     elif compiler == 'msvc':
         return find_in_path(header, os.environ.get('INCLUDE', ''))
     else:
         # hope someone notices this ...
         raise NotImplementedError(compiler)
 
-def is_comment(line):
-    return re.match(r'\s*#.*', line)
-
-def main(outdir, compiler, template_file, header_list_file):
-    if not os.path.isdir(outdir):
-        os.mkdir(outdir)
-
+# The 'unused' arg is the output file from the file_generate action. We actually
+# generate all the files in header_list
+def gen_wrappers(unused, outdir, compiler, template_file, *header_list):
     template = open(template_file, 'r').read()
 
-    for header in open(header_list_file, 'r'):
-        header = header.rstrip()
-        if 0 == len(header) or is_comment(header):
-            continue
-
+    for header in header_list:
         path = header_path(header, compiler)
         with FileAvoidWrite(os.path.join(outdir, header)) as f:
             f.write(string.Template(template).substitute(HEADER=header,
                                                          HEADER_PATH=path))
-
-
-if __name__ == '__main__':
-    if 5 != len(sys.argv):
-        print("""Usage:
-  python {0} OUT_DIR ('msvc'|'gcc') TEMPLATE_FILE HEADER_LIST_FILE
-""".format(sys.argv[0]), file=sys.stderr)
-        sys.exit(1)
-
-    main(*sys.argv[1:])
new file mode 100644
--- /dev/null
+++ b/config/make-system-wrappers.py
@@ -0,0 +1,23 @@
+# 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/.
+from __future__ import print_function
+import os
+import sys
+from mozbuild.util import FileAvoidWrite
+
+header_template = '''#pragma GCC system_header
+#pragma GCC visibility push(default)
+#include_next <%(header)s>
+#pragma GCC visibility pop
+'''
+
+
+# The 'unused' arg is the output file from the file_generate action. We actually
+# generate all the files in header_list
+def gen_wrappers(unused, outdir, *header_list):
+    for header in header_list:
+        with FileAvoidWrite(os.path.join(outdir, header)) as f:
+            f.write(header_template % {
+                'header': header,
+            })
--- a/config/moz.build
+++ b/config/moz.build
@@ -37,8 +37,40 @@ PYTHON_UNITTEST_MANIFESTS += [
 
 if CONFIG['GNU_CC'] and CONFIG['MOZ_OPTIMIZE']:
     CFLAGS += ['-O3']
 
 HOST_DEFINES = {
     'UNICODE': True,
     '_UNICODE': True,
 }
+
+include('stl-headers.mozbuild')
+if CONFIG['WRAP_STL_INCLUDES']:
+    stl_compiler = None
+    if CONFIG['GNU_CXX']:
+        stl_compiler = 'gcc'
+    elif CONFIG['_MSC_VER']:
+        stl_compiler = 'msvc'
+
+    if stl_compiler:
+        template_file = SRCDIR + '/%s-stl-wrapper.template.h' % stl_compiler
+        output_dir = '../dist/stl_wrappers'
+        # We have to use a sentinel file as the first file because the
+        # file_generate action will create it for us, but we want to create all
+        # the files in gen_wrappers()
+        outputs = tuple(['stl.sentinel'] + ['%s/%s' % (output_dir, h) for h in stl_headers])
+        GENERATED_FILES += [outputs]
+        stl = GENERATED_FILES[outputs]
+        stl.script = 'make-stl-wrappers.py:gen_wrappers'
+        stl.flags = [output_dir, stl_compiler, template_file]
+        stl.flags.extend(stl_headers)
+
+if CONFIG['WRAP_SYSTEM_INCLUDES']:
+    include('system-headers.mozbuild')
+    output_dir = '../dist/system_wrappers'
+    outputs = tuple(['system-header.sentinel'] + ['%s/%s' % (output_dir, h) for h in stl_headers + system_headers])
+    GENERATED_FILES += [outputs]
+    system = GENERATED_FILES[outputs]
+    system.script = 'make-system-wrappers.py:gen_wrappers'
+    system.flags = [output_dir]
+    system.flags.extend(stl_headers)
+    system.flags.extend(system_headers)
deleted file mode 100644
--- a/config/stl-headers
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# This file contains a list the of STL headers that have been reviewed
-# for exception safety and approved. See
-#
-#   https://bugzilla.mozilla.org/show_bug.cgi?id=551254
-#
-# At build time, each header listed here is converted into a "wrapper
-# header" that is installed into dist/stl_includes.
-#
-# If you would like to request a new STL header <foo> be added, please
-# file a Core:XPCOM bug with a title like "STL: Review exception
-# safety of <foo> for gcc and MSVC".
-#
-
-new
-
-# FIXME: these headers haven't been reviewed yet, but we use them
-# unsafely in Gecko, so we might as well prevent them from
-# throwing exceptions
-algorithm
-atomic
-deque
-functional
-ios
-iosfwd
-iostream
-istream
-iterator
-limits
-list
-map
-memory
-ostream
-set
-stack
-string
-thread
-type_traits
-unordered_map
-unordered_set
-utility
-vector
-cassert
-climits
-cmath
-cstdarg
-cstdio
-cstdlib
-cstring
-cwchar
-tuple
-xutility
new file mode 100644
--- /dev/null
+++ b/config/stl-headers.mozbuild
@@ -0,0 +1,57 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This list contains the of STL headers that have been reviewed for exception
+# safety and approved. See
+#
+#   https://bugzilla.mozilla.org/show_bug.cgi?id=551254
+#
+# At build time, each header listed here is converted into a "wrapper
+# header" that is installed into dist/stl_includes.
+#
+# If you would like to request a new STL header <foo> be added, please
+# file a Core:XPCOM bug with a title like "STL: Review exception
+# safety of <foo> for gcc and MSVC".
+stl_headers = [
+    'new',
+
+    # FIXME: these headers haven't been reviewed yet, but we use them
+    # unsafely in Gecko, so we might as well prevent them from
+    # throwing exceptions
+    'algorithm',
+    'atomic',
+    'deque',
+    'functional',
+    'ios',
+    'iosfwd',
+    'iostream',
+    'istream',
+    'iterator',
+    'limits',
+    'list',
+    'map',
+    'memory',
+    'ostream',
+    'set',
+    'stack',
+    'string',
+    'thread',
+    'type_traits',
+    'unordered_map',
+    'unordered_set',
+    'utility',
+    'vector',
+    'cassert',
+    'climits',
+    'cmath',
+    'cstdarg',
+    'cstdio',
+    'cstdlib',
+    'cstring',
+    'cwchar',
+    'tuple',
+    'xutility',
+]
deleted file mode 100644
--- a/config/system-headers
+++ /dev/null
@@ -1,1347 +0,0 @@
-atomic.h
-nspr.h
-plarena.h
-plarenas.h
-plbase64.h
-plerror.h
-plgetopt.h
-plhash.h
-plstr.h
-pratom.h
-prbit.h
-prclist.h
-prcmon.h
-prcountr.h
-prcpucfg.h
-prcvar.h
-prdtoa.h
-prenv.h
-prerr.h
-prerror.h
-prinet.h
-prinit.h
-prinrval.h
-prio.h
-pripcsem.h
-private
-prlink.h
-prlock.h
-prlog.h
-prlong.h
-prmem.h
-prmon.h
-prmwait.h
-prnetdb.h
-prolock.h
-prpdce.h
-prprf.h
-prproces.h
-prrng.h
-prrwlock.h
-prshm.h
-prshma.h
-prsystem.h
-prthread.h
-prtime.h
-prtpool.h
-prtrace.h
-prtypes.h
-prvrsion.h
-prwin16.h
-base64.h
-blapit.h
-cert.h
-certdb.h
-certt.h
-ciferfam.h
-cmmf.h
-cmmft.h
-cms.h
-cmsreclist.h
-cmst.h
-crmf.h
-crmft.h
-cryptohi.h
-cryptoht.h
-ecl-exp.h
-hasht.h
-jar-ds.h
-jar.h
-jarfile.h
-key.h
-keyhi.h
-keyt.h
-keythi.h
-nss.h
-nssb64.h
-nssb64t.h
-nssbase.h
-nssbaset.h
-nssck.api
-nssckbi.h
-nssckepv.h
-nssckft.h
-nssckfw.h
-nssckfwc.h
-nssckfwt.h
-nssckg.h
-nssckmdt.h
-nssckt.h
-nssilckt.h
-nssilock.h
-nsslocks.h
-nssrwlk.h
-nssrwlkt.h
-nssutil.h
-ocsp.h
-ocspt.h
-p12.h
-p12plcy.h
-p12t.h
-pk11func.h
-pk11pqg.h
-pk11priv.h
-pk11pub.h
-pk11sdr.h
-pkcs11.h
-pkcs11f.h
-pkcs11n.h
-pkcs11p.h
-pkcs11t.h
-pkcs11u.h
-pkcs12.h
-pkcs12t.h
-pkcs7t.h
-portreg.h
-preenc.h
-secasn1.h
-secasn1t.h
-seccomon.h
-secder.h
-secdert.h
-secdig.h
-secdigt.h
-secerr.h
-sechash.h
-secitem.h
-secmime.h
-secmod.h
-secmodt.h
-secoid.h
-secoidt.h
-secpkcs5.h
-secpkcs7.h
-secport.h
-shsign.h
-smime.h
-ssl.h
-sslerr.h
-sslproto.h
-sslt.h
-utilmodt.h
-utilpars.h
-utilparst.h
-utilrename.h
-A4Stuff.h
-activscp.h
-AEDataModel.h
-AEObjects.h
-AEPackObject.h
-AERegistry.h
-AEUtils.h
-afxcmn.h
-afxcoll.h
-afxcview.h
-afxdisp.h
-afxdtctl.h
-afxext.h
-afxmt.h
-afxpriv.h
-afxtempl.h
-afxwin.h
-Aliases.h
-all.h
-alloca.h
-alloc.h
-alsa/asoundlib.h
-#ifdef ANDROID
-android/ashmem.h
-android/log.h
-android/looper.h
-android/native_window.h
-android_audio/AudioSystem.h
-#endif
-ansi_parms.h
-a.out.h
-app/Cursor.h
-Appearance.h
-AppFileInfo.h
-AppKit.h
-AppleEvents.h
-Application.h
-app/Message.h
-app/MessageRunner.h
-arpa/inet.h
-arpa/nameser.h
-array
-asm/page.h
-asm/sigcontext.h
-asm/signal.h
-ASRegistry.h
-assert.h
-atk/atk.h
-atlcom.h
-atlconv.h
-atlctl.cpp
-atlctl.h
-ATLCTL.H
-atlhost.h
-atlimpl.cpp
-atlwin.cpp
-ATSTypes.h
-ATSUnicode.h
-#ifdef ANDROID
-audio_effects/effect_aec.h
-audio_effects/effect_ns.h
-AudioParameter.h
-AudioSystem.h
-AudioTrack.h
-avc_utils.h
-#endif
-Balloons.h
-base/pblock.h
-base/PCR_Base.h
-base/session.h
-basetyps.h
-be/app/Application.h
-Beep.h
-be/kernel/image.h
-be/kernel/OS.h
-bfd.h
-#ifdef ANDROID
-binder/Binder.h
-binder/BinderService.h
-binder/IBinder.h
-binder/IInterface.h
-binder/IMemory.h
-binder/IPCThreadState.h
-binder/IPermissionController.h
-binder/IServiceManager.h
-binder/Parcel.h
-binder/ProcessState.h
-#endif
-Bitmap.h
-bitset
-blapi.h
-bsd/libc.h
-bsd/syscall.h
-bstring.h
-builtin.h
-Button.h
-byteswap.h
-pixman.h
-cairo.h
-cairo-atsui.h
-cairo-beos.h
-cairo-ft.h
-cairo-glitz.h
-cairo-gobject.h
-cairo-pdf.h
-cairo-ps.h
-cairo-tee.h
-cairo-quartz.h
-cairo-win32.h
-cairo-xlib.h
-cairo-xlib-xrender.h
-cairo-directfb.h
-cairo-qpainter.h
-cairo-qt.h
-complex
-dfiff.h
-exception
-ffi.h
-fusion/reactor.h
-fusion/property.h
-fusion/conf.h
-fusion/build.h
-fusion/hash.h
-fusion/shm/shm.h
-fusion/shm/shm_internal.h
-fusion/shm/pool.h
-fusion/ref.h
-fusion/fusion_internal.h
-fusion/lock.h
-fusion/types.h
-fusion/vector.h
-fusion/call.h
-fusion/shmalloc.h
-fusion/protocol.h
-fusion/fusion.h
-fusion/arena.h
-fusion/object.h
-dgiff.h
-direct/util.h
-direct/memcpy.h
-direct/interface.h
-direct/conf.h
-direct/tree.h
-direct/signals.h
-direct/build.h
-direct/interface_implementation.h
-direct/utf8.h
-direct/serial.h
-direct/hash.h
-direct/direct.h
-direct/clock.h
-direct/types.h
-direct/mem.h
-direct/thread.h
-direct/debug.h
-direct/stream.h
-direct/messages.h
-direct/trace.h
-direct/modules.h
-direct/log.h
-direct/system.h
-direct/list.h
-dfb_types.h
-directfb_strings.h
-directfb_keyboard.h
-callconv.h
-#ifdef ANDROID
-camera/Camera.h
-camera/CameraParameters.h
-#endif
-Carbon/Carbon.h
-CarbonEvents.h
-Carbon.h
-c_asm.h
-cctype
-cderr.h
-cerrno
-CFBase.h
-CFBundle.h
-CFData.h
-CFDictionary.h
-cf.h
-CFNumber.h
-CFPlugIn.h
-CFPreferences.h
-CFString.h
-CFURL.h
-CGAffineTransform.h
-CheckBox.h
-Clipboard.h
-cmplrs/stsupport.h
-Cocoa/Cocoa.h
-CodeFragments.h
-#ifdef ANDROID
-ColorConverter.h
-#endif
-comdef.h
-commctrl.h
-COMMCTRL.H
-commdlg.h
-compat.h
-condapi.h
-ConditionalMacros.h
-conio.h
-console.h
-ControlDefinitions.h
-Controls.h
-CoreFoundation/CoreFoundation.h
-CoreServices/CoreServices.h
-CPalmRec.cpp
-Cpalmrec.h
-CPCatgry.cpp
-CPDbBMgr.h
-CPString.cpp
-CPString.h
-crtdbg.h
-crt_externs.h
-crypt.h
-cstddef
-ctime
-ctype.h
-curl/curl.h
-curl/easy.h
-curses.h
-#ifdef ANDROID
-cutils/android_reboot.h
-cutils/atomic.h
-cutils/compiler.h
-cutils/log.h
-cutils/native_handle.h
-cutils/properties.h
-cutils/sockets.h
-#endif
-cxxabi.h
-DateTimeUtils.h
-dbus/dbus.h
-dbus/dbus-glib.h
-dbus/dbus-glib-lowlevel.h
-ddeml.h
-Debug.h
-dem.h
-descrip.h
-Devices.h
-Dialogs.h
-direct.h
-dirent.h
-DiskInit.h
-dlfcn.h
-dlgs.h
-dl.h
-docobj.h
-dos/dosextens.h
-dos.h
-Drag.h
-DriverServices.h
-DriverSynchronization.h
-DropInPanel.h
-dvidef.h
-elf.h
-endian.h
-Entry.h
-errno.h
-Errors.h
-Events.h
-exdisp.h
-ExDisp.h
-exe386.h
-execinfo.h
-extras.h
-fcntl.h
-features.h
-fibdef.h
-File.h
-filehdr.h
-files.h
-Files.h
-FindDirectory.h
-Finder.h
-FinderRegistry.h
-FixMath.h
-float.h
-fnmatch.h
-Folders.h
-fontconfig/fontconfig.h
-fontconfig/fcfreetype.h
-Font.h
-Fonts.h
-#ifdef ANDROID
-foundation/ABase.h
-foundation/ABitReader.h
-foundation/ABuffer.h
-foundation/ADebug.h
-foundation/AHandler.h
-foundation/AHandlerReflector.h
-foundation/ALooper.h
-foundation/AMessage.h
-foundation/AString.h
-foundation/base64.h
-foundation/hexdump.h
-#endif
-fp.h
-fpieee.h
-frame/log.h
-frame/req.h
-freetype/freetype.h
-freetype/ftcache.h
-freetype/ftfntfmt.h
-freetype/ftglyph.h
-freetype/ftsynth.h
-freetype/ftoutln.h
-freetype/ttnameid.h
-freetype/tttables.h
-freetype/t1tables.h
-freetype/ftlcdfil.h
-freetype/ftsizes.h
-freetype/ftadvanc.h
-freetype/ftbitmap.h
-freetype/ftxf86.h
-freetype.h
-ftcache.h
-ftfntfmt.h
-ftglyph.h
-ftsynth.h
-ftoutln.h
-ttnameid.h
-tttables.h
-t1tables.h
-ftlcdfil.h
-ftsizes.h
-ftadvanc.h
-ftbitmap.h
-ftxf86.h
-fribidi/fribidi.h
-FSp_fopen.h
-fstream
-fstream.h
-ft2build.h
-fts.h
-gconf/gconf-client.h
-Gdiplus.h
-gdk/gdk.h
-gdk/gdkkeysyms.h
-gdk/gdkprivate.h
-gdk/gdkx.h
-gdk/gdkdirectfb.h
-gdk/gdkwayland.h
-gdk-pixbuf/gdk-pixbuf.h
-Gestalt.h
-getopt.h
-glibconfig.h
-glib.h
-glib-object.h
-glob.h
-gmodule.h
-gnome.h
-gnu/libc-version.h
-gps.h
-grp.h
-gssapi_generic.h
-gssapi/gssapi_generic.h
-gssapi/gssapi.h
-gssapi.h
-gtk/gtk.h
-gtk/gtkx.h
-gtk/gtkunixprint.h
-#ifdef ANDROID
-gui/BufferQueue.h
-gui/ConsumerBase.h
-gui/GraphicBufferAlloc.h
-gui/IConsumerListener.h
-gui/IGraphicBufferAlloc.h
-gui/IGraphicBufferProducer.h
-gui/ISurfaceComposer.h
-gui/ISurfaceComposerClient.h
-gui/ISurfaceTexture.h
-gui/Surface.h
-gui/SurfaceComposerClient.h
-gui/SurfaceTextureClient.h
-hardware/audio.h
-hardware/gralloc.h
-hardware/hardware.h
-hardware/hwcomposer.h
-hardware/lights.h
-hardware/power.h
-hardware_legacy/power.h
-hardware_legacy/uevent.h
-hardware_legacy/vibrator.h
-#endif
-HIToolbox/HIToolbox.h
-hlink.h
-#ifdef ANDROID
-HTTPBase.h
-#endif
-ia64/sys/inline.h
-Icons.h
-iconv.h
-ieeefp.h
-ifaddrs.h
-image.h
-imagehlp.h
-imm.h
-initguid.h
-initializer_list
-InterfaceDefs.h
-InternetConfig.h
-IntlResources.h
-ints.h
-intshcut.h
-inttypes.h
-iodef.h
-io.h
-IOKit/IOKitLib.h
-IOKit/IOMessage.h
-IOKit/pwr_mgt/IOPMLib.h
-iomanip
-iostream.h
-#if MOZ_JACK==1
-jack/jack.h
-jack/statistics.h
-#endif
-JavaControl.h
-JavaEmbedding/JavaControl.h
-JavaVM/jni.h
-JManager.h
-JNIEnvTests.h
-jni.h
-#if MOZ_SYSTEM_JPEG==1
-jpeglib.h
-#endif
-JVMManagerTests.h
-Kerberos/Kerberos.h
-kernel/image.h
-kernel/OS.h
-LAction.h
-langinfo.h
-LApplication.h
-LArray.h
-LArrayIterator.h
-LAttachable.h
-LAttachment.h
-LaunchServices.h
-lber.h
-LBroadcaster.h
-LButton.h
-lcache.h
-LCaption.h
-LCheckBox.h
-LCicnButton.h
-LClipboard.h
-LCommander.h
-LComparator.h
-LControl.h
-ldap.h
-ldaplog.h
-ldappr.h
-ldap_ssl.h
-LDataStream.h
-ldfcn.h
-LDialogBox.h
-ldif.h
-LDocApplication.h
-LDocument.h
-LDragAndDrop.h
-LDragTask.h
-LEditField.h
-LEditText.h
-LEventDispatcher.h
-LFile.h
-LFileStream.h
-LFileTypeList.h
-LFocusBox.h
-LGrafPortView.h
-LHandleStream.h
-libc_r.h
-libelf.h
-libelf/libelf.h
-libgen.h
-libgnome/gnome-url.h
-libgnome/libgnome.h
-libgnomeui/gnome-icon-lookup.h
-libgnomeui/gnome-icon-theme.h
-libgnomeui/gnome-ui-init.h
-limits.h
-link.h
-#ifdef ANDROID
-linux/android_alarm.h
-linux/ashmem.h
-#endif
-linux/ioprio.h
-linux/kernel.h
-linux/limits.h
-linux/rtc.h
-linux/version.h
-List.h
-Lists.h
-LListBox.h
-LListener.h
-LMenuBar.h
-LMenu.h
-LModelDirector.h
-LModelObject.h
-LModelProperty.h
-loader.h
-locale
-locale.h
-LOffscreenView.h
-logkeys.h
-logstrng.h
-Looper.h
-LowMem.h
-LPane.h
-LPeriodical.h
-LPicture.h
-LPlaceHolder.h
-LPrintout.h
-LProgressBar.h
-LPushButton.h
-LRadioGroup.h
-LRadioGroupView.h
-LRunArray.h
-LScroller.h
-LSharable.h
-LSingleDoc.h
-LStaticText.h
-LStdControl.h
-LStream.h
-LString.h
-LTabGroup.h
-LTabGroupView.h
-LTableArrayStorage.h
-LTableMonoGeometry.h
-LTableSingleSelector.h
-LTableView.h
-LTextEditView.h
-LTextTableView.h
-LUndoer.h
-LVariableArray.h
-LView.h
-LWindow.h
-m68881.h
-MacErrors.h
-MacHeadersCarbon.h
-machine/ansi.h
-machine/builtins.h
-machine/clock.h
-machine/endian.h
-machine/frame.h
-machine/inline.h
-machine/limits.h
-machine/signal.h
-machine/trap.h
-mach/mach_host.h
-mach/mach_init.h
-mach/mach_interface.h
-mach/mach_port.h
-mach-o/dyld.h
-MacLocales.h
-MacMemory.h
-MacTCP.h
-MacTypes.h
-MacWindows.h
-malloc.h
-malloc_np.h
-mapicode.h
-mapidefs.h
-mapiguid.h
-mapi.h
-mapitags.h
-mapiutil.h
-mapix.h
-Math64.h
-math.h
-mbstring.h
-#ifdef ANDROID
-android/native_window.h
-android/native_window_jni.h
-media/AudioEffect.h
-media/AudioSystem.h
-media/ICrypto.h
-media/IOMX.h
-media/MediaProfiles.h
-media/MediaRecorderBase.h
-media/openmax/OMX_Audio.h
-media/stagefright/AACWriter.h
-media/stagefright/AMRWriter.h
-media/stagefright/AudioSource.h
-media/stagefright/DataSource.h
-media/stagefright/foundation/ABase.h
-media/stagefright/foundation/ABitReader.h
-media/stagefright/foundation/ABuffer.h
-media/stagefright/foundation/ADebug.h
-media/stagefright/foundation/AHandler.h
-media/stagefright/foundation/AHandlerReflector.h
-media/stagefright/foundation/ALooper.h
-media/stagefright/foundation/AMessage.h
-media/stagefright/foundation/AString.h
-media/stagefright/foundation/base64.h
-media/stagefright/foundation/hexdump.h
-media/stagefright/MediaBuffer.h
-media/stagefright/MediaBufferGroup.h
-media/stagefright/MediaCodec.h
-media/stagefright/MediaCodecList.h
-media/stagefright/MediaCodecSource.h
-media/stagefright/MediaDefs.h
-media/stagefright/MediaErrors.h
-media/stagefright/MediaExtractor.h
-media/stagefright/MediaSource.h
-media/stagefright/MediaWriter.h
-media/stagefright/MetaData.h
-media/stagefright/MPEG2TSWriter.h
-media/stagefright/MPEG4Writer.h
-media/stagefright/OMXClient.h
-media/stagefright/OMXCodec.h
-media/stagefright/openmax/OMX_Core.h
-media/stagefright/openmax/OMX_Index.h
-media/stagefright/openmax/OMX_IVCommon.h
-media/stagefright/openmax/OMX_Types.h
-media/stagefright/openmax/OMX_Video.h
-media/stagefright/Utils.h
-#endif
-mem.h
-memory.h
-Memory.h
-MenuBar.h
-Menu.h
-Menus.h
-Message.h
-Mime.h
-MixedMode.h
-mlang.h
-mmsystem.h
-model.h
-Movies.h
-mpw/errno.h
-mshtmhst.h
-mshtml.h
-mswsock.h
-Multiprocessing.h
-mutex.h
-Navigation.h
-ncompat.h
-ncurses.h
-netCore.h
-netdb.h
-net/if.h
-netinet/in.h
-netinet/in_systm.h
-netinet/tcp.h
-newexe.h
-new.h
-nl_types.h
-NodeInfo.h
-nsswitch.h
-objbase.h
-objidl.h
-Objsafe.h
-ojiapitests.h
-ole2.h
-oleidl.h
-#ifdef ANDROID
-OMX.h
-OMX_Component.h
-#endif
-OpenGL/OpenGL.h
-OpenTptInternet.h
-OpenTransport.h
-OS.h
-osreldate.h
-OSUtils.h
-Packages.h
-Palettes.h
-PALM_CMN.H
-pango/pango-modules.h
-pango/pangocairo.h
-pango/pangofc-decoder.h
-pango/pangofc-font.h
-pango/pangofc-fontmap.h
-pango/pango-break.h
-pango/pango-fontmap.h
-pango/pango.h
-pango/pangoxft.h
-pango/pango-utils.h
-pascal.h
-Patches.h
-Path.h
-pcfs/pc_dir.h
-Pgenerr.h
-PGenErr.h
-Ph.h
-PLStringFuncs.h
-PMApplication.h
-pmddim.h
-poll.h
-Polygon.h
-port.h
-portable.h
-Power.h
-PP_ClassHeaders.cp
-PP_Constants.h
-PPCToolbox.h
-PP_DebugHeaders.cp
-PP_KeyCodes.h
-PP_Macros.h
-PP_Messages.h
-PP_Prefix.h
-PP_Resources.h
-PP_Types.h
-Printing.h
-Print/PMPrintingDialogExtensions.h
-#ifdef ANDROID
-private/android_filesystem_config.h
-private/qucomextra_p.h
-#endif
-Processes.h
-process.h
-Process.h
-proto/dos.h
-proto/exec.h
-psap.h
-Pt.h
-pthread.h
-pthread_np.h
-pulse/pulseaudio.h
-pwd.h
-Python.h
-QDOffscreen.h
-queue
-Quickdraw.h
-QuickDraw.h
-QuickTimeComponents.h
-quipu/attr.h
-regex.h
-Region.h
-resolv.h
-Resources.h
-Retrace.h
-rld_interface.h
-Roster.h
-rpc.h
-rpcproxy.h
-rpc/types.h
-sane/sane.h
-sane/sanei.h
-sane/saneopts.h
-sanitizer/asan_interface.h
-sched.h
-Scrap.h
-Screen.h
-Script.h
-ScrollBar.h
-sec.h
-secrng.h
-security.h
-secutil.h
-semaphore.h
-servprov.h
-setjmp.h
-SFNTLayoutTypes.h
-SFNTTypes.h
-sha1.h
-share.h
-shellapi.h
-shlguid.h
-shlobj.h
-sigcontext.h
-signal.h
-SimpleGameSound.h
-SIOUX.h
-size_t.h
-sndio.h
-someincludefile.h
-Sound.h
-soundcard.h
-sqlite3.h
-sstream
-#ifdef ANDROID
-stagefright/AACWriter.h
-stagefright/AMRWriter.h
-stagefright/AudioSource.h
-stagefright/DataSource.h
-stagefright/foundation/ABase.h
-stagefright/foundation/ABitReader.h
-stagefright/foundation/ABuffer.h
-stagefright/foundation/ADebug.h
-stagefright/foundation/AHandler.h
-stagefright/foundation/AHandlerReflector.h
-stagefright/foundation/ALooper.h
-stagefright/foundation/AMessage.h
-stagefright/foundation/AString.h
-stagefright/foundation/base64.h
-stagefright/foundation/hexdump.h
-stagefright/MediaBuffer.h
-stagefright/MediaBufferGroup.h
-stagefright/MediaCodec.h
-stagefright/MediaDefs.h
-stagefright/MediaErrors.h
-stagefright/MediaExtractor.h
-stagefright/MediaSource.h
-stagefright/MediaWriter.h
-stagefright/MetaData.h
-stagefright/MPEG2TSWriter.h
-stagefright/MPEG4Writer.h
-stagefright/OMXCodec.h
-stagefright/OMXClient.h
-stagefright/openmax/OMX_Component.h
-stagefright/openmax/OMX_Core.h
-stagefright/openmax/OMX_Index.h
-stagefright/openmax/OMX_IVCommon.h
-stagefright/openmax/OMX_Types.h
-stagefright/openmax/OMX_Video.h
-stagefright/Utils.h
-#endif
-StandardFile.h
-starlet.h
-stat.h
-statreg.cpp
-statreg.h
-stdarg.h
-stdbool.h
-stddef.h
-stdint.h
-stdio.h
-stdlib.h
-storage/FindDirectory.h
-StorageKit.h
-StringCompare.h
-string.h
-String.h
-strings.h
-Strings.h
-StringView.h
-stropts.h
-strstrea.h
-structs.h
-stsdef.h
-SupportDefs.h
-support/String.h
-support/SupportDefs.h
-support/TLS.h
-#ifdef ANDROID
-suspend/autosuspend.h
-#endif
-svrcore.h
-symconst.h
-sym.h
-synch.h
-syncmgr.h
-sys/atomic_op.h
-sys/auxv.h
-sys/bitypes.h
-sys/byteorder.h
-syscall.h
-sys/cdefs.h
-sys/cfgodm.h
-sys/elf.h
-sys/endian.h
-sys/epoll.h
-sys/errno.h
-sys/eventfd.h
-sys/fault.h
-sys/fcntl.h
-sys/file.h
-sys/filio.h
-sys/frame.h
-sys/immu.h
-sys/inotify.h
-sys/inttypes.h
-sys/ioccom.h
-sys/ioctl.h
-sys/ipc.h
-sys/klog.h
-sys/ldr.h
-sys/link.h
-sys/locking.h
-syslog.h
-sys/lwp.h
-sys/machine.h
-sys/mman.h
-sys/mmu.h
-sys/mount.h
-sys/mpctl.h
-sys/param.h
-sys/pda.h
-sys/poll.h
-sys/ppc.h
-sys/prctl.h
-sys/priv.h
-sys/procfs.h
-sys/pstat.h
-sys/ptrace.h
-sys/queue.h
-sys/quota.h
-sys/reboot.h
-sys/reg.h
-sys/regset.h
-sys/resource.h
-sys/sched.h
-sys/select.h
-sys/sem.h
-sys/sendfile.h
-sys/shm.h
-sys/siginfo.h
-sys/signal.h
-sys/socket.h
-sys/sockio.h
-sys/sparc/frame.h
-sys/stack.h
-sys/statfs.h
-sys/stat.h
-sys/statvfs.h
-sys/syscall.h
-sys/sysctl.h
-sys/sysinfo.h
-sys/sysmacros.h
-sys/sysmp.h
-sys/syssgi.h
-sys/system_properties.h
-sys/systeminfo.h
-sys/timeb.h
-sys/time.h
-sys/times.h
-sys/ttycom.h
-sys/types.h
-sys/ucontext.h
-sys/uio.h
-sys/un.h
-sys/unistd.h
-sys/utsname.h
-sys/vfs.h
-sys/wait.h
-#ifdef ANDROID
-sysutils/NetlinkEvent.h
-system/audio.h
-system/graphics.h
-system/window.h
-#endif
-tables.h
-TArray.h
-TArrayIterator.h
-task.h
-tchar.h
-TCHAR.H
-termios.h
-TextCommon.h
-TextEdit.h
-TextEncodingConverter.h
-TextServices.h
-TextUtils.h
-TextView.h
-th/PCR_Th.h
-thread.h
-ThreadManagerTests.h
-Threads.h
-time.h
-Timer.h
-tlhelp32.h
-ToolUtils.h
-tr1/functional
-trace.h
-Traps.h
-typeinfo
-types.h
-Types.h
-UAppleEventsMgr.h
-UAttachments.h
-ucontext.h
-uconv.h
-UCursor.h
-ucx$inetdef.h
-UDebugging.h
-UDesktop.h
-UDrawingState.h
-UDrawingUtils.h
-UEnvironment.h
-UEventMgr.h
-UException.h
-UExtractFromAEDesc.h
-UGWorld.h
-#ifdef ANDROID
-ui/ANativeObjectBase.h
-ui/egl/android_natives.h
-ui/Fence.h
-ui/FramebufferNativeWindow.h
-ui/GraphicBuffer.h
-ui/Rect.h
-ui/Region.h
-#endif
-UKeyFilters.h
-ulocks.h
-ulserrno.h
-UMemoryMgr.h
-UModalDialogs.h
-UNavServicesDialogs.h
-UnicodeBlockObjects.h
-UnicodeConverter.h
-UnicodeUtilities.h
-unidef.h
-unikbd.h
-unistd.h
-unix.h
-unixio.h
-unknwn.h
-UPrinting.h
-UQuickTime.h
-UReanimator.h
-URegions.h
-URegistrar.h
-UResourceMgr.h
-#ifdef ANDROID
-utils/BitSet.h
-utils/CallStack.h
-utils/Errors.h
-utils/FileMap.h
-utils/KeyedVector.h
-utils/List.h
-utils/Log.h
-utils/Looper.h
-utils/PropertyMap.h
-utils/RefBase.h
-utils/String16.h
-utils/String8.h
-utils/threads.h
-utils/TextOutput.h
-utils/Timers.h
-utils/Trace.h
-utils/TypeHelpers.h
-utils/Unicode.h
-utils/Vector.h
-utils/VectorImpl.h
-#endif
-urlhist.h
-urlmon.h
-UScrap.h
-UScreenPort.h
-UTCUtils.h
-UTETextAction.h
-UTEViewTextAction.h
-UTextEdit.h
-UTextTraits.h
-utime.h
-UWindows.h
-values.h
-varargs.h
-vcclr.h
-View.h
-Volume.h
-#ifdef ANDROID
-vr/gvr/capi/include/gvr.h
-vr/gvr/capi/include/gvr_controller.h
-#endif
-wab.h
-wait.h
-wchar.h
-wctype.h
-winbase.h
-win/compobj.h
-windef.h
-Window.h
-windows.h
-Windows.h
-windowsx.h
-Wininet.h
-winnls.h
-winperf.h
-winreg.h
-Winreg.h
-winsock2.h
-winsock.h
-winspool.h
-winsvc.h
-winuser.h
-winver.h
-wmem.h
-workbench/startup.h
-wtypes.h
-wx/image.h
-wx/listctrl.h
-wx/log.h
-wx/toolbar.h
-wx/wx.h
-wx/xrc/xmlres.h
-xcb/xcb.h
-xcb/shm.h
-X11/cursorfont.h
-X11/extensions/Print.h
-X11/extensions/shape.h
-X11/extensions/scrnsaver.h
-X11/extensions/XShm.h
-X11/extensions/Xrender.h
-X11/extensions/Xfixes.h
-X11/extensions/Xdamage.h
-X11/extensions/Xcomposite.h
-X11/ImUtil.h
-X11/Intrinsic.h
-X11/keysymdef.h
-X11/keysym.h
-X11/Shell.h
-X11/StringDefs.h
-X11/Xatom.h
-X11/Xft/Xft.h
-X11/Xfuncproto.h
-X11/X.h
-X11/XKBlib.h
-X11/Xlib.h
-X11/Xlibint.h
-X11/Xlib-xcb.h
-X11/Xlocale.h
-X11/Xos.h
-X11/Xutil.h
-zmouse.h
-soundtouch/SoundTouch.h
-soundtouch/SoundTouchFactory.h
-#if MOZ_LIBAV_FFT==1
-libavcodec/avfft.h
-#endif
-#if MOZ_SYSTEM_PNG==1
-png.h
-#endif
-#if MOZ_SYSTEM_ZLIB==1
-zlib.h
-#endif
-#ifdef MOZ_ENABLE_STARTUP_NOTIFICATION
-libsn/sn.h
-libsn/sn-common.h
-libsn/sn-launchee.h
-libsn/sn-launcher.h
-libsn/sn-monitor.h
-libsn/sn-util.h
-#endif
-#if MOZ_SYSTEM_HUNSPELL==1
-hunspell.hxx
-#endif
-#if MOZ_SYSTEM_BZ2==1
-bzlib.h
-#endif
-gio/gio.h
-#if MOZ_SYSTEM_LIBEVENT==1
-event.h
-event2/event.h
-event2/event_compat.h
-event2/event_struct.h
-#else
-sys/event.h
-#endif
-#ifdef MOZ_ENABLE_LIBPROXY
-proxy.h
-#endif
-#ifdef MOZ_ENABLE_CONTENTMANAGER
-SelectSingleContentItemPage.h
-SelectMultipleContentItemsPage.h
-QtSparql/qsparqlconnection.h
-QtSparql/qsparqlquery.h
-QtSparql/qsparqlresult.h
-#endif
-
-#if MOZ_TREE_PIXMAN!=1
-pixman.h
-#endif
-#if MOZ_SYSTEM_LIBVPX==1
-vpx/svc_context.h
-vpx/vpx_codec.h
-vpx/vpx_decoder.h
-vpx/vpx_encoder.h
-vpx/vp8cx.h
-vpx/vp8dx.h
-vpx_mem/vpx_mem.h
-#endif
-gst/gst.h
-gst/app/gstappsink.h
-gst/app/gstappsrc.h
-gst/video/video.h
-sys/msg.h
-sys/ipc.h
-sys/thr.h
-sys/user.h
-kvm.h
-spawn.h
-err.h
-xlocale.h
-#ifdef MOZ_SYSTEM_ICU
-unicode/locid.h
-unicode/numsys.h
-unicode/plurrule.h
-unicode/timezone.h
-unicode/ucal.h
-unicode/uchar.h
-unicode/uclean.h
-unicode/ucol.h
-unicode/udat.h
-unicode/udatpg.h
-unicode/udisplaycontext.h
-unicode/uenum.h
-unicode/unistr.h
-unicode/unorm.h
-unicode/unum.h
-unicode/upluralrules.h
-unicode/ureldatefmt.h
-unicode/ustring.h
-unicode/utypes.h
-#endif
-libutil.h
-unwind.h
-fenv.h
new file mode 100644
--- /dev/null
+++ b/config/system-headers.mozbuild
@@ -0,0 +1,1346 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+system_headers = [
+    'A4Stuff.h',
+    'activscp.h',
+    'AEDataModel.h',
+    'AEObjects.h',
+    'AEPackObject.h',
+    'AERegistry.h',
+    'AEUtils.h',
+    'afxcmn.h',
+    'afxcoll.h',
+    'afxcview.h',
+    'afxdisp.h',
+    'afxdtctl.h',
+    'afxext.h',
+    'afxmt.h',
+    'afxpriv.h',
+    'afxtempl.h',
+    'afxwin.h',
+    'Aliases.h',
+    'all.h',
+    'alloca.h',
+    'alloc.h',
+    'alsa/asoundlib.h',
+    'ansi_parms.h',
+    'a.out.h',
+    'app/Cursor.h',
+    'Appearance.h',
+    'AppFileInfo.h',
+    'AppKit.h',
+    'AppleEvents.h',
+    'Application.h',
+    'app/Message.h',
+    'app/MessageRunner.h',
+    'arpa/inet.h',
+    'arpa/nameser.h',
+    'array',
+    'asm/page.h',
+    'asm/sigcontext.h',
+    'asm/signal.h',
+    'ASRegistry.h',
+    'assert.h',
+    'atk/atk.h',
+    'atlcom.h',
+    'atlconv.h',
+    'atlctl.cpp',
+    'atlctl.h',
+    'ATLCTL.H',
+    'atlhost.h',
+    'atlimpl.cpp',
+    'atlwin.cpp',
+    'atomic.h',
+    'ATSTypes.h',
+    'ATSUnicode.h',
+    'Balloons.h',
+    'base64.h',
+    'base/pblock.h',
+    'base/PCR_Base.h',
+    'base/session.h',
+    'basetyps.h',
+    'be/app/Application.h',
+    'Beep.h',
+    'be/kernel/image.h',
+    'be/kernel/OS.h',
+    'bfd.h',
+    'Bitmap.h',
+    'bitset',
+    'blapi.h',
+    'blapit.h',
+    'bsd/libc.h',
+    'bsd/syscall.h',
+    'bstring.h',
+    'builtin.h',
+    'Button.h',
+    'byteswap.h',
+    'cairo-atsui.h',
+    'cairo-beos.h',
+    'cairo-directfb.h',
+    'cairo-ft.h',
+    'cairo-glitz.h',
+    'cairo-gobject.h',
+    'cairo.h',
+    'cairo-pdf.h',
+    'cairo-ps.h',
+    'cairo-qpainter.h',
+    'cairo-qt.h',
+    'cairo-quartz.h',
+    'cairo-tee.h',
+    'cairo-win32.h',
+    'cairo-xlib.h',
+    'cairo-xlib-xrender.h',
+    'callconv.h',
+    'Carbon/Carbon.h',
+    'CarbonEvents.h',
+    'Carbon.h',
+    'c_asm.h',
+    'cctype',
+    'cderr.h',
+    'cerrno',
+    'certdb.h',
+    'cert.h',
+    'certt.h',
+    'CFBase.h',
+    'CFBundle.h',
+    'CFData.h',
+    'CFDictionary.h',
+    'cf.h',
+    'CFNumber.h',
+    'CFPlugIn.h',
+    'CFPreferences.h',
+    'CFString.h',
+    'CFURL.h',
+    'CGAffineTransform.h',
+    'CheckBox.h',
+    'ciferfam.h',
+    'Clipboard.h',
+    'cmmf.h',
+    'cmmft.h',
+    'cmplrs/stsupport.h',
+    'cms.h',
+    'cmsreclist.h',
+    'cmst.h',
+    'Cocoa/Cocoa.h',
+    'CodeFragments.h',
+    'comdef.h',
+    'commctrl.h',
+    'COMMCTRL.H',
+    'commdlg.h',
+    'compat.h',
+    'complex',
+    'condapi.h',
+    'ConditionalMacros.h',
+    'conio.h',
+    'console.h',
+    'ControlDefinitions.h',
+    'Controls.h',
+    'CoreFoundation/CoreFoundation.h',
+    'CoreServices/CoreServices.h',
+    'CPalmRec.cpp',
+    'Cpalmrec.h',
+    'CPCatgry.cpp',
+    'CPDbBMgr.h',
+    'CPString.cpp',
+    'CPString.h',
+    'crmf.h',
+    'crmft.h',
+    'crtdbg.h',
+    'crt_externs.h',
+    'crypt.h',
+    'cryptohi.h',
+    'cryptoht.h',
+    'cstddef',
+    'ctime',
+    'ctype.h',
+    'curl/curl.h',
+    'curl/easy.h',
+    'curses.h',
+    'cxxabi.h',
+    'DateTimeUtils.h',
+    'dbus/dbus-glib.h',
+    'dbus/dbus-glib-lowlevel.h',
+    'dbus/dbus.h',
+    'ddeml.h',
+    'Debug.h',
+    'dem.h',
+    'descrip.h',
+    'Devices.h',
+    'dfb_types.h',
+    'dfiff.h',
+    'dgiff.h',
+    'Dialogs.h',
+    'direct/build.h',
+    'direct/clock.h',
+    'direct/conf.h',
+    'direct/debug.h',
+    'direct/direct.h',
+    'directfb_keyboard.h',
+    'directfb_strings.h',
+    'direct.h',
+    'direct/hash.h',
+    'direct/interface.h',
+    'direct/interface_implementation.h',
+    'direct/list.h',
+    'direct/log.h',
+    'direct/memcpy.h',
+    'direct/mem.h',
+    'direct/messages.h',
+    'direct/modules.h',
+    'direct/serial.h',
+    'direct/signals.h',
+    'direct/stream.h',
+    'direct/system.h',
+    'direct/thread.h',
+    'direct/trace.h',
+    'direct/tree.h',
+    'direct/types.h',
+    'direct/utf8.h',
+    'direct/util.h',
+    'dirent.h',
+    'DiskInit.h',
+    'dlfcn.h',
+    'dlgs.h',
+    'dl.h',
+    'docobj.h',
+    'dos/dosextens.h',
+    'dos.h',
+    'Drag.h',
+    'DriverServices.h',
+    'DriverSynchronization.h',
+    'DropInPanel.h',
+    'dvidef.h',
+    'ecl-exp.h',
+    'elf.h',
+    'endian.h',
+    'Entry.h',
+    'err.h',
+    'errno.h',
+    'Errors.h',
+    'Events.h',
+    'exception',
+    'exdisp.h',
+    'ExDisp.h',
+    'exe386.h',
+    'execinfo.h',
+    'extras.h',
+    'fcntl.h',
+    'features.h',
+    'fenv.h',
+    'ffi.h',
+    'fibdef.h',
+    'File.h',
+    'filehdr.h',
+    'files.h',
+    'Files.h',
+    'FindDirectory.h',
+    'Finder.h',
+    'FinderRegistry.h',
+    'FixMath.h',
+    'float.h',
+    'fnmatch.h',
+    'Folders.h',
+    'fontconfig/fcfreetype.h',
+    'fontconfig/fontconfig.h',
+    'Font.h',
+    'Fonts.h',
+    'fp.h',
+    'fpieee.h',
+    'frame/log.h',
+    'frame/req.h',
+    'freetype/freetype.h',
+    'freetype/ftadvanc.h',
+    'freetype/ftbitmap.h',
+    'freetype/ftcache.h',
+    'freetype/ftfntfmt.h',
+    'freetype/ftglyph.h',
+    'freetype/ftlcdfil.h',
+    'freetype/ftoutln.h',
+    'freetype/ftsizes.h',
+    'freetype/ftsynth.h',
+    'freetype/ftxf86.h',
+    'freetype.h',
+    'freetype/t1tables.h',
+    'freetype/ttnameid.h',
+    'freetype/tttables.h',
+    'fribidi/fribidi.h',
+    'FSp_fopen.h',
+    'fstream',
+    'fstream.h',
+    'ft2build.h',
+    'ftadvanc.h',
+    'ftbitmap.h',
+    'ftcache.h',
+    'ftfntfmt.h',
+    'ftglyph.h',
+    'ftlcdfil.h',
+    'ftoutln.h',
+    'fts.h',
+    'ftsizes.h',
+    'ftsynth.h',
+    'ftxf86.h',
+    'fusion/arena.h',
+    'fusion/build.h',
+    'fusion/call.h',
+    'fusion/conf.h',
+    'fusion/fusion.h',
+    'fusion/fusion_internal.h',
+    'fusion/hash.h',
+    'fusion/lock.h',
+    'fusion/object.h',
+    'fusion/property.h',
+    'fusion/protocol.h',
+    'fusion/reactor.h',
+    'fusion/ref.h',
+    'fusion/shmalloc.h',
+    'fusion/shm/pool.h',
+    'fusion/shm/shm.h',
+    'fusion/shm/shm_internal.h',
+    'fusion/types.h',
+    'fusion/vector.h',
+    'gconf/gconf-client.h',
+    'Gdiplus.h',
+    'gdk/gdkdirectfb.h',
+    'gdk/gdk.h',
+    'gdk/gdkkeysyms.h',
+    'gdk/gdkprivate.h',
+    'gdk/gdkwayland.h',
+    'gdk/gdkx.h',
+    'gdk-pixbuf/gdk-pixbuf.h',
+    'Gestalt.h',
+    'getopt.h',
+    'gio/gio.h',
+    'glibconfig.h',
+    'glib.h',
+    'glib-object.h',
+    'glob.h',
+    'gmodule.h',
+    'gnome.h',
+    'gnu/libc-version.h',
+    'gps.h',
+    'grp.h',
+    'gssapi_generic.h',
+    'gssapi/gssapi_generic.h',
+    'gssapi/gssapi.h',
+    'gssapi.h',
+    'gst/app/gstappsink.h',
+    'gst/app/gstappsrc.h',
+    'gst/gst.h',
+    'gst/video/video.h',
+    'gtk/gtk.h',
+    'gtk/gtkunixprint.h',
+    'gtk/gtkx.h',
+    'hasht.h',
+    'HIToolbox/HIToolbox.h',
+    'hlink.h',
+    'ia64/sys/inline.h',
+    'Icons.h',
+    'iconv.h',
+    'ieeefp.h',
+    'ifaddrs.h',
+    'image.h',
+    'imagehlp.h',
+    'imm.h',
+    'initguid.h',
+    'initializer_list',
+    'InterfaceDefs.h',
+    'InternetConfig.h',
+    'IntlResources.h',
+    'ints.h',
+    'intshcut.h',
+    'inttypes.h',
+    'iodef.h',
+    'io.h',
+    'IOKit/IOKitLib.h',
+    'IOKit/IOMessage.h',
+    'IOKit/pwr_mgt/IOPMLib.h',
+    'iomanip',
+    'iostream.h',
+    'jar-ds.h',
+    'jarfile.h',
+    'jar.h',
+    'JavaControl.h',
+    'JavaEmbedding/JavaControl.h',
+    'JavaVM/jni.h',
+    'JManager.h',
+    'JNIEnvTests.h',
+    'jni.h',
+    'JVMManagerTests.h',
+    'Kerberos/Kerberos.h',
+    'kernel/image.h',
+    'kernel/OS.h',
+    'key.h',
+    'keyhi.h',
+    'keyt.h',
+    'keythi.h',
+    'kvm.h',
+    'LAction.h',
+    'langinfo.h',
+    'LApplication.h',
+    'LArray.h',
+    'LArrayIterator.h',
+    'LAttachable.h',
+    'LAttachment.h',
+    'LaunchServices.h',
+    'lber.h',
+    'LBroadcaster.h',
+    'LButton.h',
+    'lcache.h',
+    'LCaption.h',
+    'LCheckBox.h',
+    'LCicnButton.h',
+    'LClipboard.h',
+    'LCommander.h',
+    'LComparator.h',
+    'LControl.h',
+    'ldap.h',
+    'ldaplog.h',
+    'ldappr.h',
+    'ldap_ssl.h',
+    'LDataStream.h',
+    'ldfcn.h',
+    'LDialogBox.h',
+    'ldif.h',
+    'LDocApplication.h',
+    'LDocument.h',
+    'LDragAndDrop.h',
+    'LDragTask.h',
+    'LEditField.h',
+    'LEditText.h',
+    'LEventDispatcher.h',
+    'LFile.h',
+    'LFileStream.h',
+    'LFileTypeList.h',
+    'LFocusBox.h',
+    'LGrafPortView.h',
+    'LHandleStream.h',
+    'libc_r.h',
+    'libelf.h',
+    'libelf/libelf.h',
+    'libgen.h',
+    'libgnome/gnome-url.h',
+    'libgnome/libgnome.h',
+    'libgnomeui/gnome-icon-lookup.h',
+    'libgnomeui/gnome-icon-theme.h',
+    'libgnomeui/gnome-ui-init.h',
+    'libutil.h',
+    'limits.h',
+    'link.h',
+    'linux/ioprio.h',
+    'linux/kernel.h',
+    'linux/limits.h',
+    'linux/rtc.h',
+    'linux/version.h',
+    'List.h',
+    'Lists.h',
+    'LListBox.h',
+    'LListener.h',
+    'LMenuBar.h',
+    'LMenu.h',
+    'LModelDirector.h',
+    'LModelObject.h',
+    'LModelProperty.h',
+    'loader.h',
+    'locale',
+    'locale.h',
+    'LOffscreenView.h',
+    'logkeys.h',
+    'logstrng.h',
+    'Looper.h',
+    'LowMem.h',
+    'LPane.h',
+    'LPeriodical.h',
+    'LPicture.h',
+    'LPlaceHolder.h',
+    'LPrintout.h',
+    'LProgressBar.h',
+    'LPushButton.h',
+    'LRadioGroup.h',
+    'LRadioGroupView.h',
+    'LRunArray.h',
+    'LScroller.h',
+    'LSharable.h',
+    'LSingleDoc.h',
+    'LStaticText.h',
+    'LStdControl.h',
+    'LStream.h',
+    'LString.h',
+    'LTabGroup.h',
+    'LTabGroupView.h',
+    'LTableArrayStorage.h',
+    'LTableMonoGeometry.h',
+    'LTableSingleSelector.h',
+    'LTableView.h',
+    'LTextEditView.h',
+    'LTextTableView.h',
+    'LUndoer.h',
+    'LVariableArray.h',
+    'LView.h',
+    'LWindow.h',
+    'm68881.h',
+    'MacErrors.h',
+    'MacHeadersCarbon.h',
+    'machine/ansi.h',
+    'machine/builtins.h',
+    'machine/clock.h',
+    'machine/endian.h',
+    'machine/frame.h',
+    'machine/inline.h',
+    'machine/limits.h',
+    'machine/signal.h',
+    'machine/trap.h',
+    'mach/mach_host.h',
+    'mach/mach_init.h',
+    'mach/mach_interface.h',
+    'mach/mach_port.h',
+    'mach-o/dyld.h',
+    'MacLocales.h',
+    'MacMemory.h',
+    'MacTCP.h',
+    'MacTypes.h',
+    'MacWindows.h',
+    'malloc.h',
+    'malloc_np.h',
+    'mapicode.h',
+    'mapidefs.h',
+    'mapiguid.h',
+    'mapi.h',
+    'mapitags.h',
+    'mapiutil.h',
+    'mapix.h',
+    'Math64.h',
+    'math.h',
+    'mbstring.h',
+    'mem.h',
+    'memory.h',
+    'Memory.h',
+    'MenuBar.h',
+    'Menu.h',
+    'Menus.h',
+    'Message.h',
+    'Mime.h',
+    'MixedMode.h',
+    'mlang.h',
+    'mmsystem.h',
+    'model.h',
+    'Movies.h',
+    'mpw/errno.h',
+    'mshtmhst.h',
+    'mshtml.h',
+    'mswsock.h',
+    'Multiprocessing.h',
+    'mutex.h',
+    'Navigation.h',
+    'ncompat.h',
+    'ncurses.h',
+    'netCore.h',
+    'netdb.h',
+    'net/if.h',
+    'netinet/in.h',
+    'netinet/in_systm.h',
+    'netinet/tcp.h',
+    'newexe.h',
+    'new.h',
+    'nl_types.h',
+    'NodeInfo.h',
+    'nspr.h',
+    'nssb64.h',
+    'nssb64t.h',
+    'nssbase.h',
+    'nssbaset.h',
+    'nssck.api',
+    'nssckbi.h',
+    'nssckepv.h',
+    'nssckft.h',
+    'nssckfwc.h',
+    'nssckfw.h',
+    'nssckfwt.h',
+    'nssckg.h',
+    'nssckmdt.h',
+    'nssckt.h',
+    'nss.h',
+    'nssilckt.h',
+    'nssilock.h',
+    'nsslocks.h',
+    'nssrwlk.h',
+    'nssrwlkt.h',
+    'nssutil.h',
+    'nsswitch.h',
+    'objbase.h',
+    'objidl.h',
+    'Objsafe.h',
+    'ocsp.h',
+    'ocspt.h',
+    'ojiapitests.h',
+    'ole2.h',
+    'oleidl.h',
+    'OpenGL/OpenGL.h',
+    'OpenTptInternet.h',
+    'OpenTransport.h',
+    'OS.h',
+    'osreldate.h',
+    'OSUtils.h',
+    'p12.h',
+    'p12plcy.h',
+    'p12t.h',
+    'Packages.h',
+    'Palettes.h',
+    'PALM_CMN.H',
+    'pango/pango-break.h',
+    'pango/pangocairo.h',
+    'pango/pangofc-decoder.h',
+    'pango/pangofc-font.h',
+    'pango/pangofc-fontmap.h',
+    'pango/pango-fontmap.h',
+    'pango/pango.h',
+    'pango/pango-modules.h',
+    'pango/pango-utils.h',
+    'pango/pangoxft.h',
+    'pascal.h',
+    'Patches.h',
+    'Path.h',
+    'pcfs/pc_dir.h',
+    'Pgenerr.h',
+    'PGenErr.h',
+    'Ph.h',
+    'pixman.h',
+    'pk11func.h',
+    'pk11pqg.h',
+    'pk11priv.h',
+    'pk11pub.h',
+    'pk11sdr.h',
+    'pkcs11f.h',
+    'pkcs11.h',
+    'pkcs11n.h',
+    'pkcs11p.h',
+    'pkcs11t.h',
+    'pkcs11u.h',
+    'pkcs12.h',
+    'pkcs12t.h',
+    'pkcs7t.h',
+    'plarena.h',
+    'plarenas.h',
+    'plbase64.h',
+    'plerror.h',
+    'plgetopt.h',
+    'plhash.h',
+    'plstr.h',
+    'PLStringFuncs.h',
+    'PMApplication.h',
+    'pmddim.h',
+    'poll.h',
+    'Polygon.h',
+    'portable.h',
+    'port.h',
+    'portreg.h',
+    'Power.h',
+    'PP_ClassHeaders.cp',
+    'PP_Constants.h',
+    'PPCToolbox.h',
+    'PP_DebugHeaders.cp',
+    'PP_KeyCodes.h',
+    'PP_Macros.h',
+    'PP_Messages.h',
+    'PP_Prefix.h',
+    'PP_Resources.h',
+    'PP_Types.h',
+    'pratom.h',
+    'prbit.h',
+    'prclist.h',
+    'prcmon.h',
+    'prcountr.h',
+    'prcpucfg.h',
+    'prcvar.h',
+    'prdtoa.h',
+    'preenc.h',
+    'prenv.h',
+    'prerr.h',
+    'prerror.h',
+    'prinet.h',
+    'prinit.h',
+    'prinrval.h',
+    'Printing.h',
+    'Print/PMPrintingDialogExtensions.h',
+    'prio.h',
+    'pripcsem.h',
+    'private',
+    'prlink.h',
+    'prlock.h',
+    'prlog.h',
+    'prlong.h',
+    'prmem.h',
+    'prmon.h',
+    'prmwait.h',
+    'prnetdb.h',
+    'Processes.h',
+    'process.h',
+    'Process.h',
+    'prolock.h',
+    'proto/dos.h',
+    'proto/exec.h',
+    'prpdce.h',
+    'prprf.h',
+    'prproces.h',
+    'prrng.h',
+    'prrwlock.h',
+    'prshma.h',
+    'prshm.h',
+    'prsystem.h',
+    'prthread.h',
+    'prtime.h',
+    'prtpool.h',
+    'prtrace.h',
+    'prtypes.h',
+    'prvrsion.h',
+    'prwin16.h',
+    'psap.h',
+    'Pt.h',
+    'pthread.h',
+    'pthread_np.h',
+    'pulse/pulseaudio.h',
+    'pwd.h',
+    'Python.h',
+    'QDOffscreen.h',
+    'queue',
+    'Quickdraw.h',
+    'QuickDraw.h',
+    'QuickTimeComponents.h',
+    'quipu/attr.h',
+    'regex.h',
+    'Region.h',
+    'resolv.h',
+    'Resources.h',
+    'Retrace.h',
+    'rld_interface.h',
+    'Roster.h',
+    'rpc.h',
+    'rpcproxy.h',
+    'rpc/types.h',
+    'sane/sane.h',
+    'sane/sanei.h',
+    'sane/saneopts.h',
+    'sanitizer/asan_interface.h',
+    'sched.h',
+    'Scrap.h',
+    'Screen.h',
+    'Script.h',
+    'ScrollBar.h',
+    'secasn1.h',
+    'secasn1t.h',
+    'seccomon.h',
+    'secder.h',
+    'secdert.h',
+    'secdig.h',
+    'secdigt.h',
+    'secerr.h',
+    'sec.h',
+    'sechash.h',
+    'secitem.h',
+    'secmime.h',
+    'secmod.h',
+    'secmodt.h',
+    'secoid.h',
+    'secoidt.h',
+    'secpkcs5.h',
+    'secpkcs7.h',
+    'secport.h',
+    'secrng.h',
+    'security.h',
+    'secutil.h',
+    'semaphore.h',
+    'servprov.h',
+    'setjmp.h',
+    'SFNTLayoutTypes.h',
+    'SFNTTypes.h',
+    'sha1.h',
+    'share.h',
+    'shellapi.h',
+    'shlguid.h',
+    'shlobj.h',
+    'shsign.h',
+    'sigcontext.h',
+    'signal.h',
+    'SimpleGameSound.h',
+    'SIOUX.h',
+    'size_t.h',
+    'smime.h',
+    'sndio.h',
+    'someincludefile.h',
+    'soundcard.h',
+    'Sound.h',
+    'soundtouch/SoundTouchFactory.h',
+    'soundtouch/SoundTouch.h',
+    'spawn.h',
+    'sqlite3.h',
+    'sslerr.h',
+    'ssl.h',
+    'sslproto.h',
+    'sslt.h',
+    'sstream',
+    'StandardFile.h',
+    'starlet.h',
+    'stat.h',
+    'statreg.cpp',
+    'statreg.h',
+    'stdarg.h',
+    'stdbool.h',
+    'stddef.h',
+    'stdint.h',
+    'stdio.h',
+    'stdlib.h',
+    'storage/FindDirectory.h',
+    'StorageKit.h',
+    'StringCompare.h',
+    'string.h',
+    'String.h',
+    'strings.h',
+    'Strings.h',
+    'StringView.h',
+    'stropts.h',
+    'strstrea.h',
+    'structs.h',
+    'stsdef.h',
+    'SupportDefs.h',
+    'support/String.h',
+    'support/SupportDefs.h',
+    'support/TLS.h',
+    'svrcore.h',
+    'symconst.h',
+    'sym.h',
+    'synch.h',
+    'syncmgr.h',
+    'sys/atomic_op.h',
+    'sys/auxv.h',
+    'sys/bitypes.h',
+    'sys/byteorder.h',
+    'syscall.h',
+    'sys/cdefs.h',
+    'sys/cfgodm.h',
+    'sys/elf.h',
+    'sys/endian.h',
+    'sys/epoll.h',
+    'sys/errno.h',
+    'sys/eventfd.h',
+    'sys/fault.h',
+    'sys/fcntl.h',
+    'sys/file.h',
+    'sys/filio.h',
+    'sys/frame.h',
+    'sys/immu.h',
+    'sys/inotify.h',
+    'sys/inttypes.h',
+    'sys/ioccom.h',
+    'sys/ioctl.h',
+    'sys/ipc.h',
+    'sys/klog.h',
+    'sys/ldr.h',
+    'sys/link.h',
+    'sys/locking.h',
+    'syslog.h',
+    'sys/lwp.h',
+    'sys/machine.h',
+    'sys/mman.h',
+    'sys/mmu.h',
+    'sys/mount.h',
+    'sys/mpctl.h',
+    'sys/msg.h',
+    'sys/param.h',
+    'sys/pda.h',
+    'sys/poll.h',
+    'sys/ppc.h',
+    'sys/prctl.h',
+    'sys/priv.h',
+    'sys/procfs.h',
+    'sys/pstat.h',
+    'sys/ptrace.h',
+    'sys/queue.h',
+    'sys/quota.h',
+    'sys/reboot.h',
+    'sys/reg.h',
+    'sys/regset.h',
+    'sys/resource.h',
+    'sys/sched.h',
+    'sys/select.h',
+    'sys/sem.h',
+    'sys/sendfile.h',
+    'sys/shm.h',
+    'sys/siginfo.h',
+    'sys/signal.h',
+    'sys/socket.h',
+    'sys/sockio.h',
+    'sys/sparc/frame.h',
+    'sys/stack.h',
+    'sys/statfs.h',
+    'sys/stat.h',
+    'sys/statvfs.h',
+    'sys/syscall.h',
+    'sys/sysctl.h',
+    'sys/sysinfo.h',
+    'sys/sysmacros.h',
+    'sys/sysmp.h',
+    'sys/syssgi.h',
+    'sys/systeminfo.h',
+    'sys/system_properties.h',
+    'sys/thr.h',
+    'sys/timeb.h',
+    'sys/time.h',
+    'sys/times.h',
+    'sys/ttycom.h',
+    'sys/types.h',
+    'sys/ucontext.h',
+    'sys/uio.h',
+    'sys/un.h',
+    'sys/unistd.h',
+    'sys/user.h',
+    'sys/utsname.h',
+    'sys/vfs.h',
+    'sys/wait.h',
+    't1tables.h',
+    'tables.h',
+    'TArray.h',
+    'TArrayIterator.h',
+    'task.h',
+    'tchar.h',
+    'TCHAR.H',
+    'termios.h',
+    'TextCommon.h',
+    'TextEdit.h',
+    'TextEncodingConverter.h',
+    'TextServices.h',
+    'TextUtils.h',
+    'TextView.h',
+    'th/PCR_Th.h',
+    'thread.h',
+    'ThreadManagerTests.h',
+    'Threads.h',
+    'time.h',
+    'Timer.h',
+    'tlhelp32.h',
+    'ToolUtils.h',
+    'tr1/functional',
+    'trace.h',
+    'Traps.h',
+    'ttnameid.h',
+    'tttables.h',
+    'typeinfo',
+    'types.h',
+    'Types.h',
+    'UAppleEventsMgr.h',
+    'UAttachments.h',
+    'ucontext.h',
+    'uconv.h',
+    'UCursor.h',
+    'UDebugging.h',
+    'UDesktop.h',
+    'UDrawingState.h',
+    'UDrawingUtils.h',
+    'UEnvironment.h',
+    'UEventMgr.h',
+    'UException.h',
+    'UExtractFromAEDesc.h',
+    'UGWorld.h',
+    'UKeyFilters.h',
+    'ulocks.h',
+    'ulserrno.h',
+    'UMemoryMgr.h',
+    'UModalDialogs.h',
+    'UNavServicesDialogs.h',
+    'UnicodeBlockObjects.h',
+    'UnicodeConverter.h',
+    'UnicodeUtilities.h',
+    'unidef.h',
+    'unikbd.h',
+    'unistd.h',
+    'unix.h',
+    'unixio.h',
+    'unknwn.h',
+    'unwind.h',
+    'UPrinting.h',
+    'UQuickTime.h',
+    'UReanimator.h',
+    'URegions.h',
+    'URegistrar.h',
+    'UResourceMgr.h',
+    'urlhist.h',
+    'urlmon.h',
+    'UScrap.h',
+    'UScreenPort.h',
+    'UTCUtils.h',
+    'UTETextAction.h',
+    'UTEViewTextAction.h',
+    'UTextEdit.h',
+    'UTextTraits.h',
+    'utilmodt.h',
+    'utilpars.h',
+    'utilparst.h',
+    'utilrename.h',
+    'utime.h',
+    'UWindows.h',
+    'values.h',
+    'varargs.h',
+    'vcclr.h',
+    'View.h',
+    'Volume.h',
+    'wab.h',
+    'wait.h',
+    'wchar.h',
+    'wctype.h',
+    'winbase.h',
+    'win/compobj.h',
+    'windef.h',
+    'Window.h',
+    'windows.h',
+    'Windows.h',
+    'windowsx.h',
+    'Wininet.h',
+    'winnls.h',
+    'winperf.h',
+    'winreg.h',
+    'Winreg.h',
+    'winsock2.h',
+    'winsock.h',
+    'winspool.h',
+    'winsvc.h',
+    'winuser.h',
+    'winver.h',
+    'wmem.h',
+    'workbench/startup.h',
+    'wtypes.h',
+    'wx/image.h',
+    'wx/listctrl.h',
+    'wx/log.h',
+    'wx/toolbar.h',
+    'wx/wx.h',
+    'wx/xrc/xmlres.h',
+    'X11/cursorfont.h',
+    'X11/extensions/Print.h',
+    'X11/extensions/scrnsaver.h',
+    'X11/extensions/shape.h',
+    'X11/extensions/Xcomposite.h',
+    'X11/extensions/Xdamage.h',
+    'X11/extensions/Xfixes.h',
+    'X11/extensions/Xrender.h',
+    'X11/extensions/XShm.h',
+    'X11/ImUtil.h',
+    'X11/Intrinsic.h',
+    'X11/keysymdef.h',
+    'X11/keysym.h',
+    'X11/Shell.h',
+    'X11/StringDefs.h',
+    'X11/Xatom.h',
+    'X11/Xft/Xft.h',
+    'X11/Xfuncproto.h',
+    'X11/X.h',
+    'X11/XKBlib.h',
+    'X11/Xlib.h',
+    'X11/Xlibint.h',
+    'X11/Xlib-xcb.h',
+    'X11/Xlocale.h',
+    'X11/Xos.h',
+    'X11/Xutil.h',
+    'xcb/shm.h',
+    'xcb/xcb.h',
+    'xlocale.h',
+    'zmouse.h',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+    system_headers += [
+        'android/ashmem.h',
+        'android_audio/AudioSystem.h',
+        'android/log.h',
+        'android/looper.h',
+        'android/native_window.h',
+        'android/native_window.h',
+        'android/native_window_jni.h',
+        'audio_effects/effect_aec.h',
+        'audio_effects/effect_ns.h',
+        'AudioParameter.h',
+        'AudioSystem.h',
+        'AudioTrack.h',
+        'avc_utils.h',
+        'binder/Binder.h',
+        'binder/BinderService.h',
+        'binder/IBinder.h',
+        'binder/IInterface.h',
+        'binder/IMemory.h',
+        'binder/IPCThreadState.h',
+        'binder/IPermissionController.h',
+        'binder/IServiceManager.h',
+        'binder/Parcel.h',
+        'binder/ProcessState.h',
+        'camera/Camera.h',
+        'camera/CameraParameters.h',
+        'ColorConverter.h',
+        'cutils/android_reboot.h',
+        'cutils/atomic.h',
+        'cutils/compiler.h',
+        'cutils/log.h',
+        'cutils/native_handle.h',
+        'cutils/properties.h',
+        'cutils/sockets.h',
+        'foundation/ABase.h',
+        'foundation/ABitReader.h',
+        'foundation/ABuffer.h',
+        'foundation/ADebug.h',
+        'foundation/AHandler.h',
+        'foundation/AHandlerReflector.h',
+        'foundation/ALooper.h',
+        'foundation/AMessage.h',
+        'foundation/AString.h',
+        'foundation/base64.h',
+        'foundation/hexdump.h',
+        'gui/BufferQueue.h',
+        'gui/ConsumerBase.h',
+        'gui/GraphicBufferAlloc.h',
+        'gui/IConsumerListener.h',
+        'gui/IGraphicBufferAlloc.h',
+        'gui/IGraphicBufferProducer.h',
+        'gui/ISurfaceComposerClient.h',
+        'gui/ISurfaceComposer.h',
+        'gui/ISurfaceTexture.h',
+        'gui/SurfaceComposerClient.h',
+        'gui/Surface.h',
+        'gui/SurfaceTextureClient.h',
+        'hardware/audio.h',
+        'hardware/gralloc.h',
+        'hardware/hardware.h',
+        'hardware/hwcomposer.h',
+        'hardware_legacy/power.h',
+        'hardware_legacy/uevent.h',
+        'hardware_legacy/vibrator.h',
+        'hardware/lights.h',
+        'hardware/power.h',
+        'HTTPBase.h',
+        'linux/android_alarm.h',
+        'linux/ashmem.h',
+        'media/AudioEffect.h',
+        'media/AudioSystem.h',
+        'media/ICrypto.h',
+        'media/IOMX.h',
+        'media/MediaProfiles.h',
+        'media/MediaRecorderBase.h',
+        'media/openmax/OMX_Audio.h',
+        'media/stagefright/AACWriter.h',
+        'media/stagefright/AMRWriter.h',
+        'media/stagefright/AudioSource.h',
+        'media/stagefright/DataSource.h',
+        'media/stagefright/foundation/ABase.h',
+        'media/stagefright/foundation/ABitReader.h',
+        'media/stagefright/foundation/ABuffer.h',
+        'media/stagefright/foundation/ADebug.h',
+        'media/stagefright/foundation/AHandler.h',
+        'media/stagefright/foundation/AHandlerReflector.h',
+        'media/stagefright/foundation/ALooper.h',
+        'media/stagefright/foundation/AMessage.h',
+        'media/stagefright/foundation/AString.h',
+        'media/stagefright/foundation/base64.h',
+        'media/stagefright/foundation/hexdump.h',
+        'media/stagefright/MediaBufferGroup.h',
+        'media/stagefright/MediaBuffer.h',
+        'media/stagefright/MediaCodec.h',
+        'media/stagefright/MediaCodecList.h',
+        'media/stagefright/MediaCodecSource.h',
+        'media/stagefright/MediaDefs.h',
+        'media/stagefright/MediaErrors.h',
+        'media/stagefright/MediaExtractor.h',
+        'media/stagefright/MediaSource.h',
+        'media/stagefright/MediaWriter.h',
+        'media/stagefright/MetaData.h',
+        'media/stagefright/MPEG2TSWriter.h',
+        'media/stagefright/MPEG4Writer.h',
+        'media/stagefright/OMXClient.h',
+        'media/stagefright/OMXCodec.h',
+        'media/stagefright/openmax/OMX_Core.h',
+        'media/stagefright/openmax/OMX_Index.h',
+        'media/stagefright/openmax/OMX_IVCommon.h',
+        'media/stagefright/openmax/OMX_Types.h',
+        'media/stagefright/openmax/OMX_Video.h',
+        'media/stagefright/Utils.h',
+        'OMX_Component.h',
+        'OMX.h',
+        'stagefright/AACWriter.h',
+        'stagefright/AMRWriter.h',
+        'stagefright/AudioSource.h',
+        'stagefright/DataSource.h',
+        'stagefright/foundation/ABase.h',
+        'stagefright/foundation/ABitReader.h',
+        'stagefright/foundation/ABuffer.h',
+        'stagefright/foundation/ADebug.h',
+        'stagefright/foundation/AHandler.h',
+        'stagefright/foundation/AHandlerReflector.h',
+        'stagefright/foundation/ALooper.h',
+        'stagefright/foundation/AMessage.h',
+        'stagefright/foundation/AString.h',
+        'stagefright/foundation/base64.h',
+        'stagefright/foundation/hexdump.h',
+        'stagefright/MediaBufferGroup.h',
+        'stagefright/MediaBuffer.h',
+        'stagefright/MediaCodec.h',
+        'stagefright/MediaDefs.h',
+        'stagefright/MediaErrors.h',
+        'stagefright/MediaExtractor.h',
+        'stagefright/MediaSource.h',
+        'stagefright/MediaWriter.h',
+        'stagefright/MetaData.h',
+        'stagefright/MPEG2TSWriter.h',
+        'stagefright/MPEG4Writer.h',
+        'stagefright/OMXClient.h',
+        'stagefright/OMXCodec.h',
+        'stagefright/openmax/OMX_Component.h',
+        'stagefright/openmax/OMX_Core.h',
+        'stagefright/openmax/OMX_Index.h',
+        'stagefright/openmax/OMX_IVCommon.h',
+        'stagefright/openmax/OMX_Types.h',
+        'stagefright/openmax/OMX_Video.h',
+        'stagefright/Utils.h',
+        'suspend/autosuspend.h',
+        'system/audio.h',
+        'system/graphics.h',
+        'system/window.h',
+        'sysutils/NetlinkEvent.h',
+        'ui/ANativeObjectBase.h',
+        'ui/egl/android_natives.h',
+        'ui/Fence.h',
+        'ui/FramebufferNativeWindow.h',
+        'ui/GraphicBuffer.h',
+        'ui/Rect.h',
+        'ui/Region.h',
+        'utils/BitSet.h',
+        'utils/CallStack.h',
+        'utils/Errors.h',
+        'utils/FileMap.h',
+        'utils/KeyedVector.h',
+        'utils/List.h',
+        'utils/Log.h',
+        'utils/Looper.h',
+        'utils/PropertyMap.h',
+        'utils/RefBase.h',
+        'utils/String16.h',
+        'utils/String8.h',
+        'utils/TextOutput.h',
+        'utils/threads.h',
+        'utils/Timers.h',
+        'utils/Trace.h',
+        'utils/TypeHelpers.h',
+        'utils/Unicode.h',
+        'utils/Vector.h',
+        'utils/VectorImpl.h',
+        'vr/gvr/capi/include/gvr_controller.h',
+        'vr/gvr/capi/include/gvr.h',
+    ]
+
+if CONFIG['MOZ_JACK']:
+    system_headers += [
+        'jack/jack.h',
+        'jack/statistics.h',
+    ]
+
+if CONFIG['MOZ_SYSTEM_JPEG']:
+    system_headers += [
+        'jpeglib.h',
+    ]
+
+if CONFIG['MOZ_LIBAV_FFT']:
+    system_headers += [
+        'libavcodec/avfft.h',
+    ]
+
+if CONFIG['MOZ_SYSTEM_PNG']:
+    system_headers += [
+        'png.h',
+    ]
+
+if CONFIG['MOZ_SYSTEM_ZLIB']:
+    system_headers += [
+        'zlib.h',
+    ]
+
+if CONFIG['MOZ_ENABLE_STARTUP_NOTIFICATION']:
+    system_headers += [
+        'libsn/sn-common.h',
+        'libsn/sn.h',
+        'libsn/sn-launchee.h',
+        'libsn/sn-launcher.h',
+        'libsn/sn-monitor.h',
+        'libsn/sn-util.h',
+    ]
+
+if CONFIG['MOZ_SYSTEM_HUNSPELL']:
+    system_headers += [
+        'hunspell.hxx',
+    ]
+
+if CONFIG['MOZ_SYSTEM_BZ2']:
+    system_headers += [
+        'bzlib.h',
+    ]
+
+if CONFIG['MOZ_SYSTEM_LIBEVENT']:
+    system_headers += [
+        'event2/event_compat.h',
+        'event2/event.h',
+        'event2/event_struct.h',
+        'event.h',
+    ]
+else:
+    system_headers += [
+        'sys/event.h',
+    ]
+
+if CONFIG['MOZ_ENABLE_LIBPROXY']:
+    system_headers += [
+        'proxy.h',
+    ]
+
+if CONFIG['MOZ_ENABLE_CONTENTMANAGER']:
+    system_headers += [
+        'QtSparql/qsparqlconnection.h',
+        'QtSparql/qsparqlquery.h',
+        'QtSparql/qsparqlresult.h',
+        'SelectMultipleContentItemsPage.h',
+        'SelectSingleContentItemPage.h',
+    ]
+
+if not CONFIG['MOZ_TREE_PIXMAN']:
+    system_headers += [
+        'pixman.h',
+    ]
+
+if CONFIG['MOZ_SYSTEM_LIBVPX']:
+    system_headers += [
+        'vpx_mem/vpx_mem.h',
+        'vpx/svc_context.h',
+        'vpx/vp8cx.h',
+        'vpx/vp8dx.h',
+        'vpx/vpx_codec.h',
+        'vpx/vpx_decoder.h',
+        'vpx/vpx_encoder.h',
+    ]
+
+if CONFIG['MOZ_SYSTEM_ICU']:
+    system_headers += [
+        'unicode/locid.h',
+        'unicode/numsys.h',
+        'unicode/plurrule.h',
+        'unicode/timezone.h',
+        'unicode/ucal.h',
+        'unicode/uchar.h',
+        'unicode/uclean.h',
+        'unicode/ucol.h',
+        'unicode/udat.h',
+        'unicode/udatpg.h',
+        'unicode/udisplaycontext.h',
+        'unicode/uenum.h',
+        'unicode/unistr.h',
+        'unicode/unorm.h',
+        'unicode/unum.h',
+        'unicode/upluralrules.h',
+        'unicode/ureldatefmt.h',
+        'unicode/ustring.h',
+        'unicode/utypes.h',
+    ]
--- a/devtools/client/jsonview/test/browser_jsonview_row_selection.js
+++ b/devtools/client/jsonview/test/browser_jsonview_row_selection.js
@@ -57,12 +57,61 @@ add_task(async function () {
     await assertRowSelected(i);
   }
 
   // Now synthetize the keyup, this shouldn't change selected row.
   await BrowserTestUtils.synthesizeKey("VK_DOWN", {type: "keyup"}, tab.linkedBrowser);
   await assertRowSelected(numRows - 1);
 });
 
+add_task(async function () {
+  info("Test 3 JSON row selection started");
+
+  // Create a JSON with a row taller than the panel.
+  let json = JSON.stringify([0, "a ".repeat(1e4), 1]);
+  await addJsonViewTab("data:application/json," + encodeURI(json));
+
+  is(await getElementCount(".treeRow"), 3, "Got the expected number of rows.");
+  await assertRowSelected(null);
+  await evalInContent("var scroller = $('.jsonPanelBox .panelContent')");
+  await evalInContent("var row = $('.treeRow:nth-child(2)')");
+  ok(await evalInContent("scroller.clientHeight < row.clientHeight"),
+     "The row is taller than the scroller.");
+  is(await evalInContent("scroller.scrollTop"), 0, "Initially scrolled to the top.");
+
+  // Select the tall row.
+  await evalInContent("row.click()");
+  await assertRowSelected(2);
+  is(await evalInContent("scroller.scrollTop"), await evalInContent("row.offsetTop"),
+     "Scrolled to the top of the row.");
+
+  // Select the last row.
+  await evalInContent("$('.treeRow:last-child').click()");
+  await assertRowSelected(3);
+  is(await evalInContent("scroller.scrollTop + scroller.offsetHeight"),
+     await evalInContent("scroller.scrollHeight"), "Scrolled to the bottom.");
+
+  // Select the tall row.
+  await evalInContent("row.click()");
+  await assertRowSelected(2);
+  is(await evalInContent("scroller.scrollTop + scroller.offsetHeight"),
+     await evalInContent("row.offsetTop + row.offsetHeight"),
+     "Scrolled to the bottom of the row.");
+
+  // Scroll up a bit, so that both the top and bottom of the row are not visible.
+  let scroll = await evalInContent(
+    "scroller.scrollTop = Math.ceil((scroller.scrollTop + row.offsetTop) / 2)");
+  ok(await evalInContent("scroller.scrollTop > row.offsetTop"),
+     "The top of the row is not visible.");
+  ok(await evalInContent("scroller.scrollTop + scroller.offsetHeight")
+     < await evalInContent("row.offsetTop + row.offsetHeight"),
+     "The bottom of the row is not visible.");
+
+  // Select the tall row.
+  await evalInContent("row.click()");
+  await assertRowSelected(2);
+  is(await evalInContent("scroller.scrollTop"), scroll, "Scroll did not change");
+});
+
 async function assertRowSelected(rowNum) {
   let idx = evalInContent("[].indexOf.call($$('.treeRow'), $('.treeRow.selected'))");
   is(await idx + 1, +rowNum, `${rowNum ? "The row #" + rowNum : "No row"} is selected.`);
 }
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -316,23 +316,17 @@ charts.cacheDisabled=Empty cache
 # LOCALIZATION NOTE (charts.totalSize): This is the label displayed
 # in the performance analysis view for total requests size, in kilobytes.
 charts.totalSize=Size: %S KB
 
 # LOCALIZATION NOTE (charts.totalSeconds): Semi-colon list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # This is the label displayed in the performance analysis view for the
 # total requests time, in seconds.
-charts.totalSeconds=Time: %1$S second;Time: %1$S seconds
-
-# LOCALIZATION NOTE (charts.totalSecondsNonBlocking): Semi-colon list of plural forms.
-# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
-# This is the label displayed in the performance analysis view for the
-# total requests time (non-blocking), in seconds.
-charts.totalSecondsNonBlocking=Non blocking time: %1$S second;Non blocking time: %1$S seconds
+charts.totalSeconds=Time: #1 second;Time: #1 seconds
 
 # LOCALIZATION NOTE (charts.totalCached): This is the label displayed
 # in the performance analysis view for total cached responses.
 charts.totalCached=Cached responses: %S
 
 # LOCALIZATION NOTE (charts.totalCount): This is the label displayed
 # in the performance analysis view for total requests.
 charts.totalCount=Total requests: %S
@@ -349,21 +343,16 @@ charts.type=Type
 # in the header column in the performance analysis view for transferred
 # size of the request.
 charts.transferred=Transferred
 
 # LOCALIZATION NOTE (charts.totalCount): This is the label displayed
 # in the header column in the performance analysis view for time of request.
 charts.time=Time
 
-# LOCALIZATION NOTE (charts.nonBlockingTime): This is the label displayed
-# in the header column in the performance analysis view for non blocking
-# time of request.
-charts.nonBlockingTime=Non blocking time
-
 # LOCALIZATION NOTE (netRequest.headers): A label used for Headers tab
 # This tab displays list of HTTP headers
 netRequest.headers=Headers
 
 # LOCALIZATION NOTE (netRequest.response): A label used for Response tab
 # This tab displays HTTP response body
 netRequest.response=Response
 
--- a/devtools/client/netmonitor/src/components/StatisticsPanel.js
+++ b/devtools/client/netmonitor/src/components/StatisticsPanel.js
@@ -127,49 +127,40 @@ class StatisticsPanel extends Component 
       title,
       header: {
         cached: "",
         count: "",
         label: L10N.getStr("charts.type"),
         size: L10N.getStr("charts.size"),
         transferredSize: L10N.getStr("charts.transferred"),
         time: L10N.getStr("charts.time"),
-        nonBlockingTime: L10N.getStr("charts.nonBlockingTime"),
       },
       data,
       strings: {
         size: (value) =>
           L10N.getFormatStr("charts.sizeKB", getSizeWithDecimals(value / 1024)),
         transferredSize: (value) =>
           L10N.getFormatStr("charts.transferredSizeKB",
             getSizeWithDecimals(value / 1024)),
         time: (value) =>
           L10N.getFormatStr("charts.totalS", getTimeWithDecimals(value / 1000)),
-        nonBlockingTime: (value) =>
-          L10N.getFormatStr("charts.totalS", getTimeWithDecimals(value / 1000)),
       },
       totals: {
         cached: (total) => L10N.getFormatStr("charts.totalCached", total),
         count: (total) => L10N.getFormatStr("charts.totalCount", total),
         size: (total) =>
           L10N.getFormatStr("charts.totalSize", getSizeWithDecimals(total / 1024)),
         transferredSize: total =>
           L10N.getFormatStr("charts.totalTransferredSize",
             getSizeWithDecimals(total / 1024)),
         time: (total) => {
           let seconds = total / 1000;
           let string = getTimeWithDecimals(seconds);
           return PluralForm.get(seconds,
-            L10N.getFormatStr("charts.totalSeconds", string));
-        },
-        nonBlockingTime: (total) => {
-          let seconds = total / 1000;
-          let string = getTimeWithDecimals(seconds);
-          return PluralForm.get(seconds,
-            L10N.getFormatStr("charts.totalSecondsNonBlocking", string));
+            L10N.getStr("charts.totalSeconds")).replace("#1", string);
         },
       },
       sorted: true,
     });
 
     chart.on("click", (_, { label }) => {
       // Reset FilterButtons and enable one filter exclusively
       this.props.closeStatistics();
@@ -189,17 +180,16 @@ class StatisticsPanel extends Component 
   sanitizeChartDataSource(requests, emptyCache) {
     const data = FILTER_TAGS.map((type) => ({
       cached: 0,
       count: 0,
       label: type,
       size: 0,
       transferredSize: 0,
       time: 0,
-      nonBlockingTime: 0,
     }));
 
     for (let request of requests) {
       let type;
 
       if (Filters.html(request)) {
         // "html"
         type = 0;
@@ -229,19 +219,16 @@ class StatisticsPanel extends Component 
         // "other"
         type = 8;
       }
 
       if (emptyCache || !this.responseIsFresh(request)) {
         data[type].time += request.totalTime || 0;
         data[type].size += request.contentSize || 0;
         data[type].transferredSize += request.transferredSize || 0;
-        let nonBlockingTime =
-           request.eventTimings.totalTime - request.eventTimings.timings.blocked;
-        data[type].nonBlockingTime += nonBlockingTime || 0;
       } else {
         data[type].cached++;
       }
       data[type].count++;
     }
 
     return data.filter(e => e.count > 0);
   }
--- a/devtools/client/shared/components/tree/TreeView.js
+++ b/devtools/client/shared/components/tree/TreeView.js
@@ -282,17 +282,29 @@ define(function (require, exports, modul
       return rows.find(row => this.isSelected(row.props.member.path));
     }
 
     selectRow(row) {
       row = findDOMNode(row);
       this.setState(Object.assign({}, this.state, {
         selected: row.id
       }));
-      row.scrollIntoView({block: "nearest"});
+
+      // If the top or bottom side of the row is not visible and there is available space
+      // beyond the opposite one, then attempt to scroll the hidden side into view, but
+      // without hiding the visible side.
+      let scroller = scrollContainer(row);
+      if (!scroller) {
+        return;
+      }
+      let scrollToTop = row.offsetTop;
+      let scrollToBottom = scrollToTop + row.offsetHeight - scroller.offsetHeight;
+      let max = Math.max(scrollToTop, scrollToBottom);
+      let min = Math.min(scrollToTop, scrollToBottom);
+      scroller.scrollTop = Math.max(min, Math.min(max, scroller.scrollTop));
     }
 
     isSelected(nodePath) {
       return nodePath === this.state.selected;
     }
 
     // Filtering & Sorting
 
@@ -497,11 +509,23 @@ define(function (require, exports, modul
     // The default column is usually the first one.
     return [{id: "default"}, ...columns];
   }
 
   function isLongString(value) {
     return typeof value == "string" && value.length > 50;
   }
 
+  function scrollContainer(element) {
+    let parent = element.parentElement;
+    let window = element.ownerDocument.defaultView;
+    if (!parent || !window) {
+      return null;
+    }
+    if (window.getComputedStyle(parent).overflowY != "visible") {
+      return parent;
+    }
+    return scrollContainer(parent);
+  }
+
   // Exports from this module
   module.exports = TreeView;
 });
--- a/devtools/shim/aboutdevtools/aboutdevtools.js
+++ b/devtools/shim/aboutdevtools/aboutdevtools.js
@@ -18,16 +18,22 @@ const TELEMETRY_NOINSTALL_EXITS = "devto
 const MESSAGES = {
   AboutDebugging: "about-debugging-message",
   ContextMenu: "inspect-element-message",
   HamburgerMenu: "menu-message",
   KeyShortcut: "key-shortcut-message",
   SystemMenu: "menu-message",
 };
 
+// Google analytics parameters that should be added to all outgoing links.
+const GA_PARAMETERS = [
+  ["utm_source", "devtools"],
+  ["utm_medium", "onboarding"],
+];
+
 const ABOUTDEVTOOLS_STRINGS = "chrome://devtools-shim/locale/aboutdevtools.properties";
 const aboutDevtoolsBundle = Services.strings.createBundle(ABOUTDEVTOOLS_STRINGS);
 
 const KEY_SHORTCUTS_STRINGS = "chrome://devtools-shim/locale/key-shortcuts.properties";
 const keyShortcutsBundle = Services.strings.createBundle(KEY_SHORTCUTS_STRINGS);
 
 // URL constructor doesn't support about: scheme,
 // we have to use http in order to have working searchParams.
@@ -181,16 +187,24 @@ window.addEventListener("load", function
   document.getElementById("close").addEventListener("click", onCloseButtonClick);
   Services.prefs.addObserver(DEVTOOLS_ENABLED_PREF, updatePage);
 
   let featuresContainer = document.querySelector(".features-list");
   for (let feature of features) {
     featuresContainer.appendChild(createFeatureEl(feature));
   }
 
+  // Add Google Analytics parameters to all the external links.
+  let externalLinks = [...document.querySelectorAll("a.external")];
+  for (let link of externalLinks) {
+    let linkUrl = new URL(link.getAttribute("href"));
+    GA_PARAMETERS.forEach(([key, value]) => linkUrl.searchParams.set(key, value));
+    link.setAttribute("href", linkUrl.href);
+  }
+
   // Update the current page based on the current value of DEVTOOLS_ENABLED_PREF.
   updatePage();
 
   try {
     if (reason) {
       Services.telemetry.getHistogramById(TELEMETRY_OPENED_REASON).add(reason);
     }
 
--- a/dom/base/RangeBoundary.h
+++ b/dom/base/RangeBoundary.h
@@ -404,18 +404,64 @@ public:
     mRef = aOther.mRef;
     mOffset = aOther.mOffset;
     return *this;
   }
 
   template<typename A, typename B>
   bool operator==(const RangeBoundaryBase<A, B>& aOther) const
   {
-    return mParent == aOther.mParent &&
-      (mRef ? mRef == aOther.mRef : mOffset == aOther.mOffset);
+    if (mParent != aOther.mParent) {
+      return false;
+    }
+
+    if (mOffset.isSome() && aOther.mOffset.isSome()) {
+      // If both mOffset are set, we need to compare both mRef too because
+      // the relation of mRef and mOffset have already broken by DOM tree
+      // changes.
+      if (mOffset != aOther.mOffset) {
+        return false;
+      }
+      if (mRef == aOther.mRef) {
+        return true;
+      }
+      if (NS_WARN_IF(mRef && aOther.mRef)) {
+        // In this case, relation between mRef and mOffset of one of or both of
+        // them doesn't match with current DOM tree since the DOM tree might
+        // have been changed after computing mRef or mOffset.
+        return false;
+      }
+      // If one of mRef hasn't been computed yet, we should compare them only
+      // with mOffset.  Perhaps, we shouldn't copy mRef from non-nullptr one to
+      // nullptr one since if we copy it here, it may be unexpected behavior for
+      // some callers.
+      return true;
+    }
+
+    if (mOffset.isSome() && !mRef &&
+        !aOther.mOffset.isSome() && aOther.mRef) {
+      // If this has only mOffset and the other has only mRef, this needs to
+      // compute mRef now.
+      EnsureRef();
+      return mRef == aOther.mRef;
+    }
+
+    if (!mOffset.isSome() && mRef &&
+        aOther.mOffset.isSome() && !aOther.mRef) {
+      // If this has only mRef and the other has only mOffset, the other needs
+      // to compute mRef now.
+      aOther.EnsureRef();
+      return mRef == aOther.mRef;
+    }
+
+    // If mOffset of one of them hasn't been computed from mRef yet, we should
+    // compare only with mRef.  Perhaps, we shouldn't copy mOffset from being
+    // some one to not being some one since if we copy it here, it may be
+    // unexpected behavior for some callers.
+    return mRef == aOther.mRef;
   }
 
   template<typename A, typename B>
   bool operator!=(const RangeBoundaryBase<A, B>& aOther) const
   {
     return !(*this == aOther);
   }
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1732,22 +1732,16 @@ void HTMLMediaElement::ShutdownDecoder()
     mMediaSource->CompletePendingTransactions();
   }
   mDecoder->Shutdown();
   mDecoder = nullptr;
 }
 
 void HTMLMediaElement::AbortExistingLoads()
 {
-  // If there is no existing decoder then we don't have anything to
-  // report. This prevents reporting the initial load from an
-  // empty video element as a failed EME load.
-  if (mDecoder) {
-    ReportEMETelemetry();
-  }
   // Abort any already-running instance of the resource selection algorithm.
   mLoadWaitStatus = NOT_WAITING;
 
   // Set a new load ID. This will cause events which were enqueued
   // with a different load ID to silently be cancelled.
   mCurrentLoadID++;
 
   // Immediately reject or resolve the already-dispatched
@@ -4586,28 +4580,16 @@ void HTMLMediaElement::HiddenVideoStop()
   if (!mVideoDecodeSuspendTimer) {
     return;
   }
   mVideoDecodeSuspendTimer->Cancel();
   mVideoDecodeSuspendTimer = nullptr;
 }
 
 void
-HTMLMediaElement::ReportEMETelemetry()
-{
-  // Report telemetry for EME videos when a page is unloaded.
-  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
-  if (mIsEncrypted && Preferences::GetBool("media.eme.enabled")) {
-    Telemetry::Accumulate(Telemetry::VIDEO_EME_PLAY_SUCCESS, mLoadedDataFired);
-    LOG(LogLevel::Debug, ("%p VIDEO_EME_PLAY_SUCCESS = %s",
-                       this, mLoadedDataFired ? "true" : "false"));
-  }
-}
-
-void
 HTMLMediaElement::ReportTelemetry()
 {
   // Report telemetry for videos when a page is unloaded. We
   // want to know data on what state the video is at when
   // the user has exited.
   enum UnloadedState {
     ENDED = 0,
     PAUSED = 1,
@@ -6458,17 +6440,16 @@ void HTMLMediaElement::SuspendOrResumeEl
       this, aPauseElement, aSuspendEvents, OwnerDoc()->Hidden()));
 
   if (aPauseElement != mPausedForInactiveDocumentOrChannel) {
     mPausedForInactiveDocumentOrChannel = aPauseElement;
     UpdateSrcMediaStreamPlaying();
     UpdateAudioChannelPlayingState();
     if (aPauseElement) {
       ReportTelemetry();
-      ReportEMETelemetry();
 
       // For EME content, we may force destruction of the CDM client (and CDM
       // instance if this is the last client for that CDM instance) and
       // the CDM's decoder. This ensures the CDM gets reliable and prompt
       // shutdown notifications, as it may have book-keeping it needs
       // to do on shutdown.
       if (mMediaKeys) {
         nsAutoString keySystem;
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1236,18 +1236,16 @@ protected:
    */
   void HiddenVideoStart();
   /**
    * Video is not playing anymore and/or has become visible.
    * Used to track hidden-video telemetry.
    */
   void HiddenVideoStop();
 
-  void ReportEMETelemetry();
-
   void ReportTelemetry();
 
   // Seeks to aTime seconds. aSeekType can be Exact to seek to exactly the
   // seek target, or PrevSyncPoint if a quicker but less precise seek is
   // desired, and we'll seek to the sync point (keyframe and/or start of the
   // next block of audio samples) preceeding seek target.
   already_AddRefed<Promise> Seek(double aTime, SeekTarget::Type aSeekType, ErrorResult& aRv);
 
--- a/dom/media/ChannelMediaResource.cpp
+++ b/dom/media/ChannelMediaResource.cpp
@@ -24,32 +24,30 @@ mozilla::LazyLogModule gMediaResourceLog
 
 namespace mozilla {
 
 ChannelMediaResource::ChannelMediaResource(MediaResourceCallback* aCallback,
                                            nsIChannel* aChannel,
                                            nsIURI* aURI,
                                            bool aIsPrivateBrowsing)
   : BaseMediaResource(aCallback, aChannel, aURI)
-  , mReopenOnError(false)
   , mCacheStream(this, aIsPrivateBrowsing)
-  , mSuspendAgent(mChannel, mCacheStream)
+  , mSuspendAgent(mCacheStream)
 {
 }
 
 ChannelMediaResource::ChannelMediaResource(
   MediaResourceCallback* aCallback,
   nsIChannel* aChannel,
   nsIURI* aURI,
   const MediaChannelStatistics& aStatistics)
   : BaseMediaResource(aCallback, aChannel, aURI)
-  , mReopenOnError(false)
   , mCacheStream(this, /* aIsPrivateBrowsing = */ false)
   , mChannelStatistics(aStatistics)
-  , mSuspendAgent(mChannel, mCacheStream)
+  , mSuspendAgent(mCacheStream)
 {
 }
 
 ChannelMediaResource::~ChannelMediaResource()
 {
   MOZ_ASSERT(mClosed);
   MOZ_ASSERT(!mChannel);
   MOZ_ASSERT(!mListener);
@@ -81,17 +79,17 @@ ChannelMediaResource::Listener::OnStartR
 nsresult
 ChannelMediaResource::Listener::OnStopRequest(nsIRequest* aRequest,
                                               nsISupports* aContext,
                                               nsresult aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!mResource)
     return NS_OK;
-  return mResource->OnStopRequest(aRequest, aStatus);
+  return mResource->OnStopRequest(aRequest, aStatus, mReopenOnError);
 }
 
 nsresult
 ChannelMediaResource::Listener::OnDataAvailable(nsIRequest* aRequest,
                                                 nsISupports* aContext,
                                                 nsIInputStream* aStream,
                                                 uint64_t aOffset,
                                                 uint32_t aCount)
@@ -291,19 +289,18 @@ ChannelMediaResource::OnStartRequest(nsI
   // Update principals before OnDataAvailable() putting the data in the cache.
   // This is important, we want to make sure all principals are updated before
   // any consumer can see the new data.
   UpdatePrincipal();
 
   mCacheStream.NotifyDataStarted(mLoadID, startOffset, seekable);
   mIsTransportSeekable = seekable;
   mChannelStatistics.Start();
-  mReopenOnError = false;
 
-  mSuspendAgent.UpdateSuspendedStatusIfNeeded();
+  mSuspendAgent.Delegate(mChannel);
 
   // Fires an initial progress event.
   owner->DownloadProgressed();
 
   // TODO: Don't turn this on until we fix all data races.
   nsCOMPtr<nsIThreadRetargetableRequest> retarget;
   if (Preferences::GetBool("media.omt_data_delivery.enabled", false) &&
       (retarget = do_QueryInterface(aRequest))) {
@@ -366,32 +363,34 @@ ChannelMediaResource::ParseContentRangeH
 
   LOG("Received bytes [%" PRId64 "] to [%" PRId64 "] of [%" PRId64 "] for decoder[%p]",
       aRangeStart, aRangeEnd, aRangeTotal, mCallback.get());
 
   return NS_OK;
 }
 
 nsresult
-ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
+ChannelMediaResource::OnStopRequest(nsIRequest* aRequest,
+                                    nsresult aStatus,
+                                    bool aReopenOnError)
 {
   NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
   NS_ASSERTION(!mSuspendAgent.IsSuspended(),
                "How can OnStopRequest fire while we're suspended?");
   MOZ_DIAGNOSTIC_ASSERT(!mClosed);
 
   mChannelStatistics.Stop();
 
   // Note that aStatus might have succeeded --- this might be a normal close
   // --- even in situations where the server cut us off because we were
   // suspended. So we need to "reopen on error" in that case too. The only
   // cases where we don't need to reopen are when *we* closed the stream.
   // But don't reopen if we need to seek and we don't think we can... that would
   // cause us to just re-read the stream, which would be really bad.
-  if (mReopenOnError && aStatus != NS_ERROR_PARSED_DATA_CACHED &&
+  if (aReopenOnError && aStatus != NS_ERROR_PARSED_DATA_CACHED &&
       aStatus != NS_BINDING_ABORTED &&
       (GetOffset() == 0 || (GetLength() > 0 && GetOffset() != GetLength() &&
                             mIsTransportSeekable))) {
     // If the stream did close normally, restart the channel if we're either
     // at the start of the resource, or if the server is seekable and we're
     // not at the end of stream. We don't restart the stream if we're at the
     // end because not all web servers handle this case consistently; see:
     // https://bugzilla.mozilla.org/show_bug.cgi?id=1373618#c36
@@ -420,18 +419,19 @@ ChannelMediaResource::OnStopRequest(nsIR
 }
 
 nsresult
 ChannelMediaResource::OnChannelRedirect(nsIChannel* aOld,
                                         nsIChannel* aNew,
                                         uint32_t aFlags,
                                         int64_t aOffset)
 {
+  // OnChannelRedirect() is followed by OnStartRequest() where we will
+  // call mSuspendAgent.Delegate().
   mChannel = aNew;
-  mSuspendAgent.NotifyChannelOpened(mChannel);
   return SetupChannelHeaders(aOffset);
 }
 
 nsresult
 ChannelMediaResource::CopySegmentToCache(nsIInputStream* aInStream,
                                          void* aClosure,
                                          const char* aFromSegment,
                                          uint32_t aToOffset,
@@ -621,17 +621,17 @@ ChannelMediaResource::CloneData(MediaRes
 
 void ChannelMediaResource::CloseChannel()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   mChannelStatistics.Stop();
 
   if (mChannel) {
-    mSuspendAgent.NotifyChannelClosing();
+    mSuspendAgent.Revoke();
     // The status we use here won't be passed to the decoder, since
     // we've already revoked the listener. It can however be passed
     // to nsDocumentViewer::LoadComplete if our channel is the one
     // that kicked off creation of a video document. We don't want that
     // document load to think there was an error.
     // NS_ERROR_PARSED_DATA_CACHED is the best thing we have for that
     // at the moment.
     mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
@@ -725,17 +725,17 @@ ChannelMediaResource::Resume()
   MOZ_DIAGNOSTIC_ASSERT(element);
 
   if (mSuspendAgent.Resume()) {
     if (mChannel) {
       // Just wake up our existing channel
       mChannelStatistics.Start();
       // if an error occurs after Resume, assume it's because the server
       // timed out the connection and we should reopen it.
-      mReopenOnError = true;
+      mListener->SetReopenOnError();
       element->DownloadResumed();
     } else {
       int64_t totalLength = GetLength();
       // If mOffset is at the end of the stream, then we shouldn't try to
       // seek to it. The seek will fail and be wasted anyway. We can leave
       // the channel dead; if the media cache wants to read some other data
       // in the future, it will call CacheClientSeek itself which will reopen the
       // channel.
@@ -813,18 +813,16 @@ ChannelMediaResource::RecreateChannel()
 
   nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
   if (cos) {
     // Unconditionally disable throttling since we want the media to fluently
     // play even when we switch the tab to background.
     cos->AddClassFlags(nsIClassOfService::DontThrottle);
   }
 
-  mSuspendAgent.NotifyChannelOpened(mChannel);
-
   // Tell the cache to reset the download status when the channel is reopened.
   mCacheStream.NotifyChannelRecreated();
 
   return rv;
 }
 
 void
 ChannelMediaResource::CacheClientNotifyDataReceived()
@@ -1062,47 +1060,49 @@ ChannelSuspendAgent::Resume()
     }
     mCacheStream.NotifyClientSuspended(false);
     return true;
   }
   return false;
 }
 
 void
-ChannelSuspendAgent::UpdateSuspendedStatusIfNeeded()
+ChannelSuspendAgent::Delegate(nsIChannel* aChannel)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (!mIsChannelSuspended && IsSuspended()) {
+  MOZ_ASSERT(aChannel);
+  MOZ_ASSERT(!mChannel, "The previous channel not closed.");
+  MOZ_ASSERT(!mIsChannelSuspended);
+
+  mChannel = aChannel;
+  // Ensure the suspend status of the channel matches our suspend count.
+  if (IsSuspended()) {
     SuspendInternal();
   }
 }
 
 void
-ChannelSuspendAgent::NotifyChannelOpened(nsIChannel* aChannel)
+ChannelSuspendAgent::Revoke()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aChannel);
-  mChannel = aChannel;
-}
 
-void
-ChannelSuspendAgent::NotifyChannelClosing()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mChannel);
-  // Before close the channel, it need to be resumed to make sure its internal
+  if (!mChannel) {
+    // Channel already revoked. Nothing to do.
+    return;
+  }
+
+  // Before closing the channel, it needs to be resumed to make sure its internal
   // state is correct. Besides, We need to suspend the channel after recreating.
   if (mIsChannelSuspended) {
     mChannel->Resume();
     mIsChannelSuspended = false;
   }
   mChannel = nullptr;
 }
 
 bool
 ChannelSuspendAgent::IsSuspended()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return (mSuspendCount > 0);
 }
 
-
 } // mozilla namespace
--- a/dom/media/ChannelMediaResource.h
+++ b/dom/media/ChannelMediaResource.h
@@ -19,47 +19,41 @@ namespace mozilla {
 
 /**
  * This class is responsible for managing the suspend count and report suspend
  * status of channel.
  **/
 class ChannelSuspendAgent
 {
 public:
-  ChannelSuspendAgent(nsIChannel* aChannel, MediaCacheStream& aCacheStream)
-    : mChannel(aChannel)
-    , mCacheStream(aCacheStream)
+  explicit ChannelSuspendAgent(MediaCacheStream& aCacheStream)
+    : mCacheStream(aCacheStream)
   {
   }
 
   // True when the channel has been suspended or needs to be suspended.
   bool IsSuspended();
 
   // Return true when the channel is logically suspended, i.e. the suspend
   // count goes from 0 to 1.
   bool Suspend();
 
   // Return true only when the suspend count is equal to zero.
   bool Resume();
 
-  // Call after opening channel, set channel and check whether the channel
-  // needs to be suspended.
-  void NotifyChannelOpened(nsIChannel* aChannel);
-
-  // Call before closing channel, reset the channel internal status if needed.
-  void NotifyChannelClosing();
-
-  // Check whether we need to suspend the channel.
-  void UpdateSuspendedStatusIfNeeded();
+  // Tell the agent to manage the suspend status of the channel.
+  void Delegate(nsIChannel* aChannel);
+  // Stop the management of the suspend status of the channel.
+  void Revoke();
 
 private:
   // Only suspends channel but not changes the suspend count.
   void SuspendInternal();
 
-  nsIChannel* mChannel;
+  nsIChannel* mChannel = nullptr;
   MediaCacheStream& mCacheStream;
   uint32_t mSuspendCount = 0;
   bool mIsChannelSuspended = false;
 };
 
 /**
  * This is the MediaResource implementation that wraps Necko channels.
  * Much of its functionality is actually delegated to MediaCache via
@@ -176,37 +170,44 @@ public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIREQUESTOBSERVER
     NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSICHANNELEVENTSINK
     NS_DECL_NSIINTERFACEREQUESTOR
     NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
 
     void Revoke();
+    void SetReopenOnError() { mReopenOnError = true; }
 
   private:
     Mutex mMutex;
     // mResource should only be modified on the main thread with the lock.
     // So it can be read without lock on the main thread or on other threads
     // with the lock.
     RefPtr<ChannelMediaResource> mResource;
+    // When this flag is set, if we get a network error we should silently
+    // reopen the stream. Main thread only.
+    bool mReopenOnError = false;
+
     const int64_t mOffset;
     const uint32_t mLoadID;
   };
   friend class Listener;
 
   nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override;
 
 protected:
   nsresult Seek(int64_t aOffset, bool aResume);
 
   bool IsSuspendedByCache();
   // These are called on the main thread by Listener.
   nsresult OnStartRequest(nsIRequest* aRequest, int64_t aRequestOffset);
-  nsresult OnStopRequest(nsIRequest* aRequest, nsresult aStatus);
+  nsresult OnStopRequest(nsIRequest* aRequest,
+                         nsresult aStatus,
+                         bool aReopenOnError);
   nsresult OnDataAvailable(uint32_t aLoadID,
                            nsIInputStream* aStream,
                            uint32_t aCount);
   nsresult OnChannelRedirect(nsIChannel* aOld,
                              nsIChannel* aNew,
                              uint32_t aFlags,
                              int64_t aOffset);
 
@@ -250,19 +251,16 @@ protected:
   // The last reported seekability state for the underlying channel
   bool mIsTransportSeekable = false;
   RefPtr<Listener> mListener;
   // A mono-increasing integer to uniquely identify the channel we are loading.
   uint32_t mLoadID = 0;
   // Used by the cache to store the offset to seek to when we are resumed.
   // -1 means no seek initiated by the cache is waiting.
   int64_t mPendingSeekOffset = -1;
-  // When this flag is set, if we get a network error we should silently
-  // reopen the stream.
-  bool               mReopenOnError;
 
   // Any thread access
   MediaCacheStream mCacheStream;
 
   MediaChannelStatistics mChannelStatistics;
   ChannelSuspendAgent mSuspendAgent;
 };
 
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -2512,30 +2512,34 @@ MediaCacheStream::Tell()
 }
 
 nsresult
 MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
 
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
-  if (mClosed)
-    return NS_ERROR_FAILURE;
 
   // Cache the offset in case it is changed again when we are waiting for the
   // monitor to be notified to avoid reading at the wrong position.
   auto streamOffset = mStreamOffset;
 
   uint32_t count = 0;
   // Read one block (or part of a block) at a time
   while (count < aCount) {
+    if (mClosed) {
+      return NS_ERROR_ABORT;
+    }
+
     int32_t streamBlock = OffsetToBlockIndex(streamOffset);
     if (streamBlock < 0) {
-      break;
+      LOGE("Stream %p invalid offset=%" PRId64, this, streamOffset);
+      return NS_ERROR_ILLEGAL_VALUE;
     }
+
     uint32_t offsetInStreamBlock = uint32_t(streamOffset - streamBlock*BLOCK_SIZE);
     int64_t size = std::min<int64_t>(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
 
     if (mStreamLength >= 0) {
       // Don't try to read beyond the end of the stream
       int64_t bytesRemaining = mStreamLength - streamOffset;
       if (bytesRemaining <= 0) {
         // Get out of here and return NS_OK
@@ -2546,96 +2550,92 @@ MediaCacheStream::Read(char* aBuffer, ui
       size = std::min(size, int64_t(INT32_MAX));
     }
 
     int32_t cacheBlock =
       size_t(streamBlock) < mBlocks.Length() ? mBlocks[streamBlock] : -1;
     if (cacheBlock < 0) {
       // We don't have a complete cached block here.
 
-      if (count > 0) {
-        // Some data has been read, so return what we've got instead of
-        // blocking or trying to find a stream with a partial block.
-        break;
-      }
-
       // See if the data is available in the partial cache block of any
       // stream reading this resource. We need to do this in case there is
       // another stream with this resource that has all the data to the end of
       // the stream but the data doesn't end on a block boundary.
       MediaCacheStream* streamWithPartialBlock = nullptr;
       MediaCache::ResourceStreamIterator iter(mMediaCache, mResourceID);
       while (MediaCacheStream* stream = iter.Next()) {
         if (OffsetToBlockIndexUnchecked(stream->mChannelOffset) ==
               streamBlock &&
-            streamOffset < stream->mChannelOffset) {
+            streamOffset < stream->mChannelOffset &&
+            stream->mChannelOffset == stream->mStreamLength) {
           streamWithPartialBlock = stream;
           break;
         }
       }
       if (streamWithPartialBlock) {
         // We can just use the data in mPartialBlockBuffer. In fact we should
         // use it rather than waiting for the block to fill and land in
         // the cache.
         int64_t bytes = std::min<int64_t>(size, streamWithPartialBlock->mChannelOffset - streamOffset);
         // Clamp bytes until 64-bit file size issues are fixed.
         bytes = std::min(bytes, int64_t(INT32_MAX));
         MOZ_ASSERT(bytes >= 0 && bytes <= aCount, "Bytes out of range.");
-        memcpy(aBuffer,
-          streamWithPartialBlock->mPartialBlockBuffer.get() + offsetInStreamBlock, bytes);
+        memcpy(aBuffer + count,
+               streamWithPartialBlock->mPartialBlockBuffer.get() +
+                 offsetInStreamBlock,
+               bytes);
         if (mCurrentMode == MODE_METADATA) {
           streamWithPartialBlock->mMetadataInPartialBlockBuffer = true;
         }
         streamOffset += bytes;
-        count = bytes;
+        count += bytes;
+        // Break for we've reached EOS and have nothing more to read.
         break;
       }
 
       if (mStreamOffset != streamOffset) {
         // Updat mStreamOffset before we drop the lock. We need to run
         // Update() again since stream reading strategy might have changed.
         mStreamOffset = streamOffset;
         mMediaCache->QueueUpdate();
       }
 
       // No data has been read yet, so block
       mon.Wait();
-      if (mClosed) {
-        // We may have successfully read some data, but let's just throw
-        // that out.
-        return NS_ERROR_FAILURE;
-      }
       continue;
     }
 
     mMediaCache->NoteBlockUsage(
       this, cacheBlock, streamOffset, mCurrentMode, TimeStamp::Now());
 
     int64_t offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
     int32_t bytes;
     MOZ_ASSERT(size >= 0 && size <= INT32_MAX, "Size out of range.");
     nsresult rv = mMediaCache->ReadCacheFile(
       offset, aBuffer + count, int32_t(size), &bytes);
     if (NS_FAILED(rv)) {
-      if (count == 0)
-        return rv;
-      // If we did successfully read some data, may as well return it
-      break;
+      nsCString name;
+      GetErrorName(rv, name);
+      LOGE("Stream %p ReadCacheFile failed, rv=%s", this, name.Data());
+      return rv;
     }
     streamOffset += bytes;
     count += bytes;
   }
 
-  if (count > 0) {
-    // Some data was read, so queue an update since block priorities may
-    // have changed
-    mMediaCache->QueueUpdate();
+  *aBytes = count;
+  if (count == 0) {
+    return NS_OK;
   }
+
+  // Some data was read, so queue an update since block priorities may
+  // have changed
+  mMediaCache->QueueUpdate();
+
   LOG("Stream %p Read at %" PRId64 " count=%d", this, streamOffset-count, count);
-  *aBytes = count;
   mStreamOffset = streamOffset;
   return NS_OK;
 }
 
 nsresult
 MediaCacheStream::ReadAt(int64_t aOffset, char* aBuffer,
                          uint32_t aCount, uint32_t* aBytes)
 {
--- a/dom/media/MediaResource.cpp
+++ b/dom/media/MediaResource.cpp
@@ -409,83 +409,42 @@ MediaResourceIndex::CacheOrReadAt(int64_
 }
 
 nsresult
 MediaResourceIndex::UncachedReadAt(int64_t aOffset,
                                    char* aBuffer,
                                    uint32_t aCount,
                                    uint32_t* aBytes) const
 {
-  *aBytes = 0;
   if (aOffset < 0) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
-  if (aCount != 0) {
-    for (;;) {
-      uint32_t bytesRead = 0;
-      nsresult rv = mResource->ReadAt(aOffset, aBuffer, aCount, &bytesRead);
-      if (NS_FAILED(rv)) {
-        return rv;
-      }
-      if (bytesRead == 0) {
-        break;
-      }
-      *aBytes += bytesRead;
-      aCount -= bytesRead;
-      if (aCount == 0) {
-        break;
-      }
-      aOffset += bytesRead;
-      if (aOffset < 0) {
-        // Very unlikely overflow.
-        return NS_ERROR_FAILURE;
-      }
-      aBuffer += bytesRead;
-    }
+  if (aCount == 0) {
+    *aBytes = 0;
+    return NS_OK;
   }
-  return NS_OK;
+  return mResource->ReadAt(aOffset, aBuffer, aCount, aBytes);
 }
 
 nsresult
 MediaResourceIndex::UncachedRangedReadAt(int64_t aOffset,
                                          char* aBuffer,
                                          uint32_t aRequestedCount,
                                          uint32_t aExtraCount,
                                          uint32_t* aBytes) const
 {
-  *aBytes = 0;
   uint32_t count = aRequestedCount + aExtraCount;
   if (aOffset < 0 || count < aRequestedCount) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
-  if (count != 0) {
-    for (;;) {
-      uint32_t bytesRead = 0;
-      nsresult rv = mResource->ReadAt(aOffset, aBuffer, count, &bytesRead);
-      if (NS_FAILED(rv)) {
-        return rv;
-      }
-      if (bytesRead == 0) {
-        break;
-      }
-      *aBytes += bytesRead;
-      count -= bytesRead;
-      if (count <= aExtraCount) {
-        // We have read at least aRequestedCount, don't loop anymore.
-        break;
-      }
-      aOffset += bytesRead;
-      if (aOffset < 0) {
-        // Very unlikely overflow.
-        return NS_ERROR_FAILURE;
-      }
-      aBuffer += bytesRead;
-    }
+  if (count == 0) {
+    *aBytes = 0;
+    return NS_OK;
   }
-  return NS_OK;
+  return mResource->ReadAt(aOffset, aBuffer, count, aBytes);
 }
 
 nsresult
 MediaResourceIndex::Seek(int32_t aWhence, int64_t aOffset)
 {
   switch (aWhence) {
     case SEEK_SET:
       break;
@@ -511,40 +470,27 @@ MediaResourceIndex::Seek(int32_t aWhence
   mOffset = aOffset;
 
   return NS_OK;
 }
 
 already_AddRefed<MediaByteBuffer>
 MediaResourceIndex::MediaReadAt(int64_t aOffset, uint32_t aCount) const
 {
+  NS_ENSURE_TRUE(aOffset >= 0, nullptr);
   RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
-  if (aOffset < 0) {
-    return bytes.forget();
-  }
   bool ok = bytes->SetLength(aCount, fallible);
   NS_ENSURE_TRUE(ok, nullptr);
-  char* curr = reinterpret_cast<char*>(bytes->Elements());
-  const char* start = curr;
-  while (aCount > 0) {
-    uint32_t bytesRead;
-    nsresult rv = mResource->ReadAt(aOffset, curr, aCount, &bytesRead);
-    NS_ENSURE_SUCCESS(rv, nullptr);
-    if (!bytesRead) {
-      break;
-    }
-    aOffset += bytesRead;
-    if (aOffset < 0) {
-      // Very unlikely overflow.
-      break;
-    }
-    aCount -= bytesRead;
-    curr += bytesRead;
-  }
-  bytes->SetLength(curr - start);
+
+  uint32_t bytesRead = 0;
+  nsresult rv = mResource->ReadAt(
+    aOffset, reinterpret_cast<char*>(bytes->Elements()), aCount, &bytesRead);
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
+  bytes->SetLength(bytesRead);
   return bytes.forget();
 }
 
 already_AddRefed<MediaByteBuffer>
 MediaResourceIndex::CachedMediaReadAt(int64_t aOffset, uint32_t aCount) const
 {
   RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
   bool ok = bytes->SetLength(aCount, fallible);
--- a/dom/media/gmp/GMPService.cpp
+++ b/dom/media/gmp/GMPService.cpp
@@ -263,20 +263,20 @@ GeckoMediaPluginService::GetCDM(const No
                holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()), __func__);
                return;
              }
              if (helper) {
                cdm->SetCrashHelper(helper);
              }
              holder->Resolve(cdm, __func__);
            },
-           [rawHolder] {
+           [rawHolder](MediaResult result) {
              nsPrintfCString reason(
-               "%s::%s failed since GetContentParent rejects the promise.",
-               __CLASS__, __FUNCTION__);
+               "%s::%s failed since GetContentParent rejects the promise with reason %s.",
+               __CLASS__, __FUNCTION__, result.Description().get());
              UniquePtr<PromiseHolder> holder(rawHolder);
              holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()), __func__);
            });
 
   return promise;
 }
 
 void
--- a/dom/media/gmp/GMPService.h
+++ b/dom/media/gmp/GMPService.h
@@ -48,17 +48,17 @@ struct NodeId
   {
   }
   nsString mOrigin;
   nsString mTopLevelOrigin;
   nsString mGMPName;
 };
 
 typedef MozPromise<RefPtr<GMPContentParent::CloseBlocker>,
-                   nsresult,
+                   MediaResult,
                    /* IsExclusive = */ true>
   GetGMPContentParentPromise;
 typedef MozPromise<RefPtr<ChromiumCDMParent>,
                    MediaResult,
                    /* IsExclusive = */ true>
   GetCDMParentPromise;
 
 class GeckoMediaPluginService : public mozIGeckoMediaPluginService
--- a/dom/media/gmp/GMPServiceChild.cpp
+++ b/dom/media/gmp/GMPServiceChild.cpp
@@ -79,55 +79,62 @@ GeckoMediaPluginServiceChild::GetContent
 
       nsTArray<base::ProcessId> alreadyBridgedTo;
       child->GetAlreadyBridgedTo(alreadyBridgedTo);
 
       base::ProcessId otherProcess;
       nsCString displayName;
       uint32_t pluginId = 0;
       ipc::Endpoint<PGMPContentParent> endpoint;
+      nsCString errorDescription = NS_LITERAL_CSTRING("");
+
       bool ok = child->SendLaunchGMP(nodeIdString,
                                      api,
                                      tags,
                                      alreadyBridgedTo,
                                      &pluginId,
                                      &otherProcess,
                                      &displayName,
                                      &endpoint,
-                                     &rv);
+                                     &rv,
+                                     &errorDescription);
       if (helper && pluginId) {
         // Note: Even if the launch failed, we need to connect the crash
         // helper so that if the launch failed due to the plugin crashing,
         // we can report the crash via the crash reporter. The crash
         // handling notification will arrive shortly if the launch failed
         // due to the plugin crashing.
         self->ConnectCrashHelper(pluginId, helper);
       }
 
       if (!ok || NS_FAILED(rv)) {
-        LOGD(("GeckoMediaPluginServiceChild::GetContentParent SendLaunchGMP "
-              "failed rv=0x%x",
-              static_cast<uint32_t>(rv)));
-        holder->Reject(rv, __func__);
+        MediaResult error(
+          rv,
+          nsPrintfCString("GeckoMediaPluginServiceChild::GetContentParent "
+                          "SendLaunchGMPForNodeId failed with description (%s)",
+                          errorDescription.get()));
+
+        LOGD(("%s", error.Description().get()));
+        holder->Reject(error, __func__);
         return;
       }
 
       RefPtr<GMPContentParent> parent =
         child->GetBridgedGMPContentParent(otherProcess, Move(endpoint));
       if (!alreadyBridgedTo.Contains(otherProcess)) {
         parent->SetDisplayName(displayName);
         parent->SetPluginId(pluginId);
       }
       RefPtr<GMPContentParent::CloseBlocker> blocker(
         new GMPContentParent::CloseBlocker(parent));
       holder->Resolve(blocker, __func__);
     },
-    [rawHolder](nsresult rv) {
+    [rawHolder](MediaResult result) {
       UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
-      holder->Reject(rv, __func__);
+      holder->Reject(result, __func__);
     });
 
   return promise;
 }
 
 RefPtr<GetGMPContentParentPromise>
 GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
                                                const NodeId& aNodeId,
@@ -141,68 +148,77 @@ GeckoMediaPluginServiceChild::GetContent
   RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
   RefPtr<AbstractThread> thread(GetAbstractGMPThread());
 
   NodeIdData nodeId(aNodeId.mOrigin, aNodeId.mTopLevelOrigin, aNodeId.mGMPName);
   nsCString api(aAPI);
   nsTArray<nsCString> tags(aTags);
   RefPtr<GMPCrashHelper> helper(aHelper);
   RefPtr<GeckoMediaPluginServiceChild> self(this);
-  GetServiceChild()->Then(thread, __func__,
+  GetServiceChild()->Then(
+    thread,
+    __func__,
     [self, nodeId, api, tags, helper, rawHolder](GMPServiceChild* child) {
       UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
       nsresult rv;
 
       nsTArray<base::ProcessId> alreadyBridgedTo;
       child->GetAlreadyBridgedTo(alreadyBridgedTo);
 
       base::ProcessId otherProcess;
       nsCString displayName;
       uint32_t pluginId = 0;
       ipc::Endpoint<PGMPContentParent> endpoint;
+      nsCString errorDescription = NS_LITERAL_CSTRING("");
 
       bool ok = child->SendLaunchGMPForNodeId(nodeId,
                                               api,
                                               tags,
                                               alreadyBridgedTo,
                                               &pluginId,
                                               &otherProcess,
                                               &displayName,
                                               &endpoint,
-                                              &rv);
+                                              &rv,
+                                              &errorDescription);
 
       if (helper && pluginId) {
         // Note: Even if the launch failed, we need to connect the crash
         // helper so that if the launch failed due to the plugin crashing,
         // we can report the crash via the crash reporter. The crash
         // handling notification will arrive shortly if the launch failed
         // due to the plugin crashing.
         self->ConnectCrashHelper(pluginId, helper);
       }
 
       if (!ok || NS_FAILED(rv)) {
-        LOGD(("GeckoMediaPluginServiceChild::GetContentParent SendLaunchGMP failed rv=%" PRIu32,
-              static_cast<uint32_t>(rv)));
-        holder->Reject(rv, __func__);
+        MediaResult error(
+          rv,
+          nsPrintfCString("GeckoMediaPluginServiceChild::GetContentParent "
+                          "SendLaunchGMPForNodeId failed with description (%s)",
+                          errorDescription.get()));
+
+        LOGD(("%s", error.Description().get()));
+        holder->Reject(error, __func__);
         return;
       }
 
       RefPtr<GMPContentParent> parent = child->GetBridgedGMPContentParent(otherProcess,
                                                                           Move(endpoint));
       if (!alreadyBridgedTo.Contains(otherProcess)) {
         parent->SetDisplayName(displayName);
         parent->SetPluginId(pluginId);
       }
 
       RefPtr<GMPContentParent::CloseBlocker> blocker(new GMPContentParent::CloseBlocker(parent));
       holder->Resolve(blocker, __func__);
     },
-    [rawHolder](nsresult rv) {
+    [rawHolder](MediaResult result) {
       UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
-      holder->Reject(rv, __func__);
+      holder->Reject(result, __func__);
     });
 
   return promise;
 }
 
 typedef mozilla::dom::GMPCapabilityData GMPCapabilityData;
 typedef mozilla::dom::GMPAPITags GMPAPITags;
 
--- a/dom/media/gmp/GMPServiceChild.h
+++ b/dom/media/gmp/GMPServiceChild.h
@@ -2,16 +2,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 GMPServiceChild_h_
 #define GMPServiceChild_h_
 
 #include "GMPService.h"
+#include "MediaResult.h"
 #include "base/process.h"
 #include "mozilla/ipc/Transport.h"
 #include "mozilla/gmp/PGMPServiceChild.h"
 #include "nsRefPtrHashtable.h"
 #include "mozilla/dom/ContentChild.h"
 
 namespace mozilla {
 namespace gmp {
@@ -60,17 +61,18 @@ protected:
     GMPCrashHelper* aHelper,
     const NodeId& aNodeId,
     const nsCString& aAPI,
     const nsTArray<nsCString>& aTags) override;
 
 private:
   friend class OpenPGMPServiceChild;
 
-  typedef MozPromise<GMPServiceChild*, nsresult, /* IsExclusive = */ true> GetServiceChildPromise;
+  typedef MozPromise<GMPServiceChild*, MediaResult, /* IsExclusive = */ true>
+    GetServiceChildPromise;
   RefPtr<GetServiceChildPromise> GetServiceChild();
 
   nsTArray<MozPromiseHolder<GetServiceChildPromise>> mGetServiceChildPromises;
   UniquePtr<GMPServiceChild> mServiceChild;
 };
 
 class GMPServiceChild : public PGMPServiceChild
 {
--- a/dom/media/gmp/GMPServiceParent.cpp
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -1730,56 +1730,65 @@ mozilla::ipc::IPCResult
 GMPServiceParent::RecvLaunchGMP(const nsCString& aNodeId,
                                 const nsCString& aAPI,
                                 nsTArray<nsCString>&& aTags,
                                 nsTArray<ProcessId>&& aAlreadyBridgedTo,
                                 uint32_t* aOutPluginId,
                                 ProcessId* aOutProcessId,
                                 nsCString* aOutDisplayName,
                                 Endpoint<PGMPContentParent>* aOutEndpoint,
-                                nsresult* aOutRv)
+                                nsresult* aOutRv,
+                                nsCString* aOutErrorDescription)
 {
   if (mService->IsShuttingDown()) {
     *aOutRv = NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+    *aOutErrorDescription = NS_LITERAL_CSTRING("Service is shutting down.");
     return IPC_OK();
   }
 
   RefPtr<GMPParent> gmp = mService->SelectPluginForAPI(aNodeId, aAPI, aTags);
   if (gmp) {
     *aOutPluginId = gmp->GetPluginId();
   } else {
     *aOutRv = NS_ERROR_FAILURE;
+    *aOutErrorDescription = NS_LITERAL_CSTRING("SelectPluginForAPI returns nullptr.");
     *aOutPluginId = 0;
     return IPC_OK();
   }
 
   if (!gmp->EnsureProcessLoaded(aOutProcessId)) {
     *aOutRv = NS_ERROR_FAILURE;
+    *aOutErrorDescription = NS_LITERAL_CSTRING("Process has not loaded.");
     return IPC_OK();
   }
 
   *aOutDisplayName = gmp->GetDisplayName();
 
   if (aAlreadyBridgedTo.Contains(*aOutProcessId)) {
     *aOutRv = NS_OK;
     return IPC_OK();
   }
 
   Endpoint<PGMPContentParent> parent;
   Endpoint<PGMPContentChild> child;
-  if (NS_WARN_IF(NS_FAILED(PGMPContent::CreateEndpoints(
-        OtherPid(), *aOutProcessId, &parent, &child)))) {
-    *aOutRv = NS_ERROR_FAILURE;
+  nsresult rv =
+    PGMPContent::CreateEndpoints(OtherPid(), *aOutProcessId, &parent, &child);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    *aOutRv = rv;
+    *aOutErrorDescription =
+      NS_LITERAL_CSTRING("PGMPContent::CreateEndpoints failed.");
     return IPC_OK();
   }
 
   *aOutEndpoint = Move(parent);
 
   if (!gmp->SendInitGMPContentChild(Move(child))) {
     *aOutRv = NS_ERROR_FAILURE;
+    *aOutErrorDescription =
+      NS_LITERAL_CSTRING("SendInitGMPContentChild failed.");
     return IPC_OK();
   }
 
   gmp->IncrementGMPContentChildCount();
 
   *aOutRv = NS_OK;
   return IPC_OK();
 }
@@ -1789,34 +1798,37 @@ GMPServiceParent::RecvLaunchGMPForNodeId
   const NodeIdData& aNodeId,
   const nsCString& aApi,
   nsTArray<nsCString>&& aTags,
   nsTArray<ProcessId>&& aAlreadyBridgedTo,
   uint32_t* aOutPluginId,
   ProcessId* aOutId,
   nsCString* aOutDisplayName,
   Endpoint<PGMPContentParent>* aOutEndpoint,
-  nsresult* aOutRv)
+  nsresult* aOutRv,
+  nsCString* aOutErrorDescription)
 {
   nsCString nodeId;
   nsresult rv = mService->GetNodeId(
     aNodeId.mOrigin(), aNodeId.mTopLevelOrigin(), aNodeId.mGMPName(), nodeId);
   if (!NS_SUCCEEDED(rv)) {
     *aOutRv = rv;
+    *aOutErrorDescription = NS_LITERAL_CSTRING("GetNodeId failed.");
     return IPC_OK();
   }
   return RecvLaunchGMP(nodeId,
                        aApi,
                        Move(aTags),
                        Move(aAlreadyBridgedTo),
                        aOutPluginId,
                        aOutId,
                        aOutDisplayName,
                        aOutEndpoint,
-                       aOutRv);
+                       aOutRv,
+                       aOutErrorDescription);
 }
 
 mozilla::ipc::IPCResult
 GMPServiceParent::RecvGetGMPNodeId(const nsString& aOrigin,
                                    const nsString& aTopLevelOrigin,
                                    const nsString& aGMPName,
                                    nsCString* aID)
 {
--- a/dom/media/gmp/GMPServiceParent.h
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -241,28 +241,30 @@ public:
   ipc::IPCResult RecvLaunchGMP(const nsCString& aNodeId,
                                const nsCString& aAPI,
                                nsTArray<nsCString>&& aTags,
                                nsTArray<ProcessId>&& aAlreadyBridgedTo,
                                uint32_t* aOutPluginId,
                                ProcessId* aOutID,
                                nsCString* aOutDisplayName,
                                Endpoint<PGMPContentParent>* aOutEndpoint,
-                               nsresult* aOutRv) override;
+                               nsresult* aOutRv,
+                               nsCString* aOutErrorDescription) override;
 
   ipc::IPCResult RecvLaunchGMPForNodeId(
     const NodeIdData& nodeId,
     const nsCString& aAPI,
     nsTArray<nsCString>&& aTags,
     nsTArray<ProcessId>&& aAlreadyBridgedTo,
     uint32_t* aOutPluginId,
     ProcessId* aOutID,
     nsCString* aOutDisplayName,
     Endpoint<PGMPContentParent>* aOutEndpoint,
-    nsresult* aOutRv) override;
+    nsresult* aOutRv,
+    nsCString* aOutErrorDescription) override;
 
 private:
   void CloseTransport(Monitor* aSyncMonitor, bool* aCompleted);
 
   RefPtr<GeckoMediaPluginServiceParent> mService;
 };
 
 } // namespace gmp
--- a/dom/media/gmp/PGMPService.ipdl
+++ b/dom/media/gmp/PGMPService.ipdl
@@ -17,27 +17,29 @@ parent:
   sync LaunchGMP(nsCString nodeId,
                  nsCString api,
                  nsCString[] tags,
                  ProcessId[] alreadyBridgedTo)
     returns (uint32_t pluginId,
              ProcessId id,
              nsCString displayName,
              Endpoint<PGMPContentParent> endpoint,
-             nsresult aResult);
+             nsresult aResult,
+             nsCString aErrorDescription);
 
   sync LaunchGMPForNodeId(NodeIdData nodeId,
                           nsCString api,
                           nsCString[] tags,
                           ProcessId[] alreadyBridgedTo)
     returns (uint32_t pluginId,
              ProcessId id,
              nsCString displayName,
              Endpoint<PGMPContentParent> endpoint,
-             nsresult aResult);
+             nsresult aResult,
+             nsCString aErrorDescription);
 
   sync GetGMPNodeId(nsString origin, nsString topLevelOrigin, nsString gmpName)
     returns (nsCString id);
 child:
   async BeginShutdown();
 };
 
 } // namespace gmp
--- a/dom/media/webvtt/vtt.jsm
+++ b/dom/media/webvtt/vtt.jsm
@@ -300,18 +300,24 @@ const { XPCOMUtils } = require("resource
     v: "title",
     lang: "lang"
   };
 
   var NEEDS_PARENT = {
     rt: "ruby"
   };
 
+  const PARSE_CONTENT_MODE = {
+    NORMAL_CUE: "normal_cue",
+    PSUEDO_CUE: "pseudo_cue",
+    DOCUMENT_FRAGMENT: "document_fragment",
+    REGION_CUE: "region_cue",
+  }
   // Parse content into a document fragment.
-  function parseContent(window, input, bReturnFrag) {
+  function parseContent(window, input, mode) {
     function nextToken() {
       // Check for end-of-string.
       if (!input) {
         return null;
       }
 
       // Consume 'n' characters from the input.
       function consume(result) {
@@ -379,26 +385,30 @@ const { XPCOMUtils } = require("resource
       if (f[1]) {
         f = f[1].slice(0, 3).padEnd(3, "0");
       } else {
         f = "000";
       }
       return hours + ':' + minutes + ':' + seconds + '.' + f;
     }
 
-    var isFirefoxSupportPseudo = (/firefox/i.test(window.navigator.userAgent))
-          && Services.prefs.getBoolPref("media.webvtt.pseudo.enabled");
     var root;
-    if (bReturnFrag) {
-      root = window.document.createDocumentFragment();
-    } else if (isFirefoxSupportPseudo) {
-      root = window.document.createElement("div", {pseudo: "::cue"});
-    } else {
-      root = window.document.createElement("div");
+    switch (mode) {
+      case PARSE_CONTENT_MODE.PSUEDO_CUE:
+        root = window.document.createElement("div", {pseudo: "::cue"});
+        break;
+      case PARSE_CONTENT_MODE.NORMAL_CUE:
+      case PARSE_CONTENT_MODE.REGION_CUE:
+        root = window.document.createElement("div");
+        break;
+      case PARSE_CONTENT_MODE.DOCUMENT_FRAGMENT:
+        root = window.document.createDocumentFragment();
+        break;
     }
+
     var current = root,
         t,
         tagStack = [];
 
     while ((t = nextToken()) !== null) {
       if (t[0] === '<') {
         if (t[1] === "/") {
           // If the closing tag matches, move back up to the parent node.
@@ -489,17 +499,21 @@ const { XPCOMUtils } = require("resource
       backgroundColor = "rgb(0, 0, 0)";
     }
 
     StyleBox.call(this);
     this.cue = cue;
 
     // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
     // have inline positioning and will function as the cue background box.
-    this.cueDiv = parseContent(window, cue.text, false);
+    if (isFirefoxSupportPseudo) {
+      this.cueDiv = parseContent(window, cue.text, PARSE_CONTENT_MODE.PSUEDO_CUE);
+    } else {
+      this.cueDiv = parseContent(window, cue.text, PARSE_CONTENT_MODE.NORMAL_CUE);
+    }
     var styles = {
       color: color,
       backgroundColor: backgroundColor,
       display: "inline",
       font: styleOptions.font,
       whiteSpace: "pre-line",
     };
     if (isFirefoxSupportPseudo) {
@@ -581,16 +595,89 @@ const { XPCOMUtils } = require("resource
         height: this.formatStyle(box.height, "px"),
         width: this.formatStyle(box.width, "px")
       });
     };
   }
   CueStyleBox.prototype = _objCreate(StyleBox.prototype);
   CueStyleBox.prototype.constructor = CueStyleBox;
 
+  function RegionNodeBox(window, region, container) {
+    StyleBox.call(this);
+
+    var boxLineHeight = container.height * 0.0533 // 0.0533vh ? 5.33vh
+    var boxHeight = boxLineHeight * region.lines;
+    var boxWidth = container.width * region.width / 100; // convert percentage to px
+
+    var regionNodeStyles = {
+      position: "absolute",
+      height: boxHeight + "px",
+      width: boxWidth + "px",
+      top: (region.viewportAnchorY * container.height / 100) - (region.regionAnchorY * boxHeight / 100) + "px",
+      left: (region.viewportAnchorX * container.width / 100) - (region.regionAnchorX * boxWidth / 100) + "px",
+      lineHeight: boxLineHeight + "px",
+      writingMode: "horizontal-tb",
+      backgroundColor: "rgba(0, 0, 0, 0.8)",
+      wordWrap: "break-word",
+      overflowWrap: "break-word",
+      font: (boxLineHeight/1.3) + "px sans-serif",
+      color: "rgba(255, 255, 255, 1)",
+      overflow: "hidden",
+      minHeight: "0px",
+      maxHeight: boxHeight + "px",
+      display: "inline-flex",
+      flexFlow: "column",
+      justifyContent: "flex-end",
+    };
+
+    this.div = window.document.createElement("div");
+    this.div.id = region.id; // useless?
+    this.applyStyles(regionNodeStyles);
+  }
+  RegionNodeBox.prototype = _objCreate(StyleBox.prototype);
+  RegionNodeBox.prototype.constructor = RegionNodeBox;
+
+  function RegionCueStyleBox(window, cue) {
+    StyleBox.call(this);
+    this.cueDiv = parseContent(window, cue.text, PARSE_CONTENT_MODE.REGION_CUE);
+
+    var regionCueStyles = {
+      position: "relative",
+      writingMode: "horizontal-tb",
+      unicodeBidi: "plaintext",
+      width: "auto",
+      height: "auto",
+      textAlign: cue.align,
+    };
+    // TODO: fix me, LTR and RTL ? using margin replace the "left/right"
+    // 6.1.14.3.3
+    var offset = cue.computedPosition * cue.region.width / 100;
+    // 6.1.14.3.4
+    switch (cue.align) {
+      case "start":
+      case "left":
+        regionCueStyles.left = offset + "%";
+        regionCueStyles.right = "auto";
+        break;
+      case "end":
+      case "right":
+        regionCueStyles.left = "auto";
+        regionCueStyles.right = offset + "%";
+        break;
+      case "middle":
+        break;
+    }
+
+    this.div = window.document.createElement("div");
+    this.applyStyles(regionCueStyles);
+    this.div.appendChild(this.cueDiv);
+  }
+  RegionCueStyleBox.prototype = _objCreate(StyleBox.prototype);
+  RegionCueStyleBox.prototype.constructor = RegionCueStyleBox;
+
   // Represents the co-ordinates of an Element in a way that we can easily
   // compute things with such as if it overlaps or intersects with another Element.
   // Can initialize it with either a StyleBox or another BoxPosition.
   function BoxPosition(obj) {
     var isIE8 = (typeof navigator !== "undefined") &&
       (/MSIE\s8\.0/).test(navigator.userAgent);
 
     // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
@@ -891,17 +978,17 @@ const { XPCOMUtils } = require("resource
       }
     };
   };
 
   WebVTT.convertCueToDOMTree = function(window, cuetext) {
     if (!window) {
       return null;
     }
-    return parseContent(window, cuetext, true);
+    return parseContent(window, cuetext, PARSE_CONTENT_MODE.DOCUMENT_FRAGMENT);
   };
 
   var FONT_SIZE_PERCENT = 0.05;
   var FONT_STYLE = "sans-serif";
 
   // Runs the processing model over the cues and regions passed to it.
   // @param overlay A block level element (usually a div) that the computed cues
   //                and regions will be placed into.
@@ -957,39 +1044,76 @@ const { XPCOMUtils } = require("resource
     var boxPositions = [],
         containerBox = BoxPosition.getSimpleBoxPosition(rootOfCues),
         fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
     var styleOptions = {
       font: fontSize + "px " + FONT_STYLE
     };
 
     (function() {
-      var styleBox, cue;
+      var styleBox, cue, controlBarBox;
 
       if (controlBarShown) {
+        controlBarBox = BoxPosition.getSimpleBoxPosition(controlBar);
         // Add an empty output box that cover the same region as video control bar.
-        boxPositions.push(BoxPosition.getSimpleBoxPosition(controlBar));
+        boxPositions.push(controlBarBox);
       }
 
+      // https://w3c.github.io/webvtt/#processing-model 6.1.12.1
+      // Create regionNode
+      var regionNodeBoxes = {};
+      var regionNodeBox;
+
       for (var i = 0; i < cues.length; i++) {
         cue = cues[i];
-
-        // Compute the intial position and styles of the cue div.
-        styleBox = new CueStyleBox(window, cue, styleOptions);
-        styleBox.cueDiv.style.setProperty("--cue-font-size", fontSize + "px");
-        rootOfCues.appendChild(styleBox.div);
+        if (cue.region != null) {
+         // 6.1.14.1
+          styleBox = new RegionCueStyleBox(window, cue);
 
-        // Move the cue div to it's correct line position.
-        moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
+          if (!regionNodeBoxes[cue.region.id]) {
+            // create regionNode
+            // Adjust the container hieght to exclude the controlBar
+            var adjustContainerBox = BoxPosition.getSimpleBoxPosition(rootOfCues);
+            if (controlBarShown) {
+              adjustContainerBox.height -= controlBarBox.height;
+              adjustContainerBox.bottom += controlBarBox.height;
+            }
+            regionNodeBox = new RegionNodeBox(window, cue.region, adjustContainerBox);
+            regionNodeBoxes[cue.region.id] = regionNodeBox;
+          }
+          // 6.1.14.3
+          var currentRegionBox = regionNodeBoxes[cue.region.id];
+          var currentRegionNodeDiv = currentRegionBox.div;
+          // 6.1.14.3.2
+          // TODO: fix me, it looks like the we need to set/change "top" attribute at the styleBox.div
+          // to do the "scroll up", however, we do not implement it yet?
+          if (cue.region.scroll == "up" && currentRegionNodeDiv.childElementCount > 0) {
+            styleBox.div.style.transitionProperty = "top";
+            styleBox.div.style.transitionDuration = "0.433s";
+          }
 
-        // Remember the computed div so that we don't have to recompute it later
-        // if we don't have too.
-        cue.displayState = styleBox.div;
+          currentRegionNodeDiv.appendChild(styleBox.div);
+          rootOfCues.appendChild(currentRegionNodeDiv);
+          cue.displayState = styleBox.div;
+          boxPositions.push(BoxPosition.getSimpleBoxPosition(currentRegionBox));
+        } else {
+          // Compute the intial position and styles of the cue div.
+          styleBox = new CueStyleBox(window, cue, styleOptions);
+          styleBox.cueDiv.style.setProperty("--cue-font-size", fontSize + "px");
+          rootOfCues.appendChild(styleBox.div);
 
-        boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
+          // Move the cue div to it's correct line position.
+          moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
+
+          // Remember the computed div so that we don't have to recompute it later
+          // if we don't have too.
+          cue.displayState = styleBox.div;
+
+          boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
+        }
       }
     })();
   };
 
   WebVTT.Parser = function(window, decoder) {
     this.window = window;
     this.state = "INITIAL";
     this.buffer = "";
--- a/dom/vr/test/reftest/reftest.list
+++ b/dom/vr/test/reftest/reftest.list
@@ -1,12 +1,10 @@
 # WebVR Reftests
 # Please confirm there is no other VR display connected. Otherwise, VRPuppetDisplay can't be attached.
 default-preferences pref(dom.vr.puppet.enabled,true) pref(dom.vr.test.enabled,true) pref(dom.vr.require-gesture,false) pref(dom.vr.puppet.submitframe,1) pref(dom.vr.display.rafMaxDuration,200)
 
 # VR SubmitFrame is only implemented for D3D11.1 and MacOSX now.
 # Our Windows 7 test machines don't support D3D11.1, so we run these tests on Windows 8+ only.
-# We need to continue to investigate why these reftests can be run well in local,
-# but will be suspended until terminating on reftest D3D11 debug build.
-skip-if((!winWidget&&release_or_beta)||Android||gtkWidget||(winWidget&&isDebugBuild)||!layersGPUAccelerated||/^Windows\x20NT\x206\.1/.test(http.oscpu)) == draw_rect.html wrapper.html?draw_rect.png
+skip-if((!winWidget&&release_or_beta)||Android||gtkWidget||!layersGPUAccelerated||/^Windows\x20NT\x206\.1/.test(http.oscpu)) == draw_rect.html wrapper.html?draw_rect.png
 # On MacOSX platform, getting different color interpolation result.
 # For lower resolution Mac hardware, we need to adjust it to fuzzy-if(cocoaWidget,1,1200).
-fuzzy-if(cocoaWidget,1,600) skip-if((!winWidget&&release_or_beta)||Android||gtkWidget||(winWidget&&isDebugBuild)||!layersGPUAccelerated||/^Windows\x20NT\x206\.1/.test(http.oscpu)) == change_size.html wrapper.html?change_size.png
+fuzzy-if(cocoaWidget,1,600) skip-if((!winWidget&&release_or_beta)||Android||gtkWidget||!layersGPUAccelerated||/^Windows\x20NT\x206\.1/.test(http.oscpu)) == change_size.html wrapper.html?change_size.png
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -2379,220 +2379,207 @@ EditorBase::ScrollSelectionIntoView(bool
   }
   selectionController->ScrollSelectionIntoView(
                          nsISelectionController::SELECTION_NORMAL,
                          region,
                          nsISelectionController::SCROLL_OVERFLOW_HIDDEN);
   return NS_OK;
 }
 
-void
-EditorBase::FindBetterInsertionPoint(nsCOMPtr<nsIDOMNode>& aNode,
-                                     int32_t& aOffset)
-{
-  nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
-  FindBetterInsertionPoint(node, aOffset, nullptr);
-  aNode = do_QueryInterface(node);
-}
-
-void
-EditorBase::FindBetterInsertionPoint(nsCOMPtr<nsINode>& aNode,
-                                     int32_t& aOffset,
-                                     nsCOMPtr<nsIContent>* aSelChild)
-{
-  if (aNode->IsNodeOfType(nsINode::eTEXT)) {
+EditorRawDOMPoint
+EditorBase::FindBetterInsertionPoint(const EditorRawDOMPoint& aPoint)
+{
+  if (NS_WARN_IF(!aPoint.IsSet())) {
+    return aPoint;
+  }
+
+  MOZ_ASSERT(aPoint.IsSetAndValid());
+
+  if (aPoint.Container()->IsNodeOfType(nsINode::eTEXT)) {
     // There is no "better" insertion point.
-    return;
+    return aPoint;
   }
 
   if (!IsPlaintextEditor()) {
     // We cannot find "better" insertion point in HTML editor.
     // WARNING: When you add some code to find better node in HTML editor,
     //          you need to call this before calling InsertTextImpl() in
     //          HTMLEditRules.
-    return;
-  }
-
-  nsCOMPtr<nsINode> node = aNode;
-  int32_t offset = aOffset;
+    return aPoint;
+  }
 
   nsCOMPtr<nsINode> root = GetRoot();
-  if (aNode == root) {
+  if (aPoint.Container() == root) {
     // In some cases, aNode is the anonymous DIV, and offset is 0.  To avoid
     // injecting unneeded text nodes, we first look to see if we have one
     // available.  In that case, we'll just adjust node and offset accordingly.
-    if (!offset && node->HasChildren() &&
-        node->GetFirstChild()->IsNodeOfType(nsINode::eTEXT)) {
-      aNode = node->GetFirstChild();
-      aOffset = 0;
-      if (aSelChild) {
-        *aSelChild = nullptr;
-      }
-      return;
+    if (aPoint.IsStartOfContainer() &&
+        aPoint.Container()->HasChildren() &&
+        aPoint.Container()->GetFirstChild()->IsNodeOfType(nsINode::eTEXT)) {
+      return EditorRawDOMPoint(aPoint.Container()->GetFirstChild(), 0);
     }
 
     // In some other cases, aNode is the anonymous DIV, and offset points to the
     // terminating mozBR.  In that case, we'll adjust aInOutNode and
     // aInOutOffset to the preceding text node, if any.
-    if (offset) {
+    if (!aPoint.IsStartOfContainer()) {
       if (AsHTMLEditor()) {
         // Fall back to a slow path that uses GetChildAt() for Thunderbird's
         // plaintext editor.
-        nsIContent* child = node->GetChildAt(offset - 1);
+        nsIContent* child = aPoint.GetPreviousSiblingOfChildAtOffset();
         if (child && child->IsNodeOfType(nsINode::eTEXT)) {
-          NS_ENSURE_TRUE_VOID(node->Length() <= INT32_MAX);
-          aNode = child;
-          aOffset = static_cast<int32_t>(aNode->Length());
-          if (aSelChild) {
-            *aSelChild = nullptr;
+          if (NS_WARN_IF(child->Length() > INT32_MAX)) {
+            return aPoint;
           }
-          return;
+          return EditorRawDOMPoint(child, child->Length());
         }
       } else {
         // If we're in a real plaintext editor, use a fast path that avoids
         // calling GetChildAt() which may perform a linear search.
-        nsIContent* child = node->GetLastChild();
+        nsIContent* child = aPoint.Container()->GetLastChild();
         while (child) {
           if (child->IsNodeOfType(nsINode::eTEXT)) {
-            NS_ENSURE_TRUE_VOID(node->Length() <= INT32_MAX);
-            aNode = child;
-            aOffset = static_cast<int32_t>(aNode->Length());
-            if (aSelChild) {
-              *aSelChild = nullptr;
+            if (NS_WARN_IF(child->Length() > INT32_MAX)) {
+              return aPoint;
             }
-            return;
+            return EditorRawDOMPoint(child, child->Length());
           }
           child = child->GetPreviousSibling();
         }
       }
     }
   }
 
   // Sometimes, aNode is the mozBR element itself.  In that case, we'll adjust
   // the insertion point to the previous text node, if one exists, or to the
   // parent anonymous DIV.
-  if (TextEditUtils::IsMozBR(node) && !offset) {
-    if (node->GetPreviousSibling() &&
-        node->GetPreviousSibling()->IsNodeOfType(nsINode::eTEXT)) {
-      NS_ENSURE_TRUE_VOID(node->Length() <= INT32_MAX);
-      aNode = node->GetPreviousSibling();
-      aOffset = static_cast<int32_t>(aNode->Length());
-      if (aSelChild) {
-        *aSelChild = nullptr;
+  if (TextEditUtils::IsMozBR(aPoint.Container()) &&
+      aPoint.IsStartOfContainer()) {
+    nsIContent* previousSibling = aPoint.Container()->GetPreviousSibling();
+    if (previousSibling && previousSibling->IsNodeOfType(nsINode::eTEXT)) {
+      if (NS_WARN_IF(previousSibling->Length() > INT32_MAX)) {
+        return aPoint;
       }
-      return;
+      return EditorRawDOMPoint(previousSibling, previousSibling->Length());
     }
 
-    if (node->GetParentNode() && node->GetParentNode() == root) {
-      if (aSelChild) {
-        *aSelChild = node->AsContent();
-      }
-      aNode = node->GetParentNode();
-      aOffset = 0;
-      return;
+    nsINode* parentOfContainer = aPoint.Container()->GetParentNode();
+    if (parentOfContainer && parentOfContainer == root) {
+      return EditorRawDOMPoint(parentOfContainer,
+                               aPoint.Container()->AsContent(), 0);
     }
   }
+
+  return aPoint;
 }
 
 nsresult
-EditorBase::InsertTextImpl(const nsAString& aStringToInsert,
-                           nsCOMPtr<nsINode>* aInOutNode,
-                           nsCOMPtr<nsIContent>* aInOutChildAtOffset,
-                           int32_t* aInOutOffset,
-                           nsIDocument* aDoc)
+EditorBase::InsertTextImpl(nsIDocument& aDocument,
+                           const nsAString& aStringToInsert,
+                           const EditorRawDOMPoint& aPointToInsert,
+                           EditorRawDOMPoint* aPointAfterInsertedString)
 {
   // NOTE: caller *must* have already used AutoTransactionsConserveSelection
   // stack-based class to turn off txn selection updating.  Caller also turned
   // on rules sniffing if desired.
 
-  NS_ENSURE_TRUE(aInOutNode && *aInOutNode && aInOutOffset && aDoc,
-                 NS_ERROR_NULL_POINTER);
+  if (NS_WARN_IF(!aPointToInsert.IsSet())) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  MOZ_ASSERT(aPointToInsert.IsSetAndValid());
 
   if (!ShouldHandleIMEComposition() && aStringToInsert.IsEmpty()) {
+    if (aPointAfterInsertedString) {
+      *aPointAfterInsertedString = aPointToInsert;
+    }
     return NS_OK;
   }
 
   // This method doesn't support over INT32_MAX length text since aInOutOffset
   // is int32_t*.
   CheckedInt<int32_t> lengthToInsert(aStringToInsert.Length());
-  NS_ENSURE_TRUE(lengthToInsert.isValid(), NS_ERROR_INVALID_ARG);
-
-  nsCOMPtr<nsINode> node = *aInOutNode;
-  int32_t offset = *aInOutOffset;
-  nsCOMPtr<nsIContent> child = *aInOutChildAtOffset;
-
-  MOZ_ASSERT(!node->IsContainerNode() ||
-             node->Length() == static_cast<uint32_t>(offset) ||
-             node->GetChildAt(offset) == *aInOutChildAtOffset,
-    "|child| must be a child node at |offset| in |node| unless it's a text "
-    "or some other data node, or after the last child");
+  if (NS_WARN_IF(!lengthToInsert.isValid())) {
+    return NS_ERROR_INVALID_ARG;
+  }
 
   // In some cases, the node may be the anonymous div elemnt or a mozBR
   // element.  Let's try to look for better insertion point in the nearest
   // text node if there is.
-  FindBetterInsertionPoint(node, offset, address_of(child));
+  EditorRawDOMPoint pointToInsert = FindBetterInsertionPoint(aPointToInsert);
 
   // If a neighboring text node already exists, use that
-  if (!node->IsNodeOfType(nsINode::eTEXT)) {
-    if (offset && child && child->GetPreviousSibling() &&
-        child->GetPreviousSibling()->IsNodeOfType(nsINode::eTEXT)) {
-      node = child->GetPreviousSibling();
-      offset = node->Length();
-    } else if (offset < static_cast<int32_t>(node->Length()) &&
-               child && child->IsNodeOfType(nsINode::eTEXT)) {
-      node = child;
-      offset = 0;
+  if (!pointToInsert.Container()->IsNodeOfType(nsINode::eTEXT)) {
+    nsIContent* child = nullptr;
+    if (!pointToInsert.IsStartOfContainer() &&
+        (child = pointToInsert.GetPreviousSiblingOfChildAtOffset()) &&
+        child->IsNodeOfType(nsINode::eTEXT)) {
+      pointToInsert.Set(child, child->Length());
+    } else if (!pointToInsert.IsEndOfContainer() &&
+               (child = pointToInsert.GetChildAtOffset()) &&
+               child->IsNodeOfType(nsINode::eTEXT)) {
+      pointToInsert.Set(child, 0);
     }
   }
 
   if (ShouldHandleIMEComposition()) {
     CheckedInt<int32_t> newOffset;
-    if (!node->IsNodeOfType(nsINode::eTEXT)) {
+    if (!pointToInsert.Container()->IsNodeOfType(nsINode::eTEXT)) {
       // create a text node
       RefPtr<nsTextNode> newNode =
-        EditorBase::CreateTextNode(*aDoc, EmptyString());
+        EditorBase::CreateTextNode(aDocument, EmptyString());
       // then we insert it into the dom tree
-      nsresult rv = InsertNode(*newNode, *node, offset);
+      nsresult rv = InsertNode(*newNode, *pointToInsert.Container(),
+                               pointToInsert.Offset());
       NS_ENSURE_SUCCESS(rv, rv);
-      node = newNode;
-      offset = 0;
+      pointToInsert.Set(newNode, 0);
       newOffset = lengthToInsert;
     } else {
-      newOffset = lengthToInsert + offset;
+      newOffset = lengthToInsert + pointToInsert.Offset();
       NS_ENSURE_TRUE(newOffset.isValid(), NS_ERROR_FAILURE);
     }
     nsresult rv =
-      InsertTextIntoTextNodeImpl(aStringToInsert, *node->GetAsText(), offset);
+      InsertTextIntoTextNodeImpl(aStringToInsert,
+                                 *pointToInsert.Container()->GetAsText(),
+                                 pointToInsert.Offset());
     NS_ENSURE_SUCCESS(rv, rv);
-    offset = newOffset.value();
-  } else {
-    if (node->IsNodeOfType(nsINode::eTEXT)) {
-      CheckedInt<int32_t> newOffset = lengthToInsert + offset;
-      NS_ENSURE_TRUE(newOffset.isValid(), NS_ERROR_FAILURE);
-      // we are inserting text into an existing text node.
-      nsresult rv =
-        InsertTextIntoTextNodeImpl(aStringToInsert, *node->GetAsText(), offset);
-      NS_ENSURE_SUCCESS(rv, rv);
-      offset = newOffset.value();
-    } else {
-      // we are inserting text into a non-text node.  first we have to create a
-      // textnode (this also populates it with the text)
-      RefPtr<nsTextNode> newNode =
-        EditorBase::CreateTextNode(*aDoc, aStringToInsert);
-      // then we insert it into the dom tree
-      nsresult rv = InsertNode(*newNode, *node, offset);
-      NS_ENSURE_SUCCESS(rv, rv);
-      node = newNode;
-      offset = lengthToInsert.value();
+    if (aPointAfterInsertedString) {
+      aPointAfterInsertedString->Set(pointToInsert.Container(),
+                                     newOffset.value());
     }
-  }
-
-  *aInOutNode = node;
-  *aInOutOffset = offset;
-  *aInOutChildAtOffset = nullptr;
+    return NS_OK;
+  }
+
+  if (pointToInsert.Container()->IsNodeOfType(nsINode::eTEXT)) {
+    CheckedInt<int32_t> newOffset = lengthToInsert + pointToInsert.Offset();
+    NS_ENSURE_TRUE(newOffset.isValid(), NS_ERROR_FAILURE);
+    // we are inserting text into an existing text node.
+    nsresult rv =
+      InsertTextIntoTextNodeImpl(aStringToInsert,
+                                 *pointToInsert.Container()->GetAsText(),
+                                 pointToInsert.Offset());
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (aPointAfterInsertedString) {
+      aPointAfterInsertedString->Set(pointToInsert.Container(),
+                                     newOffset.value());
+    }
+    return NS_OK;
+  }
+
+  // we are inserting text into a non-text node.  first we have to create a
+  // textnode (this also populates it with the text)
+  RefPtr<nsTextNode> newNode =
+    EditorBase::CreateTextNode(aDocument, aStringToInsert);
+  // then we insert it into the dom tree
+  nsresult rv = InsertNode(*newNode, *pointToInsert.Container(),
+                           pointToInsert.Offset());
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (aPointAfterInsertedString) {
+    aPointAfterInsertedString->Set(newNode, lengthToInsert.value());
+  }
   return NS_OK;
 }
 
 nsresult
 EditorBase::InsertTextIntoTextNodeImpl(const nsAString& aStringToInsert,
                                        Text& aTextNode,
                                        int32_t aOffset,
                                        bool aSuppressIME)
@@ -2688,17 +2675,17 @@ EditorBase::SelectEntireDocument(Selecti
 
 nsINode*
 EditorBase::GetFirstEditableNode(nsINode* aRoot)
 {
   MOZ_ASSERT(aRoot);
 
   nsIContent* node = GetLeftmostChild(aRoot);
   if (node && !IsEditable(node)) {
-    node = GetNextNode(node, /* aEditableNode = */ true);
+    node = GetNextEditableNode(*node);
   }
 
   return (node != aRoot) ? node : nullptr;
 }
 
 nsresult
 EditorBase::NotifyDocumentListeners(
               TDocumentListenerNotification aNotificationType)
@@ -3293,114 +3280,139 @@ EditorBase::GetLengthOfDOMNode(nsIDOMNod
   aCount = 0;
   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
   NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
   aCount = node->Length();
   return NS_OK;
 }
 
 nsIContent*
-EditorBase::GetPriorNode(nsINode* aParentNode,
-                         int32_t aOffset,
-                         nsINode* aChildAtOffset,
-                         bool aEditableNode,
-                         bool aNoBlockCrossing)
-{
-  MOZ_ASSERT(aParentNode);
+EditorBase::GetPreviousNodeInternal(nsINode& aNode,
+                                    bool aFindEditableNode,
+                                    bool aNoBlockCrossing)
+{
+  if (!IsDescendantOfEditorRoot(&aNode)) {
+    return nullptr;
+  }
+  return FindNode(&aNode, false, aFindEditableNode, aNoBlockCrossing);
+}
+
+nsIContent*
+EditorBase::GetPreviousNodeInternal(const EditorRawDOMPoint& aPoint,
+                                    bool aFindEditableNode,
+                                    bool aNoBlockCrossing)
+{
+  MOZ_ASSERT(aPoint.IsSetAndValid());
+  NS_WARNING_ASSERTION(!aPoint.Container()->IsNodeOfType(nsINode::eDATA_NODE) ||
+                       aPoint.Container()->IsNodeOfType(nsINode::eTEXT),
+    "GetPreviousNodeInternal() doesn't assume that the start point is a "
+    "data node except text node");
 
   // If we are at the beginning of the node, or it is a text node, then just
   // look before it.
-  if (!aOffset || aParentNode->NodeType() == nsIDOMNode::TEXT_NODE) {
-    if (aNoBlockCrossing && IsBlockNode(aParentNode)) {
+  if (aPoint.IsStartOfContainer() ||
+      aPoint.Container()->IsNodeOfType(nsINode::eTEXT)) {
+    if (aNoBlockCrossing && IsBlockNode(aPoint.Container())) {
       // If we aren't allowed to cross blocks, don't look before this block.
       return nullptr;
     }
-    return GetPriorNode(aParentNode, aEditableNode, aNoBlockCrossing);
+    return GetPreviousNodeInternal(*aPoint.Container(),
+                                   aFindEditableNode, aNoBlockCrossing);
   }
 
   // else look before the child at 'aOffset'
-  if (aChildAtOffset) {
-    return GetPriorNode(aChildAtOffset, aEditableNode, aNoBlockCrossing);
+  if (aPoint.GetChildAtOffset()) {
+    return GetPreviousNodeInternal(*aPoint.GetChildAtOffset(),
+                                   aFindEditableNode, aNoBlockCrossing);
   }
 
   // unless there isn't one, in which case we are at the end of the node
   // and want the deep-right child.
-  nsIContent* resultNode = GetRightmostChild(aParentNode, aNoBlockCrossing);
-  if (!resultNode || !aEditableNode || IsEditable(resultNode)) {
-    return resultNode;
+  nsIContent* rightMostNode =
+    GetRightmostChild(aPoint.Container(), aNoBlockCrossing);
+  if (!rightMostNode) {
+    return nullptr;
+  }
+
+  if (!aFindEditableNode || IsEditable(rightMostNode)) {
+    return rightMostNode;
   }
 
   // restart the search from the non-editable node we just found
-  return GetPriorNode(resultNode, aEditableNode, aNoBlockCrossing);
+  return GetPreviousNodeInternal(*rightMostNode,
+                                 aFindEditableNode, aNoBlockCrossing);
+}
+
+nsIContent*
+EditorBase::GetNextNodeInternal(nsINode& aNode,
+                                bool aFindEditableNode,
+                                bool aNoBlockCrossing)
+{
+  if (!IsDescendantOfEditorRoot(&aNode)) {
+    return nullptr;
+  }
+  return FindNode(&aNode, true, aFindEditableNode, aNoBlockCrossing);
 }
 
 nsIContent*
-EditorBase::GetNextNode(nsINode* aParentNode,
-                        int32_t aOffset,
-                        nsINode* aChildAtOffset,
-                        bool aEditableNode,
-                        bool aNoBlockCrossing)
-{
-  MOZ_ASSERT(aParentNode);
-
-  // if aParentNode is a text node, use its location instead
-  if (aParentNode->NodeType() == nsIDOMNode::TEXT_NODE) {
-    nsINode* parent = aParentNode->GetParentNode();
-    NS_ENSURE_TRUE(parent, nullptr);
-    aOffset = parent->IndexOf(aParentNode) + 1; // _after_ the text node
-    aParentNode = parent;
+EditorBase::GetNextNodeInternal(const EditorRawDOMPoint& aPoint,
+                                bool aFindEditableNode,
+                                bool aNoBlockCrossing)
+{
+  MOZ_ASSERT(aPoint.IsSetAndValid());
+  NS_WARNING_ASSERTION(!aPoint.Container()->IsNodeOfType(nsINode::eDATA_NODE) ||
+                       aPoint.Container()->IsNodeOfType(nsINode::eTEXT),
+    "GetNextNodeInternal() doesn't assume that the start point is a "
+    "data node except text node");
+
+  EditorRawDOMPoint point(aPoint);
+
+  // if the container is a text node, use its location instead
+  if (point.Container()->IsNodeOfType(nsINode::eTEXT)) {
+    point.Set(point.Container());
+    bool advanced = point.AdvanceOffset();
+    if (NS_WARN_IF(!advanced)) {
+      return nullptr;
+    }
   }
 
   // look at the child at 'aOffset'
-  if (aChildAtOffset) {
-    if (aNoBlockCrossing && IsBlockNode(aChildAtOffset)) {
-      MOZ_ASSERT(aChildAtOffset->IsContent());
-      return aChildAtOffset->AsContent();
+  if (point.GetChildAtOffset()) {
+    if (aNoBlockCrossing && IsBlockNode(point.GetChildAtOffset())) {
+      return point.GetChildAtOffset();
     }
 
-    nsIContent* resultNode = GetLeftmostChild(aChildAtOffset, aNoBlockCrossing);
-    if (!resultNode) {
-      MOZ_ASSERT(aChildAtOffset->IsContent());
-      return aChildAtOffset->AsContent();
+    nsIContent* leftMostNode =
+      GetLeftmostChild(point.GetChildAtOffset(), aNoBlockCrossing);
+    if (!leftMostNode) {
+      return point.GetChildAtOffset();
     }
 
-    if (!IsDescendantOfEditorRoot(resultNode)) {
+    if (!IsDescendantOfEditorRoot(leftMostNode)) {
       return nullptr;
     }
 
-    if (!aEditableNode || IsEditable(resultNode)) {
-      return resultNode;
+    if (!aFindEditableNode || IsEditable(leftMostNode)) {
+      return leftMostNode;
     }
 
     // restart the search from the non-editable node we just found
-    return GetNextNode(resultNode, aEditableNode, aNoBlockCrossing);
+    return GetNextNodeInternal(*leftMostNode,
+                               aFindEditableNode, aNoBlockCrossing);
   }
 
   // unless there isn't one, in which case we are at the end of the node
   // and want the next one.
-  if (aNoBlockCrossing && IsBlockNode(aParentNode)) {
+  if (aNoBlockCrossing && IsBlockNode(point.Container())) {
     // don't cross out of parent block
     return nullptr;
   }
 
-  return GetNextNode(aParentNode, aEditableNode, aNoBlockCrossing);
-}
-
-nsIContent*
-EditorBase::GetPriorNode(nsINode* aCurrentNode,
-                         bool aEditableNode,
-                         bool aNoBlockCrossing /* = false */)
-{
-  MOZ_ASSERT(aCurrentNode);
-
-  if (!IsDescendantOfEditorRoot(aCurrentNode)) {
-    return nullptr;
-  }
-
-  return FindNode(aCurrentNode, false, aEditableNode, aNoBlockCrossing);
+  return GetNextNodeInternal(*point.Container(),
+                             aFindEditableNode, aNoBlockCrossing);
 }
 
 nsIContent*
 EditorBase::FindNextLeafNode(nsINode* aCurrentNode,
                              bool aGoForward,
                              bool bNoBlockCrossing)
 {
   // called only by GetPriorNode so we don't need to check params.
@@ -3446,30 +3458,16 @@ EditorBase::FindNextLeafNode(nsINode* aC
     cur = parent;
   }
 
   NS_NOTREACHED("What part of for(;;) do you not understand?");
   return nullptr;
 }
 
 nsIContent*
-EditorBase::GetNextNode(nsINode* aCurrentNode,
-                        bool aEditableNode,
-                        bool bNoBlockCrossing)
-{
-  MOZ_ASSERT(aCurrentNode);
-
-  if (!IsDescendantOfEditorRoot(aCurrentNode)) {
-    return nullptr;
-  }
-
-  return FindNode(aCurrentNode, true, aEditableNode, bNoBlockCrossing);
-}
-
-nsIContent*
 EditorBase::FindNode(nsINode* aCurrentNode,
                      bool aGoForward,
                      bool aEditableNode,
                      bool bNoBlockCrossing)
 {
   if (IsEditorRoot(aCurrentNode)) {
     // Don't allow traversal above the root node! This helps
     // prevent us from accidentally editing browser content
@@ -3819,28 +3817,43 @@ EditorBase::GetStartNodeAndOffset(Select
 {
   MOZ_ASSERT(aSelection);
   MOZ_ASSERT(aStartContainer);
   MOZ_ASSERT(aStartOffset);
 
   *aStartContainer = nullptr;
   *aStartOffset = 0;
 
-  if (!aSelection->RangeCount()) {
+  EditorRawDOMPoint point = EditorBase::GetStartPoint(aSelection);
+  if (!point.IsSet()) {
     return NS_ERROR_FAILURE;
   }
 
+  NS_ADDREF(*aStartContainer = point.Container());
+  *aStartOffset = point.Offset();
+  return NS_OK;
+}
+
+// static
+EditorRawDOMPoint
+EditorBase::GetStartPoint(Selection* aSelection)
+{
+  MOZ_ASSERT(aSelection);
+
+  if (NS_WARN_IF(!aSelection->RangeCount())) {
+    return EditorRawDOMPoint();
+  }
+
   const nsRange* range = aSelection->GetRangeAt(0);
-  NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
-
-  NS_ENSURE_TRUE(range->IsPositioned(), NS_ERROR_FAILURE);
-
-  NS_IF_ADDREF(*aStartContainer = range->GetStartContainer());
-  *aStartOffset = range->StartOffset();
-  return NS_OK;
+  if (NS_WARN_IF(!range) ||
+      NS_WARN_IF(!range->IsPositioned())) {
+    return EditorRawDOMPoint();
+  }
+
+  return EditorRawDOMPoint(range->StartRef());
 }
 
 /**
  * GetEndNodeAndOffset() returns whatever the end parent & offset is of
  * the first range in the selection.
  */
 nsresult
 EditorBase::GetEndNodeAndOffset(Selection* aSelection,
@@ -3869,26 +3882,43 @@ EditorBase::GetEndNodeAndOffset(Selectio
 {
   MOZ_ASSERT(aSelection);
   MOZ_ASSERT(aEndContainer);
   MOZ_ASSERT(aEndOffset);
 
   *aEndContainer = nullptr;
   *aEndOffset = 0;
 
-  NS_ENSURE_TRUE(aSelection->RangeCount(), NS_ERROR_FAILURE);
+  EditorRawDOMPoint point = EditorBase::GetEndPoint(aSelection);
+  if (!point.IsSet()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  NS_ADDREF(*aEndContainer = point.Container());
+  *aEndOffset = point.Offset();
+  return NS_OK;
+}
+
+// static
+EditorRawDOMPoint
+EditorBase::GetEndPoint(Selection* aSelection)
+{
+  MOZ_ASSERT(aSelection);
+
+  if (NS_WARN_IF(!aSelection->RangeCount())) {
+    return EditorRawDOMPoint();
+  }
 
   const nsRange* range = aSelection->GetRangeAt(0);
-  NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
-
-  NS_ENSURE_TRUE(range->IsPositioned(), NS_ERROR_FAILURE);
-
-  NS_IF_ADDREF(*aEndContainer = range->GetEndContainer());
-  *aEndOffset = range->EndOffset();
-  return NS_OK;
+  if (NS_WARN_IF(!range) ||
+      NS_WARN_IF(!range->IsPositioned())) {
+    return EditorRawDOMPoint();
+  }
+
+  return EditorRawDOMPoint(range->EndRef());
 }
 
 nsresult
 EditorBase::GetEndChildNode(Selection* aSelection,
                             nsIContent** aEndNode)
 {
   MOZ_ASSERT(aSelection);
   MOZ_ASSERT(aEndNode);
@@ -4575,17 +4605,17 @@ EditorBase::CreateTxnForDeleteRange(nsRa
   // XXX: if isFirst && isLast, then we'll need to delete the node
   //      as well as the 1 child
 
   // build a transaction for deleting the appropriate data
   // XXX: this has to come from rule section
   if (aAction == ePrevious && isFirst) {
     // we're backspacing from the beginning of the node.  Delete the first
     // thing to our left
-    nsCOMPtr<nsIContent> priorNode = GetPriorNode(node, true);
+    nsCOMPtr<nsIContent> priorNode = GetPreviousEditableNode(*node);
     if (NS_WARN_IF(!priorNode)) {
       return nullptr;
     }
 
     // there is a priorNode, so delete its last child (if chardata, delete the
     // last char). if it has no children, delete it
     if (priorNode->IsNodeOfType(nsINode::eDATA_NODE)) {
       RefPtr<nsGenericDOMDataNode> priorNodeAsCharData =
@@ -4614,17 +4644,17 @@ EditorBase::CreateTxnForDeleteRange(nsRa
     }
     priorNode.forget(aRemovingNode);
     return deleteNodeTransaction.forget();
   }
 
   if (aAction == eNext && isLast) {
     // we're deleting from the end of the node.  Delete the first thing to our
     // right
-    nsCOMPtr<nsIContent> nextNode = GetNextNode(node, true);
+    nsCOMPtr<nsIContent> nextNode = GetNextEditableNode(*node);
     if (NS_WARN_IF(!nextNode)) {
       return nullptr;
     }
 
     // there is a nextNode, so delete its first child (if chardata, delete the
     // first char). if it has no children, delete it
     if (nextNode->IsNodeOfType(nsINode::eDATA_NODE)) {
       RefPtr<nsGenericDOMDataNode> nextNodeAsCharData =
@@ -4669,29 +4699,30 @@ EditorBase::CreateTxnForDeleteRange(nsRa
     node.forget(aRemovingNode);
     return deleteTextTransaction.forget();
   }
 
   // we're either deleting a node or chardata, need to dig into the next/prev
   // node to find out
   nsCOMPtr<nsINode> selectedNode;
   if (aAction == ePrevious) {
-    selectedNode = GetPriorNode(node, offset, child, true);
+    selectedNode =
+      GetPreviousEditableNode(EditorRawDOMPoint(node, child, offset));
   } else if (aAction == eNext) {
-    selectedNode = GetNextNode(node, offset, child, true);
+    selectedNode = GetNextEditableNode(EditorRawDOMPoint(node, child, offset));
   }
 
   while (selectedNode &&
          selectedNode->IsNodeOfType(nsINode::eDATA_NODE) &&
          !selectedNode->Length()) {
     // Can't delete an empty chardata node (bug 762183)
     if (aAction == ePrevious) {
-      selectedNode = GetPriorNode(selectedNode, true);
+      selectedNode = GetPreviousEditableNode(*selectedNode);
     } else if (aAction == eNext) {
-      selectedNode = GetNextNode(selectedNode, true);
+      selectedNode = GetNextEditableNode(*selectedNode);
     }
   }
 
   if (NS_WARN_IF(!selectedNode)) {
     return nullptr;
   }
 
   if (selectedNode->IsNodeOfType(nsINode::eDATA_NODE)) {
@@ -4973,20 +5004,20 @@ EditorBase::InitializeSelection(nsIDOMEv
   // If there is composition when this is called, we may need to restore IME
   // selection because if the editor is reframed, this already forgot IME
   // selection and the transaction.
   if (mComposition && !mIMETextNode && mIMETextLength) {
     // We need to look for the new mIMETextNode from current selection.
     // XXX If selection is changed during reframe, this doesn't work well!
     nsRange* firstRange = selection->GetRangeAt(0);
     NS_ENSURE_TRUE(firstRange, NS_ERROR_FAILURE);
-    nsCOMPtr<nsINode> startNode = firstRange->GetStartContainer();
-    int32_t startOffset = firstRange->StartOffset();
-    FindBetterInsertionPoint(startNode, startOffset, nullptr);
-    Text* textNode = startNode->GetAsText();
+    EditorRawDOMPoint atStartOfFirstRange(firstRange->StartRef());
+    EditorRawDOMPoint betterInsertionPoint =
+      FindBetterInsertionPoint(atStartOfFirstRange);
+    Text* textNode = betterInsertionPoint.Container()->GetAsText();
     MOZ_ASSERT(textNode,
                "There must be text node if mIMETextLength is larger than 0");
     if (textNode) {
       MOZ_ASSERT(textNode->Length() >= mIMETextOffset + mIMETextLength,
                  "The text node must be different from the old mIMETextNode");
       CompositionTransaction::SetIMESelection(*this, textNode, mIMETextOffset,
                                               mIMETextLength,
                                               mComposition->GetRanges());
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -281,21 +281,42 @@ public:
   void NotifyEditorObservers(NotificationForEditorObservers aNotification);
 
   // nsIEditor methods
   NS_DECL_NSIEDITOR
 
 public:
   virtual bool IsModifiableNode(nsINode* aNode);
 
-  virtual nsresult InsertTextImpl(const nsAString& aStringToInsert,
-                                  nsCOMPtr<nsINode>* aInOutNode,
-                                  nsCOMPtr<nsIContent>* aInOutChildAtOffset,
-                                  int32_t* aInOutOffset,
-                                  nsIDocument* aDoc);
+  /**
+   * InsertTextImpl() inserts aStringToInsert to aPointToInsert or better
+   * insertion point around it.  If aPointToInsert isn't in a text node,
+   * this method looks for the nearest point in a text node with
+   * FindBetterInsertionPoint().  If there is no text node, this creates
+   * new text node and put aStringToInsert to it.
+   *
+   * @param aDocument       The document of this editor.
+   * @param aStringToInsert The string to insert.
+   * @param aPointToInser   The point to insert aStringToInsert.
+   *                        Must be valid DOM point.
+   * @param aPointAfterInsertedString
+   *                        The point after inserted aStringToInsert.
+   *                        So, when this method actually inserts string,
+   *                        this is set to a point in the text node.
+   *                        Otherwise, this may be set to aPointToInsert.
+   * @return                When this succeeds to insert the string or
+   *                        does nothing during composition, returns NS_OK.
+   *                        Otherwise, an error code.
+   */
+  virtual nsresult
+  InsertTextImpl(nsIDocument& aDocument,
+                 const nsAString& aStringToInsert,
+                 const EditorRawDOMPoint& aPointToInsert,
+                 EditorRawDOMPoint* aPointAfterInsertedString = nullptr);
+
   nsresult InsertTextIntoTextNodeImpl(const nsAString& aStringToInsert,
                                       Text& aTextNode, int32_t aOffset,
                                       bool aSuppressIME = false);
 
   nsresult SetTextImpl(Selection& aSelection,
                        const nsAString& aString,
                        Text& aTextNode);
 
@@ -581,21 +602,70 @@ protected:
    * that the editor's sync/async settings for reflowing, painting, and
    * scrolling match.
    */
   nsresult ScrollSelectionIntoView(bool aScrollToAnchor);
 
   virtual bool IsBlockNode(nsINode* aNode);
 
   /**
-   * Helper for GetPriorNode() and GetNextNode().
+   * Helper for GetPreviousNodeInternal() and GetNextNodeInternal().
    */
   nsIContent* FindNextLeafNode(nsINode* aCurrentNode,
                                bool aGoForward,
                                bool bNoBlockCrossing);
+  nsIContent* FindNode(nsINode* aCurrentNode,
+                       bool aGoForward,
+                       bool aEditableNode,
+                       bool bNoBlockCrossing);
+
+  /**
+   * Get the node immediately previous node of aNode.
+   * @param atNode               The node from which we start the search.
+   * @param aFindEditableNode    If true, only return an editable node.
+   * @param aNoBlockCrossing     If true, don't move across "block" nodes,
+   *                             whatever that means.
+   * @return                     The node that occurs before aNode in
+   *                             the tree, skipping non-editable nodes if
+   *                             aFindEditableNode is true.  If there is no
+   *                             previous node, returns nullptr.
+   */
+  nsIContent* GetPreviousNodeInternal(nsINode& aNode,
+                                      bool aFindEditableNode,
+                                      bool aNoBlockCrossing);
+
+  /**
+   * And another version that takes a point in DOM tree rather than a node.
+   */
+  nsIContent* GetPreviousNodeInternal(const EditorRawDOMPoint& aPoint,
+                                      bool aFindEditableNode,
+                                      bool aNoBlockCrossing);
+
+  /**
+   * Get the node immediately next node of aNode.
+   * @param aNode                The node from which we start the search.
+   * @param aFindEditableNode    If true, only return an editable node.
+   * @param aNoBlockCrossing     If true, don't move across "block" nodes,
+   *                             whatever that means.
+   * @return                     The node that occurs after aNode in the
+   *                             tree, skipping non-editable nodes if
+   *                             aFindEditableNode is true.  If there is no
+   *                             next node, returns nullptr.
+   */
+  nsIContent* GetNextNodeInternal(nsINode& aNode,
+                                  bool aFindEditableNode,
+                                  bool bNoBlockCrossing);
+
+  /**
+   * And another version that takes a point in DOM tree rather than a node.
+   */
+  nsIContent* GetNextNodeInternal(const EditorRawDOMPoint& aPoint,
+                                  bool aFindEditableNode,
+                                  bool aNoBlockCrossing);
+
 
   virtual nsresult InstallEventListeners();
   virtual void CreateEventListeners();
   virtual void RemoveEventListeners();
 
   /**
    * Return true if spellchecking should be enabled for this editor.
    */
@@ -711,69 +781,113 @@ public:
    * @param  aNode is the node to get the length of.
    *         If aNode is text, returns number of characters.
    *         If not, returns number of children nodes.
    * @param  aCount [OUT] the result of the above calculation.
    */
   static nsresult GetLengthOfDOMNode(nsIDOMNode *aNode, uint32_t &aCount);
 
   /**
-   * Get the node immediately prior to aCurrentNode.
-   * @param aCurrentNode   the node from which we start the search
-   * @param aEditableNode  if true, only return an editable node
-   * @param aResultNode    [OUT] the node that occurs before aCurrentNode in
-   *                             the tree, skipping non-editable nodes if
-   *                             aEditableNode is true.  If there is no prior
-   *                             node, aResultNode will be nullptr.
-   * @param bNoBlockCrossing If true, don't move across "block" nodes,
-   *                         whatever that means.
+   * Get the previous node.
    */
-  nsIContent* GetPriorNode(nsINode* aCurrentNode, bool aEditableNode,
-                           bool aNoBlockCrossing = false);
-
-  /**
-   * And another version that takes a {parent,offset} pair rather than a node.
-   */
-  nsIContent* GetPriorNode(nsINode* aParentNode,
-                           int32_t aOffset,
-                           nsINode* aChildAtOffset,
-                           bool aEditableNode,
-                           bool aNoBlockCrossing = false);
-
+  nsIContent* GetPreviousNode(const EditorRawDOMPoint& aPoint)
+  {
+    return GetPreviousNodeInternal(aPoint, false, false);
+  }
+  nsIContent* GetPreviousEditableNode(const EditorRawDOMPoint& aPoint)
+  {
+    return GetPreviousNodeInternal(aPoint, true, false);
+  }
+  nsIContent* GetPreviousNodeInBlock(const EditorRawDOMPoint& aPoint)
+  {
+    return GetPreviousNodeInternal(aPoint, false, true);
+  }
+  nsIContent* GetPreviousEditableNodeInBlock(
+                const EditorRawDOMPoint& aPoint)
+  {
+    return GetPreviousNodeInternal(aPoint, true, true);
+  }
+  nsIContent* GetPreviousNode(nsINode& aNode)
+  {
+    return GetPreviousNodeInternal(aNode, false, false);
+  }
+  nsIContent* GetPreviousEditableNode(nsINode& aNode)
+  {
+    return GetPreviousNodeInternal(aNode, true, false);
+  }
+  nsIContent* GetPreviousNodeInBlock(nsINode& aNode)
+  {
+    return GetPreviousNodeInternal(aNode, false, true);
+  }
+  nsIContent* GetPreviousEditableNodeInBlock(nsINode& aNode)
+  {
+    return GetPreviousNodeInternal(aNode, true, true);
+  }
 
   /**
-   * Get the node immediately after to aCurrentNode.
-   * @param aCurrentNode   the node from which we start the search
-   * @param aEditableNode  if true, only return an editable node
-   * @param aResultNode    [OUT] the node that occurs after aCurrentNode in the
-   *                             tree, skipping non-editable nodes if
-   *                             aEditableNode is true.  If there is no prior
-   *                             node, aResultNode will be nullptr.
+   * Get the next node.
+   *
+   * Note that methods taking EditorRawDOMPoint behavior includes the
+   * child at offset as search target.  E.g., following code causes infinite
+   * loop.
+   *
+   * EditorRawDOMPoint point(aEditableNode);
+   * while (nsIContent* content = GetNextEditableNode(point)) {
+   *   // Do something...
+   *   point.Set(content);
+   * }
+   *
+   * Following code must be you expected:
+   *
+   * while (nsIContent* content = GetNextEditableNode(point)) {
+   *   // Do something...
+   *   DebugOnly<bool> advanced = point.Advanced();
+   *   MOZ_ASSERT(advanced);
+   *   point.Set(point.GetChildAtOffset());
+   * }
+   *
+   * On the other hand, the methods taking nsINode behavior must be what
+   * you want.  They start to search the result from next node of the given
+   * node.
    */
-  nsIContent* GetNextNode(nsINode* aCurrentNode,
-                          bool aEditableNode,
-                          bool bNoBlockCrossing = false);
+  nsIContent* GetNextNode(const EditorRawDOMPoint& aPoint)
+  {
+    return GetNextNodeInternal(aPoint, false, false);
+  }
+  nsIContent* GetNextEditableNode(const EditorRawDOMPoint& aPoint)
+  {
+    return GetNextNodeInternal(aPoint, true, false);
+  }
+  nsIContent* GetNextNodeInBlock(const EditorRawDOMPoint& aPoint)
+  {
+    return GetNextNodeInternal(aPoint, false, true);
+  }
+  nsIContent* GetNextEditableNodeInBlock(
+                const EditorRawDOMPoint& aPoint)
+  {
+    return GetNextNodeInternal(aPoint, true, true);
+  }
+  nsIContent* GetNextNode(nsINode& aNode)
+  {
+    return GetNextNodeInternal(aNode, false, false);
+  }
+  nsIContent* GetNextEditableNode(nsINode& aNode)
+  {
+    return GetNextNodeInternal(aNode, true, false);
+  }
+  nsIContent* GetNextNodeInBlock(nsINode& aNode)
+  {
+    return GetNextNodeInternal(aNode, false, true);
+  }
+  nsIContent* GetNextEditableNodeInBlock(nsINode& aNode)
+  {
+    return GetNextNodeInternal(aNode, true, true);
+  }
 
   /**
-   * And another version that takes a {parent,offset} pair rather than a node.
-   */
-  nsIContent* GetNextNode(nsINode* aParentNode,
-                          int32_t aOffset,
-                          nsINode* aChildAtOffset,
-                          bool aEditableNode,
-                          bool aNoBlockCrossing = false);
-
-  /**
-   * Helper for GetNextNode() and GetPriorNode().
-   */
-  nsIContent* FindNode(nsINode* aCurrentNode,
-                       bool aGoForward,
-                       bool aEditableNode,
-                       bool bNoBlockCrossing);
-  /**
    * Get the rightmost child of aCurrentNode;
    * return nullptr if aCurrentNode has no children.
    */
   nsIContent* GetRightmostChild(nsINode* aCurrentNode,
                                 bool bNoBlockCrossing = false);
 
   /**
    * Get the leftmost child of aCurrentNode;
@@ -922,22 +1036,24 @@ public:
   static nsIContent* GetNodeAtRangeOffsetPoint(const RawRangeBoundary& aPoint);
 
   static nsresult GetStartNodeAndOffset(Selection* aSelection,
                                         nsIDOMNode** outStartNode,
                                         int32_t* outStartOffset);
   static nsresult GetStartNodeAndOffset(Selection* aSelection,
                                         nsINode** aStartContainer,
                                         int32_t* aStartOffset);
+  static EditorRawDOMPoint GetStartPoint(Selection* aSelection);
   static nsresult GetEndNodeAndOffset(Selection* aSelection,
                                       nsIDOMNode** outEndNode,
                                       int32_t* outEndOffset);
   static nsresult GetEndNodeAndOffset(Selection* aSelection,
                                       nsINode** aEndContainer,
                                       int32_t* aEndOffset);
+  static EditorRawDOMPoint GetEndPoint(Selection* aSelection);
 
   static nsresult GetEndChildNode(Selection* aSelection,
                                   nsIContent** aEndNode);
 
 #if DEBUG_JOE
   static void DumpNode(nsIDOMNode* aNode, int32_t indent = 0);
 #endif
   Selection* GetSelection(SelectionType aSelectionType =
@@ -1253,32 +1369,21 @@ public:
    * the aTextNode.  If there is no IME selection, returns -1.
    */
   int32_t GetIMESelectionStartOffsetIn(nsINode* aTextNode);
 
   /**
    * FindBetterInsertionPoint() tries to look for better insertion point which
    * is typically the nearest text node and offset in it.
    *
-   * @param aNode in/out param, on input set to the node to use to start the search,
-   *              on output set to the node found as the better insertion point.
-   * @param aOffset in/out param, on input set to the offset to use to start the
-   *                search, on putput set to the offset found as the better insertion
-   *                point.
-   * @param aSelChild in/out param, on input, can be set to nullptr if the caller
-   *                  doesn't want to pass this in, or set to a pointer to an nsCOMPtr
-   *                  pointing to the child at the input node and offset, and on output
-   *                  the method will make it point to the child at the output node and
-   *                  offset returned in aNode and aOffset.
+   * @param aPoint      Insertion point which the callers found.
+   * @return            Better insertion point if there is.  If not returns
+   *                    same point as aPoint.
    */
-  void FindBetterInsertionPoint(nsCOMPtr<nsIDOMNode>& aNode,
-                                int32_t& aOffset);
-  void FindBetterInsertionPoint(nsCOMPtr<nsINode>& aNode,
-                                int32_t& aOffset,
-                                nsCOMPtr<nsIContent>* aSelChild);
+  EditorRawDOMPoint FindBetterInsertionPoint(const EditorRawDOMPoint& aPoint);
 
   /**
    * HideCaret() hides caret with nsCaret::AddForceHide() or may show carent
    * with nsCaret::RemoveForceHide().  This does NOT set visibility of
    * nsCaret.  Therefore, this is stateless.
    */
   void HideCaret(bool aHide);
 
--- a/editor/libeditor/EditorDOMPoint.h
+++ b/editor/libeditor/EditorDOMPoint.h
@@ -79,16 +79,29 @@ public:
   {
   }
 
   explicit EditorDOMPointBase(const RawRangeBoundary& aRawRangeBoundary)
     : RangeBoundaryBase<ParentType, RefType>(aRawRangeBoundary)
   {
   }
 
+  EditorDOMPointBase<nsINode*, nsIContent*>
+  AsRaw() const
+  {
+    return EditorDOMPointBase<nsINode*, nsIContent*>(*this);
+  }
+
+  template<typename A, typename B>
+  EditorDOMPointBase& operator=(const EditorDOMPointBase<A, B>& aOther)
+  {
+    RangeBoundaryBase<ParentType, RefType>::operator=(aOther);
+    return *this;
+  }
+
 private:
   static nsIContent* GetRef(nsINode* aContainerNode, nsIContent* aPointedNode)
   {
     // If referring one of a child of the container, the 'ref' should be the
     // previous sibling of the referring child.
     if (aPointedNode) {
       return aPointedNode->GetPreviousSibling();
     }
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -846,33 +846,37 @@ HTMLEditRules::GetAlignment(bool* aMixed
 
   // Get selection location
   NS_ENSURE_TRUE(htmlEditor->GetRoot(), NS_ERROR_FAILURE);
   OwningNonNull<Element> root = *htmlEditor->GetRoot();
 
   int32_t rootOffset = root->GetParentNode() ?
                        root->GetParentNode()->IndexOf(root) : -1;
 
-  NS_ENSURE_STATE(selection->GetRangeAt(0) &&
-                  selection->GetRangeAt(0)->GetStartContainer());
-  OwningNonNull<nsINode> parent =
-    *selection->GetRangeAt(0)->GetStartContainer();
-  nsIContent* child = selection->GetRangeAt(0)->GetChildAtStartOffset();
-  int32_t offset = selection->GetRangeAt(0)->StartOffset();
+  nsRange* firstRange = selection->GetRangeAt(0);
+  if (NS_WARN_IF(!firstRange)) {
+    return NS_ERROR_FAILURE;
+  }
+  EditorRawDOMPoint atStartOfSelection(firstRange->StartRef());
+  if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+    return NS_ERROR_FAILURE;
+  }
+  MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
 
   // Is the selection collapsed?
   nsCOMPtr<nsINode> nodeToExamine;
-  if (selection->Collapsed() || parent->GetAsText()) {
-    // If selection is collapsed, we want to look at 'parent' and its ancestors
-    // for divs with alignment on them.  If we are in a text node, then that is
-    // the node of interest.
-    nodeToExamine = parent;
-  } else if (parent->IsHTMLElement(nsGkAtoms::html) && offset == rootOffset) {
+  if (selection->Collapsed() || atStartOfSelection.Container()->GetAsText()) {
+    // If selection is collapsed, we want to look at the container of selection
+    // start and its ancestors for divs with alignment on them.  If we are in a
+    // text node, then that is the node of interest.
+    nodeToExamine = atStartOfSelection.Container();
+  } else if (atStartOfSelection.Container()->IsHTMLElement(nsGkAtoms::html) &&
+             atStartOfSelection.Offset() == static_cast<uint32_t>(rootOffset)) {
     // If we have selected the body, let's look at the first editable node
-    nodeToExamine = htmlEditor->GetNextNode(parent, offset, child, true);
+    nodeToExamine = htmlEditor->GetNextEditableNode(atStartOfSelection);
   } else {
     nsTArray<RefPtr<nsRange>> arrayOfRanges;
     GetPromotedRanges(selection, arrayOfRanges, EditAction::align);
 
     // Use these ranges to construct a list of nodes to act on.
     nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
     nsresult rv = GetNodesForOperation(arrayOfRanges, arrayOfNodes,
                                        EditAction::align, TouchContent::no);
@@ -1241,30 +1245,34 @@ HTMLEditRules::WillInsert(Selection& aSe
   // work with when not collapsed.  (no good way to extend start or end of
   // selection), so we ignore those types of selections.
   if (!aSelection.Collapsed()) {
     return;
   }
 
   // If we are after a mozBR in the same block, then move selection to be
   // before it
-  NS_ENSURE_TRUE_VOID(aSelection.GetRangeAt(0) &&
-                      aSelection.GetRangeAt(0)->GetStartContainer());
-  OwningNonNull<nsINode> selNode =
-    *aSelection.GetRangeAt(0)->GetStartContainer();
-  nsIContent* selChild = aSelection.GetRangeAt(0)->GetChildAtStartOffset();
-  int32_t selOffset = aSelection.GetRangeAt(0)->StartOffset();
+  nsRange* firstRange = aSelection.GetRangeAt(0);
+  if (NS_WARN_IF(!firstRange)) {
+    return;
+  }
+
+  EditorRawDOMPoint atStartOfSelection(firstRange->StartRef());
+  if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+    return;
+  }
+  MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
 
   // Get prior node
-  nsCOMPtr<nsIContent> priorNode = htmlEditor->GetPriorHTMLNode(selNode,
-                                                                selOffset,
-                                                                selChild);
+  nsCOMPtr<nsIContent> priorNode =
+    htmlEditor->GetPreviousEditableHTMLNode(atStartOfSelection);
   if (priorNode && TextEditUtils::IsMozBR(priorNode)) {
-    nsCOMPtr<Element> block1 = htmlEditor->GetBlock(selNode);
-    nsCOMPtr<Element> block2 = htmlEditor->GetBlockNodeParent(priorNode);
+    RefPtr<Element> block1 =
+      htmlEditor->GetBlock(*atStartOfSelection.Container());
+    RefPtr<Element> block2 = htmlEditor->GetBlockNodeParent(priorNode);
 
     if (block1 && block1 == block2) {
       // If we are here then the selection is right after a mozBR that is in
       // the same block as the selection.  We need to move the selection start
       // to be before the mozBR.
       EditorRawDOMPoint point(priorNode);
       nsresult rv = aSelection.Collapse(point.AsRaw());
       NS_ENSURE_SUCCESS_VOID(rv);
@@ -1321,203 +1329,243 @@ HTMLEditRules::WillInsertText(EditAction
   NS_ENSURE_STATE(doc);
 
   // for every property that is set, insert a new inline style node
   nsresult rv = CreateStyleForInsertText(*aSelection, *doc);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // get the (collapsed) selection location
   NS_ENSURE_STATE(mHTMLEditor);
-  NS_ENSURE_STATE(aSelection->GetRangeAt(0));
-  nsCOMPtr<nsINode> selNode = aSelection->GetRangeAt(0)->GetStartContainer();
-  nsCOMPtr<nsIContent> selChild =
-    aSelection->GetRangeAt(0)->GetChildAtStartOffset();
-  int32_t selOffset = aSelection->GetRangeAt(0)->StartOffset();
-  NS_ENSURE_STATE(selNode);
+  nsRange* firstRange = aSelection->GetRangeAt(0);
+  if (NS_WARN_IF(!firstRange)) {
+    return NS_ERROR_FAILURE;
+  }
+  EditorDOMPoint pointToInsert(firstRange->StartRef());
+  if (NS_WARN_IF(!pointToInsert.IsSet())) {
+    return NS_ERROR_FAILURE;
+  }
+  MOZ_ASSERT(pointToInsert.IsSetAndValid());
 
   // dont put text in places that can't have it
-  NS_ENSURE_STATE(mHTMLEditor);
-  if (!EditorBase::IsTextNode(selNode) &&
-      (!mHTMLEditor || !mHTMLEditor->CanContainTag(*selNode,
-                                                   *nsGkAtoms::textTagName))) {
+  if (NS_WARN_IF(!mHTMLEditor) ||
+      (!EditorBase::IsTextNode(pointToInsert.Container()) &&
+       !mHTMLEditor->CanContainTag(*pointToInsert.Container(),
+                                   *nsGkAtoms::textTagName))) {
     return NS_ERROR_FAILURE;
   }
 
   if (aAction == EditAction::insertIMEText) {
     // Right now the WSRunObject code bails on empty strings, but IME needs
     // the InsertTextImpl() call to still happen since empty strings are meaningful there.
     NS_ENSURE_STATE(mHTMLEditor);
     // If there is one or more IME selections, its minimum offset should be
     // the insertion point.
     int32_t IMESelectionOffset =
-      mHTMLEditor->GetIMESelectionStartOffsetIn(selNode);
+      mHTMLEditor->GetIMESelectionStartOffsetIn(pointToInsert.Container());
     if (IMESelectionOffset >= 0) {
-      selOffset = IMESelectionOffset;
-    }
+      pointToInsert.Set(pointToInsert.Container(), IMESelectionOffset);
+    }
+
     if (inString->IsEmpty()) {
-      rv = mHTMLEditor->InsertTextImpl(*inString, address_of(selNode),
-                                       address_of(selChild),
-                                       &selOffset, doc);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    } else {
-      WSRunObject wsObj(mHTMLEditor, selNode, selOffset);
-      rv = wsObj.InsertText(*inString, address_of(selNode),
-                            address_of(selChild), &selOffset, doc);
+      rv = mHTMLEditor->InsertTextImpl(*doc, *inString, pointToInsert.AsRaw());
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
-    }
-  }
+      return NS_OK;
+    }
+
+    WSRunObject wsObj(mHTMLEditor,
+                      pointToInsert.Container(), pointToInsert.Offset());
+    rv = wsObj.InsertText(*doc, *inString, pointToInsert.AsRaw());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    return NS_OK;
+  }
+
   // aAction == kInsertText
-  else {
-    // find where we are
-    nsCOMPtr<nsINode> curNode = selNode;
-    int32_t curOffset = selOffset;
-
-    // is our text going to be PREformatted?
-    // We remember this so that we know how to handle tabs.
-    bool isPRE;
-    NS_ENSURE_STATE(mHTMLEditor);
-    rv = mHTMLEditor->IsPreformatted(GetAsDOMNode(selNode), &isPRE);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // turn off the edit listener: we know how to
-    // build the "doc changed range" ourselves, and it's
-    // must faster to do it once here than to track all
-    // the changes one at a time.
-    AutoLockListener lockit(&mListenerEnabled);
-
-    // don't change my selection in subtransactions
+
+  // find where we are
+  EditorDOMPoint currentPoint(pointToInsert);
+
+  // is our text going to be PREformatted?
+  // We remember this so that we know how to handle tabs.
+  bool isPRE;
+  NS_ENSURE_STATE(mHTMLEditor);
+  rv = mHTMLEditor->IsPreformatted(GetAsDOMNode(pointToInsert.Container()),
+                                   &isPRE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // turn off the edit listener: we know how to
+  // build the "doc changed range" ourselves, and it's
+  // must faster to do it once here than to track all
+  // the changes one at a time.
+  AutoLockListener lockit(&mListenerEnabled);
+
+  // don't change my selection in subtransactions
+  NS_ENSURE_STATE(mHTMLEditor);
+  AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
+  nsAutoString tString(*inString);
+  const char16_t *unicodeBuf = tString.get();
+  int32_t pos = 0;
+  NS_NAMED_LITERAL_STRING(newlineStr, LFSTR);
+
+  {
     NS_ENSURE_STATE(mHTMLEditor);
-    AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
-    nsAutoString tString(*inString);
-    const char16_t *unicodeBuf = tString.get();
-    int32_t pos = 0;
-    NS_NAMED_LITERAL_STRING(newlineStr, LFSTR);
-
-    {
-      NS_ENSURE_STATE(mHTMLEditor);
-      AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater,
-                                address_of(selNode), &selOffset);
-
-      // for efficiency, break out the pre case separately.  This is because
-      // its a lot cheaper to search the input string for only newlines than
-      // it is to search for both tabs and newlines.
-      if (isPRE || IsPlaintextEditor()) {
-        while (unicodeBuf && pos != -1 &&
-               pos < static_cast<int32_t>(inString->Length())) {
-          int32_t oldPos = pos;
-          int32_t subStrLen;
-          pos = tString.FindChar(nsCRT::LF, oldPos);
-
-          if (pos != -1) {
-            subStrLen = pos - oldPos;
-            // if first char is newline, then use just it
-            if (!subStrLen) {
-              subStrLen = 1;
-            }
-          } else {
-            subStrLen = tString.Length() - oldPos;
-            pos = tString.Length();
+    AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, &pointToInsert);
+
+    // for efficiency, break out the pre case separately.  This is because
+    // its a lot cheaper to search the input string for only newlines than
+    // it is to search for both tabs and newlines.
+    if (isPRE || IsPlaintextEditor()) {
+      while (unicodeBuf && pos != -1 &&
+             pos < static_cast<int32_t>(inString->Length())) {
+        int32_t oldPos = pos;
+        int32_t subStrLen;
+        pos = tString.FindChar(nsCRT::LF, oldPos);
+
+        if (pos != -1) {
+          subStrLen = pos - oldPos;
+          // if first char is newline, then use just it
+          if (!subStrLen) {
+            subStrLen = 1;
           }
-
-          nsDependentSubstring subStr(tString, oldPos, subStrLen);
-
-          // is it a return?
-          if (subStr.Equals(newlineStr)) {
-            NS_ENSURE_STATE(mHTMLEditor);
-            nsCOMPtr<Element> br =
-              mHTMLEditor->CreateBRImpl(address_of(curNode), &curOffset,
-                                        nsIEditor::eNone);
-            NS_ENSURE_STATE(br);
-            pos++;
-            selChild = br->GetNextSibling();
-            MOZ_ASSERT(curNode->GetChildAt(curOffset) == selChild);
+        } else {
+          subStrLen = tString.Length() - oldPos;
+          pos = tString.Length();
+        }
+
+        nsDependentSubstring subStr(tString, oldPos, subStrLen);
+
+        // is it a return?
+        if (subStr.Equals(newlineStr)) {
+          NS_ENSURE_STATE(mHTMLEditor);
+          nsCOMPtr<nsINode> curNode = currentPoint.Container();
+          int32_t curOffset = currentPoint.Offset();
+          nsCOMPtr<Element> br =
+            mHTMLEditor->CreateBRImpl(address_of(curNode), &curOffset,
+                                      nsIEditor::eNone);
+          NS_ENSURE_STATE(br);
+          pos++;
+          if (br->GetNextSibling()) {
+            pointToInsert.Set(br->GetNextSibling());
           } else {
-            NS_ENSURE_STATE(mHTMLEditor);
-            rv = mHTMLEditor->InsertTextImpl(subStr, address_of(curNode),
-                                             address_of(selChild),
-                                             &curOffset, doc);
-            NS_ENSURE_SUCCESS(rv, rv);
+            pointToInsert.Set(curNode, curNode->Length());
           }
-        }
-      } else {
-        NS_NAMED_LITERAL_STRING(tabStr, "\t");
-        NS_NAMED_LITERAL_STRING(spacesStr, "    ");
-        char specialChars[] = {TAB, nsCRT::LF, 0};
-        while (unicodeBuf && pos != -1 &&
-               pos < static_cast<int32_t>(inString->Length())) {
-          int32_t oldPos = pos;
-          int32_t subStrLen;
-          pos = tString.FindCharInSet(specialChars, oldPos);
-
-          if (pos != -1) {
-            subStrLen = pos - oldPos;
-            // if first char is newline, then use just it
-            if (!subStrLen) {
-              subStrLen = 1;
-            }
-          } else {
-            subStrLen = tString.Length() - oldPos;
-            pos = tString.Length();
-          }
-
-          nsDependentSubstring subStr(tString, oldPos, subStrLen);
+          currentPoint.Set(curNode, curOffset);
+          MOZ_ASSERT(currentPoint == pointToInsert);
+        } else {
           NS_ENSURE_STATE(mHTMLEditor);
-          WSRunObject wsObj(mHTMLEditor, curNode, curOffset);
-
-          // is it a tab?
-          if (subStr.Equals(tabStr)) {
-            rv =
-              wsObj.InsertText(spacesStr, address_of(curNode),
-                               address_of(selChild), &curOffset, doc);
-            NS_ENSURE_SUCCESS(rv, rv);
-            pos++;
-          }
-          // is it a return?
-          else if (subStr.Equals(newlineStr)) {
-            nsCOMPtr<Element> br = wsObj.InsertBreak(address_of(curNode),
-                                                     &curOffset,
-                                                     nsIEditor::eNone);
-            NS_ENSURE_TRUE(br, NS_ERROR_FAILURE);
-            pos++;
-            selChild = br->GetNextSibling();
-            MOZ_ASSERT(curNode->GetChildAt(curOffset) == selChild);
-          } else {
-            rv = wsObj.InsertText(subStr, address_of(curNode),
-                                  address_of(selChild), &curOffset, doc);
-            NS_ENSURE_SUCCESS(rv, rv);
-          }
+          EditorRawDOMPoint pointAfterInsertedString;
+          rv = mHTMLEditor->InsertTextImpl(*doc, subStr, currentPoint.AsRaw(),
+                                           &pointAfterInsertedString);
+          NS_ENSURE_SUCCESS(rv, rv);
+          currentPoint = pointAfterInsertedString;
+          pointToInsert = pointAfterInsertedString;
         }
       }
-    }
-    aSelection->SetInterlinePosition(false);
-    if (curNode) {
-      aSelection->Collapse(curNode, curOffset);
-    }
-    // manually update the doc changed range so that AfterEdit will clean up
-    // the correct portion of the document.
-    if (!mDocChangeRange) {
-      mDocChangeRange = new nsRange(selNode);
-    }
-
-    if (curNode) {
-      rv = mDocChangeRange->SetStartAndEnd(selNode, selOffset,
-                                           curNode, curOffset);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
     } else {
-      rv = mDocChangeRange->CollapseTo(selNode, selOffset);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    }
+      NS_NAMED_LITERAL_STRING(tabStr, "\t");
+      NS_NAMED_LITERAL_STRING(spacesStr, "    ");
+      char specialChars[] = {TAB, nsCRT::LF, 0};
+      while (unicodeBuf && pos != -1 &&
+             pos < static_cast<int32_t>(inString->Length())) {
+        int32_t oldPos = pos;
+        int32_t subStrLen;
+        pos = tString.FindCharInSet(specialChars, oldPos);
+
+        if (pos != -1) {
+          subStrLen = pos - oldPos;
+          // if first char is newline, then use just it
+          if (!subStrLen) {
+            subStrLen = 1;
+          }
+        } else {
+          subStrLen = tString.Length() - oldPos;
+          pos = tString.Length();
+        }
+
+        nsDependentSubstring subStr(tString, oldPos, subStrLen);
+        NS_ENSURE_STATE(mHTMLEditor);
+        WSRunObject wsObj(mHTMLEditor, currentPoint.Container(),
+                          currentPoint.Offset());
+
+        // is it a tab?
+        if (subStr.Equals(tabStr)) {
+          EditorRawDOMPoint pointAfterInsertedSpaces;
+          rv = wsObj.InsertText(*doc, spacesStr, currentPoint.AsRaw(),
+                                &pointAfterInsertedSpaces);
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+          }
+          pos++;
+          currentPoint = pointAfterInsertedSpaces;
+          pointToInsert = pointAfterInsertedSpaces;
+        }
+        // is it a return?
+        else if (subStr.Equals(newlineStr)) {
+          nsCOMPtr<nsINode> curNode = currentPoint.Container();
+          int32_t curOffset = currentPoint.Offset();
+          nsCOMPtr<Element> br = wsObj.InsertBreak(address_of(curNode),
+                                                   &curOffset,
+                                                   nsIEditor::eNone);
+          NS_ENSURE_TRUE(br, NS_ERROR_FAILURE);
+          pos++;
+          if (br->GetNextSibling()) {
+            pointToInsert.Set(br->GetNextSibling());
+          } else {
+            pointToInsert.Set(curNode, curNode->Length());
+          }
+          currentPoint.Set(curNode, curOffset);
+          MOZ_ASSERT(currentPoint == pointToInsert);
+        } else {
+          EditorRawDOMPoint pointAfterInsertedString;
+          rv = wsObj.InsertText(*doc, subStr, currentPoint.AsRaw(),
+                                &pointAfterInsertedString);
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+          }
+          currentPoint = pointAfterInsertedString;
+          pointToInsert = pointAfterInsertedString;
+        }
+      }
+    }
+
+    // After this block, pointToInsert is updated by AutoTrackDOMPoint.
+  }
+
+  aSelection->SetInterlinePosition(false);
+
+  if (currentPoint.IsSet()) {
+    ErrorResult error;
+    aSelection->Collapse(currentPoint.AsRaw(), error);
+    if (error.Failed()) {
+      NS_WARNING("Failed to collapse at current point");
+      error.SuppressException();
+    }
+  }
+
+  // manually update the doc changed range so that AfterEdit will clean up
+  // the correct portion of the document.
+  if (!mDocChangeRange) {
+    mDocChangeRange = new nsRange(pointToInsert.Container());
+  }
+
+  if (currentPoint.IsSet()) {
+    rv = mDocChangeRange->SetStartAndEnd(pointToInsert.AsRaw(),
+                                         currentPoint.AsRaw());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    return NS_OK;
+  }
+
+  rv = mDocChangeRange->CollapseTo(pointToInsert.AsRaw());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
   return NS_OK;
 }
 
 nsresult
 HTMLEditRules::WillLoadHTML(Selection* aSelection,
                             bool* aCancel)
 {
@@ -2432,21 +2480,21 @@ HTMLEditRules::WillDeleteSelection(Selec
         *aCancel = true;
         return NS_OK;
       }
 
       // First find the relevant nodes
       nsCOMPtr<nsINode> leftNode, rightNode;
       if (aAction == nsIEditor::ePrevious) {
         NS_ENSURE_STATE(mHTMLEditor);
-        leftNode = mHTMLEditor->GetPriorHTMLNode(visNode);
+        leftNode = mHTMLEditor->GetPreviousEditableHTMLNode(*visNode);
         rightNode = startNode;
       } else {
         NS_ENSURE_STATE(mHTMLEditor);
-        rightNode = mHTMLEditor->GetNextHTMLNode(visNode);
+        rightNode = mHTMLEditor->GetNextEditableHTMLNode(*visNode);
         leftNode = startNode;
       }
 
       // Nothing to join
       if (!leftNode || !rightNode) {
         *aCancel = true;
         return NS_OK;
       }
@@ -3749,17 +3797,18 @@ HTMLEditRules::MakeBasicBlock(Selection&
       // We are removing blocks (going to "body text")
       NS_ENSURE_TRUE(htmlEditor->GetBlock(container), NS_ERROR_NULL_POINTER);
       OwningNonNull<Element> curBlock = *htmlEditor->GetBlock(container);
       if (HTMLEditUtils::IsFormatNode(curBlock)) {
         // If the first editable node after selection is a br, consume it.
         // Otherwise it gets pushed into a following block after the split,
         // which is visually bad.
         nsCOMPtr<nsIContent> brNode =
-          htmlEditor->GetNextHTMLNode(container, offset, child);
+          htmlEditor->GetNextEditableHTMLNode(
+                        EditorRawDOMPoint(container, child, offset));
         if (brNode && brNode->IsHTMLElement(nsGkAtoms::br)) {
           rv = htmlEditor->DeleteNode(brNode);
           NS_ENSURE_SUCCESS(rv, rv);
         }
         // Do the splits!
         offset = htmlEditor->SplitNodeDeep(curBlock, *container->AsContent(),
                                             offset,
                                             HTMLEditor::EmptyContainers::no);
@@ -3772,17 +3821,18 @@ HTMLEditRules::MakeBasicBlock(Selection&
         // Don't restore the selection
         selectionRestorer.Abort();
         NS_ENSURE_SUCCESS(rv, rv);
       }
       // Else nothing to do!
     } else {
       // We are making a block.  Consume a br, if needed.
       nsCOMPtr<nsIContent> brNode =
-        htmlEditor->GetNextHTMLNode(container, offset, child, true);
+        htmlEditor->GetNextEditableHTMLNodeInBlock(
+                      EditorRawDOMPoint(container, child, offset));
       if (brNode && brNode->IsHTMLElement(nsGkAtoms::br)) {
         rv = htmlEditor->DeleteNode(brNode);
         NS_ENSURE_SUCCESS(rv, rv);
         // We don't need to act on this node any more
         arrayOfNodes.RemoveElement(brNode);
         // XXX We need to recompute child here because SplitAsNeeded() and
         //     EditorBase::SplitNodeDeep() don't compute child in some cases.
         child = container->GetChildAt(offset);
@@ -4913,32 +4963,32 @@ HTMLEditRules::WillAlign(Selection& aSel
     nsCOMPtr<nsIContent> child =
       aSelection.GetRangeAt(0)->GetChildAtStartOffset();
 
     rv = SplitAsNeeded(*nsGkAtoms::div, parent, offset,
                        address_of(child));
     NS_ENSURE_SUCCESS(rv, rv);
     // Consume a trailing br, if any.  This is to keep an alignment from
     // creating extra lines, if possible.
+    EditorRawDOMPoint atChild(parent, child, offset);
     nsCOMPtr<nsIContent> brContent =
-      htmlEditor->GetNextHTMLNode(parent, offset, child);
+      htmlEditor->GetNextEditableHTMLNodeInBlock(atChild);
     if (brContent && TextEditUtils::IsBreak(brContent)) {
       // Making use of html structure... if next node after where we are
       // putting our div is not a block, then the br we found is in same block
       // we are, so it's safe to consume it.
       nsCOMPtr<nsIContent> sibling;
       if (child) {
         sibling = htmlEditor->GetNextHTMLSibling(child);
       }
       if (sibling && !IsBlockNode(*sibling)) {
         rv = htmlEditor->DeleteNode(brContent);
         NS_ENSURE_SUCCESS(rv, rv);
       }
     }
-    EditorRawDOMPoint atChild(parent, child, offset);
     RefPtr<Element> div = htmlEditor->CreateNode(nsGkAtoms::div, atChild);
     NS_ENSURE_STATE(div);
     // Remember our new block for postprocessing
     mNewBlock = div;
     // Set up the alignment on the div, using HTML or CSS
     rv = AlignBlock(*div, aAlignType, ContentsOnly::yes);
     NS_ENSURE_SUCCESS(rv, rv);
     *aHandled = true;
@@ -5207,20 +5257,22 @@ HTMLEditRules::CheckForEmptyBlock(nsINod
         }
         // Else just let selection percolate up.  We'll adjust it in
         // AfterEdit()
       }
     } else {
       if (aAction == nsIEditor::eNext || aAction == nsIEditor::eNextWord ||
           aAction == nsIEditor::eToEndOfLine) {
         // Move to the start of the next node, if any
-        nsINode* child = emptyBlock->GetNextSibling();
-        int32_t offset = blockParent->IndexOf(emptyBlock);
+        EditorRawDOMPoint afterEmptyBlock(emptyBlock);
+        DebugOnly<bool> advanced = afterEmptyBlock.AdvanceOffset();
+        NS_WARNING_ASSERTION(advanced,
+          "Failed to set selection to the after the empty block");
         nsCOMPtr<nsIContent> nextNode =
-          htmlEditor->GetNextNode(blockParent, offset + 1, child, true);
+          htmlEditor->GetNextNode(afterEmptyBlock);
         if (nextNode) {
           EditorDOMPoint pt = GetGoodSelPointForNode(*nextNode, aAction);
           nsresult rv = aSelection->Collapse(pt.AsRaw());
           NS_ENSURE_SUCCESS(rv, rv);
         } else {
           // Adjust selection to be right after it.
           EditorRawDOMPoint afterEmptyBlock(emptyBlock);
           if (NS_WARN_IF(!afterEmptyBlock.AdvanceOffset())) {
@@ -5228,21 +5280,19 @@ HTMLEditRules::CheckForEmptyBlock(nsINod
           }
           nsresult rv = aSelection->Collapse(afterEmptyBlock);
           NS_ENSURE_SUCCESS(rv, rv);
         }
       } else if (aAction == nsIEditor::ePrevious ||
                  aAction == nsIEditor::ePreviousWord ||
                  aAction == nsIEditor::eToBeginningOfLine) {
         // Move to the end of the previous node
-        int32_t offset = blockParent->IndexOf(emptyBlock);
-        nsCOMPtr<nsIContent> priorNode = htmlEditor->GetPriorNode(blockParent,
-                                                                  offset,
-                                                                  emptyBlock,
-                                                                  true);
+        EditorRawDOMPoint atEmptyBlock(emptyBlock);
+        nsCOMPtr<nsIContent> priorNode =
+          htmlEditor->GetPreviousEditableNode(atEmptyBlock);
         if (priorNode) {
           EditorDOMPoint pt = GetGoodSelPointForNode(*priorNode, aAction);
           nsresult rv = aSelection->Collapse(pt.AsRaw());
           NS_ENSURE_SUCCESS(rv, rv);
         } else {
           EditorRawDOMPoint afterEmptyBlock(emptyBlock);
           if (NS_WARN_IF(!afterEmptyBlock.AdvanceOffset())) {
             return NS_ERROR_FAILURE;
@@ -5538,19 +5588,18 @@ HTMLEditRules::NormalizeSelection(Select
         int32_t offset = -1;
         newEndNode = EditorBase::GetNodeLocation(child, &offset);
         // offset *after* child
         newEndOffset = static_cast<uint32_t>(offset + 1);
       }
       // else block is empty - we can leave selection alone here, i think.
     } else if (wsEndObj.mStartReason == WSType::thisBlock) {
       // endpoint is just after start of this block
-      nsINode* child =
-        htmlEditor->GetPriorHTMLNode(endNode, static_cast<int32_t>(endOffset),
-                                     endChild);
+      EditorRawDOMPoint atEnd(endNode, endChild, endOffset);
+      nsINode* child = htmlEditor->GetPreviousEditableHTMLNode(atEnd);
       if (child) {
         int32_t offset = -1;
         newEndNode = EditorBase::GetNodeLocation(child, &offset);
         // offset *after* child
         newEndOffset = static_cast<uint32_t>(offset + 1);
       }
       // else block is empty - we can leave selection alone here, i think.
     } else if (wsEndObj.mStartReason == WSType::br) {
@@ -5581,19 +5630,18 @@ HTMLEditRules::NormalizeSelection(Select
         int32_t offset = -1;
         newStartNode = EditorBase::GetNodeLocation(child, &offset);
         newStartOffset = static_cast<uint32_t>(offset);
       }
       // else block is empty - we can leave selection alone here, i think.
     } else if (wsStartObj.mEndReason == WSType::thisBlock) {
       // startpoint is just before end of this block
       nsINode* child =
-        htmlEditor->GetNextHTMLNode(startNode,
-                                    static_cast<int32_t>(startOffset),
-                                    startChild);
+        htmlEditor->GetNextEditableHTMLNode(
+                      EditorRawDOMPoint(startNode, startChild, startOffset));
       if (child) {
         int32_t offset = -1;
         newStartNode = EditorBase::GetNodeLocation(child, &offset);
         newStartOffset = static_cast<uint32_t>(offset);
       }
       // else block is empty - we can leave selection alone here, i think.
     } else if (wsStartObj.mEndReason == WSType::br) {
       // startpoint is just before a break.  lets adjust it to after it.
@@ -5678,111 +5726,104 @@ HTMLEditRules::GetPromotedPoint(RulesEnd
       } else {
         break;
       }
     }
 
     return EditorDOMPoint(content, newOffset);
   }
 
-  nsCOMPtr<nsINode> node = &aNode;
-  nsINode* child = node->GetChildAt(aOffset);
-  int32_t offset = aOffset;
+  EditorDOMPoint point(&aNode, aOffset);
 
   // else not a text section.  In this case we want to see if we should grab
   // any adjacent inline nodes and/or parents and other ancestors
   if (aWhere == kStart) {
     // some special casing for text nodes
-    if (node->IsNodeOfType(nsINode::eTEXT)) {
-      if (!node->GetParentNode()) {
+    if (point.Container()->IsNodeOfType(nsINode::eTEXT)) {
+      if (!point.Container()->GetParentNode()) {
         // Okay, can't promote any further
-        return EditorDOMPoint(node, offset);
-      }
-      offset = node->GetParentNode()->IndexOf(node);
-      child = node;
-      node = node->GetParentNode();
+        return point;
+      }
+      point.Set(point.Container());
     }
 
     // look back through any further inline nodes that aren't across a <br>
     // from us, and that are enclosed in the same block.
     nsCOMPtr<nsINode> priorNode =
-      htmlEditor->GetPriorHTMLNode(node, offset, child, true);
+      htmlEditor->GetPreviousEditableHTMLNodeInBlock(point.AsRaw());
 
     while (priorNode && priorNode->GetParentNode() &&
            !htmlEditor->IsVisibleBRElement(priorNode) &&
            !IsBlockNode(*priorNode)) {
-      offset = priorNode->GetParentNode()->IndexOf(priorNode);
-      child = priorNode;
-      node = priorNode->GetParentNode();
-      priorNode = htmlEditor->GetPriorHTMLNode(node, offset, child, true);
+      point.Set(priorNode);
+      priorNode = htmlEditor->GetPreviousEditableHTMLNodeInBlock(point.AsRaw());
     }
 
     // finding the real start for this point.  look up the tree for as long as
     // we are the first node in the container, and as long as we haven't hit
     // the body node.
     nsCOMPtr<nsIContent> nearNode =
-      htmlEditor->GetPriorHTMLNode(node, offset, child, true);
-    while (!nearNode && !node->IsHTMLElement(nsGkAtoms::body) &&
-           node->GetParentNode()) {
+      htmlEditor->GetPreviousEditableHTMLNodeInBlock(point.AsRaw());
+    while (!nearNode &&
+           !point.Container()->IsHTMLElement(nsGkAtoms::body) &&
+           point.Container()->GetParentNode()) {
       // some cutoffs are here: we don't need to also include them in the
       // aWhere == kEnd case.  as long as they are in one or the other it will
       // work.  special case for outdent: don't keep looking up if we have
       // found a blockquote element to act on
       if (actionID == EditAction::outdent &&
-          node->IsHTMLElement(nsGkAtoms::blockquote)) {
+          point.Container()->IsHTMLElement(nsGkAtoms::blockquote)) {
         break;
       }
 
-      int32_t parentOffset = node->GetParentNode()->IndexOf(node);
-      nsCOMPtr<nsINode> parent = node->GetParentNode();
-
       // Don't walk past the editable section. Note that we need to check
       // before walking up to a parent because we need to return the parent
       // object, so the parent itself might not be in the editable area, but
       // it's OK if we're not performing a block-level action.
       bool blockLevelAction = actionID == EditAction::indent ||
                               actionID == EditAction::outdent ||
                               actionID == EditAction::align ||
                               actionID == EditAction::makeBasicBlock;
-      if (!htmlEditor->IsDescendantOfEditorRoot(parent) &&
+      if (!htmlEditor->IsDescendantOfEditorRoot(
+                         point.Container()->GetParentNode()) &&
           (blockLevelAction ||
-           !htmlEditor->IsDescendantOfEditorRoot(node))) {
+           !htmlEditor->IsDescendantOfEditorRoot(point.Container()))) {
         break;
       }
 
-      child = node;
-      node = parent;
-      offset = parentOffset;
-      nearNode = htmlEditor->GetPriorHTMLNode(node, offset, child, true);
-    }
-    return EditorDOMPoint(node, offset);
+      point.Set(point.Container());
+      nearNode = htmlEditor->GetPreviousEditableHTMLNodeInBlock(point.AsRaw());
+    }
+    return point;
   }
 
   // aWhere == kEnd
   // some special casing for text nodes
-  if (node->IsNodeOfType(nsINode::eTEXT)) {
-    if (!node->GetParentNode()) {
+  if (point.Container()->IsNodeOfType(nsINode::eTEXT)) {
+    if (!point.Container()->GetParentNode()) {
       // Okay, can't promote any further
-      return EditorDOMPoint(node, offset);
+      return point;
     }
     // want to be after the text node
-    offset = 1 + node->GetParentNode()->IndexOf(node);
-    child = node;
-    node = node->GetParentNode();
+    point.Set(point.Container());
+    DebugOnly<bool> advanced = point.AdvanceOffset();
+    NS_WARNING_ASSERTION(advanced,
+      "Failed to advance offset to after the text node");
   }
 
   // look ahead through any further inline nodes that aren't across a <br> from
   // us, and that are enclosed in the same block.
   nsCOMPtr<nsIContent> nextNode =
-    htmlEditor->GetNextHTMLNode(node, offset, child, true);
+    htmlEditor->GetNextEditableHTMLNodeInBlock(point.AsRaw());
 
   while (nextNode && !IsBlockNode(*nextNode) && nextNode->GetParentNode()) {
-    offset = 1 + nextNode->GetParentNode()->IndexOf(nextNode);
-    child = nextNode->GetNextSibling();
-    node = nextNode->GetParentNode();
+    point.Set(nextNode);
+    if (NS_WARN_IF(!point.AdvanceOffset())) {
+      break;
+    }
     if (htmlEditor->IsVisibleBRElement(nextNode)) {
       break;
     }
 
     // Check for newlines in pre-formatted text nodes.
     bool isPRE;
     htmlEditor->IsPreformatted(nextNode->AsDOMNode(), &isPRE);
     if (isPRE) {
@@ -5794,44 +5835,43 @@ HTMLEditRules::GetPromotedPoint(RulesEnd
           if (static_cast<uint32_t>(newlinePos) + 1 == tempString.Length()) {
             // No need for special processing if the newline is at the end.
             break;
           }
           return EditorDOMPoint(nextNode, newlinePos + 1);
         }
       }
     }
-    nextNode = htmlEditor->GetNextHTMLNode(node, offset, child, true);
+    nextNode = htmlEditor->GetNextEditableHTMLNodeInBlock(point.AsRaw());
   }
 
   // finding the real end for this point.  look up the tree for as long as we
   // are the last node in the container, and as long as we haven't hit the body
   // node.
   nsCOMPtr<nsIContent> nearNode =
-    htmlEditor->GetNextHTMLNode(node, offset, child, true);
-  while (!nearNode && !node->IsHTMLElement(nsGkAtoms::body) &&
-         node->GetParentNode()) {
-    int32_t parentOffset = node->GetParentNode()->IndexOf(node);
-    nsCOMPtr<nsINode> parent = node->GetParentNode();
-
+    htmlEditor->GetNextEditableHTMLNodeInBlock(point.AsRaw());
+  while (!nearNode &&
+         !point.Container()->IsHTMLElement(nsGkAtoms::body) &&
+         point.Container()->GetParentNode()) {
     // Don't walk past the editable section. Note that we need to check before
     // walking up to a parent because we need to return the parent object, so
     // the parent itself might not be in the editable area, but it's OK.
-    if (!htmlEditor->IsDescendantOfEditorRoot(node) &&
-        !htmlEditor->IsDescendantOfEditorRoot(parent)) {
+    if (!htmlEditor->IsDescendantOfEditorRoot(point.Container()) &&
+        !htmlEditor->IsDescendantOfEditorRoot(
+                       point.Container()->GetParentNode())) {
       break;
     }
 
-    child = node->GetNextSibling();
-    node = parent;
-    // we want to be AFTER nearNode
-    offset = parentOffset + 1;
-    nearNode = htmlEditor->GetNextHTMLNode(node, offset, child, true);
-  }
-  return EditorDOMPoint(node, offset);
+    point.Set(point.Container());
+    if (NS_WARN_IF(!point.AdvanceOffset())) {
+      break;
+    }
+    nearNode = htmlEditor->GetNextEditableHTMLNodeInBlock(point.AsRaw());
+  }
+  return point;
 }
 
 /**
  * GetPromotedRanges() runs all the selection range endpoint through
  * GetPromotedPoint().
  */
 void
 HTMLEditRules::GetPromotedRanges(Selection& aSelection,
@@ -6748,23 +6788,27 @@ HTMLEditRules::ReturnInParagraph(Selecti
       newBRneeded = true;
       offset++;
     }
   } else {
     // not in a text node.
     // is there a BR prior to it?
     nsCOMPtr<nsIContent> nearNode;
     NS_ENSURE_STATE(mHTMLEditor);
-    nearNode = mHTMLEditor->GetPriorHTMLNode(node, aOffset, aChildAtOffset);
+    nearNode =
+      mHTMLEditor->GetPreviousEditableHTMLNode(
+                     EditorRawDOMPoint(node, aChildAtOffset, aOffset));
     NS_ENSURE_STATE(mHTMLEditor);
     if (!nearNode || !mHTMLEditor->IsVisibleBRElement(nearNode) ||
         TextEditUtils::HasMozAttr(GetAsDOMNode(nearNode))) {
       // is there a BR after it?
       NS_ENSURE_STATE(mHTMLEditor);
-      nearNode = mHTMLEditor->GetNextHTMLNode(node, aOffset, aChildAtOffset);
+      nearNode =
+        mHTMLEditor->GetNextEditableHTMLNode(
+                       EditorRawDOMPoint(node, aChildAtOffset, aOffset));
       NS_ENSURE_STATE(mHTMLEditor);
       if (!nearNode || !mHTMLEditor->IsVisibleBRElement(nearNode) ||
           TextEditUtils::HasMozAttr(GetAsDOMNode(nearNode))) {
         newBRneeded = true;
         parent = node;
         offset = aOffset;
         newSelNode = true;
       }
@@ -7763,47 +7807,53 @@ HTMLEditRules::CheckInterlinePosition(Se
   if (!aSelection.Collapsed()) {
     return;
   }
 
   NS_ENSURE_TRUE_VOID(mHTMLEditor);
   RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
 
   // Get the (collapsed) selection location
-  NS_ENSURE_TRUE_VOID(aSelection.GetRangeAt(0) &&
-                      aSelection.GetRangeAt(0)->GetStartContainer());
-  OwningNonNull<nsINode> selNode =
-    *aSelection.GetRangeAt(0)->GetStartContainer();
-  int32_t selOffset = aSelection.GetRangeAt(0)->StartOffset();
-  nsIContent* child = aSelection.GetRangeAt(0)->GetChildAtStartOffset();
+  nsRange* firstRange = aSelection.GetRangeAt(0);
+  if (NS_WARN_IF(!firstRange)) {
+    return;
+  }
+
+  EditorDOMPoint atStartOfSelection(firstRange->StartRef());
+  if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+    return;
+  }
+  MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
 
   // First, let's check to see if we are after a <br>.  We take care of this
   // special-case first so that we don't accidentally fall through into one of
   // the other conditionals.
   nsCOMPtr<nsIContent> node =
-    htmlEditor->GetPriorHTMLNode(selNode, selOffset, child, true);
+    htmlEditor->GetPreviousEditableHTMLNodeInBlock(atStartOfSelection.AsRaw());
   if (node && node->IsHTMLElement(nsGkAtoms::br)) {
     aSelection.SetInterlinePosition(true);
     return;
   }
 
   // Are we after a block?  If so try set caret to following content
-  if (child) {
-    node = htmlEditor->GetPriorHTMLSibling(child);
+  if (atStartOfSelection.GetChildAtOffset()) {
+    node =
+      htmlEditor->GetPriorHTMLSibling(atStartOfSelection.GetChildAtOffset());
   } else {
     node = nullptr;
   }
   if (node && IsBlockNode(*node)) {
     aSelection.SetInterlinePosition(true);
     return;
   }
 
   // Are we before a block?  If so try set caret to prior content
-  if (child) {
-    node = htmlEditor->GetNextHTMLSibling(child);
+  if (atStartOfSelection.GetChildAtOffset()) {
+    node =
+      htmlEditor->GetNextHTMLSibling(atStartOfSelection.GetChildAtOffset());
   } else {