Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 05 Jul 2016 16:07:19 +0200
changeset 303696 f08c54971dd185850d9f2abae42d604f5e820918
parent 303660 bd4db3e235a49de1eaf59e67620220fc7b0ae702 (current diff)
parent 303695 dbb31bcad5a1f60a35b5600ea1578d9b9fa55237 (diff)
child 303697 f0c70bfa8917bbb0ecd1939e40fa565377e203eb
push id79141
push usercbook@mozilla.com
push dateTue, 05 Jul 2016 14:07:42 +0000
treeherdermozilla-inbound@f08c54971dd1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone50.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 mozilla-central to mozilla-inbound
dom/base/test/bug682592-subframe-ref.html
dom/base/test/bug682592-subframe.html
taskcluster/ci/legacy/tasks/branches/b2g-inbound/job_flags.yml
taskcluster/ci/legacy/tasks/branches/fx-team/job_flags.yml
taskcluster/ci/legacy/tasks/branches/larch/job_flags.yml
taskcluster/ci/legacy/tasks/builds/b2g_aries_spark_debug.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_firefox_ui_functional.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_firefox_ui_functional_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_firefox_ui_functional_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_firefox_ui_functional_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_firefox_ui_functional_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_firefox_ui_functional_opt.yml
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -142,18 +142,30 @@ var gMainPane = {
     }
 
     const Cc = Components.classes, Ci = Components.interfaces;
     let brandName = document.getElementById("bundleBrand").getString("brandShortName");
     let bundle = document.getElementById("bundlePreferences");
     let msg = bundle.getFormattedString(e10sCheckbox.checked ?
                                         "featureEnableRequiresRestart" : "featureDisableRequiresRestart",
                                         [brandName]);
+    let restartText = bundle.getFormattedString("okToRestartButton", [brandName]);
+    let revertText = bundle.getString("revertNoRestartButton");
+
     let title = bundle.getFormattedString("shouldRestartTitle", [brandName]);
-    let shouldProceed = Services.prompt.confirm(window, title, msg)
+    let prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
+    let buttonFlags = (Services.prompt.BUTTON_POS_0 *
+                       Services.prompt.BUTTON_TITLE_IS_STRING) +
+                      (Services.prompt.BUTTON_POS_1 *
+                       Services.prompt.BUTTON_TITLE_IS_STRING) +
+                      Services.prompt.BUTTON_POS_0_DEFAULT;
+    let shouldProceed = prompts.confirmEx(window, title, msg,
+                                          buttonFlags, revertText, restartText,
+                                          null, null, {});
+
     if (shouldProceed) {
       let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
                          .createInstance(Ci.nsISupportsPRBool);
       Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
                                    "restart");
       shouldProceed = !cancelQuit.data;
 
       if (shouldProceed) {
--- a/devtools/client/debugger/debugger-view.js
+++ b/devtools/client/debugger/debugger-view.js
@@ -654,17 +654,17 @@ var DebuggerView = {
     }));
   },
 
   /**
    * Gets the visibility state of the instruments pane.
    * @return boolean
    */
   get instrumentsPaneHidden() {
-    return this._instrumentsPane.hasAttribute("pane-collapsed");
+    return this._instrumentsPane.classList.contains("pane-collapsed");
   },
 
   /**
    * Gets the currently selected tab in the instruments pane.
    * @return string
    */
   get instrumentsPaneTab() {
     return this._instrumentsPane.selectedTab.id;
@@ -684,20 +684,20 @@ var DebuggerView = {
    */
   toggleInstrumentsPane: function (aFlags, aTabIndex) {
     let pane = this._instrumentsPane;
     let button = this._instrumentsPaneToggleButton;
 
     ViewHelpers.togglePane(aFlags, pane);
 
     if (aFlags.visible) {
-      button.removeAttribute("pane-collapsed");
+      button.classList.remove("pane-collapsed");
       button.setAttribute("tooltiptext", this._collapsePaneString);
     } else {
-      button.setAttribute("pane-collapsed", "");
+      button.classList.add("pane-collapsed");
       button.setAttribute("tooltiptext", this._expandPaneString);
     }
 
     if (aTabIndex !== undefined) {
       pane.selectedIndex = aTabIndex;
     }
   },
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse.js
@@ -36,18 +36,18 @@ function test() {
 }
 
 function testPanesState() {
   let instrumentsPane =
     gDebugger.document.getElementById("instruments-pane");
   let instrumentsPaneToggleButton =
     gDebugger.document.getElementById("instruments-pane-toggle");
 
-  ok(instrumentsPane.hasAttribute("pane-collapsed") &&
-     instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+  ok(instrumentsPane.classList.contains("pane-collapsed") &&
+     instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
     "The debugger view instruments pane should initially be hidden.");
   is(gPrefs.panesVisibleOnStartup, false,
     "The debugger view instruments pane should initially be preffed as hidden.");
   isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
     "The options menu item should not be checked.");
 }
 
 function testInstrumentsPaneCollapse() {
@@ -60,18 +60,18 @@ function testInstrumentsPaneCollapse() {
   is(width, gPrefs.instrumentsWidth,
     "The instruments pane has an incorrect width.");
   is(instrumentsPane.style.marginLeft, "0px",
     "The instruments pane has an incorrect left margin.");
   is(instrumentsPane.style.marginRight, "0px",
     "The instruments pane has an incorrect right margin.");
   ok(!instrumentsPane.hasAttribute("animated"),
     "The instruments pane has an incorrect animated attribute.");
-  ok(!instrumentsPane.hasAttribute("pane-collapsed") &&
-     !instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+  ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+     !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
     "The instruments pane should at this point be visible.");
 
   gDebugger.DebuggerView.toggleInstrumentsPane({ visible: false, animated: true });
 
   is(gPrefs.panesVisibleOnStartup, false,
     "The debugger view panes should still initially be preffed as hidden.");
   isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
     "The options menu item should still not be checked.");
@@ -80,18 +80,18 @@ function testInstrumentsPaneCollapse() {
   is(width, gPrefs.instrumentsWidth,
     "The instruments pane has an incorrect width after collapsing.");
   is(instrumentsPane.style.marginLeft, margin,
     "The instruments pane has an incorrect left margin after collapsing.");
   is(instrumentsPane.style.marginRight, margin,
     "The instruments pane has an incorrect right margin after collapsing.");
   ok(instrumentsPane.hasAttribute("animated"),
     "The instruments pane has an incorrect attribute after an animated collapsing.");
-  ok(instrumentsPane.hasAttribute("pane-collapsed") &&
-     instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+  ok(instrumentsPane.classList.contains("pane-collapsed") &&
+     instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
     "The instruments pane should not be visible after collapsing.");
 
   gDebugger.DebuggerView.toggleInstrumentsPane({ visible: true, animated: false });
 
   is(gPrefs.panesVisibleOnStartup, false,
     "The debugger view panes should still initially be preffed as hidden.");
   isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
     "The options menu item should still not be checked.");
@@ -99,54 +99,54 @@ function testInstrumentsPaneCollapse() {
   is(width, gPrefs.instrumentsWidth,
     "The instruments pane has an incorrect width after uncollapsing.");
   is(instrumentsPane.style.marginLeft, "0px",
     "The instruments pane has an incorrect left margin after uncollapsing.");
   is(instrumentsPane.style.marginRight, "0px",
     "The instruments pane has an incorrect right margin after uncollapsing.");
   ok(!instrumentsPane.hasAttribute("animated"),
     "The instruments pane has an incorrect attribute after an unanimated uncollapsing.");
-  ok(!instrumentsPane.hasAttribute("pane-collapsed") &&
-     !instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+  ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+     !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
     "The instruments pane should be visible again after uncollapsing.");
 }
 
 function testPanesStartupPref() {
   let instrumentsPane =
     gDebugger.document.getElementById("instruments-pane");
   let instrumentsPaneToggleButton =
     gDebugger.document.getElementById("instruments-pane-toggle");
 
   is(gPrefs.panesVisibleOnStartup, false,
     "The debugger view panes should still initially be preffed as hidden.");
 
-  ok(!instrumentsPane.hasAttribute("pane-collapsed") &&
-     !instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+  ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+     !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
     "The debugger instruments pane should at this point be visible.");
   is(gPrefs.panesVisibleOnStartup, false,
     "The debugger view panes should initially be preffed as hidden.");
   isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
     "The options menu item should still not be checked.");
 
   gOptions._showPanesOnStartupItem.setAttribute("checked", "true");
   gOptions._toggleShowPanesOnStartup();
 
-  ok(!instrumentsPane.hasAttribute("pane-collapsed") &&
-     !instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+  ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+     !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
     "The debugger instruments pane should at this point be visible.");
   is(gPrefs.panesVisibleOnStartup, true,
     "The debugger view panes should now be preffed as visible.");
   is(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
     "The options menu item should now be checked.");
 
   gOptions._showPanesOnStartupItem.setAttribute("checked", "false");
   gOptions._toggleShowPanesOnStartup();
 
-  ok(!instrumentsPane.hasAttribute("pane-collapsed") &&
-     !instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+  ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+     !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
     "The debugger instruments pane should at this point be visible.");
   is(gPrefs.panesVisibleOnStartup, false,
     "The debugger view panes should now be preffed as hidden.");
   isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
     "The options menu item should now be unchecked.");
 }
 
 registerCleanupFunction(function () {
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -378,16 +378,28 @@ InspectorPanel.prototype = {
       } else {
         str = strings.GetStringFromName("inspector.searchResultsNone");
       }
     }
 
     this.searchResultsLabel.textContent = str;
   },
 
+  get React() {
+    return this._toolbox.React;
+  },
+
+  get ReactDOM() {
+    return this._toolbox.ReactDOM;
+  },
+
+  get browserRequire() {
+    return this._toolbox.browserRequire;
+  },
+
   /**
    * Build the sidebar.
    */
   setupSidebar: function () {
     let tabbox = this.panelDoc.querySelector("#inspector-sidebar");
     this.sidebar = new ToolSidebar(tabbox, this, "inspector", {
       showAllTabsMenu: true
     });
@@ -426,21 +438,28 @@ InspectorPanel.prototype = {
 
     this.setupSidebarToggle();
   },
 
   /**
    * Add the expand/collapse behavior for the sidebar panel.
    */
   setupSidebarToggle: function () {
-    this._paneToggleButton = this.panelDoc.getElementById("inspector-pane-toggle");
-    this._paneToggleButton.setAttribute("tooltiptext",
-      strings.GetStringFromName("inspector.collapsePane"));
-    this._paneToggleButton.addEventListener("mousedown",
-      this.onPaneToggleButtonClicked);
+    let SidebarToggle = this.React.createFactory(this.browserRequire(
+      "devtools/client/shared/components/sidebar-toggle"));
+
+    let sidebarToggle = SidebarToggle({
+      onClick: this.onPaneToggleButtonClicked,
+      collapsed: false,
+      expandPaneTitle: strings.GetStringFromName("inspector.expandPane"),
+      collapsePaneTitle: strings.GetStringFromName("inspector.collapsePane"),
+    });
+
+    let parentBox = this.panelDoc.getElementById("inspector-sidebar-toggle-box");
+    this._sidebarToggle = this.ReactDOM.render(sidebarToggle, parentBox);
   },
 
   /**
    * Reset the inspector on new root mutation.
    */
   onNewRoot: function () {
     this._defaultNode = null;
     this.selection.setNodeFront(null);
@@ -684,19 +703,16 @@ InspectorPanel.prototype = {
     });
 
     this.sidebar.off("select", this._setDefaultSidebar);
     let sidebarDestroyer = this.sidebar.destroy();
     this.sidebar = null;
 
     this.addNodeButton.removeEventListener("click", this.addNode);
     this.breadcrumbs.destroy();
-    this._paneToggleButton.removeEventListener("mousedown",
-      this.onPaneToggleButtonClicked);
-    this._paneToggleButton = null;
     this.selection.off("new-node-front", this.onNewSelection);
     this.selection.off("before-new-node", this.onBeforeNewSelection);
     this.selection.off("before-new-node-front", this.onBeforeNewSelection);
     this.selection.off("detached-front", this.onDetached);
     let markupDestroyer = this._destroyMarkup();
     this.panelWin.inspector = null;
     this.target = null;
     this.panelDoc = null;
@@ -1138,18 +1154,17 @@ InspectorPanel.prototype = {
   },
 
   /**
    * When the pane toggle button is clicked, toggle the pane, change the button
    * state and tooltip.
    */
   onPaneToggleButtonClicked: function (e) {
     let sidePane = this.panelDoc.querySelector("#inspector-sidebar");
-    let button = this._paneToggleButton;
-    let isVisible = !button.hasAttribute("pane-collapsed");
+    let isVisible = !this._sidebarToggle.state.collapsed;
 
     // Make sure the sidebar has width and height attributes before collapsing
     // because ViewHelpers needs it.
     if (isVisible) {
       let rect = sidePane.getBoundingClientRect();
       if (!sidePane.hasAttribute("width")) {
         sidePane.setAttribute("width", rect.width);
       }
@@ -1160,21 +1175,19 @@ InspectorPanel.prototype = {
 
     ViewHelpers.togglePane({
       visible: !isVisible,
       animated: true,
       delayed: true
     }, sidePane);
 
     if (isVisible) {
-      button.setAttribute("pane-collapsed", "");
-      button.setAttribute("tooltiptext", strings.GetStringFromName("inspector.expandPane"));
+      this._sidebarToggle.setState({collapsed: true});
     } else {
-      button.removeAttribute("pane-collapsed");
-      button.setAttribute("tooltiptext", strings.GetStringFromName("inspector.collapsePane"));
+      this._sidebarToggle.setState({collapsed: false});
     }
   },
 
   /**
    * Create a new node as the last child of the current selection, expand the
    * parent and select the new node.
    */
   addNode: Task.async(function* () {
--- a/devtools/client/inspector/inspector.css
+++ b/devtools/client/inspector/inspector.css
@@ -27,9 +27,13 @@
   max-width: 150px;
 }
 
 .inspector-tabpanel > * {
   /*
    * Override `-moz-user-focus:ignore;` from toolkit/content/minimal-xul.css
    */
   -moz-user-focus: normal;
-}
\ No newline at end of file
+}
+
+#inspector-sidebar-toggle-box {
+  line-height: initial;
+}
--- a/devtools/client/inspector/inspector.xul
+++ b/devtools/client/inspector/inspector.xul
@@ -6,16 +6,17 @@
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/inspector/inspector.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/inspector.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/rules.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/computed.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/fonts.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/layout.css" type="text/css"?>
+<?xml-stylesheet href="resource://devtools/client/shared/components/sidebar-toggle.css" type="text/css"?>
 
 <!DOCTYPE window [
   <!ENTITY % inspectorDTD SYSTEM "chrome://devtools/locale/inspector.dtd"> %inspectorDTD;
   <!ENTITY % styleinspectorDTD SYSTEM "chrome://devtools/locale/styleinspector.dtd"> %styleinspectorDTD;
   <!ENTITY % fontinspectorDTD SYSTEM "chrome://devtools/locale/font-inspector.dtd"> %fontinspectorDTD;
   <!ENTITY % layoutviewDTD SYSTEM "chrome://devtools/locale/layoutview.dtd"> %layoutviewDTD;
 ]>
 
@@ -34,19 +35,18 @@
           class="devtools-button" />
         <html:div class="devtools-toolbar-spacer" />
         <html:span id="inspector-searchlabel" />
         <textbox id="inspector-searchbox"
           type="search"
           timeout="50"
           class="devtools-searchinput"
           placeholder="&inspectorSearchHTML.label3;"/>
-        <html:button id="inspector-pane-toggle"
-          class="devtools-button"
-          tabindex="0" />
+        <div xmlns="http://www.w3.org/1999/xhtml"
+             id="inspector-sidebar-toggle-box" />
       </html:div>
       <vbox flex="1" id="markup-box">
       </vbox>
       <html:div id="inspector-breadcrumbs-toolbar" class="devtools-toolbar">
         <html:div id="inspector-breadcrumbs" class="breadcrumbs-widget-container"/>
       </html:div>
     </vbox>
     <splitter class="devtools-side-splitter"/>
--- a/devtools/client/inspector/test/browser_inspector_pane-toggle-01.js
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-01.js
@@ -5,23 +5,23 @@
 
 // Test that the inspector panel has a sidebar pane toggle button, and that
 // this button is visible both in BOTTOM and SIDE hosts.
 
 add_task(function* () {
   info("Open the inspector in a bottom toolbox host");
   let {toolbox, inspector} = yield openInspectorForURL("about:blank", "bottom");
 
-  let button = inspector.panelDoc.getElementById("inspector-pane-toggle");
+  let button = inspector.panelDoc.querySelector(".sidebar-toggle");
   ok(button, "The toggle button exists in the DOM");
-  is(button.parentNode.id, "inspector-toolbar",
-     "The toggle button is in the toolbar");
-  ok(button.getAttribute("tooltiptext"), "The tool tip has initial state");
-  ok(!button.hasAttribute("pane-collapsed"), "The button is in expanded state");
+  is(button.parentNode.id, "inspector-sidebar-toggle-box",
+     "The toggle button has the right parent");
+  ok(button.getAttribute("title"), "The tool tip has initial state");
+  ok(!button.classList.contains("pane-collapsed"), "The button is in expanded state");
   ok(!!button.getClientRects().length, "The button is visible");
 
   info("Switch the host to side type");
   yield toolbox.switchHost("side");
 
   ok(!!button.getClientRects().length, "The button is still visible");
-  ok(!button.hasAttribute("pane-collapsed"),
+  ok(!button.classList.contains("pane-collapsed"),
      "The button is still in expanded state");
 });
--- a/devtools/client/inspector/test/browser_inspector_pane-toggle-02.js
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-02.js
@@ -7,36 +7,36 @@
 // clicking on the toggle button and remains expanded/collapsed when switching
 // hosts.
 
 add_task(function* () {
   info("Open the inspector in a side toolbox host");
   let {toolbox, inspector} = yield openInspectorForURL("about:blank", "side");
 
   let panel = inspector.panelDoc.querySelector("#inspector-sidebar");
-  let button = inspector.panelDoc.getElementById("inspector-pane-toggle");
-  ok(!panel.hasAttribute("pane-collapsed"), "The panel is in expanded state");
+  let button = inspector.panelDoc.querySelector(".sidebar-toggle");
+  ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state");
 
   info("Listen to the end of the animation on the sidebar panel");
   let onTransitionEnd = once(panel, "transitionend");
 
   info("Click on the toggle button");
-  EventUtils.synthesizeMouseAtCenter(button, {type: "mousedown"},
+  EventUtils.synthesizeMouseAtCenter(button, {},
     inspector.panelDoc.defaultView);
 
   yield onTransitionEnd;
-  ok(panel.hasAttribute("pane-collapsed"), "The panel is in collapsed state");
+  ok(panel.classList.contains("pane-collapsed"), "The panel is in collapsed state");
   ok(!panel.hasAttribute("animated"),
     "The collapsed panel will not perform unwanted animations");
 
   info("Switch the host to bottom type");
   yield toolbox.switchHost("bottom");
-  ok(panel.hasAttribute("pane-collapsed"), "The panel is in collapsed state");
+  ok(panel.classList.contains("pane-collapsed"), "The panel is in collapsed state");
 
   info("Click on the toggle button to expand the panel again");
 
   onTransitionEnd = once(panel, "transitionend");
-  EventUtils.synthesizeMouseAtCenter(button, {type: "mousedown"},
+  EventUtils.synthesizeMouseAtCenter(button, {},
     inspector.panelDoc.defaultView);
   yield onTransitionEnd;
 
-  ok(!panel.hasAttribute("pane-collapsed"), "The panel is in expanded state");
+  ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state");
 });
--- a/devtools/client/inspector/test/browser_inspector_pane-toggle-03.js
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-03.js
@@ -4,35 +4,35 @@
 "use strict";
 
 // Test that the toggle button can collapse and expand the inspector side/bottom
 // panel, and that the appropriate attributes are updated in the process.
 
 add_task(function* () {
   let {inspector} = yield openInspectorForURL("about:blank");
 
-  let button = inspector.panelDoc.getElementById("inspector-pane-toggle");
+  let button = inspector.panelDoc.querySelector(".sidebar-toggle");
   let panel = inspector.panelDoc.querySelector("#inspector-sidebar");
 
-  ok(!button.hasAttribute("pane-collapsed"), "The button is in expanded state");
+  ok(!button.classList.contains("pane-collapsed"), "The button is in expanded state");
 
   info("Listen to the end of the animation on the sidebar panel");
   let onTransitionEnd = once(panel, "transitionend");
 
   info("Click on the toggle button");
-  EventUtils.synthesizeMouseAtCenter(button, {type: "mousedown"},
+  EventUtils.synthesizeMouseAtCenter(button, {},
     inspector.panelDoc.defaultView);
 
   yield onTransitionEnd;
-  ok(button.hasAttribute("pane-collapsed"), "The button is in collapsed state");
-  ok(panel.hasAttribute("pane-collapsed"), "The panel is in collapsed state");
+  ok(button.classList.contains("pane-collapsed"), "The button is in collapsed state");
+  ok(panel.classList.contains("pane-collapsed"), "The panel is in collapsed state");
 
   info("Listen again to the end of the animation on the sidebar panel");
   onTransitionEnd = once(panel, "transitionend");
 
   info("Click on the toggle button again");
-  EventUtils.synthesizeMouseAtCenter(button, {type: "mousedown"},
+  EventUtils.synthesizeMouseAtCenter(button, {},
     inspector.panelDoc.defaultView);
 
   yield onTransitionEnd;
-  ok(!button.hasAttribute("pane-collapsed"), "The button is in expanded state");
-  ok(!panel.hasAttribute("pane-collapsed"), "The panel is in expanded state");
+  ok(!button.classList.contains("pane-collapsed"), "The button is in expanded state");
+  ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state");
 });
--- a/devtools/client/inspector/test/browser_inspector_pane-toggle-04.js
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-04.js
@@ -14,47 +14,47 @@ add_task(function* () {
   yield new Promise(resolve => {
     let options = {"set": [
       ["devtools.toolsidebar-width.inspector", 200]
     ]};
     SpecialPowers.pushPrefEnv(options, resolve);
   });
 
   let { inspector, toolbox } = yield openInspectorForURL("about:blank");
-  let button = inspector.panelDoc.getElementById("inspector-pane-toggle");
+  let button = inspector.panelDoc.querySelector(".sidebar-toggle");
   let panel = inspector.panelDoc.querySelector("#inspector-sidebar");
 
   info("Changing toolbox host to a window.");
   yield toolbox.switchHost(Toolbox.HostType.WINDOW);
 
   let hostWindow = toolbox._host._window;
   let originalWidth = hostWindow.outerWidth;
   let originalHeight = hostWindow.outerHeight;
 
   info("Resizing window to switch to the horizontal layout.");
   hostWindow.resizeTo(800, 300);
 
   // Check the sidebar is expanded when the test starts.
-  ok(!panel.hasAttribute("pane-collapsed"), "The panel is in expanded state");
+  ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state");
 
   info("Collapse the inspector sidebar.");
   let onTransitionEnd = once(panel, "transitionend");
-  EventUtils.synthesizeMouseAtCenter(button, {type: "mousedown"},
+  EventUtils.synthesizeMouseAtCenter(button, {},
     inspector.panelDoc.defaultView);
   yield onTransitionEnd;
 
-  ok(panel.hasAttribute("pane-collapsed"), "The panel is in collapsed state");
+  ok(panel.classList.contains("pane-collapsed"), "The panel is in collapsed state");
   let currentPanelHeight = panel.getBoundingClientRect().height;
   let currentPanelMarginBottom = panel.style.marginBottom;
 
   info("Resizing window to switch to the vertical layout.");
   hostWindow.resizeTo(300, 800);
 
   // Check the panel is collapsed, and still has the same dimensions.
-  ok(panel.hasAttribute("pane-collapsed"), "The panel is still collapsed");
+  ok(panel.classList.contains("pane-collapsed"), "The panel is still collapsed");
   is(panel.getBoundingClientRect().height, currentPanelHeight,
     "The panel height has not been modified when changing the layout.");
   is(panel.style.marginBottom, currentPanelMarginBottom,
     "The panel margin-bottom has not been modified when changing the layout.");
 
   info("Restoring window original size.");
   hostWindow.resizeTo(originalWidth, originalHeight);
 });
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -245,17 +245,17 @@ var NetMonitorView = {
     }
   }),
 
   /**
    * Gets the visibility state of the network details pane.
    * @return boolean
    */
   get detailsPaneHidden() {
-    return this._detailsPane.hasAttribute("pane-collapsed");
+    return this._detailsPane.classList.contains("pane-collapsed");
   },
 
   /**
    * Sets the network details pane hidden or visible.
    *
    * @param object flags
    *        An object containing some of the following properties:
    *        - visible: true if the pane should be shown, false to hide
@@ -267,22 +267,22 @@ var NetMonitorView = {
    */
   toggleDetailsPane: function (flags, tabIndex) {
     let pane = this._detailsPane;
     let button = this._detailsPaneToggleButton;
 
     ViewHelpers.togglePane(flags, pane);
 
     if (flags.visible) {
-      this._body.removeAttribute("pane-collapsed");
-      button.removeAttribute("pane-collapsed");
+      this._body.classList.remove("pane-collapsed");
+      button.classList.remove("pane-collapsed");
       button.setAttribute("tooltiptext", this._collapsePaneString);
     } else {
-      this._body.setAttribute("pane-collapsed", "");
-      button.setAttribute("pane-collapsed", "");
+      this._body.classList.add("pane-collapsed");
+      button.classList.add("pane-collapsed");
       button.setAttribute("tooltiptext", this._expandPaneString);
     }
 
     if (tabIndex !== undefined) {
       $("#event-details-pane").selectedIndex = tabIndex;
     }
   },
 
--- a/devtools/client/netmonitor/netmonitor.css
+++ b/devtools/client/netmonitor/netmonitor.css
@@ -31,14 +31,14 @@
   /* workaround for textbox not supporting the @crop attribute */
   text-overflow: ellipsis;
 }
 
 /* Responsive sidebar */
 @media (max-width: 700px) {
   #toolbar-spacer,
   #details-pane-toggle,
-  #details-pane[pane-collapsed],
+  #details-pane.pane-collapsed,
   .requests-menu-waterfall,
   #requests-menu-network-summary-button > .toolbarbutton-text {
     display: none;
   }
 }
--- a/devtools/client/netmonitor/test/browser_net_clear.js
+++ b/devtools/client/netmonitor/test/browser_net_clear.js
@@ -29,25 +29,25 @@ function test() {
       assertNoRequestState(RequestsMenu, detailsPaneToggleButton);
 
       // Load a second request and make sure they still show up
       aMonitor.panelWin.once(aMonitor.panelWin.EVENTS.NETWORK_EVENT, () => {
         assertSingleRequestState(RequestsMenu, detailsPaneToggleButton);
 
         // Make sure we can now open the details pane
         NetMonitorView.toggleDetailsPane({ visible: true, animated: false });
-        ok(!detailsPane.hasAttribute("pane-collapsed") &&
-          !detailsPaneToggleButton.hasAttribute("pane-collapsed"),
+        ok(!detailsPane.classList.contains("pane-collapsed") &&
+          !detailsPaneToggleButton.classList.contains("pane-collapsed"),
           "The details pane should be visible after clicking the toggle button.");
 
         // Click clear and make sure the details pane closes
         EventUtils.sendMouseEvent({ type: "click" }, clearButton);
         assertNoRequestState(RequestsMenu, detailsPaneToggleButton);
-        ok(detailsPane.hasAttribute("pane-collapsed") &&
-          detailsPaneToggleButton.hasAttribute("pane-collapsed"),
+        ok(detailsPane.classList.contains("pane-collapsed") &&
+          detailsPaneToggleButton.classList.contains("pane-collapsed"),
           "The details pane should not be visible clicking 'clear'.");
 
         teardown(aMonitor).then(finish);
       });
 
       aDebuggee.location.reload();
     });
 
--- a/devtools/client/netmonitor/test/browser_net_pane-collapse.js
+++ b/devtools/client/netmonitor/test/browser_net_pane-collapse.js
@@ -8,59 +8,59 @@
 function test() {
   initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => {
     info("Starting test... ");
 
     let { document, Prefs, NetMonitorView } = aMonitor.panelWin;
     let detailsPane = document.getElementById("details-pane");
     let detailsPaneToggleButton = document.getElementById("details-pane-toggle");
 
-    ok(detailsPane.hasAttribute("pane-collapsed") &&
-       detailsPaneToggleButton.hasAttribute("pane-collapsed"),
+    ok(detailsPane.classList.contains("pane-collapsed") &&
+       detailsPaneToggleButton.classList.contains("pane-collapsed"),
       "The details pane should initially be hidden.");
 
     NetMonitorView.toggleDetailsPane({ visible: true, animated: false });
 
     let width = ~~(detailsPane.getAttribute("width"));
     is(width, Prefs.networkDetailsWidth,
       "The details pane has an incorrect width.");
     is(detailsPane.style.marginLeft, "0px",
       "The details pane has an incorrect left margin.");
     is(detailsPane.style.marginRight, "0px",
       "The details pane has an incorrect right margin.");
     ok(!detailsPane.hasAttribute("animated"),
       "The details pane has an incorrect animated attribute.");
-    ok(!detailsPane.hasAttribute("pane-collapsed") &&
-       !detailsPaneToggleButton.hasAttribute("pane-collapsed"),
+    ok(!detailsPane.classList.contains("pane-collapsed") &&
+       !detailsPaneToggleButton.classList.contains("pane-collapsed"),
       "The details pane should at this point be visible.");
 
     NetMonitorView.toggleDetailsPane({ visible: false, animated: true });
 
     let margin = -(width + 1) + "px";
     is(width, Prefs.networkDetailsWidth,
       "The details pane has an incorrect width after collapsing.");
     is(detailsPane.style.marginLeft, margin,
       "The details pane has an incorrect left margin after collapsing.");
     is(detailsPane.style.marginRight, margin,
       "The details pane has an incorrect right margin after collapsing.");
     ok(detailsPane.hasAttribute("animated"),
       "The details pane has an incorrect attribute after an animated collapsing.");
-    ok(detailsPane.hasAttribute("pane-collapsed") &&
-       detailsPaneToggleButton.hasAttribute("pane-collapsed"),
+    ok(detailsPane.classList.contains("pane-collapsed") &&
+       detailsPaneToggleButton.classList.contains("pane-collapsed"),
       "The details pane should not be visible after collapsing.");
 
     NetMonitorView.toggleDetailsPane({ visible: true, animated: false });
 
     is(width, Prefs.networkDetailsWidth,
       "The details pane has an incorrect width after uncollapsing.");
     is(detailsPane.style.marginLeft, "0px",
       "The details pane has an incorrect left margin after uncollapsing.");
     is(detailsPane.style.marginRight, "0px",
       "The details pane has an incorrect right margin after uncollapsing.");
     ok(!detailsPane.hasAttribute("animated"),
       "The details pane has an incorrect attribute after an unanimated uncollapsing.");
-    ok(!detailsPane.hasAttribute("pane-collapsed") &&
-       !detailsPaneToggleButton.hasAttribute("pane-collapsed"),
+    ok(!detailsPane.classList.contains("pane-collapsed") &&
+       !detailsPaneToggleButton.classList.contains("pane-collapsed"),
       "The details pane should be visible again after uncollapsing.");
 
     teardown(aMonitor).then(finish);
   });
 }
--- a/devtools/client/netmonitor/test/browser_net_pane-toggle.js
+++ b/devtools/client/netmonitor/test/browser_net_pane-toggle.js
@@ -13,62 +13,62 @@ function test() {
     let { RequestsMenu } = NetMonitorView;
 
     RequestsMenu.lazyUpdate = false;
 
     is(document.querySelector("#details-pane-toggle")
       .hasAttribute("disabled"), true,
       "The pane toggle button should be disabled when the frontend is opened.");
     is(document.querySelector("#details-pane-toggle")
-      .hasAttribute("pane-collapsed"), true,
+      .classList.contains("pane-collapsed"), true,
       "The pane toggle button should indicate that the details pane is " +
       "collapsed when the frontend is opened.");
     is(NetMonitorView.detailsPaneHidden, true,
       "The details pane should be hidden when the frontend is opened.");
     is(RequestsMenu.selectedItem, null,
       "There should be no selected item in the requests menu.");
 
     aMonitor.panelWin.once(aMonitor.panelWin.EVENTS.NETWORK_EVENT, () => {
       is(document.querySelector("#details-pane-toggle")
         .hasAttribute("disabled"), false,
         "The pane toggle button should be enabled after the first request.");
       is(document.querySelector("#details-pane-toggle")
-        .hasAttribute("pane-collapsed"), true,
+        .classList.contains("pane-collapsed"), true,
         "The pane toggle button should still indicate that the details pane is " +
         "collapsed after the first request.");
       is(NetMonitorView.detailsPaneHidden, true,
         "The details pane should still be hidden after the first request.");
       is(RequestsMenu.selectedItem, null,
         "There should still be no selected item in the requests menu.");
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
 
       is(document.querySelector("#details-pane-toggle")
         .hasAttribute("disabled"), false,
         "The pane toggle button should still be enabled after being pressed.");
       is(document.querySelector("#details-pane-toggle")
-        .hasAttribute("pane-collapsed"), false,
+        .classList.contains("pane-collapsed"), false,
         "The pane toggle button should now indicate that the details pane is " +
         "not collapsed anymore after being pressed.");
       is(NetMonitorView.detailsPaneHidden, false,
         "The details pane should not be hidden after toggle button was pressed.");
       isnot(RequestsMenu.selectedItem, null,
         "There should be a selected item in the requests menu.");
       is(RequestsMenu.selectedIndex, 0,
         "The first item should be selected in the requests menu.");
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
 
       is(document.querySelector("#details-pane-toggle")
         .hasAttribute("disabled"), false,
         "The pane toggle button should still be enabled after being pressed again.");
       is(document.querySelector("#details-pane-toggle")
-        .hasAttribute("pane-collapsed"), true,
+        .classList.contains("pane-collapsed"), true,
         "The pane toggle button should now indicate that the details pane is " +
         "collapsed after being pressed again.");
       is(NetMonitorView.detailsPaneHidden, true,
         "The details pane should now be hidden after the toggle button was pressed again.");
       is(RequestsMenu.selectedItem, null,
         "There should now be no selected item in the requests menu.");
 
       teardown(aMonitor).then(finish);
--- a/devtools/client/projecteditor/lib/projecteditor.js
+++ b/devtools/client/projecteditor/lib/projecteditor.js
@@ -701,17 +701,17 @@ var ProjectEditor = Class({
 
     // If no plugin wants to handle it, just use a string from the resource.
     if (!renderedByPlugin) {
       elt.textContent = resource.displayName;
     }
   },
 
   get sourcesVisible() {
-    return this.sourceToggle.hasAttribute("pane-collapsed");
+    return this.sourceToggle.classList.contains("pane-collapsed");
   },
 
   get currentShell() {
     return this.shells.currentShell;
   },
 
   get currentEditor() {
     return this.shells.currentEditor;
--- a/devtools/client/shared/components/moz.build
+++ b/devtools/client/shared/components/moz.build
@@ -10,14 +10,16 @@ DIRS += [
     'tree'
 ]
 
 DevToolsModules(
     'frame.js',
     'h-split-box.js',
     'notification-box.css',
     'notification-box.js',
+    'sidebar-toggle.css',
+    'sidebar-toggle.js',
     'stack-trace.js',
     'tree.js',
 )
 
 MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini']
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/sidebar-toggle.css
@@ -0,0 +1,24 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+.sidebar-toggle {
+  display: block;
+}
+
+.sidebar-toggle::before {
+  background-image: var(--theme-pane-collapse-image);
+}
+
+.sidebar-toggle.pane-collapsed::before {
+  background-image: var(--theme-pane-expand-image);
+}
+
+/* Rotate button icon 90deg if the toolbox container is
+  in vertical mode (sidebar displayed under the main panel) */
+@media (max-width: 700px) {
+  .sidebar-toggle::before {
+    transform: rotate(90deg);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/sidebar-toggle.js
@@ -0,0 +1,66 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { DOM, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
+
+// Shortcuts
+const { button } = DOM;
+
+/**
+ * Sidebar toggle button. This button is used to exapand
+ * and collapse Sidebar.
+ */
+var SidebarToggle = createClass({
+  displayName: "SidebarToggle",
+
+  propTypes: {
+    // Set to true if collapsed.
+    collapsed: PropTypes.bool.isRequired,
+    // Tooltip text used when the button indicates expanded state.
+    collapsePaneTitle: PropTypes.string.isRequired,
+    // Tooltip text used when the button indicates collapsed state.
+    expandPaneTitle: PropTypes.string.isRequired,
+    // Click callback
+    onClick: PropTypes.func.isRequired,
+  },
+
+  getInitialState: function () {
+    return {
+      collapsed: this.props.collapsed,
+    };
+  },
+
+  // Events
+
+  onClick: function (event) {
+    this.props.onClick(event);
+  },
+
+  // Rendering
+
+  render: function () {
+    let title = this.state.collapsed ?
+      this.props.expandPaneTitle :
+      this.props.collapsePaneTitle;
+
+    let classNames = ["devtools-button", "sidebar-toggle"];
+    if (this.state.collapsed) {
+      classNames.push("pane-collapsed");
+    }
+
+    return (
+      button({
+        className: classNames.join(" "),
+        title: title,
+        onClick: this.onClick
+      })
+    );
+  }
+});
+
+module.exports = SidebarToggle;
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -1,28 +1,29 @@
 [DEFAULT]
 support-files =
   head.js
 
+[test_frame_01.html]
 [test_HSplitBox_01.html]
 [test_notification_box_01.html]
 [test_notification_box_02.html]
 [test_notification_box_03.html]
 [test_reps_attribute.html]
 [test_reps_date-time.html]
 [test_reps_function.html]
 [test_reps_grip.html]
 [test_reps_null.html]
 [test_reps_number.html]
 [test_reps_object-with-text.html]
 [test_reps_object-with-url.html]
 [test_reps_stylesheet.html]
 [test_reps_undefined.html]
 [test_reps_window.html]
-[test_frame_01.html]
+[test_sidebar_toggle.html]
 [test_tree_01.html]
 [test_tree_02.html]
 [test_tree_03.html]
 [test_tree_04.html]
 [test_tree_05.html]
 [test_tree_06.html]
 [test_tree_07.html]
 [test_tree_08.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_sidebar_toggle.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test sidebar toggle button
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Sidebar toggle button test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
+window.onload = Task.async(function* () {
+  let SidebarToggle = browserRequire("devtools/client/shared/components/sidebar-toggle.js");
+
+  try {
+    yield test();
+  } catch(e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+
+  function test() {
+    const output1 = shallowRenderComponent(SidebarToggle, {
+      collapsed: false,
+      collapsePaneTitle: "Expand",
+      expandPaneTitle: "Collapse"
+    });
+
+    is(output1.type, "button", "Output is a button element");
+    is(output1.props.title, "Expand", "Proper title is set");
+    is(output1.props.className.indexOf("pane-collapsed"), -1,
+      "Proper class name is set");
+
+    const output2 = shallowRenderComponent(SidebarToggle, {
+      collapsed: true,
+      collapsePaneTitle: "Expand",
+      expandPaneTitle: "Collapse"
+    });
+
+    is(output2.props.title, "Collapse", "Proper title is set");
+    ok(output2.props.className.indexOf("pane-collapsed") >= 0,
+      "Proper class name is set");
+  }
+});
+</script>
+</pre>
+</body>
+</html>
--- a/devtools/client/shared/widgets/view-helpers.js
+++ b/devtools/client/shared/widgets/view-helpers.js
@@ -239,17 +239,17 @@ const ViewHelpers = exports.ViewHelpers 
 
     // Hiding is always handled via margins, not the hidden attribute.
     pane.removeAttribute("hidden");
 
     // Add a class to the pane to handle min-widths, margins and animations.
     pane.classList.add("generic-toggled-pane");
 
     // Avoid useless toggles.
-    if (flags.visible == !pane.hasAttribute("pane-collapsed")) {
+    if (flags.visible == !pane.classList.contains("pane-collapsed")) {
       if (flags.callback) {
         flags.callback();
       }
       return;
     }
 
     // The "animated" attributes enables animated toggles (slide in-out).
     if (flags.animated) {
@@ -262,24 +262,24 @@ const ViewHelpers = exports.ViewHelpers 
     let doToggle = () => {
       // Negative margins are applied to "right" and "left" to support RTL and
       // LTR directions, as well as to "bottom" to support vertical layouts.
       // Unnecessary negative margins are forced to 0 via CSS in widgets.css.
       if (flags.visible) {
         pane.style.marginLeft = "0";
         pane.style.marginRight = "0";
         pane.style.marginBottom = "0";
-        pane.removeAttribute("pane-collapsed");
+        pane.classList.remove("pane-collapsed");
       } else {
         let width = Math.floor(pane.getAttribute("width")) + 1;
         let height = Math.floor(pane.getAttribute("height")) + 1;
         pane.style.marginLeft = -width + "px";
         pane.style.marginRight = -width + "px";
         pane.style.marginBottom = -height + "px";
-        pane.setAttribute("pane-collapsed", "");
+        pane.classList.add("pane-collapsed");
       }
 
       // Wait for the animation to end before calling afterToggle()
       if (flags.animated) {
         pane.addEventListener("transitionend", function onEvent() {
           pane.removeEventListener("transitionend", onEvent, false);
           // Prevent unwanted transitions: if the panel is hidden and the layout
           // changes margins will be updated and the panel will pop out.
--- a/devtools/client/themes/debugger.css
+++ b/devtools/client/themes/debugger.css
@@ -638,17 +638,17 @@
 .theme-firebug #step-out {
   list-style-image: url(images/firebug/debugger-step-out.svg);
 }
 
 #instruments-pane-toggle {
   list-style-image: var(--theme-pane-collapse-image);
 }
 
-#instruments-pane-toggle[pane-collapsed] {
+#instruments-pane-toggle.pane-collapsed {
   list-style-image: var(--theme-pane-expand-image);
 }
 
 /* Horizontal vs. vertical layout */
 
 #vertical-layout-panes-container {
   min-height: 35vh;
   max-height: 80vh;
--- a/devtools/client/themes/inspector.css
+++ b/devtools/client/themes/inspector.css
@@ -73,34 +73,16 @@
 }
 
 #inspector-breadcrumbs .breadcrumbs-widget-item {
   white-space: nowrap;
   flex-shrink: 0;
   font: message-box;
 }
 
-/* Expand/collapse panel toolbar button */
-
-#inspector-pane-toggle::before {
-  background-image: var(--theme-pane-collapse-image);
-}
-
-#inspector-pane-toggle[pane-collapsed]::before {
-  background-image: var(--theme-pane-expand-image);
-}
-
-/* Rotate button icon 90deg if the toolbox container is
-  in vertical mode (sidebar displayed under the main panel) */
-@media (max-width: 700px) {
-  #inspector-pane-toggle::before {
-    transform: rotate(90deg);
-  }
-}
-
 /* Add element toolbar button */
 
 #inspector-element-add-button::before {
   background-image: url("chrome://devtools/skin/images/add.svg");
 }
 
 /* "no results" warning message displayed in the ruleview and in the computed view */
 
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -505,17 +505,17 @@
 }
 
 /* Network request details */
 
 #details-pane-toggle {
   list-style-image: var(--theme-pane-collapse-image);
 }
 
-#details-pane-toggle[pane-collapsed] {
+#details-pane-toggle.pane-collapsed {
   list-style-image: var(--theme-pane-expand-image);
 }
 
 /* Network request details tabpanels */
 
 .tabpanel-content {
   background-color: var(--theme-sidebar-background);
 }
--- a/devtools/client/themes/webaudioeditor.css
+++ b/devtools/client/themes/webaudioeditor.css
@@ -144,17 +144,17 @@ text {
   list-style-image: var(--theme-pane-collapse-image);
 }
 
 #inspector-pane-toggle > .toolbarbutton-icon {
   width: 16px;
   height: 16px;
 }
 
-#inspector-pane-toggle[pane-collapsed] {
+#inspector-pane-toggle.pane-collapsed {
   list-style-image: var(--theme-pane-expand-image);
 }
 
 /**
  * Automation Styles
  */
 
 #automation-param-toolbar .automation-param-button[selected] {
--- a/devtools/client/themes/widgets.css
+++ b/devtools/client/themes/widgets.css
@@ -92,17 +92,17 @@
     border-inline-start-width: 0;
     margin-inline-end: 0;
     margin-inline-start: 0;
 
     /* In some edge case the cursor is not changed to n-resize */
     cursor: n-resize;
   }
 
-  .devtools-responsive-container > .devtools-sidebar-tabs:not([pane-collapsed]) {
+  .devtools-responsive-container > .devtools-sidebar-tabs:not(.pane-collapsed) {
     /* When the panel is collapsed min/max height should not be applied because
        collapsing relies on negative margins, which implies constant height. */
     min-height: 35vh;
     max-height: 75vh;
   }
 
   .devtools-responsive-container .generic-toggled-pane {
     /* To hide generic-toggled-pane, negative margins are applied dynamically.
--- a/devtools/client/webaudioeditor/views/utils.js
+++ b/devtools/client/webaudioeditor/views/utils.js
@@ -60,17 +60,17 @@ var ToggleMixin = {
     this._viewController({ visible: false, delayed: false, animated: false });
   },
 
   /**
    * Returns a boolean indicating whether or not the view.
    * is currently being shown.
    */
   isVisible: function () {
-    return !this.el.hasAttribute("pane-collapsed");
+    return !this.el.classList.contains("pane-collapsed");
   },
 
   /**
    * Toggles the visibility of the view.
    *
    * @param object visible
    *        - visible: boolean indicating whether the panel should be shown or not
    *        - animated: boolean indiciating whether the pane should be animated
@@ -83,21 +83,21 @@ var ToggleMixin = {
       animated: animated != null ? animated : !!this._animated,
       delayed: delayed != null ? delayed : !!this._delayed,
       callback: () => window.emit(this._toggleEvent, visible)
     };
 
     ViewHelpers.togglePane(flags, this.el);
 
     if (flags.visible) {
-      this.button.removeAttribute("pane-collapsed");
+      this.button.classList.remove("pane-collapsed");
       this.button.setAttribute("tooltiptext", this._collapseString);
     }
     else {
-      this.button.setAttribute("pane-collapsed", "");
+      this.button.classList.add("pane-collapsed");
       this.button.setAttribute("tooltiptext", this._expandString);
     }
   },
 
   _onToggle: function () {
     this._viewController({ visible: !this.isVisible() });
   }
 };
deleted file mode 100644
--- a/dom/base/test/bug682592-subframe-ref.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" >
-    <title></title>
-</head>
-<body>
-<p id="content"></p>
-</body>
-</html>
-
deleted file mode 100644
--- a/dom/base/test/bug682592-subframe.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" >
-    <title></title>
-</head>
-<body>
-<p id="content"></p>
-</body>
-</html>
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -41,18 +41,16 @@ support-files =
   bug466409-empty.css
   bug466409-page.html
   bug475156.sjs
   bug482935.sjs
   bug540854.sjs
   bug578096LoadChromeScript.js
   bug638112-response.txt
   bug638112.sjs
-  bug682592-subframe-ref.html
-  bug682592-subframe.html
   bug696301-script-1.js
   bug696301-script-1.js^headers^
   bug696301-script-2.js
   bug704320.sjs
   bug704320_counter.sjs
   bug819051.sjs
   chrome/bug418986-1.js
   copypaste.js
--- a/dom/base/test/test_bug682592.html
+++ b/dom/base/test/test_bug682592.html
@@ -8,17 +8,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     <title>Test for bug 682592</title>
     <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
     <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content">
-<iframe id="iframe-ref" src="bug682592-subframe-ref.html"></iframe>
+<iframe id="iframe-ref"></iframe>
 <iframe id="iframe-test"></iframe>
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript;version=1.7">
 /** Test for Bug 682592 **/
 
 /* 
    We want to check that bidi is detected correctly. So, we have a reference
@@ -34,39 +34,42 @@ https://bugzilla.mozilla.org/show_bug.cg
    So, instead of many diferent reftests, this mochitest implements a
    reftest-like. It creates reference text fragments in reference iframe, test
    text fragments in test iframe, and compare the documents. Then, it reloads
    test iframe. Reference iframe does not need to be reloaded between tests.
    It's ok (and maybe, desired) to keep bidi always enabled in that document. 
 */
 
 SimpleTest.waitForExplicitFinish();
-SimpleTest.requestLongerTimeout(2);
+SimpleTest.requestLongerTimeout(3);
 if (navigator.platform.startsWith("Linux arm")) { /* bugs 982875, 999429 */
   SimpleTest.expectAssertions(0, 4);
 }
 
+var page = `data:text/html;charset=UTF-8,<!DOCTYPE html>
+<html><body><p id="content"></p></body></html>`;
 var refFrame = document.getElementById("iframe-ref")
 var testFrame = document.getElementById("iframe-test");
 
 refFrame.addEventListener("load", function() {
   testFrame.addEventListener("load", function() {
     try {
       tests.next();
       ok(compareSnapshots(snapshotWindow(testFrame.contentWindow), 
                           snapshotWindow(refFrame.contentWindow), true)[0], 
          "bidi is not detected correctly");
 
       testFrame.contentWindow.location.reload();
     } catch (err if err instanceof StopIteration) {
       SimpleTest.finish();
     }
   }, false);
-  testFrame.src = "bug682592-subframe.html"
+  testFrame.src = page;
 }, false);
+refFrame.src = page;
 
 var rtl = "עִבְרִית";
 var non8bit =  "ʃ";
 var is8bit = "a";
 
 // concats aStr aNumber of times
 function strMult(aStr, aNumber) {
   if (aNumber === 0) {
--- a/gfx/gl/GLScreenBuffer.cpp
+++ b/gfx/gl/GLScreenBuffer.cpp
@@ -67,62 +67,72 @@ GLScreenBuffer::Create(GLContext* gl,
 }
 
 /* static */ UniquePtr<SurfaceFactory>
 GLScreenBuffer::CreateFactory(GLContext* gl,
                               const SurfaceCaps& caps,
                               const RefPtr<layers::CompositableForwarder>& forwarder,
                               const layers::TextureFlags& flags)
 {
+  return CreateFactory(gl, caps, forwarder, forwarder->GetCompositorBackendType(), flags);
+}
+
+/* static */ UniquePtr<SurfaceFactory>
+GLScreenBuffer::CreateFactory(GLContext* gl,
+                              const SurfaceCaps& caps,
+                              const RefPtr<layers::ClientIPCAllocator>& allocator,
+                              const mozilla::layers::LayersBackend backend,
+                              const layers::TextureFlags& flags)
+{
     UniquePtr<SurfaceFactory> factory = nullptr;
     if (!gfxPrefs::WebGLForceLayersReadback()) {
-        switch (forwarder->GetCompositorBackendType()) {
+        switch (backend) {
             case mozilla::layers::LayersBackend::LAYERS_OPENGL: {
 #if defined(XP_MACOSX)
-                factory = SurfaceFactory_IOSurface::Create(gl, caps, forwarder, flags);
+                factory = SurfaceFactory_IOSurface::Create(gl, caps, allocator, flags);
 #elif defined(MOZ_WIDGET_GONK)
-                factory = MakeUnique<SurfaceFactory_Gralloc>(gl, caps, forwarder, flags);
+                factory = MakeUnique<SurfaceFactory_Gralloc>(gl, caps, allocator, flags);
 #elif defined(GL_PROVIDER_GLX)
                 if (sGLXLibrary.UseTextureFromPixmap())
-                  factory = SurfaceFactory_GLXDrawable::Create(gl, caps, forwarder, flags);
+                  factory = SurfaceFactory_GLXDrawable::Create(gl, caps, allocator, flags);
 #elif defined(MOZ_WIDGET_UIKIT)
-                factory = MakeUnique<SurfaceFactory_GLTexture>(mGLContext, caps, forwarder, mFlags);
+                factory = MakeUnique<SurfaceFactory_GLTexture>(mGLContext, caps, allocator, mFlags);
 #else
                 if (gl->GetContextType() == GLContextType::EGL) {
                     if (XRE_IsParentProcess()) {
-                        factory = SurfaceFactory_EGLImage::Create(gl, caps, forwarder, flags);
+                        factory = SurfaceFactory_EGLImage::Create(gl, caps, allocator, flags);
                     }
                 }
 #endif
                 break;
             }
             case mozilla::layers::LayersBackend::LAYERS_D3D11: {
 #ifdef XP_WIN
                 // Enable surface sharing only if ANGLE and compositing devices
                 // are both WARP or both not WARP
                 if (gl->IsANGLE() &&
                     (gl->IsWARP() == gfxWindowsPlatform::GetPlatform()->IsWARP()) &&
                     gfxWindowsPlatform::GetPlatform()->CompositorD3D11TextureSharingWorks())
                 {
-                    factory = SurfaceFactory_ANGLEShareHandle::Create(gl, caps, forwarder, flags);
+                    factory = SurfaceFactory_ANGLEShareHandle::Create(gl, caps, allocator, flags);
                 }
 
                 if (!factory && gfxPrefs::WebGLDXGLEnabled()) {
-                  factory = SurfaceFactory_D3D11Interop::Create(gl, caps, forwarder, flags);
+                  factory = SurfaceFactory_D3D11Interop::Create(gl, caps, allocator, flags);
                 }
 #endif
               break;
             }
             default:
               break;
         }
 
 #ifdef GL_PROVIDER_GLX
         if (!factory && sGLXLibrary.UseTextureFromPixmap()) {
-            factory = SurfaceFactory_GLXDrawable::Create(gl, caps, forwarder, flags);
+            factory = SurfaceFactory_GLXDrawable::Create(gl, caps, allocator, flags);
         }
 #endif
     }
 
     return factory;
 }
 
 GLScreenBuffer::GLScreenBuffer(GLContext* gl,
--- a/gfx/gl/GLScreenBuffer.h
+++ b/gfx/gl/GLScreenBuffer.h
@@ -134,16 +134,22 @@ public:
                                             const gfx::IntSize& size,
                                             const SurfaceCaps& caps);
 
     static UniquePtr<SurfaceFactory>
     CreateFactory(GLContext* gl,
                   const SurfaceCaps& caps,
                   const RefPtr<layers::CompositableForwarder>& forwarder,
                   const layers::TextureFlags& flags);
+    static UniquePtr<SurfaceFactory>
+    CreateFactory(GLContext* gl,
+                  const SurfaceCaps& caps,
+                  const RefPtr<layers::ClientIPCAllocator>& allocator,
+                  const mozilla::layers::LayersBackend backend,
+                  const layers::TextureFlags& flags);
 
 protected:
     GLContext* const mGL; // Owns us.
 public:
     const SurfaceCaps mCaps;
 protected:
     UniquePtr<SurfaceFactory> mFactory;
 
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -790,23 +790,22 @@ struct nsCSSRendering {
       case NS_STYLE_BLEND_COLOR:       return CompositionOp::OP_COLOR;
       case NS_STYLE_BLEND_LUMINOSITY:  return CompositionOp::OP_LUMINOSITY;
       default:      MOZ_ASSERT(false); return CompositionOp::OP_OVER;
     }
   }
 
   static CompositionOp GetGFXCompositeMode(uint8_t aCompositeMode) {
     switch (aCompositeMode) {
-      case NS_STYLE_MASK_COMPOSITE_ADD:        return CompositionOp::OP_OVER;
-      case NS_STYLE_MASK_COMPOSITE_SUBSTRACT:  return CompositionOp::OP_OUT;
-      case NS_STYLE_MASK_COMPOSITE_INTERSECT:  return CompositionOp::OP_IN;
-      case NS_STYLE_MASK_COMPOSITE_EXCLUDE:    return CompositionOp::OP_XOR;
-      default:              MOZ_ASSERT(false); return CompositionOp::OP_OVER;
+      case NS_STYLE_MASK_COMPOSITE_ADD:       return CompositionOp::OP_OVER;
+      case NS_STYLE_MASK_COMPOSITE_SUBTRACT:  return CompositionOp::OP_OUT;
+      case NS_STYLE_MASK_COMPOSITE_INTERSECT: return CompositionOp::OP_IN;
+      case NS_STYLE_MASK_COMPOSITE_EXCLUDE:   return CompositionOp::OP_XOR;
+      default: MOZ_ASSERT(false);             return CompositionOp::OP_OVER;
     }
-
   }
 protected:
   static gfxRect GetTextDecorationRectInternal(
       const Point& aPt, const DecorationRectParams& aParams);
 
   /**
    * Returns inflated rect for painting a decoration line.
    * Complex style decoration lines should be painted from leftmost of nearest
--- a/layout/reftests/w3c-css/submitted/masking/mask-composite-1a.html
+++ b/layout/reftests/w3c-css/submitted/masking/mask-composite-1a.html
@@ -21,31 +21,31 @@
                     url(support/blue-100x50-transparent-100x50.svg);
       }
 
       div.add {
         left: 10px;
         mask-composite: add;
       }
 
-      div.substract {
+      div.subtract {
         left: 120px;
-        mask-composite: substract;
+        mask-composite: subtract;
       }
 
       div.intersect {
         left: 230px;
         mask-composite: intersect;
       }
 
       div.exclude {
         left: 340px;
         mask-composite: exclude;
       }
     </style>
   </head>
   <body>
     <div class="add"></div>
-    <div class="substract"></div>
+    <div class="subtract"></div>
     <div class="intersect"></div>
     <div class="exclude"></div>
   </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/masking/mask-composite-1b.html
+++ b/layout/reftests/w3c-css/submitted/masking/mask-composite-1b.html
@@ -21,31 +21,31 @@
                     url(support/blue-100x50-transparent-100x50.png);
       }
 
       div.add {
         left: 10px;
         mask-composite: add;
       }
 
-      div.substract {
+      div.subtract {
         left: 120px;
-        mask-composite: substract;
+        mask-composite: subtract;
       }
 
       div.intersect {
         left: 230px;
         mask-composite: intersect;
       }
 
       div.exclude {
         left: 340px;
         mask-composite: exclude;
       }
     </style>
   </head>
   <body>
     <div class="add"></div>
-    <div class="substract"></div>
+    <div class="subtract"></div>
     <div class="intersect"></div>
     <div class="exclude"></div>
   </body>
 </html>
\ No newline at end of file
--- a/layout/reftests/w3c-css/submitted/masking/mask-composite-1c.html
+++ b/layout/reftests/w3c-css/submitted/masking/mask-composite-1c.html
@@ -26,31 +26,31 @@
                     url(#rectMask);
       }
 
       div.add {
         left: 10px;
         mask-composite: add;
       }
 
-      div.substract {
+      div.subtract {
         left: 120px;
-        mask-composite: substract;
+        mask-composite: subtract;
       }
 
       div.intersect {
         left: 230px;
         mask-composite: intersect;
       }
 
       div.exclude {
         left: 340px;
         mask-composite: exclude;
       }
     </style>
   </head>
   <body>
     <div class="add"></div>
-    <div class="substract"></div>
+    <div class="subtract"></div>
     <div class="intersect"></div>
     <div class="exclude"></div>
   </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/masking/mask-composite-2-ref.html
+++ b/layout/reftests/w3c-css/submitted/masking/mask-composite-2-ref.html
@@ -15,25 +15,25 @@
         top: 10px;
       }
 
       div.add {
         left: 10px;
         background-color: blue;
       }
 
-      div.substract {
+      div.subtract {
         left: 120px;
         background-image: url(support/blue-100x50-transparent-100x50.svg);
       }
 
       div.exclude {
         left: 340px;
         background-color: blue;
       }
     </style>
   </head>
   <body>
     <div class="add"></div>
-    <div class="substract"></div>
+    <div class="subtract"></div>
     <div class="exclude"></div>
   </body>
 </html>
\ No newline at end of file
--- a/layout/reftests/w3c-css/submitted/masking/mask-composite-2a.html
+++ b/layout/reftests/w3c-css/submitted/masking/mask-composite-2a.html
@@ -21,31 +21,31 @@
                     url(support/transparent-100x50-blue-100x50.svg);
       }
 
       div.add {
         left: 10px;
         mask-composite: add;
       }
 
-      div.substract {
+      div.subtract {
         left: 120px;
-        mask-composite: substract;
+        mask-composite: subtract;
       }
 
       div.intersect {
         left: 230px;
         mask-composite: intersect;
       }
 
       div.exclude {
         left: 340px;
         mask-composite: exclude;
       }
     </style>
   </head>
   <body>
     <div class="add"></div>
-    <div class="substract"></div>
+    <div class="subtract"></div>
     <div class="intersect"></div>
     <div class="exclude"></div>
   </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/masking/mask-composite-2b.html
+++ b/layout/reftests/w3c-css/submitted/masking/mask-composite-2b.html
@@ -21,31 +21,31 @@
                     url(support/transparent-100x50-blue-100x50.png);
       }
 
       div.add {
         left: 10px;
         mask-composite: add;
       }
 
-      div.substract {
+      div.subtract {
         left: 120px;
-        mask-composite: substract;
+        mask-composite: subtract;
       }
 
       div.intersect {
         left: 230px;
         mask-composite: intersect;
       }
 
       div.exclude {
         left: 340px;
         mask-composite: exclude;
       }
     </style>
   </head>
   <body>
     <div class="add"></div>
-    <div class="substract"></div>
+    <div class="subtract"></div>
     <div class="intersect"></div>
     <div class="exclude"></div>
   </body>
 </html>
\ No newline at end of file
--- a/layout/reftests/w3c-css/submitted/masking/mask-composite-2c.html
+++ b/layout/reftests/w3c-css/submitted/masking/mask-composite-2c.html
@@ -29,31 +29,31 @@
                     url(#rectMask2);
       }
 
       div.add {
         left: 10px;
         mask-composite: add;
       }
 
-      div.substract {
+      div.subtract {
         left: 120px;
-        mask-composite: substract;
+        mask-composite: subtract;
       }
 
       div.intersect {
         left: 230px;
         mask-composite: intersect;
       }
 
       div.exclude {
         left: 340px;
         mask-composite: exclude;
       }
     </style>
   </head>
   <body>
     <div class="add"></div>
-    <div class="substract"></div>
+    <div class="subtract"></div>
     <div class="intersect"></div>
     <div class="exclude"></div>
   </body>
 </html>
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -556,17 +556,17 @@ CSS_KEY(stretched, stretched)
 CSS_KEY(strict, strict)
 CSS_KEY(stroke, stroke)
 CSS_KEY(stroke-box, stroke_box)
 CSS_KEY(style, style)
 CSS_KEY(styleset, styleset)
 CSS_KEY(stylistic, stylistic)
 CSS_KEY(sub, sub)
 CSS_KEY(subgrid, subgrid)
-CSS_KEY(substract, substract)
+CSS_KEY(subtract, subtract)
 CSS_KEY(super, super)
 CSS_KEY(sw-resize, sw_resize)
 CSS_KEY(swash, swash)
 CSS_KEY(swap, swap)
 CSS_KEY(table, table)
 CSS_KEY(table-caption, table_caption)
 CSS_KEY(table-cell, table_cell)
 CSS_KEY(table-column, table_column)
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -958,17 +958,17 @@ const KTableEntry nsCSSProps::kImageLaye
   { eCSSKeyword_alpha, NS_STYLE_MASK_MODE_ALPHA },
   { eCSSKeyword_luminance, NS_STYLE_MASK_MODE_LUMINANCE },
   { eCSSKeyword_match_source, NS_STYLE_MASK_MODE_MATCH_SOURCE },
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kImageLayerCompositeKTable[] = {
   { eCSSKeyword_add, NS_STYLE_MASK_COMPOSITE_ADD },
-  { eCSSKeyword_substract, NS_STYLE_MASK_COMPOSITE_SUBSTRACT },
+  { eCSSKeyword_subtract, NS_STYLE_MASK_COMPOSITE_SUBTRACT },
   { eCSSKeyword_intersect, NS_STYLE_MASK_COMPOSITE_INTERSECT },
   { eCSSKeyword_exclude, NS_STYLE_MASK_COMPOSITE_EXCLUDE },
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kBlendModeKTable[] = {
     { eCSSKeyword_normal,      NS_STYLE_BLEND_NORMAL },
     { eCSSKeyword_multiply,    NS_STYLE_BLEND_MULTIPLY },
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -1162,17 +1162,17 @@ enum class FillMode : uint32_t;
 #define NS_STYLE_BLEND_EXCLUSION                    11
 #define NS_STYLE_BLEND_HUE                          12
 #define NS_STYLE_BLEND_SATURATION                   13
 #define NS_STYLE_BLEND_COLOR                        14
 #define NS_STYLE_BLEND_LUMINOSITY                   15
 
 // composite
 #define NS_STYLE_MASK_COMPOSITE_ADD                 0
-#define NS_STYLE_MASK_COMPOSITE_SUBSTRACT           1
+#define NS_STYLE_MASK_COMPOSITE_SUBTRACT            1
 #define NS_STYLE_MASK_COMPOSITE_INTERSECT           2
 #define NS_STYLE_MASK_COMPOSITE_EXCLUDE             3
 
 // See nsStyleText::mControlCharacterVisibility
 #define NS_STYLE_CONTROL_CHARACTER_VISIBILITY_HIDDEN  0
 #define NS_STYLE_CONTROL_CHARACTER_VISIBILITY_VISIBLE 1
 
 // counter system
--- a/layout/style/res/html.css
+++ b/layout/style/res/html.css
@@ -85,19 +85,16 @@ ul,
 xmp {
   unicode-bidi: isolate;
 }
 
 bdi, output {
   unicode-bidi: isolate;
 }
 bdo, bdo[dir] {
-  unicode-bidi: bidi-override;
-}
-bdo[dir="auto"] {
   unicode-bidi: isolate-override;
 }
 textarea[dir="auto"], pre[dir="auto"] { unicode-bidi: plaintext; }
 
 /* blocks */
 
 article,
 aside,
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -6885,18 +6885,18 @@ if (SupportsMaskShorthand()) {
     other_values: [
       "none alpha repeat add left top",
       "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
       "repeat url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==') alpha left top add",
       "repeat-x",
       "repeat-y",
       "no-repeat",
       "none repeat-y alpha add 0% 0%",
-      "substract",
-      "0% top substract alpha repeat none",
+      "subtract",
+      "0% top subtract alpha repeat none",
       "top",
       "left",
       "50% 50%",
       "center",
       "top / 100px",
       "left / contain",
       "left / cover",
       "10px / 10%",
@@ -6913,19 +6913,19 @@ if (SupportsMaskShorthand()) {
       "-moz-radial-gradient(10% bottom, #ffffff, black) add no-repeat",
       "-moz-linear-gradient(10px 10px -45deg, red, blue) repeat",
       "-moz-linear-gradient(10px 10px -0.125turn, red, blue) repeat",
       "-moz-repeating-radial-gradient(10% bottom, #ffffff, black) add no-repeat",
       "-moz-repeating-linear-gradient(10px 10px -45deg, red, blue) repeat",
       "-moz-element(#test) alpha",
       /* multiple mask-image */
       "url(404.png), url(404.png)",
-      "repeat-x, substract, none",
+      "repeat-x, subtract, none",
       "0% top url(404.png), url(404.png) 0% top",
-      "substract repeat-y top left url(404.png), repeat-x alpha",
+      "subtract repeat-y top left url(404.png), repeat-x alpha",
       "url(404.png), -moz-linear-gradient(20px 20px -45deg, blue, green), -moz-element(#a) alpha",
       "top left / contain, bottom right / cover",
       /* test cases with clip+origin in the shorthand */
       "url(404.png) alpha padding-box",
       "url(404.png) border-box alpha",
       "content-box url(404.png)",
       "url(404.png) alpha padding-box padding-box",
       "url(404.png) alpha padding-box border-box",
@@ -6983,18 +6983,18 @@ if (SupportsMaskShorthand()) {
     other_values: [ "alpha", "luminance", "match-source, match-source", "match-source, alpha", "alpha, luminance, match-source"],
     invalid_values: [ "match-source match-source", "alpha match-source" ]
   };
   gCSSProperties["mask-composite"] = {
     domProp: "maskComposite",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "add" ],
-    other_values: [ "substract", "intersect", "exclude", "add, add", "substract, intersect", "substract, substract, add"],
-    invalid_values: [ "add substract", "intersect exclude" ]
+    other_values: [ "subtract", "intersect", "exclude", "add, add", "subtract, intersect", "subtract, subtract, add"],
+    invalid_values: [ "add subtract", "intersect exclude" ]
   };
   gCSSProperties["mask-origin"] = {
     domProp: "maskOrigin",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "border-box" ],
     other_values: [ "padding-box", "content-box", "border-box, padding-box", "padding-box, padding-box, padding-box", "border-box, border-box" ],
     invalid_values: [ "margin-box", "padding-box padding-box" ]
--- a/layout/style/test/test_default_bidi_css.html
+++ b/layout/style/test/test_default_bidi_css.html
@@ -44,21 +44,21 @@ var tests = [
     ['bdi', {'dir': ''}, 'ltr', 'isolate'],
 
     ['output', {}, 'ltr', 'isolate'],
     ['output', {'dir': 'ltr'}, 'ltr', 'isolate'],
     ['output', {'dir': 'rtl'}, 'rtl', 'isolate'],
     ['output', {'dir': 'auto'}, 'ltr', 'isolate'],
     ['output', {'dir': ''}, 'ltr', 'isolate'],
 
-    ['bdo', {}, 'ltr', 'bidi-override'],
-    ['bdo', {'dir': 'ltr'}, 'ltr', 'bidi-override'],
-    ['bdo', {'dir': 'rtl'}, 'rtl', 'bidi-override'],
+    ['bdo', {}, 'ltr', 'isolate-override'],
+    ['bdo', {'dir': 'ltr'}, 'ltr', 'isolate-override'],
+    ['bdo', {'dir': 'rtl'}, 'rtl', 'isolate-override'],
     ['bdo', {'dir': 'auto'}, 'ltr', 'isolate-override'],
-    ['bdo', {'dir': ''}, 'ltr', 'bidi-override'],
+    ['bdo', {'dir': ''}, 'ltr', 'isolate-override'],
 
     ['textarea', {}, 'ltr', 'normal'],
     ['textarea', {'dir': 'ltr'}, 'ltr', 'isolate'],
     ['textarea', {'dir': 'rtl'}, 'rtl', 'isolate'],
     ['textarea', {'dir': 'auto'}, 'ltr', 'plaintext'],
     ['textarea', {'dir': ''}, 'ltr', 'isolate'],
 
     ['pre', {}, 'ltr', 'isolate'],
--- a/layout/tools/reftest/mach_commands.py
+++ b/layout/tools/reftest/mach_commands.py
@@ -224,16 +224,23 @@ class ReftestRunner(MozbuildObject):
         }
 
         if not kwargs["tests"]:
             kwargs["tests"] = [os.path.join(*default_manifest[kwargs["suite"]])]
 
         kwargs["extraProfileFiles"].append(
             os.path.join(self.topsrcdir, "mobile", "android", "fonts"))
 
+        hyphenation_path = os.path.join(self.topsrcdir, "intl", "locales")
+
+        for (dirpath, dirnames, filenames) in os.walk(hyphenation_path):
+            for filename in filenames:
+                if filename.endswith('.dic'):
+                    kwargs["extraProfileFiles"].append(os.path.join(dirpath, filename))
+
         if not kwargs["httpdPath"]:
             kwargs["httpdPath"] = os.path.join(self.tests_dir, "modules")
         if not kwargs["symbolsPath"]:
             kwargs["symbolsPath"] = os.path.join(self.topobjdir, "crashreporter-symbols")
         if not kwargs["xrePath"]:
             kwargs["xrePath"] = os.environ.get("MOZ_HOST_BIN")
         if not kwargs["app"]:
             kwargs["app"] = self.substs["ANDROID_PACKAGE_NAME"]
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -684,22 +684,26 @@ class RefTest(object):
             )
         finally:
             self.cleanup(profileDir)
         return status
 
     def copyExtraFilesToProfile(self, options, profile):
         "Copy extra files or dirs specified on the command line to the testing profile."
         profileDir = profile.profile
+        if not os.path.exists(os.path.join(profileDir, "hyphenation")):
+            os.makedirs(os.path.join(profileDir, "hyphenation"))
         for f in options.extraProfileFiles:
             abspath = self.getFullPath(f)
             if os.path.isfile(abspath):
                 if os.path.basename(abspath) == 'user.js':
                     extra_prefs = mozprofile.Preferences.read_prefs(abspath)
                     profile.set_preferences(extra_prefs)
+                elif os.path.basename(abspath).endswith('.dic'):
+                    shutil.copy2(abspath, os.path.join(profileDir, "hyphenation"))
                 else:
                     shutil.copy2(abspath, profileDir)
             elif os.path.isdir(abspath):
                 dest = os.path.join(profileDir, os.path.basename(abspath))
                 shutil.copytree(abspath, dest)
             else:
                 self.log.warning(
                     "runreftest.py | Failed to copy %s to profile" % abspath)
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -1080,32 +1080,36 @@ Sync11Service.prototype = {
       // If we got a 401, we do not want to create a new meta/global - we
       // should be able to get the existing meta after we get a new node.
       if (this.recordManager.response.status == 401) {
         this._log.debug("Fetching meta/global record on the server returned 401.");
         this.errorHandler.checkServerError(this.recordManager.response);
         return false;
       }
 
-      if (!this.recordManager.response.success || !newMeta) {
+      if (this.recordManager.response.status == 404) {
         this._log.debug("No meta/global record on the server. Creating one.");
         newMeta = new WBORecord("meta", "global");
         newMeta.payload.syncID = this.syncID;
         newMeta.payload.storageVersion = STORAGE_VERSION;
         newMeta.payload.declined = this.engineManager.getDeclined();
 
         newMeta.isNew = true;
 
         this.recordManager.set(this.metaURL, newMeta);
         let uploadRes = newMeta.upload(this.resource(this.metaURL));
         if (!uploadRes.success) {
           this._log.warn("Unable to upload new meta/global. Failing remote setup.");
           this.errorHandler.checkServerError(uploadRes);
           return false;
         }
+      } else if (!newMeta) {
+        this._log.warn("Unable to get meta/global. Failing remote setup.");
+        this.errorHandler.checkServerError(this.recordManager.response);
+        return false;
       } else {
         // If newMeta, then it stands to reason that meta != null.
         newMeta.isNew   = meta.isNew;
         newMeta.changed = meta.changed;
       }
 
       // Switch in the new meta object and record the new time.
       meta              = newMeta;
--- a/services/sync/tests/unit/test_service_sync_remoteSetup.js
+++ b/services/sync/tests/unit/test_service_sync_remoteSetup.js
@@ -48,25 +48,37 @@ function run_test() {
 
     let ts = new_timestamp();
     collectionsHelper.update_collection("crypto", ts);
     collectionsHelper.update_collection("clients", ts);
     collectionsHelper.update_collection("meta", ts);
     return_timestamp(request, response, ts);
   }
 
-  let server = httpd_setup({
+  const GLOBAL_PATH = "/1.1/johndoe/storage/meta/global";
+  const INFO_PATH = "/1.1/johndoe/info/collections";
+
+  let handlers = {
     "/1.1/johndoe/storage": storageHandler,
     "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()),
     "/1.1/johndoe/storage/crypto": upd("crypto", cryptoColl.handler()),
     "/1.1/johndoe/storage/clients": upd("clients", clients.handler()),
+    "/1.1/johndoe/storage/meta": upd("meta", wasCalledHandler(metaColl)),
     "/1.1/johndoe/storage/meta/global": upd("meta", wasCalledHandler(meta_global)),
-    "/1.1/johndoe/storage/meta": upd("meta", wasCalledHandler(metaColl)),
     "/1.1/johndoe/info/collections": collectionsHelper.handler
-  });
+  };
+
+  function mockHandler(path, mock) {
+    server.registerPathHandler(path, mock(handlers[path]));
+    return {
+      restore() { server.registerPathHandler(path, handlers[path]); }
+    }
+  }
+
+  let server = httpd_setup(handlers);
 
   try {
     _("Log in.");
     ensureLegacyIdentityManager();
     Service.serverURL = server.baseURI;
 
     _("Checking Status.sync with no credentials.");
     Service.verifyAndFetchSymmetricKeys();
@@ -84,16 +96,73 @@ function run_test() {
     Service.serverURL = server.baseURI;
     Service.login("johndoe", "ilovejane", syncKey);
     do_check_true(Service.isLoggedIn);
 
     _("Checking that remoteSetup returns true when credentials have changed.");
     Service.recordManager.get(Service.metaURL).payload.syncID = "foobar";
     do_check_true(Service._remoteSetup());
 
+    let returnStatusCode = (method, code) => (oldMethod) => (req, res) => {
+      if (req.method === method) {
+        res.setStatusLine(req.httpVersion, code, "");
+      } else {
+        oldMethod(req, res);
+      }
+    };
+
+    let mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 401));
+    Service.recordManager.del(Service.metaURL);
+    _("Checking that remoteSetup returns false on 401 on first get /meta/global.");
+    do_check_false(Service._remoteSetup());
+    mock.restore();
+
+    Service.login("johndoe", "ilovejane", syncKey);
+    mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 503));
+    Service.recordManager.del(Service.metaURL);
+    _("Checking that remoteSetup returns false on 503 on first get /meta/global.");
+    do_check_false(Service._remoteSetup());
+    do_check_eq(Service.status.sync, METARECORD_DOWNLOAD_FAIL);
+    mock.restore();
+
+    mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 404));
+    Service.recordManager.del(Service.metaURL);
+    _("Checking that remoteSetup recovers on 404 on first get /meta/global.");
+    do_check_true(Service._remoteSetup());
+    mock.restore();
+
+    let makeOutdatedMeta = () => {
+      Service.metaModified = 0;
+      let infoResponse = Service._fetchInfo();
+      return {
+        status: infoResponse.status,
+        obj: {
+          crypto: infoResponse.obj.crypto,
+          clients: infoResponse.obj.clients,
+          meta: 1
+        }
+      };
+    }
+
+    _("Checking that remoteSetup recovers on 404 on get /meta/global after clear cached one.");
+    mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 404));
+    Service.recordManager.set(Service.metaURL, { isNew: false });
+    do_check_true(Service._remoteSetup(makeOutdatedMeta()));
+    mock.restore();
+
+    _("Checking that remoteSetup returns false on 503 on get /meta/global after clear cached one.");
+    mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 503));
+    Service.status.sync = "";
+    Service.recordManager.set(Service.metaURL, { isNew: false });
+    do_check_false(Service._remoteSetup(makeOutdatedMeta()));
+    do_check_eq(Service.status.sync, "");
+    mock.restore();
+
+    metaColl.delete({});
+
     _("Do an initial sync.");
     let beforeSync = Date.now()/1000;
     Service.sync();
 
     _("Checking that remoteSetup returns true.");
     do_check_true(Service._remoteSetup());
 
     _("Verify that the meta record was uploaded.");
--- a/taskcluster/ci/legacy/tasks/branches/autoland/job_flags.yml
+++ b/taskcluster/ci/legacy/tasks/branches/autoland/job_flags.yml
@@ -1,26 +1,24 @@
 ---
 # For complete sample of all build and test jobs,
 # see <gecko>/testing/taskcluster/tasks/branches/base_job_flags.yml
 
 $inherits:
   from: tasks/branches/base_jobs.yml
 
 builds:
-  aries:
-    platforms:
-      - b2g
-    types:
-      debug:
-        task: tasks/builds/b2g_aries_spark_debug.yml
   aries-eng:
     platforms:
       - b2g
     types:
       opt:
         task: tasks/builds/b2g_aries_eng.yml
+      debug:
+        task: tasks/builds/b2g_aries_debug.yml
   nexus-5l-eng:
     platforms:
       - b2g
     types:
       opt:
         task: tasks/builds/b2g_nexus_5l_eng.yml
+      debug:
+        task: tasks/builds/b2g_nexus_5l_debug.yml
deleted file mode 100644
--- a/taskcluster/ci/legacy/tasks/branches/b2g-inbound/job_flags.yml
+++ /dev/null
@@ -1,26 +0,0 @@
----
-# For complete sample of all build and test jobs,
-# see <gecko>/testing/taskcluster/tasks/branches/base_job_flags.yml
-
-$inherits:
-  from: tasks/branches/base_jobs.yml
-
-builds:
-  aries:
-    platforms:
-      - b2g
-    types:
-      debug:
-        task: tasks/builds/b2g_aries_spark_debug.yml
-  aries-eng:
-    platforms:
-      - b2g
-    types:
-      opt:
-        task: tasks/builds/b2g_aries_eng.yml
-  nexus-5l-eng:
-    platforms:
-      - b2g
-    types:
-      opt:
-        task: tasks/builds/b2g_nexus_5l_eng.yml
--- a/taskcluster/ci/legacy/tasks/branches/base_job_flags.yml
+++ b/taskcluster/ci/legacy/tasks/branches/base_job_flags.yml
@@ -7,17 +7,16 @@ flags:
     - linux64_gecko  # b2g desktop linux 64 bit
     - linux64-mulet  # Firefox desktop - b2g gecko linux 64 bit
     - linux64-haz    # Firefox desktop browser, rooting hazard analysis
     - linux64-shell-haz  # JS shell, rooting hazard analysis
     - linux64-mulet-haz  # Firefox desktop - b2g gecko linux 64 bit, rooting hazard analysis
     - macosx64_gecko # b2g desktop osx 64 bit
     - win32_gecko    # b2g desktop win 32 bit
     - nexus-5l-eng
-    - aries
     - aries-eng
     - android-api-15
     - android-api-15-frontend
     - android-partner-sample1
     - android-x86
     - linux
     - linux-l10n    # Desktop l10n
     - linux64
@@ -30,18 +29,20 @@ flags:
     - macosx64
     - macosx64-st-an
 
   tests:
     - cppunit
     - crashtest
     - crashtest-e10s
     - external-media-tests
-    - firefox-ui-functional
-    - firefox-ui-functional-e10s
+    - firefox-ui-functional-local
+    - firefox-ui-functional-local-e10s
+    - firefox-ui-functional-remote
+    - firefox-ui-functional-remote-e10s
     - gaia-build
     - gaia-build-unit
     - gaia-js-integration
     - gaia-linter
     - gaia-unit
     - gaia-unit-oop
     - gtest
     - jittests
--- a/taskcluster/ci/legacy/tasks/branches/base_jobs.yml
+++ b/taskcluster/ci/legacy/tasks/branches/base_jobs.yml
@@ -256,32 +256,48 @@ tests:
   external-media-tests:
     allowed_build_tasks:
       tasks/builds/opt_linux64.yml:
         task: tasks/tests/fx_linux64_external_media_tests_opt.yml
       tasks/builds/dbg_linux64.yml:
         task: tasks/tests/fx_linux64_external_media_tests_dbg.yml
       tasks/builds/opt_linux64_pgo.yml:
         task: tasks/tests/fx_linux64_external_media_tests_opt.yml
-  firefox-ui-functional:
+  firefox-ui-functional-local:
+    allowed_build_tasks:
+      tasks/builds/opt_linux64.yml:
+        task: tasks/tests/fx_linux64_fxui_functional_local_opt.yml
+      tasks/builds/dbg_linux64.yml:
+        task: tasks/tests/fx_linux64_fxui_functional_local_dbg.yml
+      tasks/builds/opt_linux64_pgo.yml:
+        task: tasks/tests/fx_linux64_fxui_functional_local_opt.yml
+  firefox-ui-functional-local-e10s:
     allowed_build_tasks:
       tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_firefox_ui_functional_opt.yml
+        task: tasks/tests/fx_linux64_fxui_functional_local_e10s_opt.yml
       tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_firefox_ui_functional_dbg.yml
+        task: tasks/tests/fx_linux64_fxui_functional_local_e10s_dbg.yml
       tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_firefox_ui_functional_opt.yml
-  firefox-ui-functional-e10s:
+        task: tasks/tests/fx_linux64_fxui_functional_local_e10s_opt.yml
+  firefox-ui-functional-remote:
     allowed_build_tasks:
       tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_firefox_ui_functional_e10s_opt.yml
+        task: tasks/tests/fx_linux64_fxui_functional_remote_opt.yml
       tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_firefox_ui_functional_e10s_dbg.yml
+        task: tasks/tests/fx_linux64_fxui_functional_remote_dbg.yml
       tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_firefox_ui_functional_e10s_opt.yml
+        task: tasks/tests/fx_linux64_fxui_functional_remote_opt.yml
+  firefox-ui-functional-remote-e10s:
+    allowed_build_tasks:
+      tasks/builds/opt_linux64.yml:
+        task: tasks/tests/fx_linux64_fxui_functional_remote_e10s_opt.yml
+      tasks/builds/dbg_linux64.yml:
+        task: tasks/tests/fx_linux64_fxui_functional_remote_e10s_dbg.yml
+      tasks/builds/opt_linux64_pgo.yml:
+        task: tasks/tests/fx_linux64_fxui_functional_remote_e10s_opt.yml
   gtest:
     allowed_build_tasks:
       tasks/builds/opt_linux64.yml:
         task: tasks/tests/fx_linux64_gtest_opt.yml
       tasks/builds/dbg_linux64.yml:
         task: tasks/tests/fx_linux64_gtest_dbg.yml
       tasks/builds/opt_linux64_pgo.yml:
         task: tasks/tests/fx_linux64_gtest_opt.yml
deleted file mode 100644
--- a/taskcluster/ci/legacy/tasks/branches/fx-team/job_flags.yml
+++ /dev/null
@@ -1,26 +0,0 @@
----
-# For complete sample of all build and test jobs,
-# see <gecko>/testing/taskcluster/tasks/branches/base_job_flags.yml
-
-$inherits:
-  from: tasks/branches/base_jobs.yml
-
-builds:
-  aries:
-    platforms:
-      - b2g
-    types:
-      debug:
-        task: tasks/builds/b2g_aries_spark_debug.yml
-  aries-eng:
-    platforms:
-      - b2g
-    types:
-      opt:
-        task: tasks/builds/b2g_aries_eng.yml
-  nexus-5l-eng:
-    platforms:
-      - b2g
-    types:
-      opt:
-        task: tasks/builds/b2g_nexus_5l_eng.yml
deleted file mode 100644
--- a/taskcluster/ci/legacy/tasks/branches/larch/job_flags.yml
+++ /dev/null
@@ -1,26 +0,0 @@
----
-# For complete sample of all build and test jobs,
-# see <gecko>/testing/taskcluster/tasks/branches/base_job_flags.yml
-
-$inherits:
-  from: tasks/branches/base_jobs.yml
-
-builds:
-  aries:
-    platforms:
-      - b2g
-    types:
-      debug:
-        task: tasks/builds/b2g_aries_spark_debug.yml
-  aries-eng:
-    platforms:
-      - b2g
-    types:
-      opt:
-        task: tasks/builds/b2g_aries_eng.yml
-  nexus-5l-eng:
-    platforms:
-      - b2g
-    types:
-      opt:
-        task: tasks/builds/b2g_nexus_5l_eng.yml
--- a/taskcluster/ci/legacy/tasks/branches/mozilla-central/job_flags.yml
+++ b/taskcluster/ci/legacy/tasks/branches/mozilla-central/job_flags.yml
@@ -6,34 +6,32 @@
   from: tasks/branches/base_jobs.yml
 
 # Flags specific to this branch
 flags:
   post-build:
     - simulator
 
 builds:
-  aries:
-    platforms:
-      - b2g
-    types:
-      debug:
-        task: tasks/builds/b2g_aries_spark_debug.yml
   aries-eng:
     platforms:
       - b2g
     types:
       opt:
         task: tasks/builds/b2g_aries_eng.yml
+      debug:
+        task: tasks/builds/b2g_aries_debug.yml
   nexus-5l-eng:
     platforms:
       - b2g
     types:
       opt:
         task: tasks/builds/b2g_nexus_5l_eng.yml
+      debug:
+        task: tasks/builds/b2g_nexus_5l_debug.yml
 
 tests:
   gaia-build:
     allowed_build_tasks:
       tasks/builds/mulet_linux.yml:
         task: tasks/tests/mulet_build_test.yml
   gaia-build-unit:
     allowed_build_tasks:
--- a/taskcluster/ci/legacy/tasks/branches/mozilla-inbound/job_flags.yml
+++ b/taskcluster/ci/legacy/tasks/branches/mozilla-inbound/job_flags.yml
@@ -1,26 +1,24 @@
 ---
 # For complete sample of all build and test jobs,
 # see <gecko>/testing/taskcluster/tasks/branches/base_job_flags.yml
 
 $inherits:
   from: tasks/branches/base_jobs.yml
 
 builds:
-  aries:
-    platforms:
-      - b2g
-    types:
-      debug:
-        task: tasks/builds/b2g_aries_spark_debug.yml
   aries-eng:
     platforms:
       - b2g
     types:
       opt:
         task: tasks/builds/b2g_aries_eng.yml
+      debug:
+        task: tasks/builds/b2g_aries_debug.yml
   nexus-5l-eng:
     platforms:
       - b2g
     types:
       opt:
         task: tasks/builds/b2g_nexus_5l_eng.yml
+      debug:
+        task: tasks/builds/b2g_nexus_5l_debug.yml
--- a/taskcluster/ci/legacy/tasks/branches/try/job_flags.yml
+++ b/taskcluster/ci/legacy/tasks/branches/try/job_flags.yml
@@ -110,22 +110,26 @@ builds:
       opt:
         task: tasks/builds/opt_macosx64_st-an.yml
   aries-eng:
     platforms:
       - b2g
     types:
       opt:
         task: tasks/builds/b2g_aries_eng.yml
+      debug:
+        task: tasks/builds/b2g_aries_debug.yml
   nexus-5l-eng:
     platforms:
       - b2g
     types:
       opt:
         task: tasks/builds/b2g_nexus_5l_eng.yml
+      debug:
+        task: tasks/builds/b2g_nexus_5l_debug.yml
 
 post-build:
   upload-symbols:
     allowed_build_tasks:
       - tasks/builds/opt_linux64.yml
       - tasks/builds/opt_linux64_st-an.yml
       - tasks/builds/dbg_linux64.yml
       - tasks/builds/android_api_15.yml
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/builds/b2g_aries_debug.yml
@@ -0,0 +1,37 @@
+$inherits:
+  from: 'tasks/builds/b2g_phone_eng_base.yml'
+  variables:
+    build_name: 'aries'
+    build_type: 'debug'
+task:
+  scopes:
+    - 'docker-worker:cache:level-{{level}}-{{project}}-build-aries-debug'
+    - 'docker-worker:cache:level-{{level}}-{{project}}-build-aries-debug-objdir-gecko'
+    - 'docker-worker:relengapi-proxy:tooltool.download.internal'
+    - 'docker-worker:relengapi-proxy:tooltool.download.public'
+  metadata:
+    name: '[TC] B2G Aries Debug'
+
+  payload:
+    cache:
+      level-{{level}}-{{project}}-build-aries-debug: /home/worker/workspace
+      level-{{level}}-{{project}}-build-aries-debug-objdir-gecko: /home/worker/objdir-gecko
+    features:
+      relengAPIProxy: true
+    env:
+      TARGET: 'aries'
+      B2G_DEBUG: 1
+  extra:
+    treeherderEnv:
+      - production
+      - staging
+    treeherder:
+      symbol: Bd
+      groupSymbol: Aries
+      groupName: Aries Device Image
+      machine:
+        platform: b2g-device-image
+      collection:
+        debug: true
+    locations:
+      img: 'private/build/aries.zip'
deleted file mode 100644
--- a/taskcluster/ci/legacy/tasks/builds/b2g_aries_spark_debug.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-$inherits:
-  from: 'tasks/builds/b2g_phone_base.yml'
-  variables:
-    build_name: 'aries'
-    build_type: 'debug'
-task:
-  workerType: flame-kk
-  scopes:
-    - 'docker-worker:cache:level-{{level}}-{{project}}-build-aries-debug'
-    - 'docker-worker:cache:level-{{level}}-{{project}}-build-aries-debug-objdir-gecko'
-  metadata:
-    name: '[TC] B2G Aries Debug'
-
-  payload:
-    cache:
-      level-{{level}}-{{project}}-build-aries-debug: /home/worker/workspace
-      level-{{level}}-{{project}}-build-aries-debug-objdir-gecko: /home/worker/objdir-gecko
-    env:
-      TARGET: 'aries'
-      DEBUG: 0
-      VARIANT: userdebug
-      GAIA_OPTIMIZE: '1'
-      B2G_SYSTEM_APPS: '1'
-      MOZHARNESS_CONFIG: b2g/taskcluster-spark.py
-    command: ["/bin/bash", "-c", "checkout-gecko workspace && cd ./workspace/gecko/taskcluster/scripts/phone-builder && buildbot_step 'Build' ./build-phone.sh $HOME/workspace"]
-  extra:
-    treeherderEnv:
-      - production
-      - staging
-    treeherder:
-      symbol: B
-      groupSymbol: Aries
-      groupName: Aries Device Image
-      machine:
-        platform: b2g-device-image
-      collection:
-        debug: true
-    locations:
-      img: 'private/build/aries.zip'
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/builds/b2g_nexus_5l_debug.yml
@@ -0,0 +1,37 @@
+$inherits:
+  from: 'tasks/builds/b2g_phone_eng_base.yml'
+  variables:
+    build_name: 'nexus-5-l-eng'
+    build_type: 'debug'
+task:
+  scopes:
+    - 'docker-worker:cache:level-{{level}}-{{project}}-build-nexus-5-l-debug'
+    - 'docker-worker:cache:level-{{level}}-{{project}}-build-nexus-5-l-debug-objdir-gecko'
+    - 'docker-worker:relengapi-proxy:tooltool.download.internal'
+    - 'docker-worker:relengapi-proxy:tooltool.download.public'
+  metadata:
+    name: '[TC] B2G Nexus 5-L Debug'
+
+  payload:
+    cache:
+      level-{{level}}-{{project}}-build-nexus-5-l-debug: /home/worker/workspace
+      level-{{level}}-{{project}}-build-nexus-5-l-debug-objdir-gecko: /home/worker/objdir-gecko
+    features:
+      relengAPIProxy: true
+    env:
+      TARGET: 'nexus-5-l'
+      B2G_DEBUG: 1
+  extra:
+    treeherderEnv:
+      - production
+      - staging
+    treeherder:
+      symbol: Bd
+      groupSymbol: Nexus 5-L
+      groupName: Nexus 5-L Device Image
+      machine:
+        platform: b2g-device-image
+      collection:
+        debug: true
+    locations:
+      img: 'private/build/nexus-5-l.zip'
--- a/taskcluster/ci/legacy/tasks/builds/b2g_nexus_5l_eng.yml
+++ b/taskcluster/ci/legacy/tasks/builds/b2g_nexus_5l_eng.yml
@@ -1,22 +1,22 @@
 $inherits:
   from: 'tasks/builds/b2g_phone_eng_base.yml'
   variables:
     build_name: 'nexus-5-l-eng'
     build_type: 'opt'
 task:
-  workerType: flame-kk
   scopes:
     - 'docker-worker:cache:level-{{level}}-{{project}}-build-nexus-5-l-eng'
     - 'docker-worker:cache:level-{{level}}-{{project}}-build-nexus-5-l-eng-objdir-gecko'
     - 'docker-worker:relengapi-proxy:tooltool.download.internal'
     - 'docker-worker:relengapi-proxy:tooltool.download.public'
   metadata:
     name: '[TC] B2G Nexus 5-L Eng'
+
   payload:
     cache:
       level-{{level}}-{{project}}-build-nexus-5-l-eng: /home/worker/object-folder
       level-{{level}}-{{project}}-build-nexus-5-l-eng-objdir-gecko: /home/worker/objdir-gecko
     features:
       relengAPIProxy: true
     env:
       TARGET: 'nexus-5-l'
deleted file mode 100644
--- a/taskcluster/ci/legacy/tasks/tests/fx_linux64_firefox_ui_functional.yml
+++ /dev/null
@@ -1,26 +0,0 @@
----
-$inherits:
-  from: 'tasks/tests/fx_docker_desktop_generic.yml'
-task:
-  payload:
-    command:
-      - {"task-reference": "--installer-url=https://queue.taskcluster.net/v1/task/<{{build_slugid}}>/artifacts/{{build_location}}"}
-      - {"task-reference": "--test-packages-url=https://queue.taskcluster.net/v1/task/<{{build_slugid}}>/artifacts/{{test_packages_location}}"}
-    env:
-        MOZHARNESS_SCRIPT: 'mozharness/scripts/firefox_ui_tests/functional.py'
-        MOZHARNESS_CONFIG: >
-          mozharness/configs/firefox_ui_tests/taskcluster.py
-          mozharness/configs/remove_executables.py
-    maxRunTime: 5400
-  metadata:
-    name: '[TC] Linux64 firefox-ui-tests functional'
-    description: firefox-ui-tests functional run
-  extra:
-    suite:
-      name: firefox-ui
-      flavor: functional
-    treeherder:
-      groupName: Desktop firefox-ui-tests
-      groupSymbol: tc-Fxfn
-      symbol: en-US
-      tier: 2
deleted file mode 100644
--- a/taskcluster/ci/legacy/tasks/tests/fx_linux64_firefox_ui_functional_dbg.yml
+++ /dev/null
@@ -1,7 +0,0 @@
----
-$inherits:
-  from: 'tasks/tests/fx_linux64_firefox_ui_functional.yml'
-task:
-  payload:
-    command:
-      - --download-symbols=true
deleted file mode 100644
--- a/taskcluster/ci/legacy/tasks/tests/fx_linux64_firefox_ui_functional_e10s.yml
+++ /dev/null
@@ -1,27 +0,0 @@
----
-$inherits:
-  from: 'tasks/tests/fx_docker_desktop_generic.yml'
-task:
-  payload:
-    command:
-      - {"task-reference": "--installer-url=https://queue.taskcluster.net/v1/task/<{{build_slugid}}>/artifacts/{{build_location}}"}
-      - {"task-reference": "--test-packages-url=https://queue.taskcluster.net/v1/task/<{{build_slugid}}>/artifacts/{{test_packages_location}}"}
-      - --e10s
-    env:
-        MOZHARNESS_SCRIPT: 'mozharness/scripts/firefox_ui_tests/functional.py'
-        MOZHARNESS_CONFIG: >
-          mozharness/configs/firefox_ui_tests/taskcluster.py
-          mozharness/configs/remove_executables.py
-    maxRunTime: 5400
-  metadata:
-    name: '[TC] Linux64 firefox-ui-tests functional e10s'
-    description: firefox-ui-tests functional e10s run
-  extra:
-    suite:
-      name: firefox-ui
-      flavor: functional
-    treeherder:
-      groupName: Desktop firefox-ui-tests
-      groupSymbol: tc-Fxfn-e10s
-      symbol: en-US
-      tier: 2
deleted file mode 100644
--- a/taskcluster/ci/legacy/tasks/tests/fx_linux64_firefox_ui_functional_e10s_dbg.yml
+++ /dev/null
@@ -1,7 +0,0 @@
----
-$inherits:
-  from: 'tasks/tests/fx_linux64_firefox_ui_functional_e10s.yml'
-task:
-  payload:
-    command:
-      - --download-symbols=true
deleted file mode 100644
--- a/taskcluster/ci/legacy/tasks/tests/fx_linux64_firefox_ui_functional_e10s_opt.yml
+++ /dev/null
@@ -1,7 +0,0 @@
----
-$inherits:
-  from: 'tasks/tests/fx_linux64_firefox_ui_functional_e10s.yml'
-task:
-  payload:
-    command:
-      - --download-symbols=ondemand
deleted file mode 100644
--- a/taskcluster/ci/legacy/tasks/tests/fx_linux64_firefox_ui_functional_opt.yml
+++ /dev/null
@@ -1,7 +0,0 @@
----
-$inherits:
-  from: 'tasks/tests/fx_linux64_firefox_ui_functional.yml'
-task:
-  payload:
-    command:
-      - --download-symbols=ondemand
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional.yml
@@ -0,0 +1,24 @@
+---
+$inherits:
+  from: 'tasks/tests/fx_docker_desktop_generic.yml'
+task:
+  payload:
+    command:
+      - {"task-reference": "--installer-url=https://queue.taskcluster.net/v1/task/<{{build_slugid}}>/artifacts/{{build_location}}"}
+      - {"task-reference": "--test-packages-url=https://queue.taskcluster.net/v1/task/<{{build_slugid}}>/artifacts/{{test_packages_location}}"}
+    env:
+        MOZHARNESS_SCRIPT: 'mozharness/scripts/firefox_ui_tests/functional.py'
+        MOZHARNESS_CONFIG: >
+          mozharness/configs/firefox_ui_tests/taskcluster.py
+          mozharness/configs/remove_executables.py
+    maxRunTime: 5400
+  metadata:
+    name: '[TC] Linux64 firefox-ui-tests functional'
+    description: firefox-ui-tests functional run
+  extra:
+    suite:
+      name: firefox-ui
+    treeherder:
+      groupName: Desktop firefox-ui-tests
+      groupSymbol: tc-Fxfn
+      symbol: en-US
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_local.yml
@@ -0,0 +1,17 @@
+---
+$inherits:
+  from: 'tasks/tests/fx_linux64_fxui_functional.yml'
+task:
+  payload:
+    command:
+      - --tag local
+  metadata:
+    name: '[TC] Linux64 firefox-ui-tests functional local'
+    description: firefox-ui-tests functional local run
+  extra:
+    suite:
+      name: firefox-ui
+      flavor: functional local
+    treeherder:
+      groupName: Desktop firefox-ui-tests (local)
+      groupSymbol: tc-Fxfn-l
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_local_dbg.yml
@@ -0,0 +1,7 @@
+---
+$inherits:
+  from: 'tasks/tests/fx_linux64_fxui_functional_local.yml'
+task:
+  payload:
+    command:
+      - --download-symbols=true
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_local_e10s.yml
@@ -0,0 +1,13 @@
+---
+$inherits:
+  from: 'tasks/tests/fx_linux64_fxui_functional_local.yml'
+task:
+  payload:
+    command:
+      - --e10s
+  metadata:
+    name: '[TC] Linux64 firefox-ui-tests functional local e10s'
+    description: firefox-ui-tests functional local e10s run
+  extra:
+    treeherder:
+      groupSymbol: tc-Fxfn-l-e10s
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_local_e10s_dbg.yml
@@ -0,0 +1,7 @@
+---
+$inherits:
+  from: 'tasks/tests/fx_linux64_fxui_functional_local_e10s.yml'
+task:
+  payload:
+    command:
+      - --download-symbols=true
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_local_e10s_opt.yml
@@ -0,0 +1,7 @@
+---
+$inherits:
+  from: 'tasks/tests/fx_linux64_fxui_functional_local_e10s.yml'
+task:
+  payload:
+    command:
+      - --download-symbols=ondemand
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_local_opt.yml
@@ -0,0 +1,7 @@
+---
+$inherits:
+  from: 'tasks/tests/fx_linux64_fxui_functional_local.yml'
+task:
+  payload:
+    command:
+      - --download-symbols=ondemand
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_remote.yml
@@ -0,0 +1,18 @@
+---
+$inherits:
+  from: 'tasks/tests/fx_linux64_fxui_functional.yml'
+task:
+  payload:
+    command:
+      - --tag remote
+  metadata:
+    name: '[TC] Linux64 firefox-ui-tests functional remote'
+    description: firefox-ui-tests functional remote run
+  extra:
+    suite:
+      name: firefox-ui
+      flavor: functional remote
+    treeherder:
+      groupName: Desktop firefox-ui-tests (remote)
+      groupSymbol: tc-Fxfn-r
+      tier: 2
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_remote_dbg.yml
@@ -0,0 +1,7 @@
+---
+$inherits:
+  from: 'tasks/tests/fx_linux64_fxui_functional_remote.yml'
+task:
+  payload:
+    command:
+      - --download-symbols=true
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_remote_e10s.yml
@@ -0,0 +1,13 @@
+---
+$inherits:
+  from: 'tasks/tests/fx_linux64_fxui_functional_remote.yml'
+task:
+  payload:
+    command:
+      - --e10s
+  metadata:
+    name: '[TC] Linux64 firefox-ui-tests functional remote e10s'
+    description: firefox-ui-tests functional remote e10s run
+  extra:
+    treeherder:
+      groupSymbol: tc-Fxfn-r-e10s
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_remote_e10s_dbg.yml
@@ -0,0 +1,7 @@
+---
+$inherits:
+  from: 'tasks/tests/fx_linux64_fxui_functional_remote_e10s.yml'
+task:
+  payload:
+    command:
+      - --download-symbols=true
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_remote_e10s_opt.yml
@@ -0,0 +1,7 @@
+---
+$inherits:
+  from: 'tasks/tests/fx_linux64_fxui_functional_remote_e10s.yml'
+task:
+  payload:
+    command:
+      - --download-symbols=ondemand
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_remote_opt.yml
@@ -0,0 +1,7 @@
+---
+$inherits:
+  from: 'tasks/tests/fx_linux64_fxui_functional_remote.yml'
+task:
+  payload:
+    command:
+      - --download-symbols=ondemand
--- a/testing/mozharness/configs/android/androidarm.py
+++ b/testing/mozharness/configs/android/androidarm.py
@@ -69,16 +69,17 @@ config = {
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
                 "--certificate-path=%(certs_path)s",
                 "--symbols-path=%(symbols_path)s",
                 "--quiet",
                 "--log-raw=%(raw_log_file)s",
                 "--log-errorsummary=%(error_summary_file)s",
                 "--extra-profile-file=fonts",
+                "--extra-profile-file=hyphenation",
                 "--screenshot-on-fail",
             ],
         },
         "mochitest-gl": {
             "run_filename": "runtestsremote.py",
             "testsdir": "mochitest",
             "options": [
                 "--dm_trans=sut",
@@ -159,16 +160,17 @@ config = {
                 "--devicePort=%(device_port)s",
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
                 "--httpd-path",
                 "%(modules_dir)s",
                 "--symbols-path=%(symbols_path)s",
                 "--total-chunks=16",
                 "--extra-profile-file=fonts",
+                "--extra-profile-file=hyphenation",
                 "--suite=reftest",
                 "--log-raw=%(raw_log_file)s",
                 "--log-errorsummary=%(error_summary_file)s",
             ],
             "tests": ["tests/layout/reftests/reftest.list"],
         },
         "crashtest": {
             "run_filename": "remotereftest.py",
--- a/testing/mozharness/configs/android/androidarm_4_3.py
+++ b/testing/mozharness/configs/android/androidarm_4_3.py
@@ -73,16 +73,17 @@ config = {
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
                 "--certificate-path=%(certs_path)s",
                 "--symbols-path=%(symbols_path)s",
                 "--quiet",
                 "--log-raw=%(raw_log_file)s",
                 "--log-errorsummary=%(error_summary_file)s",
                 "--extra-profile-file=fonts",
+                "--extra-profile-file=hyphenation",
                 "--screenshot-on-fail",
             ],
         },
         "mochitest-gl": {
             "run_filename": "runtestsremote.py",
             "testsdir": "mochitest",
             "options": [
                 "--dm_trans=adb",
@@ -196,16 +197,17 @@ config = {
                 "--xre-path=%(xre_path)s",
                 "--utility-path=%(utility_path)s",
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
                 "--httpd-path", "%(modules_dir)s",
                 "--symbols-path=%(symbols_path)s",
                 "--total-chunks=16",
                 "--extra-profile-file=fonts",
+                "--extra-profile-file=hyphenation",
                 "--suite=reftest",
                 "--log-raw=%(raw_log_file)s",
                 "--log-errorsummary=%(error_summary_file)s",
             ],
             "tests": ["tests/layout/reftests/reftest.list",],
         },
         "reftest-debug": {
             "run_filename": "remotereftest.py",
@@ -218,16 +220,17 @@ config = {
                 "--xre-path=%(xre_path)s",
                 "--utility-path=%(utility_path)s",
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
                 "--httpd-path", "%(modules_dir)s",
                 "--symbols-path=%(symbols_path)s",
                 "--total-chunks=48",
                 "--extra-profile-file=fonts",
+                "--extra-profile-file=hyphenation",
                 "tests/layout/reftests/reftest.list",
             ],
         },
         "crashtest": {
             "run_filename": "remotereftest.py",
             "testsdir": "reftest",
             "options": [
                 "--app=%(app)s",
--- a/testing/mozharness/mozharness/mozilla/testing/firefox_ui_tests.py
+++ b/testing/mozharness/mozharness/mozilla/testing/firefox_ui_tests.py
@@ -42,16 +42,20 @@ firefox_ui_tests_config_options = [
         'default': 'https://github.com/mozilla/firefox-ui-tests.git',
         'help': 'which firefox_ui_tests repo to use',
     }],
     [['--symbols-path=SYMBOLS_PATH'], {
         'dest': 'symbols_path',
         'help': 'absolute path to directory containing breakpad '
                 'symbols, or the url of a zip file containing symbols.',
     }],
+    [['--tag=TAG'], {
+        'dest': 'tag',
+        'help': 'Subset of tests to run (local, remote).',
+    }],
 ] + copy.deepcopy(testing_config_options)
 
 # Command line arguments for update tests
 firefox_ui_update_harness_config_options = [
     [['--update-allow-mar-channel'], {
         'dest': 'update_allow_mar_channel',
         'help': 'Additional MAR channel to be allowed for updates, e.g. '
                 '"firefox-mozilla-beta" for updating a release build to '
@@ -255,16 +259,19 @@ class FirefoxUITests(TestingMixin, VCSTo
         env = env or self.query_env()
 
         if self.symbols_url:
             cmd.extend(['--symbols-path', self.symbols_url])
 
         if self.query_minidump_stackwalk():
             env['MINIDUMP_STACKWALK'] = self.minidump_stackwalk_path
 
+        if self.config.get('tag'):
+            cmd.extend(['--tag', self.config['tag']])
+
         parser = StructuredOutputParser(config=self.config,
                                         log_obj=self.log_obj,
                                         strict=False)
 
         # Add the default tests to run
         tests = [os.path.join(dirs['abs_fxui_dir'], 'tests', test) for test in self.default_tests]
         cmd.extend(tests)
 
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -4873,16 +4873,21 @@ MultiprocessBlockPolicy() {
 bool
 mozilla::BrowserTabsRemoteAutostart()
 {
   if (gBrowserTabsRemoteAutostartInitialized) {
     return gBrowserTabsRemoteAutostart;
   }
   gBrowserTabsRemoteAutostartInitialized = true;
 
+  // If we're in the content process, we are running E10S.
+  if (XRE_IsContentProcess()) {
+    gBrowserTabsRemoteAutostart = true;
+    return gBrowserTabsRemoteAutostart;
+  }
 
   bool optInPref = Preferences::GetBool("browser.tabs.remote.autostart", false);
   bool trialPref = Preferences::GetBool("browser.tabs.remote.autostart.2", false);
   bool prefEnabled = optInPref || trialPref;
   int status;
   if (optInPref) {
     status = kE10sEnabledByUser;
   } else if (trialPref) {
--- a/widget/TextEventDispatcher.h
+++ b/widget/TextEventDispatcher.h
@@ -89,16 +89,25 @@ public:
 
   /**
    * IsComposing() returns true after calling StartComposition() and before
    * calling CommitComposition().
    */
   bool IsComposing() const { return mIsComposing; }
 
   /**
+   * IsInNativeInputTransaction() returns true if native IME handler began a
+   * transaction and it's not finished yet.
+   */
+  bool IsInNativeInputTransaction() const
+  {
+    return mInputTransactionType == eNativeInputTransaction;
+  }
+
+  /**
    * IsDispatchingEvent() returns true while this instance dispatching an event.
    */
   bool IsDispatchingEvent() const { return mDispatchingEvent > 0; }
 
   /**
    * GetPseudoIMEContext() returns pseudo native IME context if there is an
    * input transaction whose type is not for native event handler.
    * Otherwise, returns nullptr.
--- a/widget/windows/TSFTextStore.cpp
+++ b/widget/windows/TSFTextStore.cpp
@@ -1179,24 +1179,23 @@ bool TSFTextStore::sHackQueryInsertForMS
 #define TEXTSTORE_DEFAULT_VIEW (1)
 
 TSFTextStore::TSFTextStore()
   : mEditCookie(0)
   , mSinkMask(0)
   , mLock(0)
   , mLockQueued(0)
   , mHandlingKeyMessage(0)
-  , mLockedContent(mComposition, mSelection)
+  , mContentForTSF(mComposition, mSelectionForTSF)
   , mRequestedAttrValues(false)
   , mIsRecordingActionsWithoutLock(false)
-  , mPendingOnSelectionChange(false)
   , mHasReturnedNoLayoutError(false)
   , mWaitingQueryLayout(false)
   , mPendingDestroy(false)
-  , mDeferClearingLockedContent(false)
+  , mDeferClearingContentForTSF(false)
   , mNativeCaretIsCreated(false)
   , mDeferNotifyingTSF(false)
   , mDeferCommittingComposition(false)
   , mDeferCancellingComposition(false)
   , mDestroyed(false)
   , mBeingDestroyed(false)
 {
   for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
@@ -1490,17 +1489,17 @@ TSFTextStore::RequestLock(DWORD dwLockFl
 
   if (!mSink) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
       ("TSF: 0x%p   TSFTextStore::RequestLock() FAILED due to "
        "any sink not stored", this));
     return E_FAIL;
   }
   if (mDestroyed &&
-      (!mLockedContent.IsInitialized() || mSelection.IsDirty())) {
+      (!mContentForTSF.IsInitialized() || mSelectionForTSF.IsDirty())) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
       ("TSF: 0x%p   TSFTextStore::RequestLock() FAILED due to "
        "being destroyed and no information of the contents", this));
     return E_FAIL;
   }
   if (!phrSession) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
       ("TSF: 0x%p   TSFTextStore::RequestLock() FAILED due to "
@@ -1582,17 +1581,17 @@ TSFTextStore::DidLockGranted()
     // composition update information here.
     CompleteLastActionIfStillIncomplete();
 
     FlushPendingActions();
   }
 
   // If the widget has gone, we don't need to notify anything.
   if (mDestroyed || !mWidget || mWidget->Destroyed()) {
-    mPendingOnSelectionChange = false;
+    mPendingSelectionChangeData.Clear();
     mHasReturnedNoLayoutError = false;
   }
 }
 
 void
 TSFTextStore::DispatchEvent(WidgetGUIEvent& aEvent)
 {
   if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) {
@@ -1605,21 +1604,21 @@ TSFTextStore::DispatchEvent(WidgetGUIEve
   }
   mWidget->DispatchWindowEvent(&aEvent);
 }
 
 void
 TSFTextStore::FlushPendingActions()
 {
   if (!mWidget || mWidget->Destroyed()) {
-    // Note that don't clear the locked contents because TIP may try to commit
+    // Note that don't clear mContentForTSF because TIP may try to commit
     // composition with a document lock.  In such case, TSFTextStore needs to
     // behave as expected by TIP.
     mPendingActions.Clear();
-    mPendingOnSelectionChange = false;
+    mPendingSelectionChangeData.Clear();
     mHasReturnedNoLayoutError = false;
     return;
   }
 
   RefPtr<nsWindowBase> kungFuDeathGrip(mWidget);
   nsresult rv = mDispatcher->BeginNativeInputTransaction();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
@@ -1659,97 +1658,105 @@ TSFTextStore::FlushPendingActions()
                    ("TSF: 0x%p   TSFTextStore::FlushPendingActions() "
                     "FAILED due to eSetSelection failure", this));
             break;
           }
         }
 
         // eCompositionStart always causes
         // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED.  Therefore, we should
-        // wait to clear the locked content until it's notified.
-        mDeferClearingLockedContent = true;
+        // wait to clear mContentForTSF until it's notified.
+        mDeferClearingContentForTSF = true;
 
         MOZ_LOG(sTextStoreLog, LogLevel::Debug,
                ("TSF: 0x%p   TSFTextStore::FlushPendingActions() "
                 "dispatching compositionstart event...", this));
         WidgetEventTime eventTime = mWidget->CurrentMessageWidgetEventTime();
         nsEventStatus status;
         rv = mDispatcher->StartComposition(status, &eventTime);
         if (NS_WARN_IF(NS_FAILED(rv))) {
           MOZ_LOG(sTextStoreLog, LogLevel::Error,
             ("TSF: 0x%p   TSFTextStore::FlushPendingActions() "
-             "FAILED to dispatch compositionstart event.", this));
-          mDeferClearingLockedContent = false;
+             "FAILED to dispatch compositionstart event, "
+             "IsComposingInContent()=%s",
+             this, GetBoolName(!IsComposingInContent())));
+          mDeferClearingContentForTSF = !IsComposingInContent();
         }
         if (!mWidget || mWidget->Destroyed()) {
           break;
         }
         break;
       }
       case PendingAction::COMPOSITION_UPDATE: {
         MOZ_LOG(sTextStoreLog, LogLevel::Debug,
                ("TSF: 0x%p   TSFTextStore::FlushPendingActions() "
                 "flushing COMPOSITION_UPDATE={ mData=\"%s\", "
                 "mRanges=0x%p, mRanges->Length()=%d }",
                 this, NS_ConvertUTF16toUTF8(action.mData).get(), action.mRanges.get(),
                 action.mRanges ? action.mRanges->Length() : 0));
 
         // eCompositionChange causes a DOM text event, the IME will be notified
         // of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED.  In this case, we
-        // should not clear the locked content until we notify the IME of the
+        // should not clear mContentForTSF until we notify the IME of the
         // composition update.
-        mDeferClearingLockedContent = true;
+        mDeferClearingContentForTSF = true;
 
         rv = mDispatcher->SetPendingComposition(action.mData,
                                                 action.mRanges);
         if (NS_WARN_IF(NS_FAILED(rv))) {
           MOZ_LOG(sTextStoreLog, LogLevel::Error,
             ("TSF: 0x%p   TSFTextStore::FlushPendingActions() "
-             "FAILED to setting pending composition...", this));
-          mDeferClearingLockedContent = false;
+             "FAILED to setting pending composition... "
+             "IsComposingInContent()=%s",
+             this, GetBoolName(IsComposingInContent())));
+          mDeferClearingContentForTSF = !IsComposingInContent();
         } else {
           MOZ_LOG(sTextStoreLog, LogLevel::Debug,
             ("TSF: 0x%p   TSFTextStore::FlushPendingActions() "
              "dispatching compositionchange event...", this));
           WidgetEventTime eventTime = mWidget->CurrentMessageWidgetEventTime();
           nsEventStatus status;
           rv = mDispatcher->FlushPendingComposition(status, &eventTime);
           if (NS_WARN_IF(NS_FAILED(rv))) {
             MOZ_LOG(sTextStoreLog, LogLevel::Error,
               ("TSF: 0x%p   TSFTextStore::FlushPendingActions() "
-               "FAILED to dispatch compositionchange event.", this));
-            mDeferClearingLockedContent = false;
+               "FAILED to dispatch compositionchange event, "
+               "IsComposingInContent()=%s",
+               this, GetBoolName(IsComposingInContent())));
+            mDeferClearingContentForTSF = !IsComposingInContent();
           }
           // Be aware, the mWidget might already have been destroyed.
         }
         break;
       }
       case PendingAction::COMPOSITION_END: {
         MOZ_LOG(sTextStoreLog, LogLevel::Debug,
                ("TSF: 0x%p   TSFTextStore::FlushPendingActions() "
                 "flushing COMPOSITION_END={ mData=\"%s\" }",
                 this, NS_ConvertUTF16toUTF8(action.mData).get()));
 
         // Dispatching eCompositionCommit causes a DOM text event, then,
         // the IME will be notified of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED.
-        // In this case, we should not clear the locked content until we notify
+        // In this case, we should not clear mContentForTSFuntil we notify
         // the IME of the composition update.
-        mDeferClearingLockedContent = true;
+        mDeferClearingContentForTSF = true;
 
         MOZ_LOG(sTextStoreLog, LogLevel::Debug,
                ("TSF: 0x%p   TSFTextStore::FlushPendingActions(), "
                 "dispatching compositioncommit event...", this));
         WidgetEventTime eventTime = mWidget->CurrentMessageWidgetEventTime();
         nsEventStatus status;
         rv = mDispatcher->CommitComposition(status, &action.mData, &eventTime);
         if (NS_WARN_IF(NS_FAILED(rv))) {
           MOZ_LOG(sTextStoreLog, LogLevel::Error,
             ("TSF: 0x%p   TSFTextStore::FlushPendingActions() "
-             "FAILED to dispatch compositioncommit event.", this));
-          mDeferClearingLockedContent = false;
+             "FAILED to dispatch compositioncommit event, "
+             "IsComposingInContent()=%s",
+             this, GetBoolName(IsComposingInContent())));
+          mDeferClearingContentForTSF = !IsComposingInContent();
         }
         break;
       }
       case PendingAction::SET_SELECTION: {
         MOZ_LOG(sTextStoreLog, LogLevel::Debug,
                ("TSF: 0x%p   TSFTextStore::FlushPendingActions() "
                 "flushing SET_SELECTION={ mSelectionStart=%d, "
                 "mSelectionLength=%d, mSelectionReversed=%s }, "
@@ -1832,36 +1839,46 @@ TSFTextStore::MaybeFlushPendingNotificat
     // If it's already been destroyed completely, this shouldn't notify TSF of
     // anything anymore.
     MOZ_LOG(sTextStoreLog, LogLevel::Debug,
       ("TSF: 0x%p   TSFTextStore::MaybeFlushPendingNotifications(), "
        "does nothing because this has already destroyed completely...", this));
     return;
   }
 
-  if (!mDeferClearingLockedContent && mLockedContent.IsInitialized()) {
-    mLockedContent.Clear();
+  if (!mDeferClearingContentForTSF && mContentForTSF.IsInitialized()) {
+    mContentForTSF.Clear();
     MOZ_LOG(sTextStoreLog, LogLevel::Debug,
            ("TSF: 0x%p   TSFTextStore::MaybeFlushPendingNotifications(), "
-            "mLockedContent is cleared", this));
+            "mContentForTSF is cleared", this));
+  }
+
+  // When there is no cached content, we can sync actual contents and TSF/TIP
+  // expecting contents.
+  if (!mContentForTSF.IsInitialized()) {
+    if (mPendingTextChangeData.IsValid()) {
+      MOZ_LOG(sTextStoreLog, LogLevel::Info,
+             ("TSF: 0x%p   TSFTextStore::MaybeFlushPendingNotifications(), "
+              "calling TSFTextStore::NotifyTSFOfTextChange()...", this));
+      NotifyTSFOfTextChange();
+    }
+    if (mPendingSelectionChangeData.IsValid()) {
+      MOZ_LOG(sTextStoreLog, LogLevel::Info,
+             ("TSF: 0x%p   TSFTextStore::MaybeFlushPendingNotifications(), "
+              "calling TSFTextStore::NotifyTSFOfSelectionChange()...", this));
+      NotifyTSFOfSelectionChange();
+    }
   }
 
   if (mHasReturnedNoLayoutError) {
     MOZ_LOG(sTextStoreLog, LogLevel::Info,
            ("TSF: 0x%p   TSFTextStore::MaybeFlushPendingNotifications(), "
             "calling TSFTextStore::NotifyTSFOfLayoutChange()...", this));
     NotifyTSFOfLayoutChange();
   }
-
-  if (mPendingOnSelectionChange) {
-    MOZ_LOG(sTextStoreLog, LogLevel::Info,
-           ("TSF: 0x%p   TSFTextStore::MaybeFlushPendingNotifications(), "
-            "calling TSFTextStore::NotifyTSFOfSelectionChange()...", this));
-    NotifyTSFOfSelectionChange();
-  }
 }
 
 STDMETHODIMP
 TSFTextStore::GetStatus(TS_STATUS* pdcs)
 {
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
     ("TSF: 0x%p TSFTextStore::GetStatus(pdcs=0x%p)", this, pdcs));
 
@@ -1958,93 +1975,106 @@ TSFTextStore::GetSelection(ULONG ulIndex
   if (ulIndex != static_cast<ULONG>(TS_DEFAULT_SELECTION) &&
       ulIndex != 0) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetSelection() FAILED due to "
             "unsupported selection", this));
     return TS_E_NOSELECTION;
   }
 
-  Selection& currentSel = CurrentSelection();
-  if (currentSel.IsDirty()) {
+  Selection& selectionForTSF = SelectionForTSFRef();
+  if (selectionForTSF.IsDirty()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetSelection() FAILED due to "
-            "CurrentSelection() failure", this));
+            "SelectionForTSFRef() failure", this));
     return E_FAIL;
   }
-  *pSelection = currentSel.ACP();
+  *pSelection = selectionForTSF.ACP();
   *pcFetched = 1;
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
          ("TSF: 0x%p   TSFTextStore::GetSelection() succeeded", this));
   return S_OK;
 }
 
+bool
+TSFTextStore::IsComposingInContent() const
+{
+  if (!mDispatcher) {
+    return false;
+  }
+  if (!mDispatcher->IsInNativeInputTransaction()) {
+    return false;
+  }
+  return mDispatcher->IsComposing();
+}
+
 TSFTextStore::Content&
-TSFTextStore::LockedContent()
+TSFTextStore::ContentForTSFRef()
 {
   // This should be called when the document is locked or the content hasn't
   // been abandoned yet.
-  if (NS_WARN_IF(!IsReadLocked() && !mLockedContent.IsInitialized())) {
+  if (NS_WARN_IF(!IsReadLocked() && !mContentForTSF.IsInitialized())) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
-           ("TSF: 0x%p   TSFTextStore::LockedContent(), FAILED, due to "
+           ("TSF: 0x%p   TSFTextStore::ContentForTSFRef(), FAILED, due to "
             "called wrong timing, IsReadLocked()=%s, "
-            "mLockedContent.IsInitialized()=%s",
+            "mContentForTSF.IsInitialized()=%s",
             this, GetBoolName(IsReadLocked()),
-            GetBoolName(mLockedContent.IsInitialized())));
-    mLockedContent.Clear();
-    return mLockedContent;
-  }
-
-  Selection& currentSel = CurrentSelection();
-  if (currentSel.IsDirty()) {
+            GetBoolName(mContentForTSF.IsInitialized())));
+    mContentForTSF.Clear();
+    return mContentForTSF;
+  }
+
+  Selection& selectionForTSF = SelectionForTSFRef();
+  if (selectionForTSF.IsDirty()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
-           ("TSF: 0x%p   TSFTextStore::LockedContent(), FAILED, due to "
-            "CurrentSelection() failure", this));
-    mLockedContent.Clear();
-    return mLockedContent;
-  }
-
-  if (!mLockedContent.IsInitialized()) {
+           ("TSF: 0x%p   TSFTextStore::ContentForTSFRef(), FAILED, due to "
+            "SelectionForTSFRef() failure", this));
+    mContentForTSF.Clear();
+    return mContentForTSF;
+  }
+
+  if (!mContentForTSF.IsInitialized()) {
     nsAutoString text;
     if (NS_WARN_IF(!GetCurrentText(text))) {
       MOZ_LOG(sTextStoreLog, LogLevel::Error,
-             ("TSF: 0x%p   TSFTextStore::LockedContent(), FAILED, due to "
+             ("TSF: 0x%p   TSFTextStore::ContentForTSFRef(), FAILED, due to "
               "GetCurrentText() failure", this));
-      mLockedContent.Clear();
-      return mLockedContent;
+      mContentForTSF.Clear();
+      return mContentForTSF;
     }
 
-    mLockedContent.Init(text);
-    // Basically, the locked content should be cleared after the document is
+    mContentForTSF.Init(text);
+    // Basically, the cached content which is expected by TSF/TIP should be
+    // cleared after active composition is committed or the document lock is
     // unlocked.  However, in e10s mode, content will be modified
-    // asynchronously.  In such case, mDeferClearingLockedContent may be
-    // true even after the document is unlocked.
-    mDeferClearingLockedContent = false;
+    // asynchronously.  In such case, mDeferClearingContentForTSF may be
+    // true until whole dispatched events are handled by the focused editor.
+    mDeferClearingContentForTSF = false;
   }
 
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
-         ("TSF: 0x%p   TSFTextStore::LockedContent(): "
-          "mLockedContent={ mText=\"%s\" (Length()=%u), "
+         ("TSF: 0x%p   TSFTextStore::ContentForTSFRef(): "
+          "mContentForTSF={ mText=\"%s\" (Length()=%u), "
           "mLastCompositionString=\"%s\" (Length()=%u), "
           "mMinTextModifiedOffset=%u }",
-          this, mLockedContent.Text().Length() <= 20 ?
-            NS_ConvertUTF16toUTF8(mLockedContent.Text()).get() : "<omitted>",
-          mLockedContent.Text().Length(),
-          NS_ConvertUTF16toUTF8(mLockedContent.LastCompositionString()).get(),
-          mLockedContent.LastCompositionString().Length(),
-          mLockedContent.MinTextModifiedOffset()));
-
-  return mLockedContent;
+          this, mContentForTSF.Text().Length() <= 20 ?
+            NS_ConvertUTF16toUTF8(mContentForTSF.Text()).get() : "<omitted>",
+          mContentForTSF.Text().Length(),
+          NS_ConvertUTF16toUTF8(mContentForTSF.LastCompositionString()).get(),
+          mContentForTSF.LastCompositionString().Length(),
+          mContentForTSF.MinTextModifiedOffset()));
+
+  return mContentForTSF;
 }
 
 bool
 TSFTextStore::GetCurrentText(nsAString& aTextContent)
 {
-  if (mLockedContent.IsInitialized()) {
-    aTextContent = mLockedContent.Text();
+  if (mContentForTSF.IsInitialized()) {
+    aTextContent = mContentForTSF.Text();
     return true;
   }
 
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(mWidget && !mWidget->Destroyed());
 
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::GetCurrentText(): "
@@ -2062,45 +2092,47 @@ TSFTextStore::GetCurrentText(nsAString& 
     return false;
   }
 
   aTextContent = queryText.mReply.mString;
   return true;
 }
 
 TSFTextStore::Selection&
-TSFTextStore::CurrentSelection()
+TSFTextStore::SelectionForTSFRef()
 {
-  if (mSelection.IsDirty()) {
+  if (mSelectionForTSF.IsDirty()) {
     MOZ_ASSERT(!mDestroyed);
     // If the window has never been available, we should crash since working
     // with broken values may make TIP confused.
     if (!mWidget || mWidget->Destroyed()) {
       MOZ_CRASH();
     }
 
     WidgetQueryContentEvent querySelection(true, eQuerySelectedText, mWidget);
     mWidget->InitEvent(querySelection);
     DispatchEvent(querySelection);
-    NS_ENSURE_TRUE(querySelection.mSucceeded, mSelection);
-
-    mSelection.SetSelection(querySelection.mReply.mOffset,
-                            querySelection.mReply.mString.Length(),
-                            querySelection.mReply.mReversed,
-                            querySelection.GetWritingMode());
+    if (NS_WARN_IF(!querySelection.mSucceeded)) {
+      return mSelectionForTSF;
+    }
+
+    mSelectionForTSF.SetSelection(querySelection.mReply.mOffset,
+                                  querySelection.mReply.mString.Length(),
+                                  querySelection.mReply.mReversed,
+                                  querySelection.GetWritingMode());
   }
 
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
-         ("TSF: 0x%p   TSFTextStore::CurrentSelection(): "
+         ("TSF: 0x%p   TSFTextStore::SelectionForTSFRef(): "
           "acpStart=%d, acpEnd=%d (length=%d), reverted=%s",
-          this, mSelection.StartOffset(), mSelection.EndOffset(),
-          mSelection.Length(),
-          GetBoolName(mSelection.IsReversed())));
-
-  return mSelection;
+          this, mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
+          mSelectionForTSF.Length(),
+          GetBoolName(mSelectionForTSF.IsReversed())));
+
+  return mSelectionForTSF;
 }
 
 static HRESULT
 GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength)
 {
   RefPtr<ITfRangeACP> rangeACP;
   aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP));
   NS_ENSURE_TRUE(rangeACP, E_FAIL);
@@ -2260,36 +2292,36 @@ TSFTextStore::RestartCompositionIfNecess
           this));
   return S_OK;
 }
 
 HRESULT
 TSFTextStore::RestartComposition(ITfCompositionView* aCompositionView,
                                  ITfRange* aNewRange)
 {
-  Selection& currentSelection = CurrentSelection();
+  Selection& selectionForTSF = SelectionForTSFRef();
 
   LONG newStart, newLength;
   HRESULT hr = GetRangeExtent(aNewRange, &newStart, &newLength);
   LONG newEnd = newStart + newLength;
 
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::RestartComposition(aCompositionView=0x%p, "
           "aNewRange=0x%p { newStart=%d, newLength=%d }), "
           "mComposition={ mStart=%d, mCompositionString.Length()=%d }, "
-          "currentSelection={ IsDirty()=%s, StartOffset()=%d, Length()=%d }",
+          "selectionForTSF={ IsDirty()=%s, StartOffset()=%d, Length()=%d }",
           this, aCompositionView, aNewRange, newStart, newLength,
           mComposition.mStart, mComposition.mString.Length(),
-          GetBoolName(currentSelection.IsDirty()),
-          currentSelection.StartOffset(), currentSelection.Length()));
-
-  if (currentSelection.IsDirty()) {
+          GetBoolName(selectionForTSF.IsDirty()),
+          selectionForTSF.StartOffset(), selectionForTSF.Length()));
+
+  if (selectionForTSF.IsDirty()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::RestartComposition() FAILED "
-            "due to CurrentSelection() failure", this));
+            "due to SelectionForTSFRef() failure", this));
     return E_FAIL;
   }
 
   if (FAILED(hr)) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::RestartComposition() FAILED "
             "due to GetRangeExtent() failure", this));
     return hr;
@@ -2306,37 +2338,37 @@ TSFTextStore::RestartComposition(ITfComp
 
   // If the new range has an overlap with the current one, we should not commit
   // the whole current range to avoid creating an odd undo transaction.
   // I.e., the overlapped range which is being composed should not appear in
   // undo transaction.
 
   // Backup current composition data and selection data.
   Composition oldComposition = mComposition;
-  Selection oldSelection = currentSelection;
+  Selection oldSelection = selectionForTSF;
 
   // Commit only the part of composition.
   LONG keepComposingStartOffset = std::max(mComposition.mStart, newStart);
   LONG keepComposingEndOffset = std::min(mComposition.EndOffset(), newEnd);
   MOZ_ASSERT(keepComposingStartOffset <= keepComposingEndOffset,
     "Why keepComposingEndOffset is smaller than keepComposingStartOffset?");
   LONG keepComposingLength = keepComposingEndOffset - keepComposingStartOffset;
   // Remove the overlapped part from the commit string.
   nsAutoString commitString(mComposition.mString);
   commitString.Cut(keepComposingStartOffset - mComposition.mStart,
                    keepComposingLength);
   // Update the composition string.
-  Content& lockedContent = LockedContent();
-  if (!lockedContent.IsInitialized()) {
+  Content& contentForTSF = ContentForTSFRef();
+  if (!contentForTSF.IsInitialized()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::RestartComposition() FAILED "
-            "due to LockedContent() failure", this));
+            "due to ContentForTSFRef() failure", this));
     return E_FAIL;
   }
-  lockedContent.ReplaceTextWith(mComposition.mStart,
+  contentForTSF.ReplaceTextWith(mComposition.mStart,
                                 mComposition.mString.Length(),
                                 commitString);
   // Record a compositionupdate action for commit the part of composing string.
   PendingAction* action = LastOrNewPendingCompositionUpdate();
   action->mData = mComposition.mString;
   action->mRanges->Clear();
   TextRange caretRange;
   caretRange.mStartOffset = caretRange.mEndOffset =
@@ -2348,29 +2380,29 @@ TSFTextStore::RestartComposition(ITfComp
   // Record compositionend action.
   RecordCompositionEndAction();
 
   // Record compositionstart action only with the new start since this method
   // hasn't restored composing string yet.
   RecordCompositionStartAction(aCompositionView, newStart, 0, false);
 
   // Restore the latest text content and selection.
-  lockedContent.ReplaceSelectedTextWith(
+  contentForTSF.ReplaceSelectedTextWith(
     nsDependentSubstring(oldComposition.mString,
                          keepComposingStartOffset - oldComposition.mStart,
                          keepComposingLength));
-  currentSelection = oldSelection;
+  selectionForTSF = oldSelection;
 
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::RestartComposition() succeeded, "
           "mComposition={ mStart=%d, mCompositionString.Length()=%d }, "
-          "currentSelection={ IsDirty()=%s, StartOffset()=%d, Length()=%d }",
+          "selectionForTSF={ IsDirty()=%s, StartOffset()=%d, Length()=%d }",
           this, mComposition.mStart, mComposition.mString.Length(),
-          GetBoolName(currentSelection.IsDirty()),
-          currentSelection.StartOffset(), currentSelection.Length()));
+          GetBoolName(selectionForTSF.IsDirty()),
+          selectionForTSF.StartOffset(), selectionForTSF.Length()));
 
   return S_OK;
 }
 
 static bool
 GetColor(const TF_DA_COLOR& aTSFColor, nscolor& aResult)
 {
   switch (aTSFColor.type) {
@@ -2465,21 +2497,21 @@ TSFTextStore::RecordCompositionUpdateAct
   if (FAILED(hr) || !enumRanges) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::RecordCompositionUpdateAction() FAILED "
             "due to attrPropetry->EnumRanges() failure", this));
     return FAILED(hr) ? hr : E_FAIL;
   }
 
   // First, put the log of content and selection here.
-  Selection& currentSel = CurrentSelection();
-  if (currentSel.IsDirty()) {
+  Selection& selectionForTSF = SelectionForTSFRef();
+  if (selectionForTSF.IsDirty()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::RecordCompositionUpdateAction() FAILED "
-            "due to CurrentSelection() failure", this));
+            "due to SelectionForTSFRef() failure", this));
     return E_FAIL;
   }
 
   PendingAction* action = LastOrNewPendingCompositionUpdate();
   action->mData = mComposition.mString;
   // The ranges might already have been initialized, however, if this is
   // called again, that means we need to overwrite the ranges with current
   // information.
@@ -2571,32 +2603,32 @@ TSFTextStore::RecordCompositionUpdateAct
   // We need to hack for Korean Input System which is Korean standard TIP.
   // It sets no change style to IME selection (the selection is always only
   // one).  So, the composition string looks like normal (or committed) string.
   // At this time, current selection range is same as the composition string
   // range.  Other applications set a wide caret which covers the composition
   // string,  however, Gecko doesn't support the wide caret drawing now (Gecko
   // doesn't support XOR drawing), unfortunately.  For now, we should change
   // the range style to undefined.
-  if (!currentSel.IsCollapsed() && action->mRanges->Length() == 1) {
+  if (!selectionForTSF.IsCollapsed() && action->mRanges->Length() == 1) {
     TextRange& range = action->mRanges->ElementAt(0);
-    LONG start = currentSel.MinOffset();
-    LONG end = currentSel.MaxOffset();
+    LONG start = selectionForTSF.MinOffset();
+    LONG end = selectionForTSF.MaxOffset();
     if ((LONG)range.mStartOffset == start - mComposition.mStart &&
         (LONG)range.mEndOffset == end - mComposition.mStart &&
         range.mRangeStyle.IsNoChangeStyle()) {
       range.mRangeStyle.Clear();
       // The looks of selected type is better than others.
       range.mRangeType = TextRangeType::eSelectedRawClause;
     }
   }
 
   // The caret position has to be collapsed.
   uint32_t caretPosition =
-    static_cast<uint32_t>(currentSel.MaxOffset() - mComposition.mStart);
+    static_cast<uint32_t>(selectionForTSF.MaxOffset() - mComposition.mStart);
 
   // If caret is in the target clause and it doesn't have specific style,
   // the target clause will be painted as normal selection range.  Since caret
   // shouldn't be in selection range on Windows, we shouldn't append caret
   // range in such case.
   const TextRange* targetClause = action->mRanges->GetTargetClause();
   if (!targetClause || targetClause->mRangeStyle.IsDefined() ||
       caretPosition < targetClause->mStartOffset ||
@@ -2627,24 +2659,35 @@ TSFTextStore::SetSelectionInternal(const
           this, pSelection->acpStart, pSelection->acpEnd,
           GetActiveSelEndName(pSelection->style.ase),
           GetBoolName(pSelection->style.fInterimChar),
           GetBoolName(aDispatchCompositionChangeEvent),
           GetBoolName(mComposition.IsComposing())));
 
   MOZ_ASSERT(IsReadWriteLocked());
 
-  Selection& currentSel = CurrentSelection();
-  if (currentSel.IsDirty()) {
+  Selection& selectionForTSF = SelectionForTSFRef();
+  if (selectionForTSF.IsDirty()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
        ("TSF: 0x%p   TSFTextStore::SetSelectionInternal() FAILED due to "
-        "CurrentSelection() failure", this));
+        "SelectionForTSFRef() failure", this));
     return E_FAIL;
   }
 
+  // If actually the range is not changing, we should do nothing.
+  // Perhaps, we can ignore the difference change because it must not be
+  // important for following edit.
+  if (selectionForTSF.EqualsExceptDirection(*pSelection)) {
+    MOZ_LOG(sTextStoreLog, LogLevel::Error,
+       ("TSF: 0x%p   TSFTextStore::SetSelectionInternal() Succeeded but "
+        "did nothing because the selection range isn't changing", this));
+    selectionForTSF.SetSelection(*pSelection);
+    return S_OK;
+  }
+
   if (mComposition.IsComposing()) {
     if (aDispatchCompositionChangeEvent) {
       HRESULT hr = RestartCompositionIfNecessary();
       if (FAILED(hr)) {
         MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::SetSelectionInternal() FAILED due to "
             "RestartCompositionIfNecessary() failure", this));
         return hr;
@@ -2653,37 +2696,74 @@ TSFTextStore::SetSelectionInternal(const
     if (pSelection->acpStart < mComposition.mStart ||
         pSelection->acpEnd > mComposition.EndOffset()) {
       MOZ_LOG(sTextStoreLog, LogLevel::Error,
          ("TSF: 0x%p   TSFTextStore::SetSelectionInternal() FAILED due to "
           "the selection being out of the composition string", this));
       return TS_E_INVALIDPOS;
     }
     // Emulate selection during compositions
-    currentSel.SetSelection(*pSelection);
+    selectionForTSF.SetSelection(*pSelection);
     if (aDispatchCompositionChangeEvent) {
       HRESULT hr = RecordCompositionUpdateAction();
       if (FAILED(hr)) {
         MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::SetSelectionInternal() FAILED due to "
             "RecordCompositionUpdateAction() failure", this));
         return hr;
       }
     }
     return S_OK;
   }
 
+  TS_SELECTION_ACP selectionInContent(*pSelection);
+
+  // If mContentForTSF caches old contents which is now different from
+  // actual contents, we need some complicated hack here...
+  // Note that this hack assumes that this is used for reconversion.
+  if (mContentForTSF.IsInitialized() &&
+      mPendingTextChangeData.IsValid() &&
+      !mPendingTextChangeData.mCausedOnlyByComposition) {
+    uint32_t startOffset = static_cast<uint32_t>(selectionInContent.acpStart);
+    uint32_t endOffset = static_cast<uint32_t>(selectionInContent.acpEnd);
+    if (mPendingTextChangeData.mStartOffset >= endOffset) {
+      // Setting selection before any changed ranges is fine.
+    } else if (mPendingTextChangeData.mRemovedEndOffset <= startOffset) {
+      // Setting selection after removed range is fine with following
+      // adjustment.
+      selectionInContent.acpStart += mPendingTextChangeData.Difference();
+      selectionInContent.acpEnd += mPendingTextChangeData.Difference();
+    } else if (startOffset == endOffset) {
+      // Moving caret position may be fine in most cases even if the insertion
+      // point has already gone but in this case, composition will be inserted
+      // to unexpected position, though.
+      // It seems that moving caret into middle of the new text is odd.
+      // Perhaps, end of it is expected by users in most cases.
+      selectionInContent.acpStart = mPendingTextChangeData.mAddedEndOffset;
+      selectionInContent.acpEnd = selectionInContent.acpStart;
+    } else {
+      // Otherwise, i.e., setting range has already gone, we cannot set
+      // selection properly.
+      MOZ_LOG(sTextStoreLog, LogLevel::Error,
+         ("TSF: 0x%p   TSFTextStore::SetSelectionInternal() FAILED due to "
+          "there is unknown content change", this));
+      return E_FAIL;
+    }
+  }
+
   CompleteLastActionIfStillIncomplete();
   PendingAction* action = mPendingActions.AppendElement();
   action->mType = PendingAction::SET_SELECTION;
-  action->mSelectionStart = pSelection->acpStart;
-  action->mSelectionLength = pSelection->acpEnd - pSelection->acpStart;
-  action->mSelectionReversed = (pSelection->style.ase == TS_AE_START);
-
-  currentSel.SetSelection(*pSelection);
+  action->mSelectionStart = selectionInContent.acpStart;
+  action->mSelectionLength =
+    selectionInContent.acpEnd - selectionInContent.acpStart;
+  action->mSelectionReversed = (selectionInContent.style.ase == TS_AE_START);
+
+  // Use TSF specified selection for updating mSelectionForTSF.
+  selectionForTSF.SetSelection(*pSelection);
 
   return S_OK;
 }
 
 STDMETHODIMP
 TSFTextStore::SetSelection(ULONG ulCount,
                            const TS_SELECTION_ACP* pSelection)
 {
@@ -2777,46 +2857,46 @@ TSFTextStore::GetText(LONG acpStart,
   if (pchPlain && cchPlainReq) *pchPlain = 0;
   if (pulRunInfoOut) *pulRunInfoOut = 0;
   if (pacpNext) *pacpNext = acpStart;
   if (prgRunInfo && ulRunInfoReq) {
     prgRunInfo->uCount = 0;
     prgRunInfo->type = TS_RT_PLAIN;
   }
 
-  Content& lockedContent = LockedContent();
-  if (!lockedContent.IsInitialized()) {
+  Content& contentForTSF = ContentForTSFRef();
+  if (!contentForTSF.IsInitialized()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetText() FAILED due to "
-            "LockedContent() failure", this));
+            "ContentForTSFRef() failure", this));
     return E_FAIL;
   }
-  if (lockedContent.Text().Length() < static_cast<uint32_t>(acpStart)) {
+  if (contentForTSF.Text().Length() < static_cast<uint32_t>(acpStart)) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetText() FAILED due to "
             "acpStart is larger offset than the actual text length", this));
     return TS_E_INVALIDPOS;
   }
   if (acpEnd != -1 &&
-      lockedContent.Text().Length() < static_cast<uint32_t>(acpEnd)) {
+      contentForTSF.Text().Length() < static_cast<uint32_t>(acpEnd)) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetText() FAILED due to "
             "acpEnd is larger offset than the actual text length", this));
     return TS_E_INVALIDPOS;
   }
   uint32_t length = (acpEnd == -1) ?
-    lockedContent.Text().Length() - static_cast<uint32_t>(acpStart) :
+    contentForTSF.Text().Length() - static_cast<uint32_t>(acpStart) :
     static_cast<uint32_t>(acpEnd - acpStart);
   if (cchPlainReq && cchPlainReq - 1 < length) {
     length = cchPlainReq - 1;
   }
   if (length) {
     if (pchPlain && cchPlainReq) {
       const char16_t* startChar =
-        lockedContent.Text().BeginReading() + acpStart;
+        contentForTSF.Text().BeginReading() + acpStart;
       memcpy(pchPlain, startChar, length * sizeof(*pchPlain));
       pchPlain[length] = 0;
       *pcchPlainOut = length;
     }
     if (prgRunInfo && ulRunInfoReq) {
       prgRunInfo->uCount = length;
       prgRunInfo->type = TS_RT_PLAIN;
       if (pulRunInfoOut) *pulRunInfoOut = 1;
@@ -3184,28 +3264,28 @@ TSFTextStore::RetrieveRequestedAttrs(ULO
       switch (i) {
         case eInputScope: {
           paAttrVals[count].varValue.vt = VT_UNKNOWN;
           RefPtr<IUnknown> inputScope = new InputScopeImpl(mInputScopes);
           paAttrVals[count].varValue.punkVal = inputScope.forget().take();
           break;
         }
         case eTextVerticalWriting: {
-          Selection& currentSelection = CurrentSelection();
+          Selection& selectionForTSF = SelectionForTSFRef();
           paAttrVals[count].varValue.vt = VT_BOOL;
           paAttrVals[count].varValue.boolVal =
-            currentSelection.GetWritingMode().IsVertical()
-            ? VARIANT_TRUE : VARIANT_FALSE;
+            selectionForTSF.GetWritingMode().IsVertical() ? VARIANT_TRUE :
+                                                            VARIANT_FALSE;
           break;
         }
         case eTextOrientation: {
-          Selection& currentSelection = CurrentSelection();
+          Selection& selectionForTSF = SelectionForTSFRef();
           paAttrVals[count].varValue.vt = VT_I4;
           paAttrVals[count].varValue.lVal =
-            currentSelection.GetWritingMode().IsVertical() ? 2700 : 0;
+            selectionForTSF.GetWritingMode().IsVertical() ? 2700 : 0;
           break;
         }
         default:
           MOZ_CRASH("Invalid index? Or not implemented yet?");
           break;
       }
     }
     count++;
@@ -3243,24 +3323,24 @@ TSFTextStore::GetEndACP(LONG* pacp)
 
   if (!pacp) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetEndACP() FAILED due to "
             "null argument", this));
     return E_INVALIDARG;
   }
 
-  Content& lockedContent = LockedContent();
-  if (!lockedContent.IsInitialized()) {
+  Content& contentForTSF = ContentForTSFRef();
+  if (!contentForTSF.IsInitialized()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetEndACP() FAILED due to "
-            "LockedContent() failure", this));
+            "ContentForTSFRef() failure", this));
     return E_FAIL;
   }
-  *pacp = static_cast<LONG>(lockedContent.Text().Length());
+  *pacp = static_cast<LONG>(contentForTSF.Text().Length());
   return S_OK;
 }
 
 STDMETHODIMP
 TSFTextStore::GetActiveView(TsViewCookie* pvcView)
 {
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
          ("TSF: 0x%p TSFTextStore::GetActiveView(pvcView=0x%p)",
@@ -3320,17 +3400,17 @@ TSFTextStore::GetACPFromPoint(TsViewCook
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetACPFromPoint() FAILED due to "
             "null pacp", this));
     return E_INVALIDARG;
   }
 
   mWaitingQueryLayout = false;
 
-  if (mDestroyed || mLockedContent.IsLayoutChanged()) {
+  if (mDestroyed || mContentForTSF.IsLayoutChanged()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetACPFromPoint() returned "
             "TS_E_NOLAYOUT", this));
     mHasReturnedNoLayoutError = true;
     return TS_E_NOLAYOUT;
   }
 
   LayoutDeviceIntPoint ourPt(pt->x, pt->y);
@@ -3402,27 +3482,27 @@ TSFTextStore::GetACPFromPoint(TsViewCook
     //     the other is after the offset, we could resolve the offset.
     //     However, dispatching 2 eQueryTextRect may be expensive.
 
     // So, use tentative offset for now.
     offset = charAtPt.mReply.mTentativeCaretOffset;
 
     // However, if it's after the last character, we need to decrement the
     // offset.
-    Content& lockedContent = LockedContent();
-    if (!lockedContent.IsInitialized()) {
+    Content& contentForTSF = ContentForTSFRef();
+    if (!contentForTSF.IsInitialized()) {
       MOZ_LOG(sTextStoreLog, LogLevel::Error,
              ("TSF: 0x%p   TSFTextStore::GetACPFromPoint() FAILED due to "
-              "LockedContent() failure", this));
+              "ContentForTSFRef() failure", this));
       return E_FAIL;
     }
-    if (lockedContent.Text().Length() <= offset) {
+    if (contentForTSF.Text().Length() <= offset) {
       // If the tentative caret is after the last character, let's return
       // the last character's offset.
-      offset = lockedContent.Text().Length() - 1;
+      offset = contentForTSF.Text().Length() - 1;
     }
   }
 
   if (NS_WARN_IF(offset > LONG_MAX)) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetACPFromPoint() FAILED due to out of "
             "range of the result", this));
     return TS_E_INVALIDPOINT;
@@ -3480,18 +3560,18 @@ TSFTextStore::GetTextExt(TsViewCookie vc
   mWaitingQueryLayout = false;
 
   // NOTE: TSF (at least on Win 8.1) doesn't return TS_E_NOLAYOUT to the
   // caller even if we return it.  It's converted to just E_FAIL.
   // However, this is fixed on Win 10.
 
   const TSFStaticSink* kSink = TSFStaticSink::GetInstance();
   if (mComposition.IsComposing() && mComposition.mStart < acpEnd &&
-      mLockedContent.IsLayoutChangedAfter(acpEnd)) {
-    const Selection& currentSel = CurrentSelection();
+      mContentForTSF.IsLayoutChangedAfter(acpEnd)) {
+    const Selection& selectionForTSF = SelectionForTSFRef();
     // The bug of Microsoft Office IME 2010 for Japanese is similar to
     // MS-IME for Win 8.1 and Win 10.  Newer version of MS Office IME is not
     // released yet.  So, we can hack it without prefs  because there must be
     // no developers who want to disable this hack for tests.
     const bool kIsMSOfficeJapaneseIME2010 =
       kSink->IsMSOfficeJapaneseIME2010Active();
     // MS IME for Japanese doesn't support asynchronous handling at deciding
     // its suggest list window position.  The feature was implemented
@@ -3504,34 +3584,35 @@ TSFTextStore::GetTextExt(TsViewCookie vc
          kSink->IsMSJapaneseIMEActive())) {
       // Basically, MS-IME tries to retrieve whole composition string rect
       // at deciding suggest window immediately after unlocking the document.
       // However, in e10s mode, the content hasn't updated yet in most cases.
       // Therefore, if the first character at the retrieving range rect is
       // available, we should use it as the result.
       if ((kIsMSOfficeJapaneseIME2010 ||
            sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar) &&
-          !mLockedContent.IsLayoutChangedAfter(acpStart) &&
+          !mContentForTSF.IsLayoutChangedAfter(acpStart) &&
           acpStart < acpEnd) {
         acpEnd = acpStart;
         MOZ_LOG(sTextStoreLog, LogLevel::Debug,
                ("TSF: 0x%p   TSFTextStore::GetTextExt() hacked the offsets "
                 "of the first character of changing range of the composition "
                 "string for TIP acpStart=%d, acpEnd=%d",
                 this, acpStart, acpEnd));
       }
       // Although, the condition is not clear, MS-IME sometimes retrieves the
       // caret rect immediately after modifying the composition string but
       // before unlocking the document.  In such case, we should return the
       // nearest character rect.
       else if ((kIsMSOfficeJapaneseIME2010 ||
                 sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret) &&
                acpStart == acpEnd &&
-               currentSel.IsCollapsed() && currentSel.EndOffset() == acpEnd) {
-        acpEnd = acpStart = mLockedContent.MinOffsetOfLayoutChanged();
+               selectionForTSF.IsCollapsed() &&
+               selectionForTSF.EndOffset() == acpEnd) {
+        acpEnd = acpStart = mContentForTSF.MinOffsetOfLayoutChanged();
         MOZ_LOG(sTextStoreLog, LogLevel::Debug,
                ("TSF: 0x%p   TSFTextStore::GetTextExt() hacked the offsets "
                 "of the caret of the composition string for TIP acpStart=%d, "
                 "acpEnd=%d", this, acpStart, acpEnd));
       }
     }
     // Free ChangJie 2010 and Easy Changjei 1.0.12.0 doesn't handle
     // ITfContextView::GetTextExt() properly.  Prehaps, it's due to the bug of
@@ -3559,17 +3640,17 @@ TSFTextStore::GetTextExt(TsViewCookie vc
       acpEnd = mComposition.mStart;
       acpStart = std::min(acpStart, acpEnd);
       MOZ_LOG(sTextStoreLog, LogLevel::Debug,
              ("TSF: 0x%p   TSFTextStore::GetTextExt() hacked the offsets for "
               "TIP acpStart=%d, acpEnd=%d", this, acpStart, acpEnd));
     }
   }
 
-  if (mLockedContent.IsLayoutChangedAfter(acpEnd)) {
+  if (mContentForTSF.IsLayoutChangedAfter(acpEnd)) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
             "(acpEnd=%d)", this, acpEnd));
     mHasReturnedNoLayoutError = true;
     return TS_E_NOLAYOUT;
   }
 
   if (mDestroyed) {
@@ -3834,31 +3915,32 @@ TSFTextStore::InsertTextAtSelection(DWOR
     if (!pacpStart || !pacpEnd) {
       MOZ_LOG(sTextStoreLog, LogLevel::Error,
              ("TSF: 0x%p   TSFTextStore::InsertTextAtSelection() FAILED due to "
               "null argument", this));
       return E_INVALIDARG;
     }
 
     // Get selection first
-    Selection& currentSel = CurrentSelection();
-    if (currentSel.IsDirty()) {
+    Selection& selectionForTSF = SelectionForTSFRef();
+    if (selectionForTSF.IsDirty()) {
       MOZ_LOG(sTextStoreLog, LogLevel::Error,
              ("TSF: 0x%p   TSFTextStore::InsertTextAtSelection() FAILED due to "
-              "CurrentSelection() failure", this));
+              "SelectionForTSFRef() failure", this));
       return E_FAIL;
     }
 
     // Simulate text insertion
-    *pacpStart = currentSel.StartOffset();
-    *pacpEnd = currentSel.EndOffset();
+    *pacpStart = selectionForTSF.StartOffset();
+    *pacpEnd = selectionForTSF.EndOffset();
     if (pChange) {
-      pChange->acpStart = currentSel.StartOffset();
-      pChange->acpOldEnd = currentSel.EndOffset();
-      pChange->acpNewEnd = currentSel.StartOffset() + static_cast<LONG>(cch);
+      pChange->acpStart = selectionForTSF.StartOffset();
+      pChange->acpOldEnd = selectionForTSF.EndOffset();
+      pChange->acpNewEnd =
+        selectionForTSF.StartOffset() + static_cast<LONG>(cch);
     }
   } else {
     if (!IsReadWriteLocked()) {
       MOZ_LOG(sTextStoreLog, LogLevel::Error,
              ("TSF: 0x%p   TSFTextStore::InsertTextAtSelection() FAILED due to "
               "not locked (read-write)", this));
       return TS_E_NOLOCK;
     }
@@ -3905,25 +3987,25 @@ TSFTextStore::InsertTextAtSelectionInter
                                             TS_TEXTCHANGE* aTextChange)
 {
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::InsertTextAtSelectionInternal("
           "aInsertStr=\"%s\", aTextChange=0x%p), IsComposing=%s",
           this, NS_ConvertUTF16toUTF8(aInsertStr).get(), aTextChange,
           GetBoolName(mComposition.IsComposing())));
 
-  Content& lockedContent = LockedContent();
-  if (!lockedContent.IsInitialized()) {
+  Content& contentForTSF = ContentForTSFRef();
+  if (!contentForTSF.IsInitialized()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::InsertTextAtSelectionInternal() failed "
-            "due to LockedContent() failure()", this));
+            "due to ContentForTSFRef() failure()", this));
     return false;
   }
 
-  TS_SELECTION_ACP oldSelection = lockedContent.Selection().ACP();
+  TS_SELECTION_ACP oldSelection = contentForTSF.Selection().ACP();
   if (!mComposition.IsComposing()) {
     // Use a temporary composition to contain the text
     PendingAction* compositionStart = mPendingActions.AppendElement();
     compositionStart->mType = PendingAction::COMPOSITION_START;
     compositionStart->mSelectionStart = oldSelection.acpStart;
     compositionStart->mSelectionLength =
       oldSelection.acpEnd - oldSelection.acpStart;
     compositionStart->mAdjustSelection = false;
@@ -3939,22 +4021,22 @@ TSFTextStore::InsertTextAtSelectionInter
              "mSelectionLength=%d }, PendingCompositionEnd={ mData=\"%s\" "
              "(Length()=%u) }",
              this, compositionStart->mSelectionStart,
              compositionStart->mSelectionLength,
              NS_ConvertUTF16toUTF8(compositionEnd->mData).get(),
              compositionEnd->mData.Length()));
   }
 
-  lockedContent.ReplaceSelectedTextWith(aInsertStr);
+  contentForTSF.ReplaceSelectedTextWith(aInsertStr);
 
   if (aTextChange) {
     aTextChange->acpStart = oldSelection.acpStart;
     aTextChange->acpOldEnd = oldSelection.acpEnd;
-    aTextChange->acpNewEnd = lockedContent.Selection().EndOffset();
+    aTextChange->acpNewEnd = contentForTSF.Selection().EndOffset();
   }
 
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::InsertTextAtSelectionInternal() "
           "succeeded: mWidget=0x%p, mWidget->Destroyed()=%s, aTextChange={ "
           "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
           this, mWidget.get(),
           GetBoolName(mWidget ? mWidget->Destroyed() : true),
@@ -4012,89 +4094,89 @@ TSFTextStore::RecordCompositionStartActi
 {
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::RecordCompositionStartAction("
           "aComposition=0x%p, aStart=%d, aLength=%d, aPreserveSelection=%s), "
           "mComposition.mView=0x%p",
           this, aComposition, aStart, aLength, GetBoolName(aPreserveSelection),
           mComposition.mView.get()));
 
-  Content& lockedContent = LockedContent();
-  if (!lockedContent.IsInitialized()) {
+  Content& contentForTSF = ContentForTSFRef();
+  if (!contentForTSF.IsInitialized()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::RecordCompositionStartAction() FAILED "
-            "due to LockedContent() failure", this));
+            "due to ContentForTSFRef() failure", this));
     return E_FAIL;
   }
 
   CompleteLastActionIfStillIncomplete();
 
   // TIP may have inserted text at selection before calling
   // OnStartComposition().  In this case, we've already created a pair of
   // pending compositionstart and pending compositionend.  If the pending
   // compositionstart occurred same range as this composition, it was the
   // start of this composition.  In such case, we should cancel the pending
   // compositionend and start composition normally.
   if (!aPreserveSelection &&
       WasTextInsertedWithoutCompositionAt(aStart, aLength)) {
     const PendingAction& pendingCompositionEnd = mPendingActions.LastElement();
     const PendingAction& pendingCompositionStart =
       mPendingActions[mPendingActions.Length() - 2];
-    lockedContent.RestoreCommittedComposition(
+    contentForTSF.RestoreCommittedComposition(
       aComposition, pendingCompositionStart, pendingCompositionEnd);
     mPendingActions.RemoveElementAt(mPendingActions.Length() - 1);
     MOZ_LOG(sTextStoreLog, LogLevel::Info,
            ("TSF: 0x%p   TSFTextStore::RecordCompositionStartAction() "
             "succeeded: restoring the committed string as composing string, "
             "mComposition={ mStart=%ld, mString.Length()=%ld, "
-            "mSelection={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
+            "mSelectionForTSF={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
             "style.fInterimChar=%s } }",
             this, mComposition.mStart, mComposition.mString.Length(),
-            mSelection.StartOffset(), mSelection.EndOffset(),
-            GetActiveSelEndName(mSelection.ActiveSelEnd()),
-            GetBoolName(mSelection.IsInterimChar())));
+            mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
+            GetActiveSelEndName(mSelectionForTSF.ActiveSelEnd()),
+            GetBoolName(mSelectionForTSF.IsInterimChar())));
     return S_OK;
   }
 
   PendingAction* action = mPendingActions.AppendElement();
   action->mType = PendingAction::COMPOSITION_START;
   action->mSelectionStart = aStart;
   action->mSelectionLength = aLength;
 
-  Selection& currentSel = CurrentSelection();
-  if (currentSel.IsDirty()) {
+  Selection& selectionForTSF = SelectionForTSFRef();
+  if (selectionForTSF.IsDirty()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::RecordCompositionStartAction() FAILED "
-            "due to CurrentSelection() failure", this));
+            "due to SelectionForTSFRef() failure", this));
     action->mAdjustSelection = true;
-  } else if (currentSel.MinOffset() != aStart ||
-             currentSel.MaxOffset() != aStart + aLength) {
+  } else if (selectionForTSF.MinOffset() != aStart ||
+             selectionForTSF.MaxOffset() != aStart + aLength) {
     // If new composition range is different from current selection range,
     // we need to set selection before dispatching compositionstart event.
     action->mAdjustSelection = true;
   } else {
     // We shouldn't dispatch selection set event before dispatching
     // compositionstart event because it may cause put caret different
     // position in HTML editor since generated flat text content and offset in
     // it are lossy data of HTML contents.
     action->mAdjustSelection = false;
   }
 
-  lockedContent.StartComposition(aComposition, *action, aPreserveSelection);
+  contentForTSF.StartComposition(aComposition, *action, aPreserveSelection);
   action->mData = mComposition.mString;
 
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
          ("TSF: 0x%p   TSFTextStore::RecordCompositionStartAction() succeeded: "
           "mComposition={ mStart=%ld, mString.Length()=%ld, "
-          "mSelection={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
+          "mSelectionForTSF={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
           "style.fInterimChar=%s } }",
           this, mComposition.mStart, mComposition.mString.Length(),
-          mSelection.StartOffset(), mSelection.EndOffset(),
-          GetActiveSelEndName(mSelection.ActiveSelEnd()),
-          GetBoolName(mSelection.IsInterimChar())));
+          mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
+          GetActiveSelEndName(mSelectionForTSF.ActiveSelEnd()),
+          GetBoolName(mSelectionForTSF.IsInterimChar())));
   return S_OK;
 }
 
 HRESULT
 TSFTextStore::RecordCompositionEndAction()
 {
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::RecordCompositionEndAction(), "
@@ -4104,24 +4186,24 @@ TSFTextStore::RecordCompositionEndAction
 
   MOZ_ASSERT(mComposition.IsComposing());
 
   CompleteLastActionIfStillIncomplete();
   PendingAction* action = mPendingActions.AppendElement();
   action->mType = PendingAction::COMPOSITION_END;
   action->mData = mComposition.mString;
 
-  Content& lockedContent = LockedContent();
-  if (!lockedContent.IsInitialized()) {
+  Content& contentForTSF = ContentForTSFRef();
+  if (!contentForTSF.IsInitialized()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::RecordCompositionEndAction() FAILED due "
-            "to LockedContent() failure", this));
+            "to ContentForTSFRef() failure", this));
     return E_FAIL;
   }
-  lockedContent.EndComposition(*action);
+  contentForTSF.EndComposition(*action);
 
   // If this composition was restart but the composition doesn't modify
   // anything, we should remove the pending composition for preventing to
   // dispatch redundant composition events.
   for (size_t i = mPendingActions.Length(), j = 1; i > 0; --i, ++j) {
     PendingAction& pendingAction = mPendingActions[i - 1];
     if (pendingAction.mType == PendingAction::COMPOSITION_START) {
       if (pendingAction.mData != action->mData) {
@@ -4246,31 +4328,31 @@ TSFTextStore::OnUpdateComposition(ITfCom
   if (FAILED(hr)) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::OnUpdateComposition() FAILED due to "
             "RecordCompositionUpdateAction() failure", this));
     return hr;
   }
 
   if (MOZ_LOG_TEST(sTextStoreLog, LogLevel::Info)) {
-    Selection& currentSel = CurrentSelection();
-    if (currentSel.IsDirty()) {
+    Selection& selectionForTSF = SelectionForTSFRef();
+    if (selectionForTSF.IsDirty()) {
       MOZ_LOG(sTextStoreLog, LogLevel::Error,
              ("TSF: 0x%p   TSFTextStore::OnUpdateComposition() FAILED due to "
-              "CurrentSelection() failure", this));
+              "SelectionForTSFRef() failure", this));
       return E_FAIL;
     }
     MOZ_LOG(sTextStoreLog, LogLevel::Info,
            ("TSF: 0x%p   TSFTextStore::OnUpdateComposition() succeeded: "
             "mComposition={ mStart=%ld, mString=\"%s\" }, "
-            "CurrentSelection()={ acpStart=%ld, acpEnd=%ld, style.ase=%s }",
+            "SelectionForTSFRef()={ acpStart=%ld, acpEnd=%ld, style.ase=%s }",
             this, mComposition.mStart,
             NS_ConvertUTF16toUTF8(mComposition.mString).get(),
-            currentSel.StartOffset(), currentSel.EndOffset(),
-            GetActiveSelEndName(currentSel.ActiveSelEnd())));
+            selectionForTSF.StartOffset(), selectionForTSF.EndOffset(),
+            GetActiveSelEndName(selectionForTSF.ActiveSelEnd())));
   }
   return S_OK;
 }
 
 STDMETHODIMP
 TSFTextStore::OnEndComposition(ITfCompositionView* pComposition)
 {
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
@@ -4553,18 +4635,17 @@ TSFTextStore::GetIMEUpdatePreference()
     }
   }
   return nsIMEUpdatePreference();
 }
 
 nsresult
 TSFTextStore::OnTextChangeInternal(const IMENotification& aIMENotification)
 {
-  const IMENotification::TextChangeDataBase& textChangeData =
-    aIMENotification.mTextChangeData;
+  const TextChangeDataBase& textChangeData = aIMENotification.mTextChangeData;
 
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::OnTextChangeInternal(aIMENotification={ "
           "mMessage=0x%08X, mTextChangeData={ mStartOffset=%lu, "
           "mRemovedEndOffset=%lu, mAddedEndOffset=%lu, "
           "mCausedOnlyByComposition=%s, "
           "mIncludingChangesDuringComposition=%s, "
           "mIncludingChangesWithoutComposition=%s }), "
@@ -4583,102 +4664,93 @@ TSFTextStore::OnTextChangeInternal(const
           GetBoolName(mComposition.IsComposing())));
 
   if (mDestroyed) {
     // If this instance is already destroyed, we shouldn't notify TSF of any
     // changes.
     return NS_OK;
   }
 
-  if (textChangeData.mCausedOnlyByComposition) {
-    // Ignore text change notifications caused only by composition since it's
-    // already been handled internally.
-    return NS_OK;
-  }
-
-  if (mComposition.IsComposing() &&
-      !textChangeData.mIncludingChangesDuringComposition) {
-    // Ignore text changes when they don't include changes caused not by
-    // composition at the latest composition because changes before current
-    // composition start shouldn't cause forcibly committing composition.
-    // In the future, we should notify TSF of such delayed text changes
-    // after current composition is active (In such case,
-    // mIncludingChangesWithoutComposition is true).
-    return NS_OK;
-  }
-
   mDeferNotifyingTSF = false;
 
-  if (IsReadLocked()) {
-    // XXX If text change occurs during the document is locked, it must be
-    //     modified by Javascript.  In such case, we should notify merged
-    //     text changes after it's unlocked.
-    return NS_OK;
-  }
-
-  mSelection.MarkDirty();
-
+  // Different from selection change, we don't modify anything with text
+  // change data.  Therefore, if neither TSF not TIP wants text change
+  // notifications, we don't need to store the changes.
   if (!mSink || !(mSinkMask & TS_AS_TEXT_CHANGE)) {
     return NS_OK;
   }
 
-  if (aIMENotification.mTextChangeData.IsInInt32Range()) {
-    TS_TEXTCHANGE textChange;
-    textChange.acpStart = static_cast<LONG>(textChangeData.mStartOffset);
-    textChange.acpOldEnd = static_cast<LONG>(textChangeData.mRemovedEndOffset);
-    textChange.acpNewEnd = static_cast<LONG>(textChangeData.mAddedEndOffset);
-    NotifyTSFOfTextChange(textChange);
-  } else {
-    MOZ_LOG(sTextStoreLog, LogLevel::Error,
-           ("TSF: 0x%p   TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
-            "offset is too big for calling "
-            "ITextStoreACPSink::OnTextChange()...",
-            this));
-  }
+  // Merge any text change data even if it's caused by composition.
+  mPendingTextChangeData.MergeWith(textChangeData);
 
   MaybeFlushPendingNotifications();
 
   return NS_OK;
 }
 
 void
-TSFTextStore::NotifyTSFOfTextChange(const TS_TEXTCHANGE& aTextChange)
+TSFTextStore::NotifyTSFOfTextChange()
 {
   MOZ_ASSERT(!mDestroyed);
-
-  // XXX We need to cache the text change ranges and notify TSF of that
-  //     the document is unlocked.
-  if (NS_WARN_IF(IsReadLocked())) {
+  MOZ_ASSERT(!IsReadLocked());
+  MOZ_ASSERT(!mComposition.IsComposing());
+  MOZ_ASSERT(mPendingTextChangeData.IsValid());
+
+  // If the text changes are caused only by composition, we don't need to
+  // notify TSF of the text changes.
+  if (mPendingTextChangeData.mCausedOnlyByComposition) {
+    mPendingTextChangeData.Clear();
     return;
   }
 
-  // Some TIPs are confused by text change notification during composition.
-  // Especially, some of them stop working for composition in our process.
-  // For preventing it, let's commit the composition.
-  if (mComposition.IsComposing()) {
-    MOZ_LOG(sTextStoreLog, LogLevel::Info,
-           ("TSF: 0x%p   TSFTextStore::NotifyTSFOfTextChange(), "
-            "committing the composition for avoiding making TIP confused...",
+  // First, forget cached selection.
+  mSelectionForTSF.MarkDirty();
+
+  // For making it safer, we should check if there is a valid sink to receive
+  // text change notification.
+  if (NS_WARN_IF(!mSink) || NS_WARN_IF(!(mSinkMask & TS_AS_TEXT_CHANGE))) {
+    MOZ_LOG(sTextStoreLog, LogLevel::Error,
+           ("TSF: 0x%p   TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
+            "mSink is not ready to call ITextStoreACPSink::OnTextChange()...",
             this));
-    CommitCompositionInternal(false);
+    mPendingTextChangeData.Clear();
     return;
   }
 
+  if (NS_WARN_IF(!mPendingTextChangeData.IsInInt32Range())) {
+    MOZ_LOG(sTextStoreLog, LogLevel::Error,
+           ("TSF: 0x%p   TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
+            "offset is too big for calling "
+            "ITextStoreACPSink::OnTextChange()...",
+            this));
+    mPendingTextChangeData.Clear();
+    return;
+   }
+
+  TS_TEXTCHANGE textChange;
+  textChange.acpStart =
+    static_cast<LONG>(mPendingTextChangeData.mStartOffset);
+  textChange.acpOldEnd =
+    static_cast<LONG>(mPendingTextChangeData.mRemovedEndOffset);
+  textChange.acpNewEnd =
+    static_cast<LONG>(mPendingTextChangeData.mAddedEndOffset);
+  mPendingTextChangeData.Clear();
+
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
          ("TSF: 0x%p   TSFTextStore::NotifyTSFOfTextChange(), calling "
           "ITextStoreACPSink::OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
-          "acpNewEnd=%ld })...", this, aTextChange.acpStart,
-          aTextChange.acpOldEnd, aTextChange.acpNewEnd));
-  mSink->OnTextChange(0, &aTextChange);
+          "acpNewEnd=%ld })...", this, textChange.acpStart,
+          textChange.acpOldEnd, textChange.acpNewEnd));
+  mSink->OnTextChange(0, &textChange);
 }
 
 nsresult
 TSFTextStore::OnSelectionChangeInternal(const IMENotification& aIMENotification)
 {
-  const IMENotification::SelectionChangeDataBase& selectionChangeData =
+  const SelectionChangeDataBase& selectionChangeData =
     aIMENotification.mSelectionChangeData;
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::OnSelectionChangeInternal("
           "aIMENotification={ mSelectionChangeData={ mOffset=%lu, "
           "Length()=%lu, mReversed=%s, mWritingMode=%s, "
           "mCausedByComposition=%s, mCausedBySelectionEvent=%s, "
           "mOccurredDuringComposition=%s } }), mDestroyed=%s, "
           "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, "
@@ -4695,106 +4767,59 @@ TSFTextStore::OnSelectionChangeInternal(
           GetBoolName(mComposition.IsComposing())));
 
   if (mDestroyed) {
     // If this instance is already destroyed, we shouldn't notify TSF of any
     // changes.
     return NS_OK;
   }
 
-  if (selectionChangeData.mCausedByComposition) {
-    // Ignore selection change notifications caused by composition since it's
-    // already been handled internally.
-    return NS_OK;
-  }
-
   mDeferNotifyingTSF = false;
 
-  // A compositionstart event handler can change selection before actually
-  // starting composition in the editor. This causes very complicated issue
-  // because TSF requests to lock the document but we allow to change the
-  // selection for web apps for keeping compatibility.
-  // For now, we should not send selection change notification until the
-  // active composition ends.  However, this causes TSF stores wrong selection
-  // offset.  That might cause TSF stopping working.  So, at next change,
-  // we should cache content *until* composition end.  Then, we will solve
-  // this issue.
-  if (mComposition.IsComposing() &&
-      !selectionChangeData.mOccurredDuringComposition) {
-    MOZ_LOG(sTextStoreLog, LogLevel::Warning,
-           ("TSF: 0x%p   TSFTextStore::OnSelectionChangeInternal(), WARNING, "
-            "ignoring selection change notification which occurred before "
-            "composition start.", this));
-    return NS_OK;
-  }
-
-  // If selection range isn't actually changed, we don't need to notify TSF
-  // of this selection change.
-  if (!mSelection.SetSelection(
-                    selectionChangeData.mOffset,
-                    selectionChangeData.Length(),
-                    selectionChangeData.mReversed,
-                    selectionChangeData.GetWritingMode())) {
-    MOZ_LOG(sTextStoreLog, LogLevel::Debug,
-           ("TSF: 0x%p   TSFTextStore::OnSelectionChangeInternal(), selection "
-            "isn't actually changed.", this));
-    return NS_OK;
-  }
-
-  if (!selectionChangeData.mCausedBySelectionEvent) {
-    // Should be notified via MaybeFlushPendingNotifications() for keeping
-    // the order of change notifications.
-    mPendingOnSelectionChange = true;
-    if (mIsRecordingActionsWithoutLock) {
-      MOZ_LOG(sTextStoreLog, LogLevel::Info,
-             ("TSF: 0x%p   TSFTextStore::OnSelectionChangeInternal(), putting "
-              "off notifying TSF of selection change...", this));
-      return NS_OK;
-    }
-  } else {
-    // If the selection change is caused by setting selection range, we don't
-    // need to notify that.  Additionally, even if there is pending selection
-    // change notification, we don't need to notify that since the selection
-    // range is changed as expected by TSF or TIP.
-    mPendingOnSelectionChange = false;
-  }
+  // Assign the new selection change data to the pending selection change data
+  // because only the latest selection data is necessary.
+  // Note that this is necessary to update mSelectionForTSF.  Therefore, even if
+  // neither TSF nor TIP wants selection change notifications, we need to
+  // store the selection information.
+  mPendingSelectionChangeData.Assign(selectionChangeData);
 
   // Flush remaining pending notifications here if it's possible.
   MaybeFlushPendingNotifications();
 
   return NS_OK;
 }
 
 void
 TSFTextStore::NotifyTSFOfSelectionChange()
 {
   MOZ_ASSERT(!mDestroyed);
-
-  if (NS_WARN_IF(IsReadLocked())) {
+  MOZ_ASSERT(!IsReadLocked());
+  MOZ_ASSERT(!mComposition.IsComposing());
+  MOZ_ASSERT(mPendingSelectionChangeData.IsValid());
+
+  // If selection range isn't actually changed, we don't need to notify TSF
+  // of this selection change.
+  if (!mSelectionForTSF.SetSelection(
+                          mPendingSelectionChangeData.mOffset,
+                          mPendingSelectionChangeData.Length(),
+                          mPendingSelectionChangeData.mReversed,
+                          mPendingSelectionChangeData.GetWritingMode())) {
+    mPendingSelectionChangeData.Clear();
+    MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+           ("TSF: 0x%p   TSFTextStore::NotifyTSFOfSelectionChange(), "
+            "selection isn't actually changed.", this));
     return;
   }
 
-  mPendingOnSelectionChange = false;
+  mPendingSelectionChangeData.Clear();
 
   if (!mSink || !(mSinkMask & TS_AS_SEL_CHANGE)) {
     return;
   }
 
-  // Some TIPs are confused by selection change notification during composition.
-  // Especially, some of them stop working for composition in our process.
-  // For preventing it, let's commit the composition.
-  if (mComposition.IsComposing()) {
-    MOZ_LOG(sTextStoreLog, LogLevel::Info,
-           ("TSF: 0x%p   TSFTextStore::NotifyTSFOfSelectionChange(), "
-            "committing the composition for avoiding making TIP confused...",
-            this));
-    CommitCompositionInternal(false);
-    return;
-  }
-
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
          ("TSF: 0x%p   TSFTextStore::NotifyTSFOfSelectionChange(), calling "
           "ITextStoreACPSink::OnSelectionChange()...", this));
   mSink->OnSelectionChange();
 }
 
 nsresult
 TSFTextStore::OnLayoutChangeInternal()
@@ -4842,20 +4867,20 @@ TSFTextStore::NotifyTSFOfLayoutChange()
 
   // If we returned TS_E_NOLAYOUT, TIP should query the computed layout again.
   mWaitingQueryLayout = returnedNoLayoutError;
 
   // For avoiding to call this method again at unlocking the document during
   // calls of OnLayoutChange(), reset mHasReturnedNoLayoutError.
   mHasReturnedNoLayoutError = false;
 
-  // Now, layout has been computed.  We should notify mLockedContent for
+  // Now, layout has been computed.  We should notify mContentForTSF for
   // making GetTextExt() and GetACPFromPoint() not return TS_E_NOLAYOUT.
-  if (mLockedContent.IsInitialized()) {
-    mLockedContent.OnLayoutChanged();
+  if (mContentForTSF.IsInitialized()) {
+    mContentForTSF.OnLayoutChanged();
   }
 
   // Now, the caret position is different from ours.  Destroy the native caret
   // if there is.
   MaybeDestroyNativeCaret();
 
   // This method should return true if either way succeeds.
   bool ret = true;
@@ -4983,19 +5008,22 @@ TSFTextStore::OnUpdateCompositionInterna
      "mDestroyed=%s, mDeferNotifyingTSF=%s",
      this, GetBoolName(mDestroyed), GetBoolName(mDeferNotifyingTSF)));
 
   // There are nothing to do after destroyed.
   if (mDestroyed) {
     return NS_OK;
   }
 
-  // Now, all sent composition events are handled by the content even in
-  // e10s mode.
-  mDeferClearingLockedContent = false;
+  // If composition is completely finished both in TSF/TIP and the focused
+  // editor which may be in a remote process, we can clear the cache until
+  // starting next composition.
+  if (!mComposition.IsComposing() && !IsComposingInContent()) {
+    mDeferClearingContentForTSF = false;
+  }
   mDeferNotifyingTSF = false;
   MaybeFlushPendingNotifications();
   return NS_OK;
 }
 
 nsresult
 TSFTextStore::OnMouseButtonEventInternal(
                 const IMENotification& aIMENotification)
@@ -5086,31 +5114,31 @@ TSFTextStore::CreateNativeCaret()
     return;
   }
 
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::CreateNativeCaret(), "
           "mComposition.IsComposing()=%s",
           this, GetBoolName(mComposition.IsComposing())));
 
-  Selection& currentSel = CurrentSelection();
-  if (currentSel.IsDirty()) {
+  Selection& selectionForTSF = SelectionForTSFRef();
+  if (selectionForTSF.IsDirty()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::CreateNativeCaret() FAILED due to "
-            "CurrentSelection() failure", this));
+            "SelectionForTSFRef() failure", this));
     return;
   }
 
   WidgetQueryContentEvent queryCaretRect(true, eQueryCaretRect, mWidget);
   mWidget->InitEvent(queryCaretRect);
 
   WidgetQueryContentEvent::Options options;
   // XXX If this is called without composition and the selection isn't
   //     collapsed, is it OK?
-  int64_t caretOffset = currentSel.MaxOffset();
+  int64_t caretOffset = selectionForTSF.MaxOffset();
   if (mComposition.IsComposing()) {
     // If there is a composition, use insertion point relative query for
     // deciding caret position because composition might be at different
     // position where TSFTextStore believes it at.
     options.mRelativeToInsertionPoint = true;
     caretOffset -= mComposition.mStart;
   }
   queryCaretRect.InitForQueryCaretRect(caretOffset, options);
--- a/widget/windows/TSFTextStore.h
+++ b/widget/windows/TSFTextStore.h
@@ -48,16 +48,22 @@ struct MSGResult;
 /*
  * Text Services Framework text store
  */
 
 class TSFTextStore final : public ITextStoreACP
                          , public ITfContextOwnerCompositionSink
                          , public ITfMouseTrackerACP
 {
+private:
+  typedef IMENotification::SelectionChangeDataBase SelectionChangeDataBase;
+  typedef IMENotification::SelectionChangeData SelectionChangeData;
+  typedef IMENotification::TextChangeDataBase TextChangeDataBase;
+  typedef IMENotification::TextChangeData TextChangeData;
+
 public: /*IUnknown*/
   STDMETHODIMP          QueryInterface(REFIID, void**);
 
   NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFTextStore)
 
 public: /*ITextStoreACP*/
   STDMETHODIMP AdviseSink(REFIID, IUnknown*, DWORD);
   STDMETHODIMP UnadviseSink(IUnknown*);
@@ -104,17 +110,16 @@ public:
   static void     Initialize(void);
   static void     Terminate(void);
 
   static bool     ProcessRawKeyMessage(const MSG& aMsg);
   static void     ProcessMessage(nsWindowBase* aWindow, UINT aMessage,
                                  WPARAM& aWParam, LPARAM& aLParam,
                                  MSGResult& aResult);
 
-
   static void     SetIMEOpenState(bool);
   static bool     GetIMEOpenState(void);
 
   static void     CommitComposition(bool aDiscard)
   {
     NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
     if (!sEnabledTextStore) {
       return;
@@ -271,19 +276,16 @@ protected:
   // aDispatchCompositionChangeEvent should be true only when this is called
   // from SetSelection.  Because otherwise, the compositionchange event should
   // not be sent from here.
   HRESULT  SetSelectionInternal(const TS_SELECTION_ACP*,
                                 bool aDispatchCompositionChangeEvent = false);
   bool     InsertTextAtSelectionInternal(const nsAString& aInsertStr,
                                          TS_TEXTCHANGE* aTextChange);
   void     CommitCompositionInternal(bool);
-  nsresult OnTextChangeInternal(const IMENotification& aIMENotification);
-  nsresult OnSelectionChangeInternal(const IMENotification& aIMENotification);
-  nsresult OnMouseButtonEventInternal(const IMENotification& aIMENotification);
   HRESULT  GetDisplayAttribute(ITfProperty* aProperty,
                                ITfRange* aRange,
                                TF_DISPLAYATTRIBUTE* aResult);
   HRESULT  RestartCompositionIfNecessary(ITfRange* pRangeNew = nullptr);
   HRESULT  RestartComposition(ITfCompositionView* aCompositionView,
                               ITfRange* aNewRange);
 
   // Following methods record composing action(s) to mPendingActions.
@@ -305,20 +307,33 @@ protected:
   void     OnLayoutInformationAvaliable();
 
   // FlushPendingActions() performs pending actions recorded in mPendingActions
   // and clear it.
   void     FlushPendingActions();
   // MaybeFlushPendingNotifications() performs pending notifications to TSF.
   void     MaybeFlushPendingNotifications();
 
+  nsresult OnTextChangeInternal(const IMENotification& aIMENotification);
+  nsresult OnSelectionChangeInternal(const IMENotification& aIMENotification);
+  nsresult OnMouseButtonEventInternal(const IMENotification& aIMENotification);
   nsresult OnLayoutChangeInternal();
   nsresult OnUpdateCompositionInternal();
 
-  void     NotifyTSFOfTextChange(const TS_TEXTCHANGE& aTextChange);
+  // mPendingSelectionChangeData stores selection change data until notifying
+  // TSF of selection change.  If two or more selection changes occur, this
+  // stores the latest selection change data because only it is necessary.
+  SelectionChangeData mPendingSelectionChangeData;
+
+  // mPendingTextChangeData stores one or more text change data until notifying
+  // TSF of text change.  If two or more text changes occur, this merges
+  // every text change data.
+  TextChangeData mPendingTextChangeData;
+
+  void     NotifyTSFOfTextChange();
   void     NotifyTSFOfSelectionChange();
   bool     NotifyTSFOfLayoutChange();
   void     NotifyTSFOfLayoutChangeAgain();
 
   HRESULT  HandleRequestAttrs(DWORD aFlags,
                               ULONG aFilterCount,
                               const TS_ATTRID* aFilterAttrs);
   void     SetInputScope(const nsString& aHTMLInputType,
@@ -402,16 +417,24 @@ protected:
   // While the document is locked, we cannot dispatch any events which cause
   // DOM events since the DOM events' handlers may modify the locked document.
   // However, even while the document is locked, TSF may queries us.
   // For that, TSFTextStore modifies mComposition even while the document is
   // locked.  With mComposition, query methods can returns the text content
   // information.
   Composition mComposition;
 
+  /**
+   * IsComposingInContent() returns true if there is a composition in the
+   * focused editor and it's caused by native IME (either TIP of TSF or IME of
+   * IMM).  I.e., returns true between eCompositionStart and
+   * eCompositionCommit(AsIs).
+   */
+  bool IsComposingInContent() const;
+
   class Selection
   {
   public:
     Selection() : mDirty(true) {}
 
     bool IsDirty() const { return mDirty; };
     void MarkDirty() { mDirty = true; }
 
@@ -526,32 +549,45 @@ protected:
     }
 
     WritingMode GetWritingMode() const
     {
       MOZ_ASSERT(!mDirty);
       return mWritingMode;
     }
 
+    bool EqualsExceptDirection(const TS_SELECTION_ACP& aACP) const
+    {
+      if (mACP.style.ase == aACP.style.ase) {
+        return mACP.acpStart == aACP.acpStart &&
+               mACP.acpEnd == aACP.acpEnd;
+      }
+      return mACP.acpStart == aACP.acpEnd &&
+             mACP.acpEnd == aACP.acpStart;
+    }
+
   private:
     TS_SELECTION_ACP mACP;
     WritingMode mWritingMode;
     bool mDirty;
   };
   // Don't access mSelection directly except at calling MarkDirty().
-  // Use CurrentSelection() instead.  This is marked as dirty when the
-  // selection or content is changed without document lock.
-  Selection mSelection;
+  // Use SelectionForTSFRef() instead.  This is modified immediately when
+  // TSF requests to set selection and not updated by selection change in
+  // content until mContentForTSF is cleared.
+  Selection mSelectionForTSF;
 
-  // Get "current selection".  If the document is locked, this initializes
-  // mSelection with the selection at the first call during a lock and returns
-  // it.  However, mSelection is NOT modified immediately.  When pending
-  // changes are flushed at unlocking the document, cached mSelection is
-  // modified.  Note that this is also called by LockedContent().
-  Selection& CurrentSelection();
+  /**
+   * Get the selection expected by TSF.  If mSelectionForTSF is already valid,
+   * this just return the reference to it.  Otherwise, this initializes it
+   * with eQuerySelectedText.  Please check if the result is valid before
+   * actually using it.
+   * Note that this is also called by ContentForTSFRef().
+   */
+  Selection& SelectionForTSFRef();
 
   struct PendingAction final
   {
     enum ActionType : uint8_t
     {
       COMPOSITION_START,
       COMPOSITION_UPDATE,
       COMPOSITION_END,
@@ -780,28 +816,34 @@ protected:
     enum : uint32_t
     {
       NOT_MODIFIED = UINT32_MAX
     };
     uint32_t mMinTextModifiedOffset;
 
     bool mInitialized;
   };
-  // mLockedContent starts to cache content of the document at first query of
-  // the content during a document lock.  This is abandoned after document is
-  // unlocked and dispatched events are handled.  This is initialized by
-  // LockedContent() automatically.  So, don't access this member directly
-  // except at calling Clear(), IsInitialized(), IsLayoutChangedAfter() or
-  // IsLayoutChanged().
-  Content mLockedContent;
+  // mContentForTSF is cache of content.  The information is expected by TSF
+  // and TIP.  Therefore, this is useful for answering the query from TSF or
+  // TIP.
+  // This is initialized by ContentForTSFRef() automatically (therefore, don't
+  // access this member directly except at calling Clear(), IsInitialized(),
+  // IsLayoutChangeAfter() or IsLayoutChanged()).
+  // This is cleared when:
+  //  - When there is no composition, the document is unlocked.
+  //  - When there is a composition, all dispatched events are handled by
+  //    the focused editor which may be in a remote process.
+  // So, if two compositions are created very quickly, this cache may not be
+  // cleared between eCompositionCommit(AsIs) and eCompositionStart.
+  Content mContentForTSF;
 
-  Content& LockedContent();
+  Content& ContentForTSFRef();
 
-  // While the documet is locked, this returns the text stored by
-  // mLockedContent.  Otherwise, return the current text content.
+  // While mContentForTSF is valid, this returns the text stored by it.
+  // Otherwise, return the current text content retrieved by eQueryTextContent.
   bool GetCurrentText(nsAString& aTextContent);
 
   class MouseTracker final
   {
   public:
     static const DWORD kInvalidCookie = static_cast<DWORD>(-1);
 
     MouseTracker();
@@ -858,39 +900,31 @@ protected:
   int32_t GetRequestedAttrIndex(const TS_ATTRID& aAttrID);
   TS_ATTRID GetAttrID(int32_t aIndex);
 
   bool mRequestedAttrValues;
 
   // If edit actions are being recorded without document lock, this is true.
   // Otherwise, false.
   bool                         mIsRecordingActionsWithoutLock;
-  // During recording actions, we shouldn't call mSink->OnSelectionChange()
-  // because it may cause TSF request new lock.  This is a problem if the
-  // selection change is caused by a call of On*Composition() without document
-  // lock since RequestLock() tries to flush the pending actions again (which
-  // are flushing).  Therefore, OnSelectionChangeInternal() sets this true
-  // during recoding actions and then, RequestLock() will call
-  // mSink->OnSelectionChange() after mLock becomes 0.
-  bool                         mPendingOnSelectionChange;
   // If GetTextExt() or GetACPFromPoint() is called and the layout hasn't been
   // calculated yet, these methods return TS_E_NOLAYOUT.  At that time,
   // mHasReturnedNoLayoutError is set to true.
   bool                         mHasReturnedNoLayoutError;
   // Before calling ITextStoreACPSink::OnLayoutChange() and
   // ITfContextOwnerServices::OnLayoutChange(), mWaitingQueryLayout is set to
   // true.  This is set to  false when GetTextExt() or GetACPFromPoint() is
   // called.
   bool                         mWaitingQueryLayout;
   // During the documet is locked, we shouldn't destroy the instance.
   // If this is true, the instance will be destroyed after unlocked.
   bool                         mPendingDestroy;
   // If this is false, MaybeFlushPendingNotifications() will clear the
-  // mLockedContent.
-  bool                         mDeferClearingLockedContent;
+  // mContentForTSF.
+  bool                         mDeferClearingContentForTSF;
   // While there is native caret, this is true.  Otherwise, false.
   bool                         mNativeCaretIsCreated;
   // While the instance is dispatching events, the event may not be handled
   // synchronously in e10s mode.  So, in such case, in strictly speaking,
   // we shouldn't query layout information.  However, TS_E_NOLAYOUT bugs of
   // ITextStoreAPC::GetTextExt() blocks us to behave ideally.
   // For preventing it to be called, we should put off notifying TSF of
   // anything until layout information becomes available.