Merge mozilla-central to mozilla-inbound. a=merge CLOSED TREE
authorBogdan Tara <btara@mozilla.com>
Thu, 10 Jan 2019 19:27:05 +0200
changeset 510379 be769aa73f883b218cf655519a08aa874500ee7e
parent 510378 842b7a62d9cefd5030c5561d536af8414899af7a (current diff)
parent 510355 d0a6668cf2fe907399cff20030b7b8218d56f005 (diff)
child 510380 c9fcbe28afdadd45d371ba150ff9c1502a19c6e3
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.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. a=merge CLOSED TREE
dom/base/nsContentIterator.cpp
dom/base/nsIContentIterator.h
editor/spellchecker/nsFilteredContentIterator.cpp
editor/spellchecker/nsFilteredContentIterator.h
gfx/webrender_bindings/revision.txt
layout/base/PresShell.cpp
layout/base/tests/test_expanding_selection_per_page.html
testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -832,31 +832,31 @@ dependencies = [
  "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "encoding_c"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_rs 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "encoding_glue"
 version = "0.1.0"
 dependencies = [
- "encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_rs 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "nserror 0.1.0",
  "nsstring 0.1.0",
 ]
 
 [[package]]
 name = "encoding_rs"
-version = "0.8.13"
+version = "0.8.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "simd 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "env_logger"
@@ -1745,17 +1745,17 @@ dependencies = [
  "nsstring 0.1.0",
 ]
 
 [[package]]
 name = "nsstring"
 version = "0.1.0"
 dependencies = [
  "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_rs 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "nsstring-gtest"
 version = "0.1.0"
 dependencies = [
  "nsstring 0.1.0",
 ]
@@ -3251,17 +3251,17 @@ dependencies = [
 "checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a"
 "checksum docopt 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d8acd393692c503b168471874953a2531df0e9ab77d0b6bbc582395743300a4a"
 "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
 "checksum dtoa-short 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "068d4026697c1a18f0b0bb8cfcad1b0c151b90d8edb9bf4c235ad68128920d1d"
 "checksum dwrote 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2ea0fd88d96838ce5ed30326338cc04a0eb4cff10e3e15d188d74112777103"
 "checksum either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18785c1ba806c258137c937e44ada9ee7e69a37e3c72077542cd2f069d78562a"
 "checksum ena 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "88dc8393b3c7352f94092497f6b52019643e493b6b890eb417cdb7c46117e621"
 "checksum encoding_c 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "769ecb8b33323998e482b218c0d13cd64c267609023b4b7ec3ee740714c318ee"
-"checksum encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1a8fa54e6689eb2549c4efed8d00d7f3b2b994a064555b0e8df4ae3764bcc4be"
+"checksum encoding_rs 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)" = "a69d152eaa438a291636c1971b0a370212165ca8a75759eb66818c5ce9b538f7"
 "checksum env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0561146661ae44c579e993456bc76d11ce1e0c7d745e57b2fa7146b6e49fa2ad"
 "checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
 "checksum euclid 0.19.4 (registry+https://github.com/rust-lang/crates.io-index)" = "dbbf962bb6f877239a34491f2e0a12c6b824f389bc789eb90f1d70d4780b0727"
 "checksum failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6dd377bcc1b1b7ce911967e3ec24fa19c3224394ec05b54aa7b083d498341ac7"
 "checksum failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "64c2d913fe8ed3b6c6518eedf4538255b989945c14c2a7d5cbff62a5e2120596"
 "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
 "checksum fixedbitset 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "85cb8fec437468d86dc7c83ca7cfc933341d561873275f22dd5eedefa63a6478"
 "checksum flate2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fac2277e84e5e858483756647a9d0aa8d9a2b7cba517fd84325a0aaa69a0909"
--- a/accessible/android/DocAccessibleWrap.cpp
+++ b/accessible/android/DocAccessibleWrap.cpp
@@ -246,15 +246,20 @@ void DocAccessibleWrap::UpdateFocusPathB
           UnspecifiedNaN<double>(), nsTArray<Attribute>()));
     }
 
     ipcDoc->SendBatch(eBatch_BoundsUpdate, boundsData);
   } else if (SessionAccessibility* sessionAcc =
                  SessionAccessibility::GetInstanceFor(this)) {
     nsTArray<AccessibleWrap*> accessibles(mFocusPath.Count());
     for (auto iter = mFocusPath.Iter(); !iter.Done(); iter.Next()) {
-      accessibles.AppendElement(
-          static_cast<AccessibleWrap*>(iter.Data().get()));
+      Accessible* accessible = iter.Data();
+      if (!accessible || accessible->IsDefunct()) {
+        MOZ_ASSERT_UNREACHABLE("Focus path cached accessible is gone.");
+        continue;
+      }
+
+      accessibles.AppendElement(static_cast<AccessibleWrap*>(accessible));
     }
 
     sessionAcc->UpdateCachedBounds(accessibles);
   }
 }
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -3683,23 +3683,36 @@ function getClosestNonBucketNode(item) {
   const parent = getParent(item);
   if (!parent) {
     return null;
   }
 
   return getClosestNonBucketNode(parent);
 }
 
-function getNonPrototypeParentGripValue(item) {
+function getParentGripNode(item) {
   const parentNode = getParent(item);
   if (!parentNode) {
     return null;
   }
 
-  const parentGripNode = getClosestGripNode(parentNode);
+  return getClosestGripNode(parentNode);
+}
+
+function getParentGripValue(item) {
+  const parentGripNode = getParentGripNode(item);
+  if (!parentGripNode) {
+    return null;
+  }
+
+  return getValue(parentGripNode);
+}
+
+function getNonPrototypeParentGripValue(item) {
+  const parentGripNode = getParentGripNode(item);
   if (!parentGripNode) {
     return null;
   }
 
   if (getType(parentGripNode) === NODE_TYPES.PROTOTYPE) {
     return getNonPrototypeParentGripValue(parentGripNode);
   }
 
@@ -3711,16 +3724,17 @@ module.exports = {
   createGetterNode,
   createSetterNode,
   getActor,
   getChildren,
   getChildrenWithEvaluations,
   getClosestGripNode,
   getClosestNonBucketNode,
   getParent,
+  getParentGripValue,
   getNonPrototypeParentGripValue,
   getNumericalPropertiesCount,
   getValue,
   makeNodesForEntries,
   makeNodesForPromiseProperties,
   makeNodesForProperties,
   makeNumericalBuckets,
   nodeHasAccessors,
@@ -6401,21 +6415,21 @@ function rootsChanged(props) {
 
 function releaseActors(state, client) {
   const actors = getActors(state);
   for (const actor of actors) {
     client.releaseActor(actor);
   }
 }
 
-function invokeGetter(node, grip, getterName) {
+function invokeGetter(node, targetGrip, receiverId, getterName) {
   return async ({ dispatch, client, getState }) => {
     try {
-      const objectClient = client.createObjectClient(grip);
-      const result = await objectClient.getPropertyValue(getterName);
+      const objectClient = client.createObjectClient(targetGrip);
+      const result = await objectClient.getPropertyValue(getterName, receiverId);
       dispatch({
         type: "GETTER_INVOKED",
         data: {
           node,
           result
         }
       });
     } catch (e) {
@@ -6999,16 +7013,17 @@ const {
   nodeIsSetter,
   nodeIsUninitializedBinding,
   nodeIsUnmappedBinding,
   nodeIsUnscopedBinding,
   nodeIsWindow,
   nodeIsLongString,
   nodeHasFullText,
   nodeHasGetter,
+  getParentGripValue,
   getNonPrototypeParentGripValue
 } = Utils.node;
 
 class ObjectInspectorItem extends Component {
   // eslint-disable-next-line complexity
   getLabelAndValue() {
     const { item, depth, expanded, mode } = this.props;
 
@@ -7073,20 +7088,21 @@ class ObjectInspectorItem extends Compon
 
       if (nodeIsLongString(item)) {
         repProps.member = {
           open: nodeHasFullText(item) && expanded
         };
       }
 
       if (nodeHasGetter(item)) {
-        const parentGrip = getNonPrototypeParentGripValue(item);
-        if (parentGrip) {
+        const targetGrip = getParentGripValue(item);
+        const receiverGrip = getNonPrototypeParentGripValue(item);
+        if (targetGrip && receiverGrip) {
           Object.assign(repProps, {
-            onInvokeGetterButtonClick: () => this.props.invokeGetter(item, parentGrip, item.name)
+            onInvokeGetterButtonClick: () => this.props.invokeGetter(item, targetGrip, receiverGrip.actor, item.name)
           });
         }
       }
 
       return {
         label,
         value: Utils.renderRep(item, repProps)
       };
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -350,16 +350,17 @@ skip-if = true  # Bug 1438979
 [browser_webconsole_non_javascript_mime_warning.js]
 [browser_webconsole_object_ctrl_click.js]
 [browser_webconsole_object_in_sidebar_keyboard_nav.js]
 [browser_webconsole_object_inspector.js]
 [browser_webconsole_object_inspector__proto__.js]
 [browser_webconsole_object_inspector_entries.js]
 [browser_webconsole_object_inspector_getters.js]
 [browser_webconsole_object_inspector_getters_prototype.js]
+[browser_webconsole_object_inspector_getters_shadowed.js]
 [browser_webconsole_object_inspector_key_sorting.js]
 [browser_webconsole_object_inspector_local_session_storage.js]
 [browser_webconsole_object_inspector_selected_text.js]
 [browser_webconsole_object_inspector_scroll.js]
 [browser_webconsole_object_inspector_while_debugging_and_inspecting.js]
 [browser_webconsole_observer_notifications.js]
 [browser_webconsole_optimized_out_vars.js]
 [browser_webconsole_output_copy.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_object_inspector_getters_shadowed.js
@@ -0,0 +1,79 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check evaluating shadowed getters in the console.
+const TEST_URI = "data:text/html;charset=utf8,<h1>Object Inspector on Getters</h1>";
+
+add_task(async function() {
+  const hud = await openNewTabAndConsole(TEST_URI);
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    const a = {
+      getter: "[A]",
+      __proto__: {
+        get getter() {
+          return "[B]";
+        },
+        __proto__: {
+          get getter() {
+            return "[C]";
+          },
+        },
+      },
+    };
+    const b = {
+      value: 1,
+      get getter() {
+        return `[A-${this.value}]`;
+      },
+      __proto__: {
+        value: 2,
+        get getter() {
+          return `[B-${this.value}]`;
+        },
+      },
+    };
+    content.wrappedJSObject.console.log("oi-test", a, b);
+  });
+
+  const node = await waitFor(() => findMessage(hud, "oi-test"));
+  const [a, b] = node.querySelectorAll(".tree");
+
+  await testObject(a, [null, "[B]", "[C]"]);
+  await testObject(b, ["[A-1]", "[B-1]"]);
+});
+
+async function testObject(oi, values) {
+  let node = oi.querySelector(".tree-node");
+  for (const value of values) {
+    await expand(node);
+    if (value != null) {
+      const getter = findObjectInspectorNodeChild(node, "getter");
+      await invokeGetter(getter);
+      ok(getter.textContent.includes(`getter: "${value}"`),
+        `Getter now has the expected "${value}" content`);
+    }
+    node = findObjectInspectorNodeChild(node, "<prototype>");
+  }
+}
+
+function expand(node) {
+  expandObjectInspectorNode(node);
+  return waitFor(() => getObjectInspectorChildrenNodes(node).length > 0);
+}
+
+function invokeGetter(node) {
+  getObjectInspectorInvokeGetterButton(node).click();
+  return waitFor(() => !getObjectInspectorInvokeGetterButton(node));
+}
+
+function findObjectInspectorNodeChild(node, nodeLabel) {
+  return getObjectInspectorChildrenNodes(node).find(child => {
+    const label = child.querySelector(".object-label");
+    return label && label.textContent === nodeLabel;
+  });
+}
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -506,23 +506,36 @@ const proto = {
    *
    * Note: Since this will evaluate getters, it can trigger execution of
    * content code and may cause side effects. This endpoint should only be used
    * when you are confident that the side-effects will be safe, or the user
    * is expecting the effects.
    *
    * @param {string} name
    *        The property we want the value of.
+   * @param {string|null} receiverId
+   *        The actorId of the receiver to be used if the property is a getter.
+   *        If null or invalid, the receiver will be the referent.
    */
-  propertyValue: function(name) {
+  propertyValue: function(name, receiverId) {
     if (!name) {
       return this.throwError("missingParameter", "no property name was specified");
     }
 
-    const value = this.obj.getProperty(name);
+    let receiver;
+    if (receiverId) {
+      const receiverActor = this.conn.getActor(receiverId);
+      if (receiverActor) {
+        receiver = receiverActor.obj;
+      }
+    }
+
+    const value = receiver
+      ? this.obj.getProperty(name, receiver)
+      : this.obj.getProperty(name);
 
     return { value: this._buildCompletion(value) };
   },
 
   /**
    * Handle a protocol request to evaluate a function and provide the value of
    * the result.
    *
--- a/devtools/server/tests/unit/test_objectgrips-fn-apply-01.js
+++ b/devtools/server/tests/unit/test_objectgrips-fn-apply-01.js
@@ -32,27 +32,27 @@ async function test_object_grip(debuggee
           return parts.reduce((acc, v) => acc + v, 0);
         },
         error() {
           throw "an error";
         },
       });
     `,
     async objClient => {
-      const obj1 = (await objClient.getPropertyValue("obj1")).value.return;
-      const obj2 = (await objClient.getPropertyValue("obj2")).value.return;
+      const obj1 = (await objClient.getPropertyValue("obj1", null)).value.return;
+      const obj2 = (await objClient.getPropertyValue("obj2", null)).value.return;
 
       const context = threadClient.pauseGrip(
-        (await objClient.getPropertyValue("context")).value.return,
+        (await objClient.getPropertyValue("context", null)).value.return,
       );
       const sum = threadClient.pauseGrip(
-        (await objClient.getPropertyValue("sum")).value.return,
+        (await objClient.getPropertyValue("sum", null)).value.return,
       );
       const error = threadClient.pauseGrip(
-        (await objClient.getPropertyValue("error")).value.return,
+        (await objClient.getPropertyValue("error", null)).value.return,
       );
 
       assert_response(await context.apply(obj1, [obj1]), {
         return: "correct context",
       });
       assert_response(await context.apply(obj2, [obj2]), {
         return: "correct context",
       });
--- a/devtools/server/tests/unit/test_objectgrips-fn-apply-02.js
+++ b/devtools/server/tests/unit/test_objectgrips-fn-apply-02.js
@@ -30,17 +30,17 @@ async function test_object_grip(debuggee
     Assert.equal(arg1.class, "Object");
 
     await threadClient.pauseGrip(arg1).threadGrip();
     return arg1;
   });
   const objClient = threadClient.pauseGrip(obj);
 
   const method = threadClient.pauseGrip(
-    (await objClient.getPropertyValue("method")).value.return,
+    (await objClient.getPropertyValue("method", null)).value.return,
   );
 
   // Ensure that we actually paused at the `debugger;` line.
   await Promise.all([
     wait_for_pause(threadClient, frame => {
       Assert.equal(frame.where.line, 4);
       Assert.equal(frame.where.column, 8);
     }),
--- a/devtools/server/tests/unit/test_objectgrips-fn-apply-03.js
+++ b/devtools/server/tests/unit/test_objectgrips-fn-apply-03.js
@@ -28,17 +28,17 @@ async function test_object_grip(debuggee
     Assert.equal(arg1.class, "Object");
 
     await threadClient.pauseGrip(arg1).threadGrip();
     return arg1;
   });
   const objClient = threadClient.pauseGrip(obj);
 
   const method = threadClient.pauseGrip(
-    (await objClient.getPropertyValue("method")).value.return,
+    (await objClient.getPropertyValue("method", null)).value.return,
   );
 
   try {
     await method.apply(obj, []);
     Assert.ok(false, "expected exception");
   } catch (err) {
     Assert.equal(err.message, "debugee object is not callable");
   }
--- a/devtools/server/tests/unit/test_objectgrips-property-value-01.js
+++ b/devtools/server/tests/unit/test_objectgrips-property-value-01.js
@@ -94,17 +94,17 @@ async function test_object_grip(debuggee
             type: "object",
             class: "Function",
             name: "method",
           },
         },
       };
 
       for (const [key, expected] of Object.entries(expectedValues)) {
-        const { value } = await objClient.getPropertyValue(key);
+        const { value } = await objClient.getPropertyValue(key, null);
 
         assert_completion(value, expected);
       }
     },
   );
 }
 
 function assert_object_argument(debuggee, threadClient, code, objectHandler) {
--- a/devtools/server/tests/unit/test_objectgrips-property-value-02.js
+++ b/devtools/server/tests/unit/test_objectgrips-property-value-02.js
@@ -35,17 +35,17 @@ async function test_object_grip(debuggee
   });
 
   // Ensure that we actually paused at the `debugger;` line.
   await Promise.all([
     wait_for_pause(threadClient, frame => {
       Assert.equal(frame.where.line, 4);
       Assert.equal(frame.where.column, 8);
     }),
-    objClient.getPropertyValue("prop"),
+    objClient.getPropertyValue("prop", null),
   ]);
 }
 
 function eval_and_resume(debuggee, threadClient, code, callback) {
   return new Promise((resolve, reject) => {
     wait_for_pause(threadClient, callback).then(resolve, reject);
 
     // This synchronously blocks until 'threadClient.resume()' above runs
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-property-value-03.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint-disable no-shadow, max-nested-callbacks */
+
+"use strict";
+
+Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
+registerCleanupFunction(() => {
+  Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
+});
+
+add_task(threadClientTest(async ({ threadClient, debuggee, client }) => {
+  debuggee.eval(function stopMe() {
+    debugger;
+  }.toString());
+
+  await test_object_grip(debuggee, threadClient);
+}));
+
+async function test_object_grip(debuggee, threadClient) {
+  eval_and_resume(
+    debuggee,
+    threadClient,
+    `
+      var obj = {
+        get getter() {
+          return objects.indexOf(this);
+        },
+      };
+      var objects = [obj, {}, [], new Boolean(), new Number(), new String()];
+      stopMe(...objects);
+    `,
+    async frame => {
+      const grips = frame.arguments;
+      const objClient = threadClient.pauseGrip(grips[0]);
+      const classes = ["Object", "Object", "Array", "Boolean", "Number", "String"];
+      for (const [i, grip] of grips.entries()) {
+        Assert.equal(grip.class, classes[i]);
+        await check_getter(objClient, grip.actor, i);
+      }
+      await check_getter(objClient, null, 0);
+      await check_getter(objClient, "invalid receiver actorId", 0);
+    }
+  );
+}
+
+function eval_and_resume(debuggee, threadClient, code, callback) {
+  return new Promise((resolve, reject) => {
+    wait_for_pause(threadClient, callback).then(resolve, reject);
+
+    // This synchronously blocks until 'threadClient.resume()' above runs
+    // because the 'paused' event runs everthing in a new event loop.
+    debuggee.eval(code);
+  });
+}
+
+function wait_for_pause(threadClient, callback = () => {}) {
+  return new Promise((resolve, reject) => {
+    threadClient.addOneTimeListener("paused", function(event, packet) {
+      (async () => {
+        try {
+          return await callback(packet.frame);
+        } finally {
+          await threadClient.resume();
+        }
+      })().then(resolve, reject);
+    });
+  });
+}
+
+async function check_getter(objClient, receiverId, expected) {
+  const {value} = await objClient.getPropertyValue("getter", receiverId);
+  Assert.equal(value.return, expected);
+}
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -160,16 +160,17 @@ reason = bug 1104838
 [test_objectgrips-18.js]
 [test_objectgrips-19.js]
 [test_objectgrips-20.js]
 [test_objectgrips-21.js]
 [test_objectgrips-22.js]
 [test_objectgrips-array-like-object.js]
 [test_objectgrips-property-value-01.js]
 [test_objectgrips-property-value-02.js]
+[test_objectgrips-property-value-03.js]
 [test_objectgrips-fn-apply-01.js]
 [test_objectgrips-fn-apply-02.js]
 [test_objectgrips-fn-apply-03.js]
 [test_promise_state-01.js]
 [test_promise_state-02.js]
 [test_promise_state-03.js]
 [test_interrupt.js]
 [test_stepping-01.js]
--- a/devtools/shared/client/object-client.js
+++ b/devtools/shared/client/object-client.js
@@ -184,21 +184,23 @@ ObjectClient.prototype = {
     type: "property",
     name: arg(0),
   }),
 
   /**
    * Request the value of the object's specified property.
    *
    * @param name string The name of the requested property.
+   * @param receiverId string|null The actorId of the receiver to be used for getters.
    * @param onResponse function Called with the request's response.
    */
   getPropertyValue: DebuggerClient.requester({
     type: "propertyValue",
     name: arg(0),
+    receiverId: arg(1),
   }),
 
   /**
    * Request the prototype of the object.
    *
    * @param onResponse function Called with the request's response.
    */
   getPrototype: DebuggerClient.requester({
--- a/devtools/shared/specs/object.js
+++ b/devtools/shared/specs/object.js
@@ -176,16 +176,17 @@ const objectSpec = generateActorSpec({
       request: {
         name: Arg(0, "string"),
       },
       response: RetVal("object.property"),
     },
     propertyValue: {
       request: {
         name: Arg(0, "string"),
+        receiverId: Arg(1, "nullable:string"),
       },
       response: RetVal("object.propertyValue"),
     },
     apply: {
       request: {
         context: Arg(0, "nullable:json"),
         arguments: Arg(1, "nullable:array:json"),
       },
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -146,17 +146,27 @@ class Animation : public DOMEventTargetH
 
   void UpdatePlaybackRate(double aPlaybackRate);
   void Reverse(ErrorResult& aRv);
 
   bool IsRunningOnCompositor() const;
 
   virtual void Tick();
   bool NeedsTicks() const {
-    return Pending() || PlayState() == AnimationPlayState::Running;
+    return Pending() ||
+           (PlayState() == AnimationPlayState::Running &&
+            // An animation with a zero playback rate doesn't need ticks even if
+            // it is running since it effectively behaves as if it is paused.
+            //
+            // It's important we return false in this case since a zero playback
+            // rate animation in the before or after phase that doesn't fill
+            // won't be relevant and hence won't be returned by GetAnimations().
+            // We don't want its timeline to keep it alive (which would happen
+            // if we return true) since otherwise it will effectively be leaked.
+            PlaybackRate() != 0.0);
   }
 
   /**
    * Set the time to use for starting or pausing a pending animation.
    *
    * Typically, when an animation is played, it does not start immediately but
    * is added to a table of pending animations on the document of its effect.
    * In the meantime it sets its hold time to the time from which playback
--- a/dom/encoding/TextEncoder.cpp
+++ b/dom/encoding/TextEncoder.cpp
@@ -19,18 +19,17 @@ void TextEncoder::Encode(JSContext* aCx,
                          JS::MutableHandle<JSObject*> aRetval,
                          ErrorResult& aRv) {
   // Given nsTSubstring<char16_t>::kMaxCapacity, it should be
   // impossible for the length computation to overflow, but
   // let's use checked math in case someone changes something
   // in the future.
   // Uint8Array::Create takes uint32_t as the length.
   CheckedInt<uint32_t> bufLen(aString.Length());
-  bufLen *= 3;
-  bufLen += 1;  // plus one is part of the contract for ConvertUTF16toUTF8
+  bufLen *= 3;  // from the contract for ConvertUTF16toUTF8
   if (!bufLen.isValid()) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
   auto data = mozilla::MakeUniqueFallible<uint8_t[]>(bufLen.value());
   if (!data) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
--- a/dom/media/mediasink/AudioSink.cpp
+++ b/dom/media/mediasink/AudioSink.cpp
@@ -182,17 +182,19 @@ nsresult AudioSink::InitializeAudioStrea
   if (NS_FAILED(rv)) {
     mAudioStream->Shutdown();
     mAudioStream = nullptr;
     return rv;
   }
 
   // Set playback params before calling Start() so they can take effect
   // as soon as the 1st DataCallback of the AudioStream fires.
-  mAudioStream->SetVolume(aParams.mVolume);
+  if (aParams.mVolume) {
+    mAudioStream->SetVolume(*aParams.mVolume);
+  }
   mAudioStream->SetPlaybackRate(aParams.mPlaybackRate);
   mAudioStream->SetPreservesPitch(aParams.mPreservesPitch);
   return mAudioStream->Start();
 }
 
 TimeUnit AudioSink::GetEndTime() const {
   int64_t written;
   {
--- a/dom/media/mediasink/AudioSinkWrapper.cpp
+++ b/dom/media/mediasink/AudioSinkWrapper.cpp
@@ -24,17 +24,19 @@ void AudioSinkWrapper::Shutdown() {
 const MediaSink::PlaybackParams& AudioSinkWrapper::GetPlaybackParams() const {
   AssertOwnerThread();
   return mParams;
 }
 
 void AudioSinkWrapper::SetPlaybackParams(const PlaybackParams& aParams) {
   AssertOwnerThread();
   if (mAudioSink) {
-    mAudioSink->SetVolume(aParams.mVolume);
+    if (aParams.mVolume) {
+      mAudioSink->SetVolume(*aParams.mVolume);
+    }
     mAudioSink->SetPlaybackRate(aParams.mPlaybackRate);
     mAudioSink->SetPreservesPitch(aParams.mPreservesPitch);
   }
   mParams = aParams;
 }
 
 RefPtr<MediaSink::EndedPromise> AudioSinkWrapper::OnEnded(TrackType aType) {
   AssertOwnerThread();
@@ -91,17 +93,17 @@ TimeUnit AudioSinkWrapper::GetPosition(T
 
 bool AudioSinkWrapper::HasUnplayedFrames(TrackType aType) const {
   AssertOwnerThread();
   return mAudioSink ? mAudioSink->HasUnplayedFrames() : false;
 }
 
 void AudioSinkWrapper::SetVolume(double aVolume) {
   AssertOwnerThread();
-  mParams.mVolume = aVolume;
+  mParams.mVolume = Some(aVolume);
   if (mAudioSink) {
     mAudioSink->SetVolume(aVolume);
   }
 }
 
 void AudioSinkWrapper::SetPlaybackRate(double aPlaybackRate) {
   AssertOwnerThread();
   if (!mAudioEnded) {
@@ -191,16 +193,20 @@ void AudioSinkWrapper::Stop() {
   AssertOwnerThread();
   MOZ_ASSERT(mIsStarted, "playback not started.");
 
   mIsStarted = false;
   mAudioEnded = true;
 
   if (mAudioSink) {
     mAudioSinkEndedPromise.DisconnectIfExists();
+    // Reset volume to signal that it should
+    // not be updated, in case the volume
+    // has been changed outside MediaElement.
+    mParams.mVolume.reset();
     mAudioSink->Shutdown();
     mAudioSink = nullptr;
     mEndedPromise = nullptr;
   }
 }
 
 bool AudioSinkWrapper::IsStarted() const {
   AssertOwnerThread();
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -474,17 +474,17 @@ void DecodedStream::SetPlaying(bool aPla
     return;
   }
 
   mPlaying = aPlaying;
 }
 
 void DecodedStream::SetVolume(double aVolume) {
   AssertOwnerThread();
-  mParams.mVolume = aVolume;
+  mParams.mVolume = Some(aVolume);
 }
 
 void DecodedStream::SetPlaybackRate(double aPlaybackRate) {
   AssertOwnerThread();
   mParams.mPlaybackRate = aPlaybackRate;
 }
 
 void DecodedStream::SetPreservesPitch(bool aPreservesPitch) {
@@ -725,17 +725,18 @@ void DecodedStream::SendData() {
   AssertOwnerThread();
   MOZ_ASSERT(mStartTime.isSome(), "Must be called after StartPlayback()");
 
   // Not yet created on the main thread. MDSM will try again later.
   if (!mData) {
     return;
   }
 
-  SendAudio(mParams.mVolume, mSameOrigin, mPrincipalHandle);
+  MOZ_ASSERT(mParams.mVolume.isSome(), "Volume should exist at that point");
+  SendAudio(mParams.mVolume.value(), mSameOrigin, mPrincipalHandle);
   SendVideo(mSameOrigin, mPrincipalHandle);
 }
 
 TimeUnit DecodedStream::GetEndTime(TrackType aType) const {
   AssertOwnerThread();
   if (aType == TrackInfo::kAudioTrack && mInfo.HasAudio() && mData) {
     auto t = mStartTime.ref() +
              FramesToTimeUnit(mData->mAudioFramesWritten, mInfo.mAudio.mRate);
--- a/dom/media/mediasink/MediaSink.h
+++ b/dom/media/mediasink/MediaSink.h
@@ -34,18 +34,18 @@ class TimeStamp;
  */
 class MediaSink {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaSink);
   typedef mozilla::TrackInfo::TrackType TrackType;
 
   struct PlaybackParams {
     PlaybackParams()
-        : mVolume(1.0), mPlaybackRate(1.0), mPreservesPitch(true) {}
-    double mVolume;
+        : mVolume(Some(1.0)), mPlaybackRate(1.0), mPreservesPitch(true) {}
+    Maybe<double> mVolume;
     double mPlaybackRate;
     bool mPreservesPitch;
     RefPtr<AudioDeviceInfo> mSink;
   };
 
   // Return the playback parameters of this sink.
   // Can be called in any state.
   virtual const PlaybackParams& GetPlaybackParams() const = 0;
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -601,22 +601,21 @@ nsresult MediaEngineWebRTCMicrophoneSour
 
   if (mState == kStopped) {
     // Already stopped - this is allowed
     return NS_OK;
   }
 
   RefPtr<MediaEngineWebRTCMicrophoneSource> that = this;
   NS_DispatchToMainThread(
-      media::NewRunnableFrom([that, stream = mStream, track = mTrackID]() {
+      media::NewRunnableFrom([that, stream = mStream]() {
         if (stream->IsDestroyed()) {
           return NS_OK;
         }
 
-        stream->SetPullingEnabled(track, false);
         stream->GraphImpl()->AppendMessage(MakeUnique<StartStopMessage>(
             that->mInputProcessing, StartStopMessage::Stop));
         CubebUtils::AudioDeviceID deviceID = that->mDeviceInfo->DeviceID();
         Maybe<CubebUtils::AudioDeviceID> id = Some(deviceID);
         stream->CloseAudioInput(id, that->mInputProcessing);
 
         return NS_OK;
       }));
--- a/gfx/layers/apz/test/mochitest/helper_long_tap.html
+++ b/gfx/layers/apz/test/mochitest/helper_long_tap.html
@@ -4,61 +4,88 @@
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width; initial-scale=1.0">
   <title>Ensure we get a touch-cancel after a contextmenu comes up</title>
   <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
   <script type="application/javascript" src="apz_test_utils.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
   <script type="application/javascript">
 
+function addMouseEventListeners(aTarget) {
+  aTarget.addEventListener("mousemove", recordEvent, true);
+  aTarget.addEventListener("mouseover", recordEvent, true);
+  aTarget.addEventListener("mouseenter", recordEvent, true);
+  aTarget.addEventListener("mouseout", recordEvent, true);
+  aTarget.addEventListener("mouseleave", recordEvent, true);
+}
+
+function removeMouseEventListeners(aTarget) {
+  aTarget.removeEventListener("mousemove", recordEvent, true);
+  aTarget.removeEventListener("mouseover", recordEvent, true);
+  aTarget.removeEventListener("mouseenter", recordEvent, true);
+  aTarget.removeEventListener("mouseout", recordEvent, true);
+  aTarget.removeEventListener("mouseleave", recordEvent, true);
+}
+
 function longPressLink() {
-  synthesizeNativeTouch(document.getElementById("b"), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, function() {
+  let target = document.getElementById("b");
+  addMouseEventListeners(target);
+  synthesizeNativeTouch(target, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, function() {
     dump("Finished synthesizing touch-start, waiting for events...\n");
   });
 }
 
 var eventsFired = 0;
 function recordEvent(e) {
+  let target = document.getElementById("b");
   if (getPlatform() == "windows") {
     // On Windows we get a mouselongtap event once the long-tap has been detected
     // by APZ, and that's what we use as the trigger to lift the finger. That then
     // triggers the contextmenu. This matches the platform convention.
     switch (eventsFired) {
       case 0: is(e.type, "touchstart", "Got a touchstart"); break;
       case 1:
         is(e.type, "mouselongtap", "Got a mouselongtap");
         synthesizeNativeTouch(document.getElementById("b"), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE);
         break;
       case 2: is(e.type, "touchend", "Got a touchend"); break;
-      case 3: is(e.type, "contextmenu", "Got a contextmenu"); e.preventDefault(); break;
+      case 3: is(e.type, "mouseover", "Got a mouseover"); break;
+      case 4: is(e.type, "mouseenter", "Got a mouseenter"); break;
+      case 5: is(e.type, "mousemove", "Got a mousemove"); break;
+      case 6: is(e.type, "contextmenu", "Got a contextmenu"); e.preventDefault(); break;
       default: ok(false, "Got an unexpected event of type " + e.type); break;
     }
     eventsFired++;
 
-    if (eventsFired == 4) {
+    if (eventsFired == 7) {
+      removeMouseEventListeners(target);
       dump("Finished waiting for events, doing an APZ flush to see if any more unexpected events come through...\n");
       flushApzRepaints(function() {
         dump("Done APZ flush, ending test...\n");
         subtestDone();
       });
     }
   } else {
     // On non-Windows platforms we get a contextmenu event once the long-tap has
     // been detected. Since we prevent-default that, we don't get a mouselongtap
     // event at all, and instead get a touchcancel.
     switch (eventsFired) {
       case 0: is(e.type, "touchstart", "Got a touchstart"); break;
-      case 1: is(e.type, "contextmenu", "Got a contextmenu"); e.preventDefault(); break;
-      case 2: is(e.type, "touchcancel", "Got a touchcancel"); break;
+      case 1: is(e.type, "mouseover", "Got a mouseover"); break;
+      case 2: is(e.type, "mouseenter", "Got a mouseenter"); break;
+      case 3: is(e.type, "mousemove", "Got a mousemove"); break;
+      case 4: is(e.type, "contextmenu", "Got a contextmenu"); e.preventDefault(); break;
+      case 5: is(e.type, "touchcancel", "Got a touchcancel"); break;
       default: ok(false, "Got an unexpected event of type " + e.type); break;
     }
     eventsFired++;
 
-    if (eventsFired == 3) {
-      synthesizeNativeTouch(document.getElementById("b"), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, function() {
+    if (eventsFired == 6) {
+      removeMouseEventListeners(target);
+      synthesizeNativeTouch(target, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, function() {
         dump("Finished synthesizing touch-end, doing an APZ flush to see if any more unexpected events come through...\n");
         flushApzRepaints(function() {
           dump("Done APZ flush, ending test...\n");
           subtestDone();
         });
       });
     }
   }
--- a/gfx/layers/apz/util/APZEventState.cpp
+++ b/gfx/layers/apz/util/APZEventState.cpp
@@ -210,16 +210,27 @@ void APZEventState::ProcessSingleTap(con
     callback->ClearTimer();
   }
 }
 
 bool APZEventState::FireContextmenuEvents(
     const nsCOMPtr<nsIPresShell>& aPresShell, const CSSPoint& aPoint,
     const CSSToLayoutDeviceScale& aScale, Modifiers aModifiers,
     const nsCOMPtr<nsIWidget>& aWidget) {
+  // Synthesize mousemove event for allowing users to emulate to move mouse
+  // cursor over the element.  As a result, users can open submenu UI which
+  // is opened when mouse cursor is moved over a link (i.e., it's a case that
+  // users cannot stay in the page after tapping it).  So, this improves
+  // accessibility in websites which are designed for desktop.
+  // Note that we don't need to check whether mousemove event is consumed or
+  // not because Chrome also ignores the result.
+  APZCCallbackHelper::DispatchSynthesizedMouseEvent(
+      eMouseMove, 0 /* time */, aPoint * aScale, aModifiers, 0 /* clickCount */,
+      aWidget);
+
   // Converting the modifiers to DOM format for the DispatchMouseEvent call
   // is the most useless thing ever because nsDOMWindowUtils::SendMouseEvent
   // just converts them back to widget format, but that API has many callers,
   // including in JS code, so it's not trivial to change.
   bool eventHandled = APZCCallbackHelper::DispatchMouseEvent(
       aPresShell, NS_LITERAL_STRING("contextmenu"), aPoint, 2, 1,
       WidgetModifiersToDOMModifiers(aModifiers), true,
       dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH,
--- a/gfx/webrender_bindings/README.webrender
+++ b/gfx/webrender_bindings/README.webrender
@@ -17,158 +17,8 @@ 4. Verify WebRender is enabled. You can 
 When making changes:
     - Make the changes you want.
     - Run |mach build| or |mach build binaries| as desired.
 
 For a debug webrender build:
     Use a debug mozconfig (ac_add_options --enable-debug)
     You can also use an opt build but make webrender less optimized by putting opt-level=0 in the [profile.release] section of your toolkit/library/rust/Cargo.toml file
     See also https://groups.google.com/forum/#!topic/mozilla.dev.servo/MbeMcqqO1fs
-
---------------------------------------------------------------------------------
-
-What if you have to pull in an update to webrender itself? You have two options,
-listed below. Both options will give you a set of patches and the ability to do
-try pushes to verify the update. After that, continue with the steps below to
-actually land the update into the tree.
-
-Option A:
-   Use a script to do the update for you. This will usually work, if you satisfy
-   all the assumptions the script is making. The script can be found at
-   https://github.com/staktrace/wrupdater/blob/master/try-latest-webrender.sh
-   and contains documentation on how to use it. Read the documentation carefully
-   before trying to use it.
-
-Option B:
-   Do the update manually. This is a little more cumbersome but may be required
-   if the script doesn't work or the repos are in a state that violates hidden
-   assumptions in the script (e.g. if the webrender_bindings/Cargo.toml file is
-   no longer in the format expected by the script). The steps to do this are,
-   roughly:
-   - Update your mozilla-central checkout to the latest code on mozilla-central.
-   - Check out and update the webrender repo to the version you want
-   - Copy over the webrender repo to gfx/wr. The easiest way to do this is
-     simply delete gfx/wr, and use |cp -R| to copy it back, and then delete the
-     gfx/wr/.git/ subfolder. Update the revision in
-     gfx/webrender_bindings/revision.txt file with the git changeset hash.
-   - If you need to modify webrender_bindings/Cargo.toml file, do so now. Changes
-     at this step usually consist of:
-     (a) Updating version numbers. Go through the version numbers of ALL the
-         dependencies in the Cargo.toml file (webrender, euclid, etc.) and make
-         sure the version numbers listed match what's in the new
-         gfx/wr/webrender/Cargo.toml and gfx/wr/webrender_api/Cargo.toml files.
-     (b) Turning on or off any new features that were added in upstream WR. This
-         used to happen a lot but is pretty rare now.
-   - Go to toolkit/library/rust and run |cargo update -p webrender -p webrender_api|.
-     If it complains about version numbers of other crates not lining up, add those
-     as well, e.g. |cargo update -p webrender -p webrender_api -p gleam -p euclid|.
-     You may need to do this a few times until you get all the crates to make it
-     happy.
-   - Run the same cargo update command from the previous step in the
-     toolkit/library/gtest/rust folder.
-   - Commit your changes locally. You'll need to do this before the next step or
-     it will complain.
-   - At the top of the tree, run |mach vendor rust| to update the rust
-     dependencies in third_party/rust.
-   - Commit your changes locally.
-   - Build and test. You may need to make changes in bindings.rs or on the C++
-     side depending on what changed in webrender. This can potentially be quite
-     tricky if you don't fully understand the API changes on the webrender side.
-     Get help if you need it. For simplicity in bisecting, try to not use your
-     new features yet, just get the build working with the minimal changes.
-   - Commit any changes from the previous step, and do a try push to make sure
-     everything is good. Generally we do two try pushes, one for builds and
-     linux tests. This should be totally green. The other forces WR enabled on
-     Windows and runs reftests, which currently fails. However if it fails with
-     more than just regular reftest failures (e.g. it crashes or has an assertion
-     failure) then that's potentially going to be a problem for Windows users
-     running WebRender and will need investigation.
-   - You now have an updated webrender, so you can land it or write gecko
-     code against the new features.
-
-Once you have followed either Option A or Option B and have a good update, you
-might want to land it in the tree. To do this:
-- Find the current wr-future-update bug, by going to https://bugzil.la/wr-future-update
-- Clone this bug (there is a little dropdown in the bottom right corner of the
-  page which gives you an option to "Create a new bug ... as a clone of this bug").
-- This will take you to a bug entry page with some stuff prepopulated. Do NOT
-  submit it yet, but make the following changes:
-  (a) Modify the "Description" to remove the SECOND instance of the text "+++ This
-      bug was initially created as a clone of ... +++". Keep the first instance
-      as it points to the bug you just cloned, and keep the rest of the text unless
-      you feel it needs changing.
-  (b) Add wr-future-update into the "Alias" field
-  (c) Clear the bugs in the "Depends on" field
-  (d) For each bug in the "Blocks" field, except for 1311790 and 1386670, go
-      to the bug and check the "See Also" link for the corresponding WR issue/PR,
-      if any. If there is a WR issue that is not yet resolved in the update you
-      are landing, leave the bug in the "Blocks" field of your clone. In a later
-      step you will remove the dependency from the update you are landing. At
-      end of this step the "Blocks" field should contain 1311790, 1386670, and
-      any bugs tracking upstream WR issues that are not fixed in the update.
-  (e) You still cannot submit the clone as a new bug, because you can't have two
-      bugs in the system with the same alias. So hold on a sec.
-- Go back to the tab with the current wr-future-update bug, and click on the edit
-  button. Make the following changes:
-  (a) Assign the bug to yourself.
-  (b) Clear the "Alias" field.
-  (c) Remove bugs from the "Blocks" field that you kept in step (d), other than
-      1311790 and 1386670. In other words, update the "Blocks" field so that it
-      contains 1311790, 1386670, and any bugs that are actually fixed by the
-      update.
-  (d) Submit your changes to this bug.
-- Now you can submit your changes to the clone bug which will create a new
-  wr-future-update bug.
-- Update your patch queue so that the patches are properly formatted with
-  bug number, reviewer, etc. and push to MozReview. This is kind of important,
-  because you want these patches to land on autoland rather than inbound. If it
-  lands on inbound there's a high chance of it conflicting with the servo-vcs-sync
-  bot that is regularly pushing to autoland, and then you'll only find out about
-  it when the sheriff tries to do a merge and backs you out. If you push to
-  autoland you're likely to find out about the problem at push time, when the
-  patches won't rebase.
-
-
-Troubleshooting tips:
-
-1. Note that when webrender is built as part of gecko, it may end up using slightly
-   different versions of its dependencies than when it is built standalone from the
-   webrender repo. The reason is that the Cargo.lock files in m-c and in the WR
-   repo may reference different versions of the dependencies. Both builds will be
-   compatible in terms of semantic versioning, but may produce different results -
-   for example the standalone webrender might use euclid 0.10.4 while the
-   one in gecko uses euclid 0.10.3. Although both choices are "valid" per
-   the semantic versioning rules in webrender's Cargo.toml, the 0.2.3 may provide
-   a bugfix that is needed for correct behaviour in webrender. If this is the case,
-   the technically "correct" fix is to change the upstream webrender Cargo.toml
-   file to require the correct version. Alternnatively, you can update the
-   Cargo.lock files in m-c to pull in the new version. The way to do this is as
-   follows:
-   - Go to toolkit/library/rust and run |cargo update -p <package> --precise <version>|.
-     Repeat this for as many libraries as you need to update. Run the same commands
-     in toolkit/library/gtest/rust and js/src (ignore any errors about unmatched
-     packages). Commit all the changes locally.
-   - Run |mach vendor rust|, which will update the corresponding libraries in
-     third_party/rust to the versions you specified.
-   The reason we don't do this by default is to work around bug 1336528. Specifically,
-   there is another crate in m-c called mozjs_sys which is built separately but uses
-   the same folder to store its rust dependencies. If one of the libraries that is
-   required by both mozjs_sys and webrender is updated without updating the other
-   project's Cargo.lock file, that results in build bustage.
-   This means that any time you do this sort of manual update of packages, you need
-   to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
-   the need to run the cargo update command in js/src as well. Hopefully this will
-   be resolved soon.
-
-2. Sometimes autoland tip has changed enough from mozilla-central (because of the
-   servo vcs-sync-bot, which will sync servo into m-c and often re-vendor third-
-   party rust dependencies) that trying to land an update based on mozilla-central
-   will not work well. As in, you'll get conflicts in Cargo.lock files or in the
-   third_party/rust directory. This is best handled by running your update steps
-   on top of autoland tip rather than central. (The script-based update in option A
-   has an env var you can set to do this). In theory you can get the same
-   result by resolving the conflict manually but Cargo.lock files are usually not
-   trivial to merge by hand. If it's just the third_party/rust dir that has conflicts
-   you can delete it and run |mach vendor rust| again to repopulate it.
-
--------------------------------------------------------------------------------
-
-The current WebRender revision can be found in gfx/webrender_bindings/revision.txt file.
deleted file mode 100644
--- a/gfx/webrender_bindings/revision.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-d3edc30cf95d3c96fd8308969b22062698a0f6ce
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1694,18 +1694,16 @@ pub extern "C" fn wr_api_capture(
 
     // Use warn! so that it gets emitted to logcat on android as well
     let border = "--------------------------\n";
     warn!("{} Capturing WR state to: {:?}\n{}", &border, &path, &border);
 
     let _ = create_dir_all(&path);
     match File::create(path.join("wr.txt")) {
         Ok(mut file) => {
-            let revision = include_bytes!("../revision.txt");
-            file.write(revision).unwrap();
             // The Gecko HG revision is available at compile time
             if let Some(moz_revision) = option_env!("GECKO_HEAD_REV") {
                 writeln!(file, "mozilla-central {}", moz_revision).unwrap();
             }
         }
         Err(e) => {
             warn!("Unable to create path '{:?}' for capture: {:?}", path, e);
             return
--- a/gfx/wr/README.md
+++ b/gfx/wr/README.md
@@ -1,11 +1,22 @@
 # WebRender
 GPU renderer for the Web content, used by Servo.
 
+Note that the canonical home for this code is in gfx/wr folder of the
+mozilla-central repository at https://hg.mozilla.org/mozilla-central. The
+Github repository at https://github.com/servo/webrender should be considered
+a downstream mirror, although it contains additional metadata (such as Github
+wiki pages) that do not exist in mozilla-central. Pull requests against the
+Github repository are still being accepted, although once reviewed, they will
+be landed on mozilla-central first and then mirrored back. If you are familiar
+with the mozilla-central contribution workflow, filing bugs in
+[Bugzilla](https://bugzilla.mozilla.org/enter_bug.cgi?product=Core&component=Graphics%3A%20WebRender)
+and submitting patches there would be preferred.
+
 ## Update as a Dependency
 After updating shaders in WebRender, go to servo and:
 
   * Go to the servo directory and do ./mach update-cargo -p webrender
   * Create a pull request to servo
 
 
 ## Use WebRender with Servo
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -2261,24 +2261,26 @@ PresShell::LineMove(bool aForward, bool 
 NS_IMETHODIMP
 PresShell::IntraLineMove(bool aForward, bool aExtend) {
   RefPtr<nsFrameSelection> frameSelection = mSelection;
   return frameSelection->IntraLineMove(aForward, aExtend);
 }
 
 NS_IMETHODIMP
 PresShell::PageMove(bool aForward, bool aExtend) {
-  nsIFrame* frame;
+  nsIFrame* frame = nullptr;
   if (!aExtend) {
     frame = do_QueryFrame(GetScrollableFrameToScroll(nsIPresShell::eVertical));
-  } else {
-    frame = mSelection->GetFrameToPageSelect();
+    // If there is no scrollable frame, get the frame to move caret instead.
   }
   if (!frame) {
-    return NS_OK;
+    frame = mSelection->GetFrameToPageSelect();
+    if (!frame) {
+      return NS_OK;
+    }
   }
   RefPtr<nsFrameSelection> frameSelection = mSelection;
   frameSelection->CommonPageMove(aForward, aExtend, frame);
   // After ScrollSelectionIntoView(), the pending notifications might be
   // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
   return ScrollSelectionIntoView(
       nsISelectionController::SELECTION_NORMAL,
       nsISelectionController::SELECTION_FOCUS_REGION,
--- a/layout/base/tests/mochitest.ini
+++ b/layout/base/tests/mochitest.ini
@@ -146,28 +146,28 @@ support-files = bug1226904.html
 [test_bug1246622.html]
 [test_bug1278021.html]
 [test_emulateMedium.html]
 [test_event_target_iframe_oop.html]
 skip-if = e10s # bug 1020135, nested oop iframes not supported
 support-files = bug921928_event_target_iframe_apps_oop.html
 [test_event_target_radius.html]
 skip-if = toolkit == 'android' # Bug 1355836
-[test_expanding_selection_per_page.html]
-support-files = window_empty_document.html
 [test_flush_on_paint.html]
 skip-if = true # Bug 688128
 [test_frame_reconstruction_for_pseudo_elements.html]
 [test_frame_reconstruction_for_svg_transforms.html]
 [test_frame_reconstruction_scroll_restore.html]
 [test_getBoxQuads_convertPointRectQuad.html]
 support-files =
   file_getBoxQuads_convertPointRectQuad_frame1.html
   file_getBoxQuads_convertPointRectQuad_frame2.html
 [test_getClientRects_emptytext.html]
+[test_moving_and_expanding_selection_per_page.html]
+support-files = window_empty_document.html
 [test_mozPaintCount.html]
 skip-if = toolkit == 'android' # android: Requires plugin support
 [test_preserve3d_sorting_hit_testing.html]
 support-files = preserve3d_sorting_hit_testing_iframe.html
 [test_preserve3d_sorting_hit_testing2.html]
 support-files = preserve3d_sorting_hit_testing2_iframe.html
 [test_reftests_with_caret.html]
 skip-if = toolkit == 'android' # Bug 1355842
rename from layout/base/tests/test_expanding_selection_per_page.html
rename to layout/base/tests/test_moving_and_expanding_selection_per_page.html
--- a/layout/base/tests/test_expanding_selection_per_page.html
+++ b/layout/base/tests/test_moving_and_expanding_selection_per_page.html
@@ -18,24 +18,21 @@ addLoadEvent(() => {
 async function doTests(aWindow) {
   const IS_WIN = navigator.platform.includes("Win");
   // On macOS and Linux, Shift + PageUp/PageDown requires native event to
   // resolve default action of PageDown and PageUp. Although macOS widget has
   // nsIWidget::AttachNativeKeyEvent(), we cannot use synthesizeKey() for the
   // following tests.  So, use nsISelectionController.pageMove() instead on
   // non-Windows platforms.
   const kUseKeyboardEvent = IS_WIN;
-  let selectionController;
-  if (!kUseKeyboardEvent) {
-    selectionController = SpecialPowers.wrap(aWindow)
-                                       .docShell
-                                       .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
-                                       .getInterface(SpecialPowers.Ci.nsISelectionDisplay)
-                                       .QueryInterface(SpecialPowers.Ci.nsISelectionController);
-  }
+  let selectionController = SpecialPowers.wrap(aWindow)
+                                         .docShell
+                                         .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+                                         .getInterface(SpecialPowers.Ci.nsISelectionDisplay)
+                                         .QueryInterface(SpecialPowers.Ci.nsISelectionController);
   // On Windows, per-page selection to start or end expands selection to same
   // column of first or last line.  On the other platforms, it expands selection
   // to start or end of first or last line.
   const kSelectToStartOrEnd = !IS_WIN;
 
   await SpecialPowers.pushPrefEnv({"set": [["general.smoothScroll", false]]});
   await SimpleTest.promiseFocus(aWindow);
 
@@ -54,258 +51,307 @@ async function doTests(aWindow) {
         return `text node in ${getElementDescription(aNode.parentElement)}`;
       case aNode.ELEMENT_NODE:
         return getElementDescription(aNode);
       default:
         return "unknown node";
     }
   }
 
-  function doSelectPageDown() {
-    if (kUseKeyboardEvent) {
-      synthesizeKey("KEY_PageDown", {shiftKey: true}, aWindow);
+  function doTest(aExpandSelection) {
+    // Note that when neither editor has focus nor in caret mode, key navigation
+    // does not call nsISelectionController::PageMove().  Therefore, in such
+    // cases, you need to call doPageDown() and doPageUp() with setting true
+    // to aUseSelectionController.
+    function doPageDown(aUseSelectionController) {
+      if (kUseKeyboardEvent && !aUseSelectionController) {
+        synthesizeKey("KEY_PageDown", {shiftKey: aExpandSelection}, aWindow);
+      } else {
+        selectionController.pageMove(true, aExpandSelection);
+      }
+    }
+
+    function doPageUp(aUseSelectionController) {
+      if (kUseKeyboardEvent && !aUseSelectionController) {
+        synthesizeKey("KEY_PageUp", {shiftKey: aExpandSelection}, aWindow);
+      } else {
+        selectionController.pageMove(false, aExpandSelection);
+      }
+    }
+
+    let doc = aWindow.document;
+    let body = doc.body;
+    let selection = doc.getSelection();
+    let container;
+
+    body.innerHTML = '<span id="s1">first line</span><br>' +
+                     '<span id="s2">second line</span><br>' +
+                     '<span id="s3">last line</span>';
+    container = doc.documentElement;
+
+    let description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in non-scrollable body: `;
+    is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
+    selection.collapse(doc.getElementById("s1").firstChild, 3);
+    doPageDown(!aExpandSelection);
+    is(container.scrollTop, 0, description + "this test shouldn't create scrollable document");
+    let range = selection.getRangeAt(0);
+    if (aExpandSelection) {
+      is(range.startContainer, doc.getElementById("s1").firstChild,
+         `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
+      is(range.startOffset, 3,
+         `${description} selection should be expanded from the first line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+    is(range.endContainer, doc.getElementById("s3").firstChild,
+       `${description} selection should be expanded into the last line (got: ${getNodeDescription(range.endContainer)})`);
+    if (kSelectToStartOrEnd) {
+      is(range.endOffset, range.endContainer.length,
+         `${description} selection should be expanded to end of the last line`);
     } else {
-      selectionController.pageMove(true, true);
+      isfuzzy(range.endOffset, 3, 2,
+              `${description} selection should be expanded to around the last line's 3rd insertion point`);
+    }
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in non-scrollable body: `;
+    selection.collapse(doc.getElementById("s3").firstChild, 3);
+    doPageUp(!aExpandSelection);
+    is(container.scrollTop, 0, description + "this test shouldn't create scrollable document");
+    range = selection.getRangeAt(0);
+    is(range.startContainer, doc.getElementById("s1").firstChild,
+       `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
+    if (kSelectToStartOrEnd) {
+      is(range.startOffset, 0,
+         `${description} selection should be expanded to start of the first line`);
+    } else {
+      isfuzzy(range.startOffset, 3, 2,
+              `${description} selection should be expanded to around the first line's 3rd insertion point`);
+    }
+    if (aExpandSelection) {
+      is(range.endContainer, doc.getElementById("s3").firstChild,
+         `${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
+      is(range.endOffset, 3,
+         `${description} selection should be expanded from the last line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+
+    body.innerHTML = '<span id="s1">first line in the body</span>' +
+                       '<div id="d1" style="height: 2em; line-height: 1em; overflow: auto;">' +
+                         '<span id="s2">first line</span><br>' +
+                         '<span id="s3">second line</span><br>' +
+                         '<span id="s4">third line</span><br>' +
+                         '<span id="s5">last line</span>' +
+                       "</div>" +
+                     '<span id="s6">last line in the body</span>';
+    container = doc.getElementById("d1");
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in scrollable area in the body: `;
+    is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
+    selection.collapse(doc.getElementById("s2").firstChild, 3);
+    doPageDown(!aExpandSelection);
+    isnot(container.scrollTop, 0, description + "should be scrolled down");
+    range = selection.getRangeAt(0);
+    if (aExpandSelection) {
+      is(range.startContainer, doc.getElementById("s2").firstChild,
+         `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
+      is(range.startOffset, 3,
+         `${description} selection should be expanded from the first line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+    is(range.endContainer, doc.getElementById("s4").firstChild,
+       `${description} selection should be expanded into the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
+    isfuzzy(range.endOffset, 3, 2,
+            `${description} selection should be expanded to around the 3rd line's 3rd insertion point`);
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in scrollable area in the body: `;
+    selection.collapse(doc.getElementById("s4").firstChild, 3);
+    let previousScrollTop = container.scrollTop;
+    doPageUp(!aExpandSelection);
+    ok(container.scrollTop < previousScrollTop, description + "should be scrolled up");
+    range = selection.getRangeAt(0);
+    is(range.startContainer, doc.getElementById("s2").firstChild,
+       `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
+    isfuzzy(range.startOffset, 3, 2,
+            `${description} selection should be expanded to around the first line's 3rd insertion point`);
+    if (aExpandSelection) {
+      is(range.endContainer, doc.getElementById("s4").firstChild,
+         `${description} selection should be expanded from the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
+      is(range.endOffset, 3,
+         `${description} selection should be expanded from the 3rd line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
     }
-  }
+
+    body.innerHTML = '<span id="s1">first line in the body</span>' +
+                       '<div id="d1" contenteditable style="height: 2em; line-height: 1em; overflow: auto;">' +
+                         '<span id="s2">first line</span><br>' +
+                         '<span id="s3">second line</span><br>' +
+                         '<span id="s4">third line</span><br>' +
+                         '<span id="s5">last line</span>' +
+                       "</div>" +
+                     '<span id="s6">last line in the body</span>';
+    container = doc.getElementById("d1");
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in scrollable editable div in the body: `;
+    is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
+    selection.collapse(doc.getElementById("s2").firstChild, 3);
+    doPageDown();
+    isnot(container.scrollTop, 0, description + "should be scrolled down");
+    range = selection.getRangeAt(0);
+    if (aExpandSelection) {
+      is(range.startContainer, doc.getElementById("s2").firstChild,
+         `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
+      is(range.startOffset, 3,
+         `${description} selection should be expanded from the first line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+    is(range.endContainer, doc.getElementById("s4").firstChild,
+       `${description} selection should be expanded into the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
+    isfuzzy(range.endOffset, 3, 2,
+            `${description} selection should be expanded to around the 3rd line's 3rd insertion point`);
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in scrollable editable div in the body: `;
+    selection.collapse(doc.getElementById("s4").firstChild, 3);
+    previousScrollTop = container.scrollTop;
+    doPageUp();
+    ok(container.scrollTop < previousScrollTop, description + "should be scrolled up");
+    range = selection.getRangeAt(0);
+    is(range.startContainer, doc.getElementById("s2").firstChild,
+       `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
+    isfuzzy(range.startOffset, 3, 2,
+            `${description} selection should be expanded to around the first line's 3rd insertion point`);
+    if (aExpandSelection) {
+      is(range.endContainer, doc.getElementById("s4").firstChild,
+         `${description} selection should be expanded from the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
+      is(range.endOffset, 3,
+         `${description} selection should be expanded from the 3rd line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+
+    body.innerHTML = '<span id="s1">first line in the body</span>' +
+                       '<div id="d1" contenteditable>' +
+                         '<span id="s2">first line</span><br>' +
+                         '<span id="s3">second line</span><br>' +
+                         '<span id="s4">third line</span><br>' +
+                         '<span id="s5">last line</span>' +
+                       "</div>" +
+                     '<span id="s6">last line in the body</span>';
+    container = doc.getElementById("d1");
 
-  function doSelectPageUp() {
-    if (kUseKeyboardEvent) {
-      synthesizeKey("KEY_PageUp", {shiftKey: true}, aWindow);
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in non-scrollable editable div in the body: `;
+    is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
+    selection.collapse(doc.getElementById("s2").firstChild, 3);
+    doPageDown();
+    is(container.scrollTop, 0, description + "editable div shouldn't be scrollable");
+    range = selection.getRangeAt(0);
+    if (aExpandSelection) {
+      is(range.startContainer, doc.getElementById("s2").firstChild,
+         `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
+      is(range.startOffset, 3,
+         `${description} selection should be expanded from the first line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+    is(range.endContainer, doc.getElementById("s5").firstChild,
+       `${description} selection should be expanded into the last line (got: ${getNodeDescription(range.endContainer)})`);
+    if (kSelectToStartOrEnd) {
+      is(range.endOffset, range.endContainer.length,
+         `${description} selection should be expanded to end of the last line`);
+    } else {
+      isfuzzy(range.endOffset, 3, 2,
+              `${description} selection should be expanded to around the last line's 3rd insertion point`);
+    }
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in non-scrollable editable div in the body: `;
+    selection.collapse(doc.getElementById("s5").firstChild, 3);
+    doPageUp();
+    is(container.scrollTop, 0, description + "editable div shouldn't be scrollable");
+    range = selection.getRangeAt(0);
+    is(range.startContainer, doc.getElementById("s2").firstChild,
+       `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
+    if (kSelectToStartOrEnd) {
+      is(range.startOffset, 0,
+         `${description} selection should be expanded to start of the first line`);
+    } else {
+      isfuzzy(range.startOffset, 3, 2,
+              `${description} selection should be expanded to around the first line's 3rd insertion point`);
+    }
+    if (aExpandSelection) {
+      is(range.endContainer, doc.getElementById("s5").firstChild,
+         `${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
+      is(range.endOffset, 3,
+         `${description} selection should be expanded from the last line's 3rd insertion point`);
     } else {
-      selectionController.pageMove(false, true);
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+
+    body.innerHTML = '<span id="s1">first line in the body</span>' +
+                       '<div id="d1" contenteditable>' +
+                         '<span id="s2">first editable line</span><br>' +
+                         '<div id="d2" style="height: 3em; line-height: 1em; overflow: auto;">' +
+                           '<span id="s3">first line</span><br>' +
+                           '<span id="s4">second line</span>' +
+                         "</div>" +
+                         '<span id="s5">last editable line</span>' +
+                       "</div>" +
+                     '<span id="s6">last line in the body</span>';
+    container = doc.getElementById("d2");
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in scrollable div (but not scrollable along y-axis) in the editable div: `;
+    is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
+    selection.collapse(doc.getElementById("s3").firstChild, 3);
+    doPageDown();
+    is(container.scrollTop, 0, description + "scrollable div in the editable div (but not scrollable along y-axis) shouldn't be scrollable");
+    range = selection.getRangeAt(0);
+    if (aExpandSelection) {
+      is(range.startContainer, doc.getElementById("s3").firstChild,
+         `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
+      is(range.startOffset, 3,
+         `${description} selection should be expanded from the first line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+    is(range.endContainer, doc.getElementById("s5").firstChild,
+       `${description} selection should be expanded into the last editable line (got: ${getNodeDescription(range.endContainer)})`);
+    if (kSelectToStartOrEnd) {
+      is(range.endOffset, range.endContainer.length,
+         `${description} selection should be expanded to end of the last editable line`);
+    } else {
+      isfuzzy(range.endOffset, 3, 2,
+              `${description} selection should be expanded to around the last editable line's 3rd insertion point`);
+    }
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in scrollable div (but not scrollable along y-axis) in the editable div: `;
+    selection.collapse(doc.getElementById("s4").firstChild, 3);
+    doPageUp();
+    is(container.scrollTop, 0, description + "scrollable div (but not scrollable along y-axis) in the editable div shouldn't be scrollable");
+    range = selection.getRangeAt(0);
+    is(range.startContainer, doc.getElementById("s2").firstChild,
+       `${description} selection should be expanded into the first editable line (got: ${getNodeDescription(range.startContainer)})`);
+    if (kSelectToStartOrEnd) {
+      is(range.startOffset, 0,
+         `${description} selection should be expanded to start of the first editable line`);
+    } else {
+      isfuzzy(range.startOffset, 3, 2,
+              `${description} selection should be expanded to around the first editable line's 3rd insertion point`);
+    }
+    if (aExpandSelection) {
+      is(range.endContainer, doc.getElementById("s4").firstChild,
+         `${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
+      is(range.endOffset, 3,
+         `${description} selection should be expanded from the last line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
     }
   }
 
-  let doc = aWindow.document;
-  let body = doc.body;
-  let selection = doc.getSelection();
-  let container;
-
-  body.innerHTML = '<span id="s1">first line</span><br>' +
-                   '<span id="s2">second line</span><br>' +
-                   '<span id="s3">last line</span>';
-  container = doc.documentElement;
-
-  let description = "Expanding selection to forward in non-scrollable body: ";
-  is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
-  selection.collapse(doc.getElementById("s1").firstChild, 3);
-  doSelectPageDown();
-  is(container.scrollTop, 0, description + "this test shouldn't create scrollable document");
-  let range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s1").firstChild,
-     `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
-  is(range.startOffset, 3,
-     `${description} selection should be expanded from the first line's 3rd insertion point`);
-  is(range.endContainer, doc.getElementById("s3").firstChild,
-     `${description} selection should be expanded into the last line (got: ${getNodeDescription(range.endContainer)})`);
-  if (kSelectToStartOrEnd) {
-    is(range.endOffset, range.endContainer.length,
-       `${description} selection should be expanded to end of the last line`);
-  } else {
-    isfuzzy(range.endOffset, 3, 2,
-            `${description} selection should be expanded to around the last line's 3rd insertion point`);
-  }
-
-  description = "Expanding selection to backward in non-scrollable body: ";
-  selection.collapse(doc.getElementById("s3").firstChild, 3);
-  doSelectPageUp();
-  is(container.scrollTop, 0, description + "this test shouldn't create scrollable document");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s1").firstChild,
-     `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
-  if (kSelectToStartOrEnd) {
-    is(range.startOffset, 0,
-       `${description} selection should be expanded to start of the first line`);
-  } else {
-    isfuzzy(range.startOffset, 3, 2,
-            `${description} selection should be expanded to around the first line's 3rd insertion point`);
-  }
-  is(range.endContainer, doc.getElementById("s3").firstChild,
-     `${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
-  is(range.endOffset, 3,
-     `${description} selection should be expanded from the last line's 3rd insertion point`);
-
-  body.innerHTML = '<span id="s1">first line in the body</span>' +
-                     '<div id="d1" style="height: 2em; line-height: 1em; overflow: auto;">' +
-                       '<span id="s2">first line</span><br>' +
-                       '<span id="s3">second line</span><br>' +
-                       '<span id="s4">third line</span><br>' +
-                       '<span id="s5">last line</span>' +
-                     "</div>" +
-                   '<span id="s6">last line in the body</span>';
-  container = doc.getElementById("d1");
-
-  description = "Expanding selection to forward in scrollable area in the body: ";
-  is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
-  selection.collapse(doc.getElementById("s2").firstChild, 3);
-  doSelectPageDown();
-  isnot(container.scrollTop, 0, description + "should be scrolled down");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s2").firstChild,
-     `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
-  is(range.startOffset, 3,
-     `${description} selection should be expanded from the first line's 3rd insertion point`);
-  is(range.endContainer, doc.getElementById("s4").firstChild,
-     `${description} selection should be expanded into the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
-  isfuzzy(range.endOffset, 3, 2,
-          `${description} selection should be expanded to around the 3rd line's 3rd insertion point`);
-
-  description = "Expanding selection to backward in scrollable area in the body: ";
-  selection.collapse(doc.getElementById("s4").firstChild, 3);
-  let previousScrollTop = container.scrollTop;
-  doSelectPageUp();
-  ok(container.scrollTop < previousScrollTop, description + "should be scrolled up");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s2").firstChild,
-     `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
-  isfuzzy(range.startOffset, 3, 2,
-          `${description} selection should be expanded to around the first line's 3rd insertion point`);
-  is(range.endContainer, doc.getElementById("s4").firstChild,
-     `${description} selection should be expanded from the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
-  is(range.endOffset, 3,
-     `${description} selection should be expanded from the 3rd line's 3rd insertion point`);
-
-  body.innerHTML = '<span id="s1">first line in the body</span>' +
-                     '<div id="d1" contenteditable style="height: 2em; line-height: 1em; overflow: auto;">' +
-                       '<span id="s2">first line</span><br>' +
-                       '<span id="s3">second line</span><br>' +
-                       '<span id="s4">third line</span><br>' +
-                       '<span id="s5">last line</span>' +
-                     "</div>" +
-                   '<span id="s6">last line in the body</span>';
-  container = doc.getElementById("d1");
-
-  description = "Expanding selection to forward in scrollable editable div in the body: ";
-  is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
-  selection.collapse(doc.getElementById("s2").firstChild, 3);
-  doSelectPageDown();
-  isnot(container.scrollTop, 0, description + "should be scrolled down");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s2").firstChild,
-     `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
-  is(range.startOffset, 3,
-     `${description} selection should be expanded from the first line's 3rd insertion point`);
-  is(range.endContainer, doc.getElementById("s4").firstChild,
-     `${description} selection should be expanded into the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
-  isfuzzy(range.endOffset, 3, 2,
-          `${description} selection should be expanded to around the 3rd line's 3rd insertion point`);
-
-  description = "Expanding selection to backward in scrollable editable div in the body: ";
-  selection.collapse(doc.getElementById("s4").firstChild, 3);
-  previousScrollTop = container.scrollTop;
-  doSelectPageUp();
-  ok(container.scrollTop < previousScrollTop, description + "should be scrolled up");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s2").firstChild,
-     `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
-  isfuzzy(range.startOffset, 3, 2,
-          `${description} selection should be expanded to around the first line's 3rd insertion point`);
-  is(range.endContainer, doc.getElementById("s4").firstChild,
-     `${description} selection should be expanded from the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
-  is(range.endOffset, 3,
-     `${description} selection should be expanded from the 3rd line's 3rd insertion point`);
-
-  body.innerHTML = '<span id="s1">first line in the body</span>' +
-                     '<div id="d1" contenteditable>' +
-                       '<span id="s2">first line</span><br>' +
-                       '<span id="s3">second line</span><br>' +
-                       '<span id="s4">third line</span><br>' +
-                       '<span id="s5">last line</span>' +
-                     "</div>" +
-                   '<span id="s6">last line in the body</span>';
-  container = doc.getElementById("d1");
-
-  description = "Expanding selection to forward in non-scrollable editable div in the body: ";
-  is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
-  selection.collapse(doc.getElementById("s2").firstChild, 3);
-  doSelectPageDown();
-  is(container.scrollTop, 0, description + "editable div shouldn't be scrollable");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s2").firstChild,
-     `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
-  is(range.startOffset, 3,
-     `${description} selection should be expanded from the first line's 3rd insertion point`);
-  is(range.endContainer, doc.getElementById("s5").firstChild,
-     `${description} selection should be expanded into the last line (got: ${getNodeDescription(range.endContainer)})`);
-  if (kSelectToStartOrEnd) {
-    is(range.endOffset, range.endContainer.length,
-       `${description} selection should be expanded to end of the last line`);
-  } else {
-    isfuzzy(range.endOffset, 3, 2,
-            `${description} selection should be expanded to around the last line's 3rd insertion point`);
-  }
-
-  description = "Expanding selection to backward in non-scrollable editable div in the body: ";
-  selection.collapse(doc.getElementById("s5").firstChild, 3);
-  doSelectPageUp();
-  is(container.scrollTop, 0, description + "editable div shouldn't be scrollable");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s2").firstChild,
-     `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
-  if (kSelectToStartOrEnd) {
-    is(range.startOffset, 0,
-       `${description} selection should be expanded to start of the first line`);
-  } else {
-    isfuzzy(range.startOffset, 3, 2,
-            `${description} selection should be expanded to around the first line's 3rd insertion point`);
-  }
-  is(range.endContainer, doc.getElementById("s5").firstChild,
-     `${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
-  is(range.endOffset, 3,
-     `${description} selection should be expanded from the last line's 3rd insertion point`);
-
-  body.innerHTML = '<span id="s1">first line in the body</span>' +
-                     '<div id="d1" contenteditable>' +
-                       '<span id="s2">first editable line</span><br>' +
-                       '<div id="d2" style="height: 3em; line-height: 1em; overflow: auto;">' +
-                         '<span id="s3">first line</span><br>' +
-                         '<span id="s4">second line</span>' +
-                       "</div>" +
-                       '<span id="s5">last editable line</span>' +
-                     "</div>" +
-                   '<span id="s6">last line in the body</span>';
-  container = doc.getElementById("d2");
-
-  description = "Expanding selection to forward in scrollable div (but not scrollable along y-axis) in the editable div: ";
-  is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
-  selection.collapse(doc.getElementById("s3").firstChild, 3);
-  doSelectPageDown();
-  is(container.scrollTop, 0, description + "scrollable div in the editable div (but not scrollable along y-axis) shouldn't be scrollable");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s3").firstChild,
-     `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
-  is(range.startOffset, 3,
-     `${description} selection should be expanded from the first line's 3rd insertion point`);
-  is(range.endContainer, doc.getElementById("s5").firstChild,
-     `${description} selection should be expanded into the last editable line (got: ${getNodeDescription(range.endContainer)})`);
-  if (kSelectToStartOrEnd) {
-    is(range.endOffset, range.endContainer.length,
-       `${description} selection should be expanded to end of the last editable line`);
-  } else {
-    isfuzzy(range.endOffset, 3, 2,
-            `${description} selection should be expanded to around the last editable line's 3rd insertion point`);
-  }
-
-  description = "Expanding selection to backward in scrollable div (but not scrollable along y-axis) in the editable div: ";
-  selection.collapse(doc.getElementById("s4").firstChild, 3);
-  doSelectPageUp();
-  is(container.scrollTop, 0, description + "scrollable div (but not scrollable along y-axis) in the editable div shouldn't be scrollable");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s2").firstChild,
-     `${description} selection should be expanded into the first editable line (got: ${getNodeDescription(range.startContainer)})`);
-  if (kSelectToStartOrEnd) {
-    is(range.startOffset, 0,
-       `${description} selection should be expanded to start of the first editable line`);
-  } else {
-    isfuzzy(range.startOffset, 3, 2,
-            `${description} selection should be expanded to around the first editable line's 3rd insertion point`);
-  }
-  is(range.endContainer, doc.getElementById("s4").firstChild,
-     `${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
-  is(range.endOffset, 3,
-     `${description} selection should be expanded from the last line's 3rd insertion point`);
+  doTest(false);
+  doTest(true);
 
   aWindow.close();
   SimpleTest.finish();
 }
 </script>
 </html>
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -1642,17 +1642,17 @@ class StaticAnalysisMonitor(object):
         return (warning, True)
 
 
 @CommandProvider
 class StaticAnalysis(MachCommandBase):
     """Utilities for running C++ static analysis checks and format."""
 
     # List of file extension to consider (should start with dot)
-    _format_include_extensions = ('.cpp', '.c', '.cc', '.h', '.java')
+    _format_include_extensions = ('.cpp', '.c', '.cc', '.h')
     # File contaning all paths to exclude from formatting
     _format_ignore_file = '.clang-format-ignore'
 
     @Command('static-analysis', category='testing',
              description='Run C++ static analysis checks')
     def static_analysis(self):
         # If not arguments are provided, just print a help message.
         mach = Mach(os.getcwd())
--- a/testing/marionette/browser.js
+++ b/testing/marionette/browser.js
@@ -8,16 +8,18 @@
 const {WebElementEventTarget} = ChromeUtils.import("chrome://marionette/content/dom.js", {});
 ChromeUtils.import("chrome://marionette/content/element.js");
 const {
   NoSuchWindowError,
   UnsupportedOperationError,
 } = ChromeUtils.import("chrome://marionette/content/error.js", {});
 const {
   MessageManagerDestroyedPromise,
+  waitForEvent,
+  waitForObserverTopic,
 } = ChromeUtils.import("chrome://marionette/content/sync.js", {});
 
 this.EXPORTED_SYMBOLS = ["browser", "Context", "WindowState"];
 
 /** @namespace */
 this.browser = {};
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
@@ -65,21 +67,21 @@ this.Context = Context;
  * @param {Tab} tab
  *     The tab whose browser needs to be returned.
  *
  * @return {Browser}
  *     The linked browser for the tab or null if no browser can be found.
  */
 browser.getBrowserForTab = function(tab) {
   // Fennec
-  if ("browser" in tab) {
+  if (tab && "browser" in tab) {
     return tab.browser;
 
   // Firefox
-  } else if ("linkedBrowser" in tab) {
+  } else if (tab && "linkedBrowser" in tab) {
     return tab.linkedBrowser;
   }
 
   return null;
 };
 
 /**
  * Return the tab browser for the specified chrome window.
@@ -282,27 +284,68 @@ browser.Context = class {
 
   /**
    * Close the current window.
    *
    * @return {Promise}
    *     A promise which is resolved when the current window has been closed.
    */
   closeWindow() {
-    return new Promise(resolve => {
-      // Wait for the window message manager to be destroyed
-      let destroyed = new MessageManagerDestroyedPromise(
-          this.window.messageManager);
+    let destroyed = new MessageManagerDestroyedPromise(
+        this.window.messageManager);
+    let unloaded = waitForEvent(this.window, "unload");
+
+    this.window.close();
+
+    return Promise.all([destroyed, unloaded]);
+  }
+
+  /**
+   * Open a new browser window.
+   *
+   * @return {Promise}
+   *     A promise resolving to the newly created chrome window.
+   */
+  async openBrowserWindow(focus = false) {
+    switch (this.driver.appName) {
+      case "firefox":
+        // Open new browser window, and wait until it is fully loaded.
+        // Also wait for the window to be focused and activated to prevent a
+        // race condition when promptly focusing to the original window again.
+        let win = this.window.OpenBrowserWindow();
 
-      this.window.addEventListener("unload", async () => {
-        await destroyed;
-        resolve();
-      }, {once: true});
-      this.window.close();
-    });
+        let activated = waitForEvent(win, "activate");
+        let focused = waitForEvent(win, "focus", {capture: true});
+        let startup = waitForObserverTopic("browser-delayed-startup-finished",
+            subject => subject == win);
+
+        // Bug 1509380 - Missing focus/activate event when Firefox is not
+        // the top-most application. As such wait for the next tick, and
+        // manually focus the newly opened window.
+        win.setTimeout(() => win.focus(), 0);
+
+        await Promise.all([activated, focused, startup]);
+
+        if (!focus) {
+          // The new window shouldn't get focused. As such set the
+          // focus back to the currently selected window.
+          activated = waitForEvent(this.window, "activate");
+          focused = waitForEvent(this.window, "focus", {capture: true});
+
+          this.window.focus();
+
+          await Promise.all([activated, focused]);
+        }
+
+        return win;
+
+      default:
+        throw new UnsupportedOperationError(
+            `openWindow() not supported in ${this.driver.appName}`);
+    }
   }
 
   /**
    * Close the current tab.
    *
    * @return {Promise}
    *     A promise which is resolved when the current tab has been closed.
    *
@@ -314,50 +357,71 @@ browser.Context = class {
     // same if only one remaining tab is open, or no tab selected at all.
     if (!this.tabBrowser ||
         !this.tabBrowser.tabs ||
         this.tabBrowser.tabs.length === 1 ||
         !this.tab) {
       return this.closeWindow();
     }
 
-    return new Promise((resolve, reject) => {
-      // Wait for the browser message manager to be destroyed
-      let browserDetached = async () => {
-        await new MessageManagerDestroyedPromise(this.messageManager);
-        resolve();
-      };
+    let destroyed = new MessageManagerDestroyedPromise(this.messageManager);
+    let tabClosed;
 
-      if (this.tabBrowser.closeTab) {
+    switch (this.driver.appName) {
+      case "fennec":
         // Fennec
-        this.tabBrowser.deck.addEventListener(
-            "TabClose", browserDetached, {once: true});
+        tabClosed = waitForEvent(this.tabBrowser.deck, "TabClose");
         this.tabBrowser.closeTab(this.tab);
+        break;
 
-      } else if (this.tabBrowser.removeTab) {
-        // Firefox
-        this.tab.addEventListener(
-            "TabClose", browserDetached, {once: true});
+      case "firefox":
+        tabClosed = waitForEvent(this.tab, "TabClose");
         this.tabBrowser.removeTab(this.tab);
+        break;
 
-      } else {
-        reject(new UnsupportedOperationError(
-            `closeTab() not supported in ${this.driver.appName}`));
-      }
-    });
+      default:
+        throw new UnsupportedOperationError(
+          `closeTab() not supported in ${this.driver.appName}`);
+    }
+
+    return Promise.all([destroyed, tabClosed]);
   }
 
   /**
-   * Opens a tab with given URI.
-   *
-   * @param {string} uri
-   *      URI to open.
+   * Open a new tab in the currently selected chrome window.
    */
-  addTab(uri) {
-    return this.tabBrowser.addTab(uri, true);
+  async openTab(focus = false) {
+    let tab = null;
+    let tabOpened = waitForEvent(this.window, "TabOpen");
+
+    switch (this.driver.appName) {
+      case "fennec":
+        tab = this.tabBrowser.addTab(null, {selected: focus});
+        break;
+
+      case "firefox":
+        this.window.BrowserOpenTab();
+        tab = this.tabBrowser.selectedTab;
+
+        // The new tab is always selected by default. If focus is not wanted,
+        // the previously tab needs to be selected again.
+        if (!focus) {
+          this.tabBrowser.selectedTab = this.tab;
+        }
+
+        break;
+
+      default:
+        throw new UnsupportedOperationError(
+          `openTab() not supported in ${this.driver.appName}`);
+    }
+
+    await tabOpened;
+
+    return tab;
   }
 
   /**
    * Set the current tab.
    *
    * @param {number=} index
    *     Tab index to switch to. If the parameter is undefined,
    *     the currently selected tab will be used.
@@ -381,26 +445,28 @@ browser.Context = class {
     }
 
     if (typeof index == "undefined") {
       this.tab = this.tabBrowser.selectedTab;
     } else {
       this.tab = this.tabBrowser.tabs[index];
 
       if (focus) {
-        if (this.tabBrowser.selectTab) {
-          // Fennec
-          this.tabBrowser.selectTab(this.tab);
+        switch (this.driver.appName) {
+          case "fennec":
+            this.tabBrowser.selectTab(this.tab);
+            break;
 
-        } else if ("selectedTab" in this.tabBrowser) {
-          // Firefox
-          this.tabBrowser.selectedTab = this.tab;
+          case "firefox":
+            this.tabBrowser.selectedTab = this.tab;
+            break;
 
-        } else {
-          throw new UnsupportedOperationError("switchToTab() not supported");
+          default:
+            throw new UnsupportedOperationError(
+              `switchToTab() not supported in ${this.driver.appName}`);
         }
       }
     }
 
     // TODO(ato): Currently tied to curBrowser, but should be moved to
     // WebElement when introduced by https://bugzil.la/1400256.
     this.eventObserver = new WebElementEventTarget(this.messageManager);
   }
--- a/testing/marionette/client/marionette_driver/marionette.py
+++ b/testing/marionette/client/marionette_driver/marionette.py
@@ -1423,16 +1423,30 @@ class Marionette(object):
         return self._send_message("WebDriver:GetChromeWindowHandles")
 
     @property
     def page_source(self):
         """A string representation of the DOM."""
         return self._send_message("WebDriver:GetPageSource",
                                   key="value")
 
+    def open(self, type=None, focus=False):
+        """Open a new window, or tab based on the specified context type.
+
+        If no context type is given the application will choose the best
+        option based on tab and window support.
+
+        :param type: Type of window to be opened. Can be one of "tab" or "window"
+        :param focus: If true, the opened window will be focused
+
+        :returns: Dict with new window handle, and type of opened window
+        """
+        body = {"type": type, "focus": focus}
+        return self._send_message("WebDriver:NewWindow", body)
+
     def close(self):
         """Close the current window, ending the session if it's the last
         window currently open.
 
         :returns: Unordered list of remaining unique window handles as strings
         """
         return self._send_message("WebDriver:CloseWindow")
 
--- a/testing/marionette/doc/internals/sync.rst
+++ b/testing/marionette/doc/internals/sync.rst
@@ -1,16 +1,24 @@
 sync module
 ===========
 
 Provides an assortment of synchronisation primitives.
 
+.. js:autofunction:: executeSoon
+
 .. js:autoclass:: MessageManagerDestroyedPromise
   :members:
 
 .. js:autoclass:: PollPromise
   :members:
 
 .. js:autoclass:: Sleep
   :members:
 
 .. js:autoclass:: TimedPromise
   :members:
+
+.. js:autofunction:: waitForEvent
+
+.. js:autofunction:: waitForMessage
+
+.. js:autofunction:: waitForObserverTopic
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -57,16 +57,18 @@ ChromeUtils.import("chrome://marionette/
 const {MarionettePrefs} = ChromeUtils.import("chrome://marionette/content/prefs.js", {});
 ChromeUtils.import("chrome://marionette/content/proxy.js");
 ChromeUtils.import("chrome://marionette/content/reftest.js");
 const {
   DebounceCallback,
   IdlePromise,
   PollPromise,
   TimedPromise,
+  waitForEvent,
+  waitForObserverTopic,
 } = ChromeUtils.import("chrome://marionette/content/sync.js", {});
 
 XPCOMUtils.defineLazyGetter(this, "logger", Log.get);
 XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
 
 this.EXPORTED_SYMBOLS = ["GeckoDriver"];
 
 const APP_ID_FIREFOX = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
@@ -101,23 +103,22 @@ const globalMessageManager = Services.mm
  * browsing context's content frame message listener via ListenerProxy.
  *
  * Throughout this prototype, functions with the argument <var>cmd</var>'s
  * documentation refers to the contents of the <code>cmd.parameter</code>
  * object.
  *
  * @class GeckoDriver
  *
- * @param {string} appId
- *     Unique identifier of the application.
  * @param {MarionetteServer} server
  *     The instance of Marionette server.
  */
-this.GeckoDriver = function(appId, server) {
-  this.appId = appId;
+this.GeckoDriver = function(server) {
+  this.appId = Services.appinfo.ID;
+  this.appName = Services.appinfo.name.toLowerCase();
   this._server = server;
 
   this.sessionID = null;
   this.wins = new browser.Windows();
   this.browsers = {};
   // points to current browser
   this.curBrowser = null;
   // top-most chrome window
@@ -1287,16 +1288,17 @@ GeckoDriver.prototype.updateIdForBrowser
  * Retrieves a listener id for the given xul browser element. In case
  * the browser is not known, an attempt is made to retrieve the id from
  * a CPOW, and null is returned if this fails.
  */
 GeckoDriver.prototype.getIdForBrowser = function(browser) {
   if (browser === null) {
     return null;
   }
+
   let permKey = browser.permanentKey;
   if (this._browserIds.has(permKey)) {
     return this._browserIds.get(permKey);
   }
 
   let winId = browser.outerWindowID;
   if (winId) {
     this._browserIds.set(permKey, winId);
@@ -2703,16 +2705,76 @@ GeckoDriver.prototype.deleteCookie = asy
   for (let c of cookie.iter(hostname, pathname)) {
     if (c.name === name) {
       cookie.remove(c);
     }
   }
 };
 
 /**
+ * Open a new top-level browsing context.
+ *
+ * @param {string=} type
+ *     Optional type of the new top-level browsing context. Can be one of
+ *     `tab` or `window`. Defaults to `tab`.
+ * @param {boolean=} focus
+ *     Optional flag if the new top-level browsing context should be opened
+ *     in foreground (focused) or background (not focused). Defaults to false.
+ *
+ * @return {Object.<string, string>}
+ *     Handle and type of the new browsing context.
+ */
+GeckoDriver.prototype.newWindow = async function(cmd) {
+  assert.open(this.getCurrentWindow(Context.Content));
+  await this._handleUserPrompts();
+
+  let focus = false;
+  if (typeof cmd.parameters.focus != "undefined") {
+    focus = assert.boolean(cmd.parameters.focus,
+        pprint`Expected "focus" to be a boolean, got ${cmd.parameters.focus}`);
+  }
+
+  let type;
+  if (typeof cmd.parameters.type != "undefined") {
+    type = assert.string(cmd.parameters.type,
+        pprint`Expected "type" to be a string, got ${cmd.parameters.type}`);
+  }
+
+  // If an invalid or no type has been specified default to a tab.
+  if (typeof type == "undefined" || !["tab", "window"].includes(type)) {
+    type = "tab";
+  }
+
+  let contentBrowser;
+
+  switch (type) {
+    case "window":
+      let win = await this.curBrowser.openBrowserWindow(focus);
+      contentBrowser = browser.getTabBrowser(win).selectedBrowser;
+      break;
+
+    default:
+      // To not fail if a new type gets added in the future, make opening
+      // a new tab the default action.
+      let tab = await this.curBrowser.openTab(focus);
+      contentBrowser = browser.getBrowserForTab(tab);
+  }
+
+  // Even with the framescript registered, the browser might not be known to
+  // the parent process yet. Wait until it is available.
+  // TODO: Fix by using `Browser:Init` or equivalent on bug 1311041
+  let windowId = await new PollPromise((resolve, reject) => {
+    let id = this.getIdForBrowser(contentBrowser);
+    this.windowHandles.includes(id) ? resolve(id) : reject();
+  });
+
+  return {handle: windowId.toString(), type};
+};
+
+/**
  * Close the currently selected tab/window.
  *
  * With multiple open tabs present the currently selected tab will
  * be closed.  Otherwise the window itself will be closed. If it is the
  * last window currently open, the window will not be closed to prevent
  * a shutdown of the application. Instead the returned list of window
  * handles is empty.
  *
@@ -3081,46 +3143,42 @@ GeckoDriver.prototype.fullscreenWindow =
 /**
  * Dismisses a currently displayed tab modal, or returns no such alert if
  * no modal is displayed.
  */
 GeckoDriver.prototype.dismissDialog = async function() {
   let win = assert.open(this.getCurrentWindow());
   this._checkIfAlertIsPresent();
 
-  await new Promise(resolve => {
-    win.addEventListener("DOMModalDialogClosed", async () => {
-      await new IdlePromise(win);
-      this.dialog = null;
-      resolve();
-    }, {once: true});
-
-    let {button0, button1} = this.dialog.ui;
-    (button1 ? button1 : button0).click();
-  });
+  let dialogClosed = waitForEvent(win, "DOMModalDialogClosed");
+
+  let {button0, button1} = this.dialog.ui;
+  (button1 ? button1 : button0).click();
+
+  await dialogClosed;
+
+  this.dialog = null;
 };
 
 /**
  * Accepts a currently displayed tab modal, or returns no such alert if
  * no modal is displayed.
  */
 GeckoDriver.prototype.acceptDialog = async function() {
   let win = assert.open(this.getCurrentWindow());
   this._checkIfAlertIsPresent();
 
-  await new Promise(resolve => {
-    win.addEventListener("DOMModalDialogClosed", async () => {
-      await new IdlePromise(win);
-      this.dialog = null;
-      resolve();
-    }, {once: true});
-
-    let {button0} = this.dialog.ui;
-    button0.click();
-  });
+  let dialogClosed = waitForEvent(win, "DOMModalDialogClosed");
+
+  let {button0} = this.dialog.ui;
+  button0.click();
+
+  await dialogClosed;
+
+  this.dialog = null;
 };
 
 /**
  * Returns the message shown in a currently displayed modal, or returns
  * a no such alert error if no modal is currently displayed.
  */
 GeckoDriver.prototype.getTextFromDialog = function() {
   assert.open(this.getCurrentWindow());
@@ -3281,25 +3339,20 @@ GeckoDriver.prototype.quit = async funct
   } else {
     mode = Ci.nsIAppStartup.eAttemptQuit;
   }
 
   this._server.acceptConnections = false;
   this.deleteSession();
 
   // delay response until the application is about to quit
-  let quitApplication = new Promise(resolve => {
-    Services.obs.addObserver(
-        (subject, topic, data) => resolve(data),
-        "quit-application");
-  });
-
+  let quitApplication = waitForObserverTopic("quit-application");
   Services.startup.quit(mode);
 
-  return {cause: await quitApplication};
+  return {cause: (await quitApplication).data};
 };
 
 GeckoDriver.prototype.installAddon = function(cmd) {
   assert.desktop();
 
   let path = cmd.parameters.path;
   let temp = cmd.parameters.temporary || false;
   if (typeof path == "undefined" || typeof path != "string" ||
@@ -3551,16 +3604,17 @@ GeckoDriver.prototype.commands = {
   "WebDriver:GetWindowRect": GeckoDriver.prototype.getWindowRect,
   "WebDriver:IsElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
   "WebDriver:IsElementEnabled": GeckoDriver.prototype.isElementEnabled,
   "WebDriver:IsElementSelected": GeckoDriver.prototype.isElementSelected,
   "WebDriver:MinimizeWindow": GeckoDriver.prototype.minimizeWindow,
   "WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
   "WebDriver:Navigate": GeckoDriver.prototype.get,
   "WebDriver:NewSession": GeckoDriver.prototype.newSession,
+  "WebDriver:NewWindow": GeckoDriver.prototype.newWindow,
   "WebDriver:PerformActions": GeckoDriver.prototype.performActions,
   "WebDriver:Refresh":  GeckoDriver.prototype.refresh,
   "WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,
   "WebDriver:SendAlertText": GeckoDriver.prototype.sendKeysToDialog,
   "WebDriver:SetTimeouts": GeckoDriver.prototype.setTimeouts,
   "WebDriver:SetWindowRect": GeckoDriver.prototype.setWindowRect,
   "WebDriver:SwitchToFrame": GeckoDriver.prototype.switchToFrame,
   "WebDriver:SwitchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
@@ -3586,10 +3640,10 @@ async function exitFullscreen(window) {
 function restoreWindow(window) {
   window.restore();
   return new PollPromise((resolve, reject) => {
     if (WindowState.from(window.windowState) != WindowState.Minimized) {
       resolve();
     } else {
       reject();
     }
-  });
+  }, {timeout: 2000});
 }
--- a/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py
+++ b/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py
@@ -1,24 +1,22 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import
 
 import sys
 
-from marionette_driver import By, Wait
+from marionette_driver import Wait
 from six import reraise
 
 
 class WindowManagerMixin(object):
 
-    _menu_item_new_tab = (By.ID, "menu_newNavigatorTab")
-
     def setUp(self):
         super(WindowManagerMixin, self).setUp()
 
         self.start_window = self.marionette.current_chrome_window_handle
         self.start_windows = self.marionette.chrome_window_handles
 
         self.start_tab = self.marionette.current_window_handle
         self.start_tabs = self.marionette.window_handles
@@ -55,75 +53,89 @@ class WindowManagerMixin(object):
         current_chrome_window_handles.remove(self.start_window)
 
         for handle in current_chrome_window_handles:
             self.marionette.switch_to_window(handle)
             self.marionette.close_chrome_window()
 
         self.marionette.switch_to_window(self.start_window)
 
-    def open_tab(self, trigger="menu"):
+    def open_tab(self, callback=None, focus=False):
         current_tabs = self.marionette.window_handles
 
         try:
-            if callable(trigger):
-                trigger()
-            elif trigger == 'menu':
-                with self.marionette.using_context("chrome"):
-                    self.marionette.find_element(*self._menu_item_new_tab).click()
+            if callable(callback):
+                callback()
+            else:
+                result = self.marionette.open(type="tab", focus=focus)
+                if result["type"] != "tab":
+                    raise Exception(
+                        "Newly opened browsing context is of type {} and not tab.".format(
+                            result["type"]))
         except Exception:
             exc, val, tb = sys.exc_info()
             reraise(exc, 'Failed to trigger opening a new tab: {}'.format(val), tb)
         else:
             Wait(self.marionette).until(
                 lambda mn: len(mn.window_handles) == len(current_tabs) + 1,
                 message="No new tab has been opened"
             )
 
             [new_tab] = list(set(self.marionette.window_handles) - set(current_tabs))
 
             return new_tab
 
-    def open_window(self, trigger=None):
+    def open_window(self, callback=None, focus=False):
         current_windows = self.marionette.chrome_window_handles
+        current_tabs = self.marionette.window_handles
 
         def loaded(handle):
             with self.marionette.using_context("chrome"):
                 return self.marionette.execute_script("""
                   Components.utils.import("resource://gre/modules/Services.jsm");
 
                   let win = Services.wm.getOuterWindowWithId(Number(arguments[0]));
                   return win.document.readyState == "complete";
                 """, script_args=[handle])
 
         try:
-            if callable(trigger):
-                trigger()
+            if callable(callback):
+                callback()
             else:
-                with self.marionette.using_context("chrome"):
-                    self.marionette.execute_script("OpenBrowserWindow();")
+                result = self.marionette.open(type="window", focus=focus)
+                if result["type"] != "window":
+                    raise Exception(
+                        "Newly opened browsing context is of type {} and not window.".format(
+                            result["type"]))
         except Exception:
             exc, val, tb = sys.exc_info()
             reraise(exc, 'Failed to trigger opening a new window: {}'.format(val), tb)
         else:
             Wait(self.marionette).until(
                 lambda mn: len(mn.chrome_window_handles) == len(current_windows) + 1,
                 message="No new window has been opened"
             )
 
             [new_window] = list(set(self.marionette.chrome_window_handles) - set(current_windows))
 
             # Before continuing ensure the window has been completed loading
             Wait(self.marionette).until(
                 lambda _: loaded(new_window),
                 message="Window with handle '{}'' did not finish loading".format(new_window))
 
-            return new_window
+            # Bug 1507771 - Return the correct handle based on the currently selected context
+            # as long as "WebDriver:NewWindow" is not handled separtely in chrome context
+            context = self.marionette._send_message("Marionette:GetContext", key="value")
+            if context == "chrome":
+                return new_window
+            elif context == "content":
+                [new_tab] = list(set(self.marionette.window_handles) - set(current_tabs))
+                return new_tab
 
-    def open_chrome_window(self, url):
+    def open_chrome_window(self, url, focus=False):
         """Open a new chrome window with the specified chrome URL.
 
         Can be replaced with "WebDriver:NewWindow" once the command
         supports opening generic chrome windows beside browsers (bug 1507771).
         """
         def open_with_js():
             with self.marionette.using_context("chrome"):
                 self.marionette.execute_async_script("""
@@ -161,9 +173,10 @@ class WindowManagerMixin(object):
                     let focused = waitForFocus(window);
                     window.focus();
                     await focused;
 
                     resolve();
                   })();
                 """, script_args=(url,))
 
-        return self.open_window(trigger=open_with_js)
+        with self.marionette.using_context("chrome"):
+            return self.open_window(callback=open_with_js, focus=focus)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_chrome.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome.py
@@ -1,53 +1,32 @@
-#Copyright 2007-2009 WebDriver committers
-#Copyright 2007-2009 Google Inc.
-#
-#Licensed under the Apache License, Version 2.0 (the "License");
-#you may not use this file except in compliance with the License.
-#You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-#Unless required by applicable law or agreed to in writing, software
-#distributed under the License is distributed on an "AS IS" BASIS,
-#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#See the License for the specific language governing permissions and
-#limitations under the License.
-
 from __future__ import absolute_import
 
-from marionette_driver import By
-
 from marionette_harness import MarionetteTestCase, WindowManagerMixin
 
 
 class ChromeTests(WindowManagerMixin, MarionetteTestCase):
 
     def setUp(self):
         super(ChromeTests, self).setUp()
 
-        self.marionette.set_context('chrome')
-
     def tearDown(self):
         self.close_all_windows()
         super(ChromeTests, self).tearDown()
 
     def test_hang_until_timeout(self):
-        def open_with_menu():
-            menu = self.marionette.find_element(By.ID, 'aboutName')
-            menu.click()
-
-        new_window = self.open_window(trigger=open_with_menu)
+        with self.marionette.using_context("chrome"):
+            new_window = self.open_window()
         self.marionette.switch_to_window(new_window)
 
         try:
             try:
                 # Raise an exception type which should not be thrown by Marionette
                 # while running this test. Otherwise it would mask eg. IOError as
                 # thrown for a socket timeout.
                 raise NotImplementedError('Exception should not cause a hang when '
-                                          'closing the chrome window')
+                                          'closing the chrome window in content '
+                                          'context')
             finally:
                 self.marionette.close_chrome_window()
                 self.marionette.switch_to_window(self.start_window)
         except NotImplementedError:
             pass
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_click.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click.py
@@ -444,25 +444,21 @@ class TestClickCloseContext(WindowManage
         self.test_page = self.marionette.absolute_url("clicks.html")
 
     def tearDown(self):
         self.close_all_tabs()
 
         super(TestClickCloseContext, self).tearDown()
 
     def test_click_close_tab(self):
-        self.marionette.navigate(self.marionette.absolute_url("windowHandles.html"))
-        tab = self.open_tab(
-            lambda: self.marionette.find_element(By.ID, "new-tab").click())
-        self.marionette.switch_to_window(tab)
+        new_tab = self.open_tab()
+        self.marionette.switch_to_window(new_tab)
 
         self.marionette.navigate(self.test_page)
         self.marionette.find_element(By.ID, "close-window").click()
 
     @skip_if_mobile("Fennec doesn't support other chrome windows")
     def test_click_close_window(self):
-        self.marionette.navigate(self.marionette.absolute_url("windowHandles.html"))
-        win = self.open_window(
-            lambda: self.marionette.find_element(By.ID, "new-window").click())
-        self.marionette.switch_to_window(win)
+        new_tab = self.open_window()
+        self.marionette.switch_to_window(new_tab)
 
         self.marionette.navigate(self.test_page)
         self.marionette.find_element(By.ID, "close-window").click()
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_key_actions.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_key_actions.py
@@ -72,27 +72,8 @@ class TestKeyActions(WindowManagerMixin,
     def test_input_with_wait(self):
         self.key_action.key_down("a").key_down("b").key_down("c").perform()
         (self.key_action.key_down(self.mod_key)
                         .key_down("a")
                         .wait(.5)
                         .key_down("x")
                         .perform())
         self.assertEqual(self.key_reporter_value, "")
-
-    @skip_if_mobile("Interacting with chrome windows not available for Fennec")
-    def test_open_in_new_window_shortcut(self):
-
-        def open_window_with_action():
-            el = self.marionette.find_element(By.TAG_NAME, "a")
-            (self.key_action.key_down(Keys.SHIFT)
-                            .press(el)
-                            .release()
-                            .key_up(Keys.SHIFT)
-                            .perform())
-
-        self.marionette.navigate(inline("<a href='#'>Click</a>"))
-        new_window = self.open_window(trigger=open_window_with_action)
-
-        self.marionette.switch_to_window(new_window)
-        self.marionette.close_chrome_window()
-
-        self.marionette.switch_to_window(self.start_window)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
@@ -49,23 +49,18 @@ class BaseNavigationTestCase(WindowManag
         self.test_page_remote = self.marionette.absolute_url("test.html")
         self.test_page_slow_resource = self.marionette.absolute_url("slow_resource.html")
 
         if self.marionette.session_capabilities["platformName"] == "mac":
             self.mod_key = Keys.META
         else:
             self.mod_key = Keys.CONTROL
 
-        def open_with_link():
-            link = self.marionette.find_element(By.ID, "new-blank-tab")
-            link.click()
-
         # Always use a blank new tab for an empty history
-        self.marionette.navigate(self.marionette.absolute_url("windowHandles.html"))
-        self.new_tab = self.open_tab(open_with_link)
+        self.new_tab = self.open_tab()
         self.marionette.switch_to_window(self.new_tab)
         Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
             lambda _: self.history_length == 1,
             message="The newly opened tab doesn't have a browser history length of 1")
 
     def tearDown(self):
         self.marionette.timeout.reset()
         self.marionette.switch_to_parent_frame()
@@ -292,17 +287,16 @@ class TestNavigate(BaseNavigationTestCas
     @skip_if_mobile("Bug 1322993 - Missing temporary folder")
     def test_focus_after_navigation(self):
         self.marionette.restart()
 
         self.marionette.navigate(inline("<input autofocus>"))
         focus_el = self.marionette.find_element(By.CSS_SELECTOR, ":focus")
         self.assertEqual(self.marionette.get_active_element(), focus_el)
 
-    @skip_if_mobile("Needs application independent method to open a new tab")
     def test_no_hang_when_navigating_after_closing_original_tab(self):
         # Close the start tab
         self.marionette.switch_to_window(self.start_tab)
         self.marionette.close()
 
         self.marionette.switch_to_window(self.new_tab)
         self.marionette.navigate(self.test_page_remote)
 
@@ -333,32 +327,16 @@ class TestNavigate(BaseNavigationTestCas
             urlbar.send_keys(self.mod_key + "x")
             urlbar.send_keys(self.test_page_remote + Keys.ENTER)
 
         Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
             lambda mn: mn.get_url() == self.test_page_remote,
             message="'{}' hasn't been loaded".format(self.test_page_remote))
         self.assertTrue(self.is_remote_tab)
 
-    @skip_if_mobile("On Android no shortcuts are available")
-    def test_navigate_shortcut_key(self):
-
-        def open_with_shortcut():
-            self.marionette.navigate(self.test_page_remote)
-            with self.marionette.using_context("chrome"):
-                main_win = self.marionette.find_element(By.ID, "main-window")
-                main_win.send_keys(self.mod_key, Keys.SHIFT, "a")
-
-        new_tab = self.open_tab(trigger=open_with_shortcut)
-        self.marionette.switch_to_window(new_tab)
-
-        Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
-            lambda mn: mn.get_url() == "about:addons",
-            message="'about:addons' hasn't been loaded")
-
 
 class TestBackForwardNavigation(BaseNavigationTestCase):
 
     def run_bfcache_test(self, test_pages):
         # Helper method to run simple back and forward testcases.
 
         def check_page_status(page, expected_history_length):
             if "alert_text" in page:
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py
@@ -248,17 +248,18 @@ class TestScreenCaptureChrome(WindowMana
             self.marionette.navigate(box)
             content_element = self.marionette.find_element(By.ID, "green")
 
         self.assertRaisesRegexp(NoSuchElementException, "Web element reference not seen before",
                                 self.marionette.screenshot, highlights=[content_element])
 
         chrome_document_element = self.document_element
         with self.marionette.using_context('content'):
-            self.assertRaisesRegexp(NoSuchElementException, "Web element reference not seen before",
+            self.assertRaisesRegexp(NoSuchElementException,
+                                    "Web element reference not seen before",
                                     self.marionette.screenshot,
                                     highlights=[chrome_document_element])
 
 
 class TestScreenCaptureContent(WindowManagerMixin, ScreenCaptureTestCase):
 
     def setUp(self):
         super(TestScreenCaptureContent, self).setUp()
@@ -269,20 +270,19 @@ class TestScreenCaptureContent(WindowMan
         super(TestScreenCaptureContent, self).tearDown()
 
     @property
     def scroll_dimensions(self):
         return tuple(self.marionette.execute_script("""
             return [document.body.scrollWidth, document.body.scrollHeight]
             """))
 
-    @skip_if_mobile("Needs application independent method to open a new tab")
     def test_capture_tab_already_closed(self):
-        tab = self.open_tab()
-        self.marionette.switch_to_window(tab)
+        new_tab = self.open_tab()
+        self.marionette.switch_to_window(new_tab)
         self.marionette.close()
 
         self.assertRaises(NoSuchWindowException, self.marionette.screenshot)
         self.marionette.switch_to_window(self.start_tab)
 
     @skip_if_mobile("Bug 1487124 - Android need its own maximum allowed dimensions")
     def test_capture_vertical_bounds(self):
         self.marionette.navigate(inline("<body style='margin-top: 32768px'>foo"))
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_chrome.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_chrome.py
@@ -1,20 +1,19 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import
 
 import os
 import sys
+
 from unittest import skipIf
 
-from marionette_driver import By
-
 # add this directory to the path
 sys.path.append(os.path.dirname(__file__))
 
 from test_switch_window_content import TestSwitchToWindowContent
 
 
 class TestSwitchWindowChrome(TestSwitchToWindowContent):
 
@@ -23,124 +22,75 @@ class TestSwitchWindowChrome(TestSwitchT
 
         self.marionette.set_context("chrome")
 
     def tearDown(self):
         self.close_all_windows()
 
         super(TestSwitchWindowChrome, self).tearDown()
 
-    def open_window_in_background(self):
-        with self.marionette.using_context("chrome"):
-            self.marionette.execute_async_script("""
-              let callback = arguments[0];
-              (async function() {
-                function promiseEvent(target, type, args) {
-                  return new Promise(r => {
-                    let params = Object.assign({once: true}, args);
-                    target.addEventListener(type, r, params);
-                  });
-                }
-                function promiseWindowFocus(w) {
-                  return Promise.all([
-                    promiseEvent(w, "focus", {capture: true}),
-                    promiseEvent(w, "activate"),
-                  ]);
-                }
-                // Open a window, wait for it to receive focus
-                let win = OpenBrowserWindow();
-                await promiseWindowFocus(win);
-
-                // Now refocus our original window and wait for that to happen.
-                let windowFocusPromise = promiseWindowFocus(window);
-                window.focus();
-                return windowFocusPromise;
-              })().then(() => {
-                // can't just pass `callback`, as we can't JSON-ify the events it'd get passed.
-                callback()
-              });
-            """)
-
-    def open_window_in_foreground(self):
-        with self.marionette.using_context("content"):
-            self.marionette.navigate(self.test_page)
-            link = self.marionette.find_element(By.ID, "new-window")
-            link.click()
-
+    @skipIf(sys.platform.startswith("linux"),
+            "Bug 1511970 - New window isn't moved to the background on Linux")
     def test_switch_tabs_for_new_background_window_without_focus_change(self):
-        # Open an addition tab in the original window so we can better check
+        # Open an additional tab in the original window so we can better check
         # the selected index in thew new window to be opened.
-        second_tab = self.open_tab(trigger=self.open_tab_in_foreground)
+        second_tab = self.open_tab(focus=True)
         self.marionette.switch_to_window(second_tab, focus=True)
         second_tab_index = self.get_selected_tab_index()
         self.assertNotEqual(second_tab_index, self.selected_tab_index)
 
-        # Opens a new background window, but we are interested in the tab
-        tab_in_new_window = self.open_tab(trigger=self.open_window_in_background)
+        # Open a new background window, but we are interested in the tab
+        with self.marionette.using_context("content"):
+            tab_in_new_window = self.open_window()
         self.assertEqual(self.marionette.current_window_handle, second_tab)
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
         self.assertEqual(self.get_selected_tab_index(), second_tab_index)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.empty_page)
 
         # Switch to the tab in the new window but don't focus it
         self.marionette.switch_to_window(tab_in_new_window, focus=False)
         self.assertEqual(self.marionette.current_window_handle, tab_in_new_window)
         self.assertNotEqual(self.marionette.current_chrome_window_handle, self.start_window)
         self.assertEqual(self.get_selected_tab_index(), second_tab_index)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), "about:blank")
 
     def test_switch_tabs_for_new_foreground_window_with_focus_change(self):
         # Open an addition tab in the original window so we can better check
         # the selected index in thew new window to be opened.
-        second_tab = self.open_tab(trigger=self.open_tab_in_foreground)
+        second_tab = self.open_tab()
         self.marionette.switch_to_window(second_tab, focus=True)
         second_tab_index = self.get_selected_tab_index()
         self.assertNotEqual(second_tab_index, self.selected_tab_index)
 
         # Opens a new window, but we are interested in the tab
-        tab_in_new_window = self.open_tab(trigger=self.open_window_in_foreground)
+        with self.marionette.using_context("content"):
+            tab_in_new_window = self.open_window(focus=True)
         self.assertEqual(self.marionette.current_window_handle, second_tab)
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
         self.assertNotEqual(self.get_selected_tab_index(), second_tab_index)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
 
         self.marionette.switch_to_window(tab_in_new_window)
         self.assertEqual(self.marionette.current_window_handle, tab_in_new_window)
         self.assertNotEqual(self.marionette.current_chrome_window_handle, self.start_window)
         self.assertNotEqual(self.get_selected_tab_index(), second_tab_index)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.empty_page)
 
         self.marionette.switch_to_window(second_tab, focus=True)
         self.assertEqual(self.marionette.current_window_handle, second_tab)
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
         # Bug 1335085 - The focus doesn't change even as requested so.
         # self.assertEqual(self.get_selected_tab_index(), second_tab_index)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
 
     def test_switch_tabs_for_new_foreground_window_without_focus_change(self):
         # Open an addition tab in the original window so we can better check
         # the selected index in thew new window to be opened.
-        second_tab = self.open_tab(trigger=self.open_tab_in_foreground)
+        second_tab = self.open_tab()
         self.marionette.switch_to_window(second_tab, focus=True)
         second_tab_index = self.get_selected_tab_index()
         self.assertNotEqual(second_tab_index, self.selected_tab_index)
 
-        # Opens a new window, but we are interested in the tab which automatically
-        # gets the focus.
-        self.open_tab(trigger=self.open_window_in_foreground)
+        self.open_window(focus=True)
         self.assertEqual(self.marionette.current_window_handle, second_tab)
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
         self.assertNotEqual(self.get_selected_tab_index(), second_tab_index)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
 
         # Switch to the second tab in the first window, but don't focus it.
         self.marionette.switch_to_window(second_tab, focus=False)
         self.assertEqual(self.marionette.current_window_handle, second_tab)
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
         self.assertNotEqual(self.get_selected_tab_index(), second_tab_index)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_content.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_content.py
@@ -1,38 +1,32 @@
 # This Source Code Form is subject to the terms of the Mozilla ublic
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import
 
-from marionette_driver import Actions, By, Wait
+from marionette_driver import By
 from marionette_driver.keys import Keys
 
-from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin
+from marionette_harness import MarionetteTestCase, WindowManagerMixin
 
 
 class TestSwitchToWindowContent(WindowManagerMixin, MarionetteTestCase):
 
     def setUp(self):
         super(TestSwitchToWindowContent, self).setUp()
 
         if self.marionette.session_capabilities["platformName"] == "mac":
             self.mod_key = Keys.META
         else:
             self.mod_key = Keys.CONTROL
 
-        self.empty_page = self.marionette.absolute_url("empty.html")
-        self.test_page = self.marionette.absolute_url("windowHandles.html")
-
         self.selected_tab_index = self.get_selected_tab_index()
 
-        with self.marionette.using_context("content"):
-            self.marionette.navigate(self.test_page)
-
     def tearDown(self):
         self.close_all_tabs()
 
         super(TestSwitchToWindowContent, self).tearDown()
 
     def get_selected_tab_index(self):
         with self.marionette.using_context("chrome"):
             return self.marionette.execute_script("""
@@ -64,110 +58,90 @@ class TestSwitchToWindowContent(WindowMa
 
                 for (let i = 0; i < tabBrowser.tabs.length; i++) {
                   if (tabBrowser.tabs[i] == tabBrowser.selectedTab) {
                     return i;
                   }
                 }
             """)
 
-    def open_tab_in_background(self):
-        with self.marionette.using_context("content"):
-            link = self.marionette.find_element(By.ID, "new-tab")
-
-            action = Actions(self.marionette)
-            action.key_down(self.mod_key).click(link).perform()
-
-    def open_tab_in_foreground(self):
-        with self.marionette.using_context("content"):
-            link = self.marionette.find_element(By.ID, "new-tab")
-            link.click()
-
     def test_switch_tabs_with_focus_change(self):
-        new_tab = self.open_tab(self.open_tab_in_foreground)
+        new_tab = self.open_tab(focus=True)
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
         self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
 
+        # Switch to new tab first because it is already selected
         self.marionette.switch_to_window(new_tab)
         self.assertEqual(self.marionette.current_window_handle, new_tab)
         self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index)
 
-        with self.marionette.using_context("content"):
-            Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
-                lambda _: self.marionette.get_url() == self.empty_page,
-                message="{} has been loaded in the newly opened tab.".format(self.empty_page))
-
+        # Switch to original tab by explicitely setting the focus
         self.marionette.switch_to_window(self.start_tab, focus=True)
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
         self.assertEqual(self.get_selected_tab_index(), self.selected_tab_index)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
-
-        self.marionette.switch_to_window(new_tab)
-        self.marionette.close()
-        self.marionette.switch_to_window(self.start_tab)
-
-        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
-        self.assertEqual(self.get_selected_tab_index(), self.selected_tab_index)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
-
-    def test_switch_tabs_without_focus_change(self):
-        new_tab = self.open_tab(self.open_tab_in_foreground)
-        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
-        self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
-
-        # Switch to new tab first because it is already selected
-        self.marionette.switch_to_window(new_tab)
-        self.assertEqual(self.marionette.current_window_handle, new_tab)
-
-        self.marionette.switch_to_window(self.start_tab, focus=False)
-        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
-        self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index)
-
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
 
         self.marionette.switch_to_window(new_tab)
         self.marionette.close()
 
         self.marionette.switch_to_window(self.start_tab)
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
         self.assertEqual(self.get_selected_tab_index(), self.selected_tab_index)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
+
+    def test_switch_tabs_without_focus_change(self):
+        new_tab = self.open_tab(focus=True)
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+        self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index)
+
+        # Switch to new tab first because it is already selected
+        self.marionette.switch_to_window(new_tab)
+        self.assertEqual(self.marionette.current_window_handle, new_tab)
+
+        # Switch to original tab by explicitely not setting the focus
+        self.marionette.switch_to_window(self.start_tab, focus=False)
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+        self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index)
+
+        self.marionette.switch_to_window(new_tab)
+        self.marionette.close()
+
+        self.marionette.switch_to_window(self.start_tab)
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+        self.assertEqual(self.get_selected_tab_index(), self.selected_tab_index)
 
     def test_switch_from_content_to_chrome_window_should_not_change_selected_tab(self):
-        new_tab = self.open_tab(self.open_tab_in_foreground)
+        new_tab = self.open_tab(focus=True)
 
         self.marionette.switch_to_window(new_tab)
         self.assertEqual(self.marionette.current_window_handle, new_tab)
         new_tab_index = self.get_selected_tab_index()
 
         self.marionette.switch_to_window(self.start_window)
         self.assertEqual(self.marionette.current_window_handle, new_tab)
         self.assertEqual(self.get_selected_tab_index(), new_tab_index)
 
-    @skip_if_mobile("New windows not supported in Fennec")
-    def test_switch_to_new_private_browsing_window_has_to_register_browsers(self):
+    def test_switch_to_new_private_browsing_tab(self):
         # Test that tabs (browsers) are correctly registered for a newly opened
-        # private browsing window. This has to also happen without explicitely
+        # private browsing window/tab. This has to also happen without explicitely
         # switching to the tab itself before using any commands in content scope.
         #
         # Note: Not sure why this only affects private browsing windows only.
+        new_tab = self.open_tab(focus=True)
+        self.marionette.switch_to_window(new_tab)
 
-        def open_private_browsing_window():
+        def open_private_browsing_window_firefox():
             with self.marionette.using_context("content"):
-                self.marionette.navigate("about:privatebrowsing")
-                button = self.marionette.find_element(By.ID, "startPrivateBrowsing")
-                button.click()
+                self.marionette.find_element(By.ID, "startPrivateBrowsing").click()
 
-        new_window = self.open_window(open_private_browsing_window)
-        self.marionette.switch_to_window(new_window)
-        self.assertEqual(self.marionette.current_chrome_window_handle, new_window)
-        self.assertNotEqual(self.marionette.current_window_handle, self.start_tab)
+        def open_private_browsing_tab_fennec():
+            with self.marionette.using_context("content"):
+                self.marionette.find_element(By.ID, "newPrivateTabLink").click()
 
         with self.marionette.using_context("content"):
-            self.marionette.execute_script(" return true; ")
+            self.marionette.navigate("about:privatebrowsing")
+            if self.marionette.session_capabilities["browserName"] == "fennec":
+                new_pb_tab = self.open_tab(open_private_browsing_tab_fennec)
+            else:
+                new_pb_tab = self.open_tab(open_private_browsing_window_firefox)
+
+        self.marionette.switch_to_window(new_pb_tab)
+        self.assertEqual(self.marionette.current_window_handle, new_pb_tab)
+
+        self.marionette.execute_script(" return true; ")
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_chrome.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_chrome.py
@@ -16,24 +16,24 @@ class TestCloseWindow(WindowManagerMixin
 
     def tearDown(self):
         self.close_all_windows()
         self.close_all_tabs()
 
         super(TestCloseWindow, self).tearDown()
 
     def test_close_chrome_window_for_browser_window(self):
-        win = self.open_window()
-        self.marionette.switch_to_window(win)
+        new_window = self.open_window()
+        self.marionette.switch_to_window(new_window)
 
-        self.assertNotIn(win, self.marionette.window_handles)
+        self.assertNotIn(new_window, self.marionette.window_handles)
         chrome_window_handles = self.marionette.close_chrome_window()
-        self.assertNotIn(win, chrome_window_handles)
+        self.assertNotIn(new_window, chrome_window_handles)
         self.assertListEqual(self.start_windows, chrome_window_handles)
-        self.assertNotIn(win, self.marionette.window_handles)
+        self.assertNotIn(new_window, self.marionette.window_handles)
 
     def test_close_chrome_window_for_non_browser_window(self):
         win = self.open_chrome_window("chrome://marionette/content/test.xul")
         self.marionette.switch_to_window(win)
 
         self.assertIn(win, self.marionette.chrome_window_handles)
         self.assertNotIn(win, self.marionette.window_handles)
         chrome_window_handles = self.marionette.close_chrome_window()
@@ -45,30 +45,30 @@ class TestCloseWindow(WindowManagerMixin
         self.close_all_windows()
 
         self.assertListEqual([], self.marionette.close_chrome_window())
         self.assertListEqual([self.start_tab], self.marionette.window_handles)
         self.assertListEqual([self.start_window], self.marionette.chrome_window_handles)
         self.assertIsNotNone(self.marionette.session)
 
     def test_close_window_for_browser_tab(self):
-        tab = self.open_tab()
-        self.marionette.switch_to_window(tab)
+        new_tab = self.open_tab()
+        self.marionette.switch_to_window(new_tab)
 
         window_handles = self.marionette.close()
-        self.assertNotIn(tab, window_handles)
+        self.assertNotIn(new_tab, window_handles)
         self.assertListEqual(self.start_tabs, window_handles)
 
     def test_close_window_for_browser_window_with_single_tab(self):
-        win = self.open_window()
-        self.marionette.switch_to_window(win)
+        new_window = self.open_window()
+        self.marionette.switch_to_window(new_window)
 
         self.assertEqual(len(self.start_tabs) + 1, len(self.marionette.window_handles))
         window_handles = self.marionette.close()
-        self.assertNotIn(win, window_handles)
+        self.assertNotIn(new_window, window_handles)
         self.assertListEqual(self.start_tabs, window_handles)
         self.assertListEqual(self.start_windows, self.marionette.chrome_window_handles)
 
     def test_close_window_for_last_open_tab(self):
         self.close_all_tabs()
 
         self.assertListEqual([], self.marionette.close())
         self.assertListEqual([self.start_tab], self.marionette.window_handles)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_content.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_content.py
@@ -19,98 +19,97 @@ class TestCloseWindow(WindowManagerMixin
     def tearDown(self):
         self.close_all_windows()
         self.close_all_tabs()
 
         super(TestCloseWindow, self).tearDown()
 
     @skip_if_mobile("Interacting with chrome windows not available for Fennec")
     def test_close_chrome_window_for_browser_window(self):
-        win = self.open_window()
-        self.marionette.switch_to_window(win)
+        with self.marionette.using_context("chrome"):
+            new_window = self.open_window()
+        self.marionette.switch_to_window(new_window)
 
-        self.assertNotIn(win, self.marionette.window_handles)
+        self.assertIn(new_window, self.marionette.chrome_window_handles)
         chrome_window_handles = self.marionette.close_chrome_window()
-        self.assertNotIn(win, chrome_window_handles)
+        self.assertNotIn(new_window, chrome_window_handles)
         self.assertListEqual(self.start_windows, chrome_window_handles)
-        self.assertNotIn(win, self.marionette.window_handles)
+        self.assertNotIn(new_window, self.marionette.window_handles)
 
     @skip_if_mobile("Interacting with chrome windows not available for Fennec")
     def test_close_chrome_window_for_non_browser_window(self):
-        win = self.open_chrome_window("chrome://marionette/content/test.xul")
-        self.marionette.switch_to_window(win)
+        new_window = self.open_chrome_window("chrome://marionette/content/test.xul")
+        self.marionette.switch_to_window(new_window)
 
-        self.assertIn(win, self.marionette.chrome_window_handles)
-        self.assertNotIn(win, self.marionette.window_handles)
+        self.assertIn(new_window, self.marionette.chrome_window_handles)
+        self.assertNotIn(new_window, self.marionette.window_handles)
         chrome_window_handles = self.marionette.close_chrome_window()
-        self.assertNotIn(win, chrome_window_handles)
+        self.assertNotIn(new_window, chrome_window_handles)
         self.assertListEqual(self.start_windows, chrome_window_handles)
-        self.assertNotIn(win, self.marionette.window_handles)
+        self.assertNotIn(new_window, self.marionette.window_handles)
 
     @skip_if_mobile("Interacting with chrome windows not available for Fennec")
     def test_close_chrome_window_for_last_open_window(self):
         self.close_all_windows()
 
         self.assertListEqual([], self.marionette.close_chrome_window())
         self.assertListEqual([self.start_tab], self.marionette.window_handles)
         self.assertListEqual([self.start_window], self.marionette.chrome_window_handles)
         self.assertIsNotNone(self.marionette.session)
 
-    @skip_if_mobile("Needs application independent method to open a new tab")
     def test_close_window_for_browser_tab(self):
-        tab = self.open_tab()
-        self.marionette.switch_to_window(tab)
+        new_tab = self.open_tab()
+        self.marionette.switch_to_window(new_tab)
 
         window_handles = self.marionette.close()
-        self.assertNotIn(tab, window_handles)
+        self.assertNotIn(new_tab, window_handles)
         self.assertListEqual(self.start_tabs, window_handles)
 
-    @skip_if_mobile("Needs application independent method to open a new tab")
     def test_close_window_with_dismissed_beforeunload_prompt(self):
-        tab = self.open_tab()
-        self.marionette.switch_to_window(tab)
+        new_tab = self.open_tab()
+        self.marionette.switch_to_window(new_tab)
 
         self.marionette.navigate(inline("""
           <input type="text">
           <script>
             window.addEventListener("beforeunload", function (event) {
               event.preventDefault();
             });
           </script>
         """))
 
         self.marionette.find_element(By.TAG_NAME, "input").send_keys("foo")
         self.marionette.close()
 
     @skip_if_mobile("Interacting with chrome windows not available for Fennec")
     def test_close_window_for_browser_window_with_single_tab(self):
-        win = self.open_window()
-        self.marionette.switch_to_window(win)
+        new_tab = self.open_window()
+        self.marionette.switch_to_window(new_tab)
 
-        self.assertEqual(len(self.start_tabs) + 1, len(self.marionette.window_handles))
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
         window_handles = self.marionette.close()
-        self.assertNotIn(win, window_handles)
+        self.assertNotIn(new_tab, window_handles)
         self.assertListEqual(self.start_tabs, window_handles)
         self.assertListEqual(self.start_windows, self.marionette.chrome_window_handles)
 
     def test_close_window_for_last_open_tab(self):
         self.close_all_tabs()
 
         self.assertListEqual([], self.marionette.close())
         self.assertListEqual([self.start_tab], self.marionette.window_handles)
         self.assertListEqual([self.start_window], self.marionette.chrome_window_handles)
         self.assertIsNotNone(self.marionette.session)
 
     @skip_if_mobile("discardBrowser is only available in Firefox")
     def test_close_browserless_tab(self):
         self.close_all_tabs()
 
         test_page = self.marionette.absolute_url("windowHandles.html")
-        tab = self.open_tab()
-        self.marionette.switch_to_window(tab)
+        new_tab = self.open_tab()
+        self.marionette.switch_to_window(new_tab)
         self.marionette.navigate(test_page)
         self.marionette.switch_to_window(self.start_tab)
 
         with self.marionette.using_context("chrome"):
             self.marionette.execute_async_script("""
               Components.utils.import("resource:///modules/BrowserWindowTracker.jsm");
 
               let win = BrowserWindowTracker.getTopWindow();
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py
@@ -1,29 +1,27 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import
 
 import types
 
-from marionette_driver import By, errors, Wait
+from marionette_driver import errors
 
 from marionette_harness import MarionetteTestCase, WindowManagerMixin
 
 
 class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
 
     def setUp(self):
         super(TestWindowHandles, self).setUp()
 
-        self.empty_page = self.marionette.absolute_url("empty.html")
-        self.test_page = self.marionette.absolute_url("windowHandles.html")
-        self.marionette.navigate(self.test_page)
+        self.xul_dialog = "chrome://marionette/content/test_dialog.xul"
 
         self.marionette.set_context("chrome")
 
     def tearDown(self):
         self.close_all_windows()
         self.close_all_tabs()
 
         super(TestWindowHandles, self).tearDown()
@@ -37,265 +35,171 @@ class TestWindowHandles(WindowManagerMix
 
         for handle in self.marionette.chrome_window_handles:
             self.assertIsInstance(handle, types.StringTypes)
 
         for handle in self.marionette.window_handles:
             self.assertIsInstance(handle, types.StringTypes)
 
     def test_chrome_window_handles_with_scopes(self):
-        # Open a browser and a non-browser (about window) chrome window
-        self.open_window(
-            trigger=lambda: self.marionette.execute_script("OpenBrowserWindow();"))
+        new_browser = self.open_window()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1)
+        self.assertIn(new_browser, self.marionette.chrome_window_handles)
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
 
-        self.open_window(
-            trigger=lambda: self.marionette.find_element(By.ID, "aboutName").click())
+        new_dialog = self.open_chrome_window(self.xul_dialog)
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 2)
+        self.assertIn(new_dialog, self.marionette.chrome_window_handles)
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
 
         chrome_window_handles_in_chrome_scope = self.marionette.chrome_window_handles
         window_handles_in_chrome_scope = self.marionette.window_handles
 
         with self.marionette.using_context("content"):
             self.assertEqual(self.marionette.chrome_window_handles,
                              chrome_window_handles_in_chrome_scope)
             self.assertEqual(self.marionette.window_handles,
                              window_handles_in_chrome_scope)
 
-    def test_chrome_window_handles_after_opening_new_dialog(self):
-        xul_dialog = "chrome://marionette/content/test_dialog.xul"
-        new_win = self.open_chrome_window(xul_dialog)
+    def test_chrome_window_handles_after_opening_new_chrome_window(self):
+        new_window = self.open_chrome_window(self.xul_dialog)
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1)
+        self.assertIn(new_window, self.marionette.chrome_window_handles)
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
 
-        # Check that the new tab has the correct page loaded
-        self.marionette.switch_to_window(new_win)
+        # Check that the new chrome window has the correct URL loaded
+        self.marionette.switch_to_window(new_window)
         self.assert_window_handles()
-        self.assertEqual(self.marionette.current_chrome_window_handle, new_win)
-        self.assertEqual(self.marionette.get_url(), xul_dialog)
+        self.assertEqual(self.marionette.current_chrome_window_handle, new_window)
+        self.assertEqual(self.marionette.get_url(), self.xul_dialog)
 
-        # Close the opened dialog and carry on in our original tab.
+        # Close the chrome window, and carry on in our original window.
         self.marionette.close_chrome_window()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows))
+        self.assertNotIn(new_window, self.marionette.chrome_window_handles)
 
         self.marionette.switch_to_window(self.start_window)
         self.assert_window_handles()
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
 
     def test_chrome_window_handles_after_opening_new_window(self):
-        def open_with_link():
-            with self.marionette.using_context("content"):
-                link = self.marionette.find_element(By.ID, "new-window")
-                link.click()
-
-        # We open a new window but are actually interested in the new tab
-        new_win = self.open_window(trigger=open_with_link)
+        new_window = self.open_window()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1)
+        self.assertIn(new_window, self.marionette.chrome_window_handles)
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
 
-        # Check that the new tab has the correct page loaded
-        self.marionette.switch_to_window(new_win)
+        self.marionette.switch_to_window(new_window)
         self.assert_window_handles()
-        self.assertEqual(self.marionette.current_chrome_window_handle, new_win)
-        with self.marionette.using_context("content"):
-            Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
-                lambda mn: mn.get_url() == self.empty_page,
-                message="{} did not load after opening a new tab".format(self.empty_page))
+        self.assertEqual(self.marionette.current_chrome_window_handle, new_window)
 
-        # Ensure navigate works in our current window
-        other_page = self.marionette.absolute_url("test.html")
-        with self.marionette.using_context("content"):
-            self.marionette.navigate(other_page)
-            self.assertEqual(self.marionette.get_url(), other_page)
-
-        # Close the opened window and carry on in our original tab.
+        # Close the opened window and carry on in our original window.
         self.marionette.close()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows))
+        self.assertNotIn(new_window, self.marionette.chrome_window_handles)
 
         self.marionette.switch_to_window(self.start_window)
         self.assert_window_handles()
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
 
     def test_window_handles_after_opening_new_tab(self):
-        def open_with_link():
-            with self.marionette.using_context("content"):
-                link = self.marionette.find_element(By.ID, "new-tab")
-                link.click()
-
-        new_tab = self.open_tab(trigger=open_with_link)
+        with self.marionette.using_context("content"):
+            new_tab = self.open_tab()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+        self.assertIn(new_tab, self.marionette.window_handles)
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
 
         self.marionette.switch_to_window(new_tab)
         self.assert_window_handles()
         self.assertEqual(self.marionette.current_window_handle, new_tab)
-        with self.marionette.using_context("content"):
-            Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
-                lambda mn: mn.get_url() == self.empty_page,
-                message="{} did not load after opening a new tab".format(self.empty_page))
-
-        # Ensure navigate works in our current tab
-        other_page = self.marionette.absolute_url("test.html")
-        with self.marionette.using_context("content"):
-            self.marionette.navigate(other_page)
-            self.assertEqual(self.marionette.get_url(), other_page)
 
         self.marionette.switch_to_window(self.start_tab)
         self.assert_window_handles()
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
 
         self.marionette.switch_to_window(new_tab)
         self.marionette.close()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+        self.assertNotIn(new_tab, self.marionette.window_handles)
 
         self.marionette.switch_to_window(self.start_tab)
+        self.assert_window_handles()
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+    def test_window_handles_after_opening_new_foreground_tab(self):
+        with self.marionette.using_context("content"):
+            new_tab = self.open_tab(focus=True)
+        self.assert_window_handles()
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+        self.assertIn(new_tab, self.marionette.window_handles)
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
 
-    def test_window_handles_after_opening_new_dialog(self):
-        xul_dialog = "chrome://marionette/content/test_dialog.xul"
-        new_win = self.open_chrome_window(xul_dialog)
+        # We still have the default tab set as our window handle. This
+        # get_url command should be sent immediately, and not be forever-queued.
+        with self.marionette.using_context("content"):
+            self.marionette.get_url()
+
+        self.marionette.switch_to_window(new_tab)
+        self.assert_window_handles()
+        self.assertEqual(self.marionette.current_window_handle, new_tab)
+
+        self.marionette.close()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+        self.assertNotIn(new_tab, self.marionette.window_handles)
+
+        self.marionette.switch_to_window(self.start_tab)
+        self.assert_window_handles()
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
 
-        self.marionette.switch_to_window(new_win)
+    def test_window_handles_after_opening_new_chrome_window(self):
+        new_window = self.open_chrome_window(self.xul_dialog)
         self.assert_window_handles()
-        self.assertEqual(self.marionette.get_url(), xul_dialog)
+        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+        self.assertNotIn(new_window, self.marionette.window_handles)
+        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+        self.marionette.switch_to_window(new_window)
+        self.assert_window_handles()
+        self.assertEqual(self.marionette.get_url(), self.xul_dialog)
 
         # Check that the opened dialog is not accessible via window handles
         with self.assertRaises(errors.NoSuchWindowException):
             self.marionette.current_window_handle
         with self.assertRaises(errors.NoSuchWindowException):
             self.marionette.close()
 
         # Close the dialog and carry on in our original tab.
         self.marionette.close_chrome_window()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
 
         self.marionette.switch_to_window(self.start_tab)
         self.assert_window_handles()
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+    def test_window_handles_after_closing_original_tab(self):
         with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
-
-    def test_window_handles_after_opening_new_window(self):
-        def open_with_link():
-            with self.marionette.using_context("content"):
-                link = self.marionette.find_element(By.ID, "new-window")
-                link.click()
-
-        # We open a new window but are actually interested in the new tab
-        new_tab = self.open_tab(trigger=open_with_link)
+            new_tab = self.open_tab()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
-        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
-
-        # Check that the new tab has the correct page loaded
-        self.marionette.switch_to_window(new_tab)
-        self.assert_window_handles()
-        self.assertEqual(self.marionette.current_window_handle, new_tab)
-        with self.marionette.using_context("content"):
-            Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
-                lambda mn: mn.get_url() == self.empty_page,
-                message="{} did not load after opening a new tab".format(self.empty_page))
-
-        # Ensure navigate works in our current window
-        other_page = self.marionette.absolute_url("test.html")
-        with self.marionette.using_context("content"):
-            self.marionette.navigate(other_page)
-            self.assertEqual(self.marionette.get_url(), other_page)
-
-        # Close the opened window and carry on in our original tab.
-        self.marionette.close()
-        self.assert_window_handles()
-        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
-
-        self.marionette.switch_to_window(self.start_tab)
-        self.assert_window_handles()
-        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
-
-    def test_window_handles_after_closing_original_tab(self):
-        def open_with_link():
-            with self.marionette.using_context("content"):
-                link = self.marionette.find_element(By.ID, "new-tab")
-                link.click()
-
-        new_tab = self.open_tab(trigger=open_with_link)
-        self.assert_window_handles()
-        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+        self.assertIn(new_tab, self.marionette.window_handles)
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
 
         self.marionette.close()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
-
-        self.marionette.switch_to_window(new_tab)
-        self.assert_window_handles()
-        self.assertEqual(self.marionette.current_window_handle, new_tab)
-        with self.marionette.using_context("content"):
-            Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
-                lambda mn: mn.get_url() == self.empty_page,
-                message="{} did not load after opening a new tab".format(self.empty_page))
-
-    def test_window_handles_no_switch(self):
-        """Regression test for bug 1294456.
-        This test is testing the case where Marionette attempts to send a
-        command to a window handle when the browser has opened and selected
-        a new tab. Before bug 1294456 landed, the Marionette driver was getting
-        confused about which window handle the client cared about, and assumed
-        it was the window handle for the newly opened and selected tab.
-
-        This caused Marionette to think that the browser needed to do a remoteness
-        flip in the e10s case, since the tab opened by menu_newNavigatorTab is
-        about:newtab (which is currently non-remote). This meant that commands
-        sent to what should have been the original window handle would be
-        queued and never sent, since the remoteness flip in the new tab was
-        never going to happen.
-        """
-        def open_with_menu():
-            menu_new_tab = self.marionette.find_element(By.ID, 'menu_newNavigatorTab')
-            menu_new_tab.click()
-
-        new_tab = self.open_tab(trigger=open_with_menu)
-        self.assert_window_handles()
-
-        # We still have the default tab set as our window handle. This
-        # get_url command should be sent immediately, and not be forever-queued.
-        with self.marionette.using_context("content"):
-            self.assertEqual(self.marionette.get_url(), self.test_page)
-
-        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
-        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+        self.assertIn(new_tab, self.marionette.window_handles)
 
         self.marionette.switch_to_window(new_tab)
         self.assert_window_handles()
         self.assertEqual(self.marionette.current_window_handle, new_tab)
 
-        self.marionette.close()
-        self.assert_window_handles()
-        self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
-
-        self.marionette.switch_to_window(self.start_tab)
-        self.assert_window_handles()
-        self.assertEqual(self.marionette.current_window_handle, self.start_tab)
-
     def test_window_handles_after_closing_last_window(self):
         self.close_all_windows()
         self.assertEqual(self.marionette.close_chrome_window(), [])
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py
@@ -2,135 +2,96 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import
 
 import types
 import urllib
 
-from marionette_driver import By, errors, Wait
+from marionette_driver import errors
 
 from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin
 
 
 def inline(doc):
     return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
 
 
 class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
 
     def setUp(self):
         super(TestWindowHandles, self).setUp()
 
-        self.empty_page = self.marionette.absolute_url("empty.html")
-        self.test_page = self.marionette.absolute_url("windowHandles.html")
-        self.marionette.navigate(self.test_page)
+        self.xul_dialog = "chrome://marionette/content/test_dialog.xul"
 
     def tearDown(self):
         self.close_all_tabs()
 
         super(TestWindowHandles, self).tearDown()
 
     def assert_window_handles(self):
         try:
             self.assertIsInstance(self.marionette.current_window_handle, types.StringTypes)
         except errors.NoSuchWindowException:
             pass
 
         for handle in self.marionette.window_handles:
             self.assertIsInstance(handle, types.StringTypes)
 
-    def test_window_handles_after_opening_new_tab(self):
-        def open_with_link():
-            link = self.marionette.find_element(By.ID, "new-tab")
-            link.click()
-
-        new_tab = self.open_tab(trigger=open_with_link)
+    def tst_window_handles_after_opening_new_tab(self):
+        new_tab = self.open_tab()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
 
         self.marionette.switch_to_window(new_tab)
         self.assert_window_handles()
         self.assertEqual(self.marionette.current_window_handle, new_tab)
-        Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
-            lambda mn: mn.get_url() == self.empty_page,
-            message="{} did not load after opening a new tab".format(self.empty_page))
 
         self.marionette.switch_to_window(self.start_tab)
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
-        self.assertEqual(self.marionette.get_url(), self.test_page)
 
         self.marionette.switch_to_window(new_tab)
         self.marionette.close()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
 
         self.marionette.switch_to_window(self.start_tab)
         self.assert_window_handles()
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
 
-    def test_window_handles_after_opening_new_browser_window(self):
-        def open_with_link():
-            link = self.marionette.find_element(By.ID, "new-window")
-            link.click()
-
-        # We open a new window but are actually interested in the new tab
-        new_tab = self.open_tab(trigger=open_with_link)
+    def tst_window_handles_after_opening_new_browser_window(self):
+        new_tab = self.open_window()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
 
-        # Check that the new tab has the correct page loaded
         self.marionette.switch_to_window(new_tab)
         self.assert_window_handles()
         self.assertEqual(self.marionette.current_window_handle, new_tab)
-        Wait(self.marionette, self.marionette.timeout.page_load).until(
-            lambda _: self.marionette.get_url() == self.empty_page,
-            message="The expected page '{}' has not been loaded".format(self.empty_page))
-
-        # Ensure navigate works in our current window
-        other_page = self.marionette.absolute_url("test.html")
-        self.marionette.navigate(other_page)
-        self.assertEqual(self.marionette.get_url(), other_page)
 
         # Close the opened window and carry on in our original tab.
         self.marionette.close()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
 
         self.marionette.switch_to_window(self.start_tab)
         self.assert_window_handles()
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
-        self.assertEqual(self.marionette.get_url(), self.test_page)
 
     @skip_if_mobile("Fennec doesn't support other chrome windows")
-    def test_window_handles_after_opening_new_non_browser_window(self):
-        def open_with_link():
-            self.marionette.navigate(inline("""
-              <a id="blob-download" download="foo.html">Download</a>
-
-              <script>
-                const string = "test";
-                const blob = new Blob([string], { type: "text/html" });
-
-                const link = document.getElementById("blob-download");
-                link.href = URL.createObjectURL(blob);
-              </script>
-            """))
-            link = self.marionette.find_element(By.ID, "blob-download")
-            link.click()
-
-        new_win = self.open_window(trigger=open_with_link)
+    def tst_window_handles_after_opening_new_non_browser_window(self):
+        new_window = self.open_chrome_window(self.xul_dialog)
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+        self.assertNotIn(new_window, self.marionette.window_handles)
 
-        self.marionette.switch_to_window(new_win)
+        self.marionette.switch_to_window(new_window)
         self.assert_window_handles()
 
         # Check that the opened window is not accessible via window handles
         with self.assertRaises(errors.NoSuchWindowException):
             self.marionette.current_window_handle
         with self.assertRaises(errors.NoSuchWindowException):
             self.marionette.close()
 
@@ -139,31 +100,26 @@ class TestWindowHandles(WindowManagerMix
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
 
         self.marionette.switch_to_window(self.start_tab)
         self.assert_window_handles()
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
 
     def test_window_handles_after_closing_original_tab(self):
-        def open_with_link():
-            link = self.marionette.find_element(By.ID, "new-tab")
-            link.click()
-
-        new_tab = self.open_tab(trigger=open_with_link)
+        new_tab = self.open_tab()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+        self.assertIn(new_tab, self.marionette.window_handles)
 
         self.marionette.close()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+        self.assertNotIn(self.start_tab, self.marionette.window_handles)
 
         self.marionette.switch_to_window(new_tab)
         self.assert_window_handles()
         self.assertEqual(self.marionette.current_window_handle, new_tab)
-        Wait(self.marionette, self.marionette.timeout.page_load).until(
-            lambda _: self.marionette.get_url() == self.empty_page,
-            message="The expected page '{}' has not been loaded".format(self.empty_page))
 
-    def test_window_handles_after_closing_last_tab(self):
+    def tst_window_handles_after_closing_last_tab(self):
         self.close_all_tabs()
         self.assertEqual(self.marionette.close(), [])
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_window_management.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_management.py
@@ -16,93 +16,87 @@ class TestNoSuchWindowContent(WindowMana
         super(TestNoSuchWindowContent, self).setUp()
 
     def tearDown(self):
         self.close_all_tabs()
         super(TestNoSuchWindowContent, self).tearDown()
 
     @skip_if_mobile("Fennec doesn't support other chrome windows")
     def test_closed_chrome_window(self):
-
-        def open_with_link():
-            with self.marionette.using_context("content"):
-                test_page = self.marionette.absolute_url("windowHandles.html")
-                self.marionette.navigate(test_page)
-                self.marionette.find_element(By.ID, "new-window").click()
-
-        win = self.open_window(open_with_link)
-        self.marionette.switch_to_window(win)
+        with self.marionette.using_context("chrome"):
+            new_window = self.open_window()
+        self.marionette.switch_to_window(new_window)
         self.marionette.close_chrome_window()
 
         # When closing a browser window both handles are not available
         for context in ("chrome", "content"):
             with self.marionette.using_context(context):
                 with self.assertRaises(NoSuchWindowException):
                     self.marionette.current_chrome_window_handle
                 with self.assertRaises(NoSuchWindowException):
                     self.marionette.current_window_handle
 
         self.marionette.switch_to_window(self.start_window)
 
         with self.assertRaises(NoSuchWindowException):
-            self.marionette.switch_to_window(win)
+            self.marionette.switch_to_window(new_window)
 
     @skip_if_mobile("Fennec doesn't support other chrome windows")
     def test_closed_chrome_window_while_in_frame(self):
-        win = self.open_chrome_window("chrome://marionette/content/test.xul")
-        self.marionette.switch_to_window(win)
+        new_window = self.open_chrome_window("chrome://marionette/content/test.xul")
+        self.marionette.switch_to_window(new_window)
         with self.marionette.using_context("chrome"):
             self.marionette.switch_to_frame("iframe")
         self.marionette.close_chrome_window()
 
         with self.assertRaises(NoSuchWindowException):
             self.marionette.current_window_handle
         with self.assertRaises(NoSuchWindowException):
             self.marionette.current_chrome_window_handle
 
         self.marionette.switch_to_window(self.start_window)
 
         with self.assertRaises(NoSuchWindowException):
-            self.marionette.switch_to_window(win)
+            self.marionette.switch_to_window(new_window)
 
     def test_closed_tab(self):
-        with self.marionette.using_context("content"):
-            tab = self.open_tab()
-            self.marionette.switch_to_window(tab)
-            self.marionette.close()
+        new_tab = self.open_tab()
+        self.marionette.switch_to_window(new_tab)
+        self.marionette.close()
 
         # Check that only the content window is not available in both contexts
         for context in ("chrome", "content"):
             with self.marionette.using_context(context):
                 with self.assertRaises(NoSuchWindowException):
                     self.marionette.current_window_handle
                 self.marionette.current_chrome_window_handle
 
         self.marionette.switch_to_window(self.start_tab)
 
         with self.assertRaises(NoSuchWindowException):
-            self.marionette.switch_to_window(tab)
+            self.marionette.switch_to_window(new_tab)
 
     def test_closed_tab_while_in_frame(self):
+        new_tab = self.open_tab()
+        self.marionette.switch_to_window(new_tab)
+
         with self.marionette.using_context("content"):
-            tab = self.open_tab()
-            self.marionette.switch_to_window(tab)
             self.marionette.navigate(self.marionette.absolute_url("test_iframe.html"))
             frame = self.marionette.find_element(By.ID, "test_iframe")
             self.marionette.switch_to_frame(frame)
-            self.marionette.close()
+        self.marionette.close()
 
-            with self.assertRaises(NoSuchWindowException):
-                self.marionette.current_window_handle
-            self.marionette.current_chrome_window_handle
+        with self.assertRaises(NoSuchWindowException):
+            self.marionette.current_window_handle
+        self.marionette.current_chrome_window_handle
 
         self.marionette.switch_to_window(self.start_tab)
 
         with self.assertRaises(NoSuchWindowException):
-            self.marionette.switch_to_window(tab)
+            self.marionette.switch_to_window(new_tab)
 
 
 class TestNoSuchWindowChrome(TestNoSuchWindowContent):
 
     def setUp(self):
         super(TestNoSuchWindowChrome, self).setUp()
         self.marionette.set_context("chrome")
 
@@ -116,47 +110,27 @@ class TestSwitchWindow(WindowManagerMixi
     def setUp(self):
         super(TestSwitchWindow, self).setUp()
         self.marionette.set_context("chrome")
 
     def tearDown(self):
         self.close_all_windows()
         super(TestSwitchWindow, self).tearDown()
 
-    def test_windows(self):
-        def open_browser_with_js():
-            self.marionette.execute_script(" window.open(); ")
-
-        new_window = self.open_window(trigger=open_browser_with_js)
+    def test_switch_window_after_open_and_close(self):
+        with self.marionette.using_context("chrome"):
+            new_window = self.open_window()
+        self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1)
+        self.assertIn(new_window, self.marionette.chrome_window_handles)
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
 
-        # switch to the other window
+        # switch to the new chrome window and close it
         self.marionette.switch_to_window(new_window)
         self.assertEqual(self.marionette.current_chrome_window_handle, new_window)
         self.assertNotEqual(self.marionette.current_chrome_window_handle, self.start_window)
 
-        # switch back and close original window
+        self.marionette.close_chrome_window()
+        self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows))
+        self.assertNotIn(new_window, self.marionette.chrome_window_handles)
+
+        # switch back to the original chrome window
         self.marionette.switch_to_window(self.start_window)
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
-        self.marionette.close_chrome_window()
-
-        self.assertNotIn(self.start_window, self.marionette.chrome_window_handles)
-        self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows))
-
-    def test_should_load_and_close_a_window(self):
-        def open_window_with_link():
-            test_html = self.marionette.absolute_url("test_windows.html")
-            with self.marionette.using_context("content"):
-                self.marionette.navigate(test_html)
-                self.marionette.find_element(By.LINK_TEXT, "Open new window").click()
-
-        new_window = self.open_window(trigger=open_window_with_link)
-        self.marionette.switch_to_window(new_window)
-        self.assertEqual(self.marionette.current_chrome_window_handle, new_window)
-        self.assertEqual(len(self.marionette.chrome_window_handles), 2)
-
-        with self.marionette.using_context('content'):
-            self.assertEqual(self.marionette.title, "We Arrive Here")
-
-        # Let's close and check
-        self.marionette.close_chrome_window()
-        self.marionette.switch_to_window(self.start_window)
-        self.assertEqual(len(self.marionette.chrome_window_handles), 1)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_window_status_content.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_status_content.py
@@ -10,100 +10,87 @@ from marionette_driver.errors import NoS
 from marionette_harness import MarionetteTestCase, WindowManagerMixin, skip_if_mobile
 
 
 class TestNoSuchWindowContent(WindowManagerMixin, MarionetteTestCase):
 
     def setUp(self):
         super(TestNoSuchWindowContent, self).setUp()
 
-        self.test_page = self.marionette.absolute_url("windowHandles.html")
-        with self.marionette.using_context("content"):
-            self.marionette.navigate(self.test_page)
-
     def tearDown(self):
         self.close_all_windows()
         super(TestNoSuchWindowContent, self).tearDown()
 
-    def open_tab_in_foreground(self):
-        with self.marionette.using_context("content"):
-            link = self.marionette.find_element(By.ID, "new-tab")
-            link.click()
-
     @skip_if_mobile("Fennec doesn't support other chrome windows")
     def test_closed_chrome_window(self):
-
-        def open_with_link():
-            with self.marionette.using_context("content"):
-                test_page = self.marionette.absolute_url("windowHandles.html")
-                self.marionette.navigate(test_page)
-                self.marionette.find_element(By.ID, "new-window").click()
-
-        win = self.open_window(open_with_link)
-        self.marionette.switch_to_window(win)
+        with self.marionette.using_context("chrome"):
+            new_window = self.open_window()
+        self.marionette.switch_to_window(new_window)
         self.marionette.close_chrome_window()
 
         # When closing a browser window both handles are not available
         for context in ("chrome", "content"):
             with self.marionette.using_context(context):
                 with self.assertRaises(NoSuchWindowException):
                     self.marionette.current_chrome_window_handle
                 with self.assertRaises(NoSuchWindowException):
                     self.marionette.current_window_handle
 
         self.marionette.switch_to_window(self.start_window)
 
         with self.assertRaises(NoSuchWindowException):
-            self.marionette.switch_to_window(win)
+            self.marionette.switch_to_window(new_window)
 
     @skip_if_mobile("Fennec doesn't support other chrome windows")
     def test_closed_chrome_window_while_in_frame(self):
-        win = self.open_chrome_window("chrome://marionette/content/test.xul")
-        self.marionette.switch_to_window(win)
+        new_window = self.open_chrome_window("chrome://marionette/content/test.xul")
+        self.marionette.switch_to_window(new_window)
+
         with self.marionette.using_context("chrome"):
             self.marionette.switch_to_frame("iframe")
         self.marionette.close_chrome_window()
 
         with self.assertRaises(NoSuchWindowException):
             self.marionette.current_window_handle
         with self.assertRaises(NoSuchWindowException):
             self.marionette.current_chrome_window_handle
 
         self.marionette.switch_to_window(self.start_window)
 
         with self.assertRaises(NoSuchWindowException):
-            self.marionette.switch_to_window(win)
+            self.marionette.switch_to_window(new_window)
 
     def test_closed_tab(self):
-        with self.marionette.using_context("content"):
-            tab = self.open_tab(self.open_tab_in_foreground)
-            self.marionette.switch_to_window(tab)
-            self.marionette.close()
+        new_tab = self.open_tab(focus=True)
+        self.marionette.switch_to_window(new_tab)
+        self.marionette.close()
 
         # Check that only the content window is not available in both contexts
         for context in ("chrome", "content"):
             with self.marionette.using_context(context):
                 with self.assertRaises(NoSuchWindowException):
                     self.marionette.current_window_handle
                 self.marionette.current_chrome_window_handle
 
         self.marionette.switch_to_window(self.start_tab)
 
         with self.assertRaises(NoSuchWindowException):
-            self.marionette.switch_to_window(tab)
+            self.marionette.switch_to_window(new_tab)
 
     def test_closed_tab_while_in_frame(self):
+        new_tab = self.open_tab()
+        self.marionette.switch_to_window(new_tab)
+
         with self.marionette.using_context("content"):
-            tab = self.open_tab(self.open_tab_in_foreground)
-            self.marionette.switch_to_window(tab)
             self.marionette.navigate(self.marionette.absolute_url("test_iframe.html"))
             frame = self.marionette.find_element(By.ID, "test_iframe")
             self.marionette.switch_to_frame(frame)
-            self.marionette.close()
+
+        self.marionette.close()
 
-            with self.assertRaises(NoSuchWindowException):
-                self.marionette.current_window_handle
-            self.marionette.current_chrome_window_handle
+        with self.assertRaises(NoSuchWindowException):
+            self.marionette.current_window_handle
+        self.marionette.current_chrome_window_handle
 
         self.marionette.switch_to_window(self.start_tab)
 
         with self.assertRaises(NoSuchWindowException):
-            self.marionette.switch_to_window(tab)
+            self.marionette.switch_to_window(new_tab)
--- a/testing/marionette/server.js
+++ b/testing/marionette/server.js
@@ -6,17 +6,16 @@
 
 const CC = Components.Constructor;
 
 const ServerSocket = CC(
     "@mozilla.org/network/server-socket;1",
     "nsIServerSocket",
     "initSpecialConnection");
 
-ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.import("chrome://marionette/content/assert.js");
 const {GeckoDriver} = ChromeUtils.import("chrome://marionette/content/driver.js", {});
 const {WebElement} = ChromeUtils.import("chrome://marionette/content/element.js", {});
 const {
   error,
   UnknownCommandError,
@@ -69,17 +68,17 @@ class TCPListener {
    *
    * Determines the application to initialise the driver with.
    *
    * @return {GeckoDriver}
    *     A driver instance.
    */
   driverFactory() {
     MarionettePrefs.contentListener = false;
-    return new GeckoDriver(Services.appinfo.ID, this);
+    return new GeckoDriver(this);
   }
 
   set acceptConnections(value) {
     if (value) {
       if (!this.socket) {
         try {
           const flags = KeepWhenOffline | LoopbackOnly;
           const backlog = 1;
--- a/testing/marionette/sync.js
+++ b/testing/marionette/sync.js
@@ -8,33 +8,53 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const {
   error,
   stack,
   TimeoutError,
 } = ChromeUtils.import("chrome://marionette/content/error.js", {});
+const {truncate} = ChromeUtils.import("chrome://marionette/content/format.js", {});
 const {Log} = ChromeUtils.import("chrome://marionette/content/log.js", {});
 
 XPCOMUtils.defineLazyGetter(this, "log", Log.get);
 
 this.EXPORTED_SYMBOLS = [
+  "executeSoon",
   "DebounceCallback",
   "IdlePromise",
   "MessageManagerDestroyedPromise",
   "PollPromise",
   "Sleep",
   "TimedPromise",
+  "waitForEvent",
+  "waitForMessage",
+  "waitForObserverTopic",
 ];
 
 const {TYPE_ONE_SHOT, TYPE_REPEATING_SLACK} = Ci.nsITimer;
 
 const PROMISE_TIMEOUT = AppConstants.DEBUG ? 4500 : 1500;
 
+
+/**
+ * Dispatch a function to be executed on the main thread.
+ *
+ * @param {function} func
+ *     Function to be executed.
+ */
+function executeSoon(func) {
+  if (typeof func != "function") {
+    throw new TypeError();
+  }
+
+  Services.tm.dispatchToMainThread(func);
+}
+
 /**
  * @callback Condition
  *
  * @param {function(*)} resolve
  *     To be called when the condition has been met.  Will return the
  *     resolved value.
  * @param {function} reject
  *     To be called when the condition has not been met.  Will cause
@@ -67,66 +87,74 @@ const PROMISE_TIMEOUT = AppConstants.DEB
  *
  *     let els = new PollPromise((resolve, reject) => {
  *       let res = document.querySelectorAll("p");
  *       if (res.length > 0) {
  *         resolve(Array.from(res));
  *       } else {
  *         reject([]);
  *       }
- *     });
+ *     }, {timeout: 1000});
  *
  * @param {Condition} func
  *     Function to run off the main thread.
- * @param {number=} [timeout=2000] timeout
- *     Desired timeout.  If 0 or less than the runtime evaluation
+ * @param {number=} [timeout] timeout
+ *     Desired timeout if wanted.  If 0 or less than the runtime evaluation
  *     time of ``func``, ``func`` is guaranteed to run at least once.
- *     The default is 2000 milliseconds.
+ *     Defaults to using no timeout.
  * @param {number=} [interval=10] interval
  *     Duration between each poll of ``func`` in milliseconds.
  *     Defaults to 10 milliseconds.
  *
  * @return {Promise.<*>}
  *     Yields the value passed to ``func``'s
  *     ``resolve`` or ``reject`` callbacks.
  *
  * @throws {*}
  *     If ``func`` throws, its error is propagated.
  * @throws {TypeError}
  *     If `timeout` or `interval`` are not numbers.
  * @throws {RangeError}
  *     If `timeout` or `interval` are not unsigned integers.
  */
-function PollPromise(func, {timeout = 2000, interval = 10} = {}) {
+function PollPromise(func, {timeout = null, interval = 10} = {}) {
   const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 
   if (typeof func != "function") {
     throw new TypeError();
   }
-  if (!(typeof timeout == "number" && typeof interval == "number")) {
+  if (timeout != null && typeof timeout != "number") {
     throw new TypeError();
   }
-  if ((!Number.isInteger(timeout) || timeout < 0) ||
+  if (typeof interval != "number") {
+    throw new TypeError();
+  }
+  if ((timeout && (!Number.isInteger(timeout) || timeout < 0)) ||
       (!Number.isInteger(interval) || interval < 0)) {
     throw new RangeError();
   }
 
   return new Promise((resolve, reject) => {
-    const start = new Date().getTime();
-    const end = start + timeout;
+    let start, end;
+
+    if (Number.isInteger(timeout)) {
+      start = new Date().getTime();
+      end = start + timeout;
+    }
 
     let evalFn = () => {
       new Promise(func).then(resolve, rejected => {
         if (error.isError(rejected)) {
           throw rejected;
         }
 
-        // return if timeout is 0, allowing |func| to be evaluated at
-        // least once
-        if (start == end || new Date().getTime() >= end) {
+        // return if there is a timeout and set to 0,
+        // allowing |func| to be evaluated at least once
+        if (typeof end != "undefined" &&
+            (start == end || new Date().getTime() >= end)) {
           resolve(rejected);
         }
       }).catch(reject);
     };
 
     // the repeating slack timer waits |interval|
     // before invoking |evalFn|
     evalFn();
@@ -346,8 +374,197 @@ class DebounceCallback {
     this.timer.cancel();
     this.timer.initWithCallback(() => {
       this.timer.cancel();
       this.fn(ev);
     }, this.timeout, TYPE_ONE_SHOT);
   }
 }
 this.DebounceCallback = DebounceCallback;
+
+/**
+ * Wait for an event to be fired on a specified element.
+ *
+ * This method has been duplicated from BrowserTestUtils.jsm.
+ *
+ * Because this function is intended for testing, any error in checkFn
+ * will cause the returned promise to be rejected instead of waiting for
+ * the next event, since this is probably a bug in the test.
+ *
+ * Usage::
+ *
+ *    let promiseEvent = waitForEvent(element, "eventName");
+ *    // Do some processing here that will cause the event to be fired
+ *    // ...
+ *    // Now wait until the Promise is fulfilled
+ *    let receivedEvent = await promiseEvent;
+ *
+ * The promise resolution/rejection handler for the returned promise is
+ * guaranteed not to be called until the next event tick after the event
+ * listener gets called, so that all other event listeners for the element
+ * are executed before the handler is executed::
+ *
+ *    let promiseEvent = waitForEvent(element, "eventName");
+ *    // Same event tick here.
+ *    await promiseEvent;
+ *    // Next event tick here.
+ *
+ * If some code, such like adding yet another event listener, needs to be
+ * executed in the same event tick, use raw addEventListener instead and
+ * place the code inside the event listener::
+ *
+ *    element.addEventListener("load", () => {
+ *      // Add yet another event listener in the same event tick as the load
+ *      // event listener.
+ *      p = waitForEvent(element, "ready");
+ *    }, { once: true });
+ *
+ * @param {Element} subject
+ *     The element that should receive the event.
+ * @param {string} eventName
+ *     Name of the event to listen to.
+ * @param {Object=} options
+ *     Extra options.
+ * @param {boolean=} options.capture
+ *     True to use a capturing listener.
+ * @param {function(Event)=} options.checkFn
+ *     Called with the ``Event`` object as argument, should return ``true``
+ *     if the event is the expected one, or ``false`` if it should be
+ *     ignored and listening should continue. If not specified, the first
+ *     event with the specified name resolves the returned promise.
+ * @param {boolean=} options.wantsUntrusted
+ *     True to receive synthetic events dispatched by web content.
+ *
+ * @return {Promise.<Event>}
+ *     Promise which resolves to the received ``Event`` object, or rejects
+ *     in case of a failure.
+ */
+function waitForEvent(subject, eventName,
+    {capture = false, checkFn = null, wantsUntrusted = false} = {}) {
+  if (subject == null || !("addEventListener" in subject)) {
+    throw new TypeError();
+  }
+  if (typeof eventName != "string") {
+    throw new TypeError();
+  }
+  if (capture != null && typeof capture != "boolean") {
+    throw new TypeError();
+  }
+  if (checkFn != null && typeof checkFn != "function") {
+    throw new TypeError();
+  }
+  if (wantsUntrusted != null && typeof wantsUntrusted != "boolean") {
+    throw new TypeError();
+  }
+
+  return new Promise((resolve, reject) => {
+    subject.addEventListener(eventName, function listener(event) {
+      log.trace(`Received DOM event ${event.type} for ${event.target}`);
+      try {
+        if (checkFn && !checkFn(event)) {
+          return;
+        }
+        subject.removeEventListener(eventName, listener, capture);
+        executeSoon(() => resolve(event));
+      } catch (ex) {
+        try {
+          subject.removeEventListener(eventName, listener, capture);
+        } catch (ex2) {
+          // Maybe the provided object does not support removeEventListener.
+        }
+        executeSoon(() => reject(ex));
+      }
+    }, capture, wantsUntrusted);
+  });
+}
+
+/**
+ * Wait for a message to be fired from a particular message manager.
+ *
+ * This method has been duplicated from BrowserTestUtils.jsm.
+ *
+ * @param {nsIMessageManager} messageManager
+ *     The message manager that should be used.
+ * @param {string} messageName
+ *     The message to wait for.
+ * @param {Object=} options
+ *     Extra options.
+ * @param {function(Message)=} options.checkFn
+ *     Called with the ``Message`` object as argument, should return ``true``
+ *     if the message is the expected one, or ``false`` if it should be
+ *     ignored and listening should continue. If not specified, the first
+ *     message with the specified name resolves the returned promise.
+ *
+ * @return {Promise.<Object>}
+ *     Promise which resolves to the data property of the received
+ *     ``Message``.
+ */
+function waitForMessage(messageManager, messageName,
+    {checkFn = undefined} = {}) {
+  if (messageManager == null || !("addMessageListener" in messageManager)) {
+    throw new TypeError();
+  }
+  if (typeof messageName != "string") {
+    throw new TypeError();
+  }
+  if (checkFn && typeof checkFn != "function") {
+    throw new TypeError();
+  }
+
+  return new Promise(resolve => {
+    messageManager.addMessageListener(messageName, function onMessage(msg) {
+      log.trace(`Received ${messageName} for ${msg.target}`);
+      if (checkFn && !checkFn(msg)) {
+        return;
+      }
+      messageManager.removeMessageListener(messageName, onMessage);
+      resolve(msg.data);
+    });
+  });
+}
+
+/**
+ * Wait for the specified observer topic to be observed.
+ *
+ * This method has been duplicated from TestUtils.jsm.
+ *
+ * Because this function is intended for testing, any error in checkFn
+ * will cause the returned promise to be rejected instead of waiting for
+ * the next notification, since this is probably a bug in the test.
+ *
+ * @param {string} topic
+ *     The topic to observe.
+ * @param {Object=} options
+ *     Extra options.
+ * @param {function(String,Object)=} options.checkFn
+ *     Called with ``subject``, and ``data`` as arguments, should return true
+ *     if the notification is the expected one, or false if it should be
+ *     ignored and listening should continue. If not specified, the first
+ *     notification for the specified topic resolves the returned promise.
+ *
+ * @return {Promise.<Array<String, Object>>}
+ *     Promise which resolves to an array of ``subject``, and ``data`` from
+ *     the observed notification.
+ */
+function waitForObserverTopic(topic, {checkFn = null} = {}) {
+  if (typeof topic != "string") {
+    throw new TypeError();
+  }
+  if (checkFn != null && typeof checkFn != "function") {
+    throw new TypeError();
+  }
+
+  return new Promise((resolve, reject) => {
+    Services.obs.addObserver(function observer(subject, topic, data) {
+      log.trace(`Received observer notification ${topic}`);
+      try {
+        if (checkFn && !checkFn(subject, data)) {
+          return;
+        }
+        Services.obs.removeObserver(observer, topic);
+        resolve({subject, data});
+      } catch (ex) {
+        Services.obs.removeObserver(observer, topic);
+        reject(ex);
+      }
+    }, topic);
+  });
+}
--- a/testing/marionette/test/unit/test_sync.js
+++ b/testing/marionette/test/unit/test_sync.js
@@ -1,21 +1,96 @@
 /* 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/. */
 
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
 const {
   DebounceCallback,
   IdlePromise,
   PollPromise,
   Sleep,
   TimedPromise,
+  waitForEvent,
+  waitForMessage,
+  waitForObserverTopic,
 } = ChromeUtils.import("chrome://marionette/content/sync.js", {});
 
-const DEFAULT_TIMEOUT = 2000;
+/**
+ * Mimic a DOM node for listening for events.
+ */
+class MockElement {
+  constructor() {
+    this.capture = false;
+    this.func = null;
+    this.eventName = null;
+    this.untrusted = false;
+  }
+
+  addEventListener(name, func, capture, untrusted) {
+    this.eventName = name;
+    this.func = func;
+    if (capture != null) {
+      this.capture = capture;
+    }
+    if (untrusted != null) {
+      this.untrusted = untrusted;
+    }
+  }
+
+  click() {
+    if (this.func) {
+      let details = {
+        capture: this.capture,
+        target: this,
+        type: this.eventName,
+        untrusted: this.untrusted,
+      };
+      this.func(details);
+    }
+  }
+
+  removeEventListener(name, func) {
+    this.capture = false;
+    this.func = null;
+    this.eventName = null;
+    this.untrusted = false;
+  }
+}
+
+/**
+ * Mimic a message manager for sending messages.
+ */
+class MessageManager {
+  constructor() {
+    this.func = null;
+    this.message = null;
+  }
+
+  addMessageListener(message, func) {
+    this.func = func;
+    this.message = message;
+  }
+
+  removeMessageListener(message) {
+    this.func = null;
+    this.message = null;
+  }
+
+  send(message, data) {
+    if (this.func) {
+      this.func({
+        data,
+        message,
+        target: this,
+      });
+    }
+  }
+}
 
 /**
  * Mimics nsITimer, but instead of using a system clock you can
  * preprogram it to invoke the callback after a given number of ticks.
  */
 class MockTimer {
   constructor(ticksBeforeFiring) {
     this.goal = ticksBeforeFiring;
@@ -30,34 +105,53 @@ class MockTimer {
     }
   }
 
   cancel() {
     this.cancelled = true;
   }
 }
 
+add_test(function test_executeSoon_callback() {
+  // executeSoon() is already defined for xpcshell in head.js. As such import
+  // our implementation into a custom namespace.
+  let sync = {};
+  ChromeUtils.import("chrome://marionette/content/sync.js", sync);
+
+  for (let func of ["foo", null, true, [], {}]) {
+    Assert.throws(() => sync.executeSoon(func), /TypeError/);
+  }
+
+  let a;
+  sync.executeSoon(() => { a = 1; });
+  executeSoon(() => equal(1, a));
+
+  run_next_test();
+});
+
 add_test(function test_PollPromise_funcTypes() {
   for (let type of ["foo", 42, null, undefined, true, [], {}]) {
     Assert.throws(() => new PollPromise(type), /TypeError/);
   }
   new PollPromise(() => {});
   new PollPromise(function() {});
 
   run_next_test();
 });
 
 add_test(function test_PollPromise_timeoutTypes() {
-  for (let timeout of ["foo", null, true, [], {}]) {
+  for (let timeout of ["foo", true, [], {}]) {
     Assert.throws(() => new PollPromise(() => {}, {timeout}), /TypeError/);
   }
   for (let timeout of [1.2, -1]) {
     Assert.throws(() => new PollPromise(() => {}, {timeout}), /RangeError/);
   }
-  new PollPromise(() => {}, {timeout: 42});
+  for (let timeout of [null, undefined, 42]) {
+    new PollPromise(resolve => resolve(1), {timeout});
+  }
 
   run_next_test();
 });
 
 add_test(function test_PollPromise_intervalTypes() {
   for (let interval of ["foo", null, true, [], {}]) {
     Assert.throws(() => new PollPromise(() => {}, {interval}), /TypeError/);
   }
@@ -70,65 +164,62 @@ add_test(function test_PollPromise_inter
 });
 
 add_task(async function test_PollPromise_retvalTypes() {
   for (let typ of [true, false, "foo", 42, [], {}]) {
     strictEqual(typ, await new PollPromise(resolve => resolve(typ)));
   }
 });
 
-add_task(async function test_PollPromise_timeoutElapse() {
-  let nevals = 0;
-  let start = new Date().getTime();
-  await new PollPromise((resolve, reject) => {
-    ++nevals;
-    reject();
-  });
-  let end = new Date().getTime();
-  greaterOrEqual((end - start), DEFAULT_TIMEOUT);
-  greaterOrEqual(nevals, 15);
-});
-
 add_task(async function test_PollPromise_rethrowError() {
   let nevals = 0;
   let err;
   try {
     await PollPromise(() => {
       ++nevals;
       throw new Error();
     });
   } catch (e) {
     err = e;
   }
   equal(1, nevals);
   ok(err instanceof Error);
 });
 
 add_task(async function test_PollPromise_noTimeout() {
+  let nevals = 0;
+  await new PollPromise((resolve, reject) => {
+    ++nevals;
+    nevals < 100 ? reject() : resolve();
+  });
+  equal(100, nevals);
+});
+
+add_task(async function test_PollPromise_zeroTimeout() {
   // run at least once when timeout is 0
   let nevals = 0;
   let start = new Date().getTime();
   await new PollPromise((resolve, reject) => {
     ++nevals;
     reject();
   }, {timeout: 0});
   let end = new Date().getTime();
   equal(1, nevals);
-  less((end - start), DEFAULT_TIMEOUT);
+  less((end - start), 500);
 });
 
-add_task(async function test_PollPromise_timeout() {
+add_task(async function test_PollPromise_timeoutElapse() {
   let nevals = 0;
   let start = new Date().getTime();
   await new PollPromise((resolve, reject) => {
     ++nevals;
     reject();
   }, {timeout: 100});
   let end = new Date().getTime();
-  greater(nevals, 1);
+  lessOrEqual(nevals, 11);
   greaterOrEqual((end - start), 100);
 });
 
 add_task(async function test_PollPromise_interval() {
   let nevals = 0;
   await new PollPromise((resolve, reject) => {
     ++nevals;
     reject();
@@ -208,8 +299,160 @@ add_task(async function test_DebounceCal
   // we only expect the last one to fire
   debouncer.handleEvent(uniqueEvent);
   debouncer.handleEvent(uniqueEvent);
   debouncer.handleEvent(uniqueEvent);
 
   equal(ncalls, 1);
   ok(debouncer.timer.cancelled);
 });
+
+add_task(async function test_waitForEvent_subjectAndEventNameTypes() {
+  let element = new MockElement();
+
+  for (let subject of ["foo", 42, null, undefined, true, [], {}]) {
+    Assert.throws(() => waitForEvent(subject, "click"), /TypeError/);
+  }
+
+  for (let eventName of [42, null, undefined, true, [], {}]) {
+    Assert.throws(() => waitForEvent(element, eventName), /TypeError/);
+  }
+
+  let clicked = waitForEvent(element, "click");
+  element.click();
+  let event = await clicked;
+  equal(element, event.target);
+});
+
+add_task(async function test_waitForEvent_captureTypes() {
+  let element = new MockElement();
+
+  for (let capture of ["foo", 42, [], {}]) {
+    Assert.throws(() => waitForEvent(
+        element, "click", {capture}), /TypeError/);
+  }
+
+  for (let capture of [null, undefined, false, true]) {
+    let expected_capture = (capture == null) ? false : capture;
+
+    element = new MockElement();
+    let clicked = waitForEvent(element, "click", {capture});
+    element.click();
+    let event = await clicked;
+    equal(element, event.target);
+    equal(expected_capture, event.capture);
+  }
+});
+
+add_task(async function test_waitForEvent_checkFnTypes() {
+  let element = new MockElement();
+
+  for (let checkFn of ["foo", 42, true, [], {}]) {
+    Assert.throws(() => waitForEvent(
+        element, "click", {checkFn}), /TypeError/);
+  }
+
+  let count;
+  for (let checkFn of [null, undefined, event => count++ > 0]) {
+    let expected_count = (checkFn == null) ? 0 : 2;
+    count = 0;
+
+    element = new MockElement();
+    let clicked = waitForEvent(element, "click", {checkFn});
+    element.click();
+    element.click();
+    let event = await clicked;
+    equal(element, event.target);
+    equal(expected_count, count);
+  }
+});
+
+add_task(async function test_waitForEvent_wantsUntrustedTypes() {
+  let element = new MockElement();
+
+  for (let wantsUntrusted of ["foo", 42, [], {}]) {
+    Assert.throws(() => waitForEvent(
+        element, "click", {wantsUntrusted}), /TypeError/);
+  }
+
+  for (let wantsUntrusted of [null, undefined, false, true]) {
+    let expected_untrusted = (wantsUntrusted == null) ? false : wantsUntrusted;
+
+    element = new MockElement();
+    let clicked = waitForEvent(element, "click", {wantsUntrusted});
+    element.click();
+    let event = await clicked;
+    equal(element, event.target);
+    equal(expected_untrusted, event.untrusted);
+  }
+});
+
+add_task(async function test_waitForMessage_messageManagerAndMessageTypes() {
+  let messageManager = new MessageManager();
+
+  for (let manager of ["foo", 42, null, undefined, true, [], {}]) {
+    Assert.throws(() => waitForMessage(manager, "message"), /TypeError/);
+  }
+
+  for (let message of [42, null, undefined, true, [], {}]) {
+    Assert.throws(() => waitForEvent(messageManager, message), /TypeError/);
+  }
+
+  let data = {"foo": "bar"};
+  let sent = waitForMessage(messageManager, "message");
+  messageManager.send("message", data);
+  equal(data, await sent);
+});
+
+add_task(async function test_waitForMessage_checkFnTypes() {
+  let messageManager = new MessageManager();
+
+  for (let checkFn of ["foo", 42, true, [], {}]) {
+    Assert.throws(() => waitForMessage(
+        messageManager, "message", {checkFn}), /TypeError/);
+  }
+
+  let data1 = {"fo": "bar"};
+  let data2 = {"foo": "bar"};
+
+  for (let checkFn of [null, undefined, msg => "foo" in msg.data]) {
+    let expected_data = (checkFn == null) ? data1 : data2;
+
+    messageManager = new MessageManager();
+    let sent = waitForMessage(messageManager, "message", {checkFn});
+    messageManager.send("message", data1);
+    messageManager.send("message", data2);
+    equal(expected_data, await sent);
+  }
+});
+
+add_task(async function test_waitForObserverTopic_topicTypes() {
+  for (let topic of [42, null, undefined, true, [], {}]) {
+    Assert.throws(() => waitForObserverTopic(topic), /TypeError/);
+  }
+
+  let data = {"foo": "bar"};
+  let sent = waitForObserverTopic("message");
+  Services.obs.notifyObservers(this, "message", data);
+  let result = await sent;
+  equal(this, result.subject);
+  equal(data, result.data);
+});
+
+add_task(async function test_waitForObserverTopic_checkFnTypes() {
+  for (let checkFn of ["foo", 42, true, [], {}]) {
+    Assert.throws(() => waitForObserverTopic(
+        "message", {checkFn}), /TypeError/);
+  }
+
+  let data1 = {"fo": "bar"};
+  let data2 = {"foo": "bar"};
+
+  for (let checkFn of [null, undefined, (subject, data) => data == data2]) {
+    let expected_data = (checkFn == null) ? data1 : data2;
+
+    let sent = waitForObserverTopic("message");
+    Services.obs.notifyObservers(this, "message", data1);
+    Services.obs.notifyObservers(this, "message", data2);
+    let result = await sent;
+    equal(expected_data, result.data);
+  }
+});
--- a/testing/marionette/transport.js
+++ b/testing/marionette/transport.js
@@ -5,24 +5,27 @@
 "use strict";
 
 /* global Pipe, ScriptableInputStream */
 
 const CC = Components.Constructor;
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/EventEmitter.jsm");
-const {StreamUtils} =
-    ChromeUtils.import("chrome://marionette/content/stream-utils.js", {});
-const {Packet, JSONPacket, BulkPacket} =
-    ChromeUtils.import("chrome://marionette/content/packets.js", {});
-
-const executeSoon = function(func) {
-  Services.tm.dispatchToMainThread(func);
-};
+const {
+  StreamUtils,
+} = ChromeUtils.import("chrome://marionette/content/stream-utils.js", {});
+const {
+  BulkPacket,
+  JSONPacket,
+  Packet,
+} = ChromeUtils.import("chrome://marionette/content/packets.js", {});
+const {
+  executeSoon,
+} = ChromeUtils.import("chrome://marionette/content/sync.js", {});
 
 const flags = {wantVerbose: false, wantLogging: false};
 
 const dumpv =
   flags.wantVerbose ?
   function(msg) { dump(msg + "\n"); } :
   function() {};
 
--- a/testing/web-platform/mach_commands.py
+++ b/testing/web-platform/mach_commands.py
@@ -75,16 +75,19 @@ class WebPlatformTestsRunnerSetup(Mozbui
                 kwargs["host_key_path"] = os.path.join(cert_root, "web-platform.test.key")
 
             if kwargs["host_cert_path"] is None:
                 kwargs["host_cert_path"] = os.path.join(cert_root, "web-platform.test.pem")
 
         if kwargs["log_mach_screenshot"] is None:
             kwargs["log_mach_screenshot"] = True
 
+        if kwargs["lsan_dir"] is None:
+            kwargs["lsan_dir"] = os.path.join(self.topsrcdir, "build", "sanitizers")
+
         kwargs["capture_stdio"] = True
 
         return kwargs
 
     def kwargs_firefox(self, kwargs):
         import mozinfo
         from wptrunner import wptcommandline
         kwargs = self.kwargs_common(kwargs)
--- a/testing/web-platform/meta/webdriver/tests/execute_script/promise.py.ini
+++ b/testing/web-platform/meta/webdriver/tests/execute_script/promise.py.ini
@@ -1,10 +1,9 @@
 [promise.py]
-  expected: TIMEOUT
   [test_promise_timeout]
     expected: FAIL
 
   [test_promise_reject_timeout]
     expected: FAIL
 
   [test_promise_resolve_timeout]
     expected: FAIL
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -69,16 +69,17 @@ def browser_kwargs(test_type, run_info_d
             "extra_prefs": kwargs["extra_prefs"],
             "test_type": test_type,
             "debug_info": kwargs["debug_info"],
             "symbols_path": kwargs["symbols_path"],
             "stackwalk_binary": kwargs["stackwalk_binary"],
             "certutil_binary": kwargs["certutil_binary"],
             "ca_certificate_path": config.ssl_config["ca_cert_path"],
             "e10s": kwargs["gecko_e10s"],
+            "lsan_dir": kwargs["lsan_dir"],
             "stackfix_dir": kwargs["stackfix_dir"],
             "binary_args": kwargs["binary_args"],
             "timeout_multiplier": get_timeout_multiplier(test_type,
                                                          run_info_data,
                                                          **kwargs),
             "leak_check": run_info_data["debug"] and (kwargs["leak_check"] is not False),
             "asan": run_info_data.get("asan"),
             "stylo_threads": kwargs["stylo_threads"],
@@ -162,17 +163,17 @@ def update_properties():
 
 class FirefoxBrowser(Browser):
     used_ports = set()
     init_timeout = 70
     shutdown_timeout = 70
 
     def __init__(self, logger, binary, prefs_root, test_type, extra_prefs=None, debug_info=None,
                  symbols_path=None, stackwalk_binary=None, certutil_binary=None,
-                 ca_certificate_path=None, e10s=False, stackfix_dir=None,
+                 ca_certificate_path=None, e10s=False, lsan_dir=None, stackfix_dir=None,
                  binary_args=None, timeout_multiplier=None, leak_check=False, asan=False,
                  stylo_threads=1, chaos_mode_flags=None, config=None, headless=None, **kwargs):
         Browser.__init__(self, logger)
         self.binary = binary
         self.prefs_root = prefs_root
         self.test_type = test_type
         self.extra_prefs = extra_prefs
         self.marionette_port = None
@@ -191,16 +192,17 @@ class FirefoxBrowser(Browser):
                                                         self.symbols_path)
         else:
             self.stack_fixer = None
 
         if timeout_multiplier:
             self.init_timeout = self.init_timeout * timeout_multiplier
 
         self.asan = asan
+        self.lsan_dir = lsan_dir
         self.lsan_allowed = None
         self.lsan_max_stack_depth = None
         self.mozleak_allowed = None
         self.mozleak_thresholds = None
         self.leak_check = leak_check
         self.leak_report_file = None
         self.lsan_handler = None
         self.stylo_threads = stylo_threads
@@ -232,17 +234,17 @@ class FirefoxBrowser(Browser):
             self.lsan_handler = mozleak.LSANLeaks(self.logger,
                                                   scope=group_metadata.get("scope", "/"),
                                                   allowed=self.lsan_allowed,
                                                   maxNumRecordedFrames=self.lsan_max_stack_depth)
 
         env = test_environment(xrePath=os.path.dirname(self.binary),
                                debugger=self.debug_info is not None,
                                log=self.logger,
-                               lsanPath=self.prefs_root)
+                               lsanPath=self.lsan_dir)
 
         env["STYLO_THREADS"] = str(self.stylo_threads)
         if self.chaos_mode_flags is not None:
             env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags)
         if self.headless:
             env["MOZ_HEADLESS"] = "1"
 
         preferences = self.load_prefs()
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/wptcommandline.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/wptcommandline.py
@@ -246,16 +246,18 @@ scheme host and port.""")
 
     gecko_group = parser.add_argument_group("Gecko-specific")
     gecko_group.add_argument("--prefs-root", dest="prefs_root", action="store", type=abs_path,
                              help="Path to the folder containing browser prefs")
     gecko_group.add_argument("--disable-e10s", dest="gecko_e10s", action="store_false", default=True,
                              help="Run tests without electrolysis preferences")
     gecko_group.add_argument("--stackfix-dir", dest="stackfix_dir", action="store",
                              help="Path to directory containing assertion stack fixing scripts")
+    gecko_group.add_argument("--lsan-dir", dest="lsan_dir", action="store",
+                             help="Path to directory containing LSAN suppressions file")
     gecko_group.add_argument("--setpref", dest="extra_prefs", action='append',
                              default=[], metavar="PREF=VALUE",
                              help="Defines an extra user preference (overrides those in prefs_root)")
     gecko_group.add_argument("--leak-check", dest="leak_check", action="store_true", default=None,
                              help="Enable leak checking (enabled by default for debug builds, "
                              "silently ignored for opt)")
     gecko_group.add_argument("--no-leak-check", dest="leak_check", action="store_false", default=None,
                              help="Disable leak checking")
@@ -520,16 +522,19 @@ def check_args(kwargs):
             print >> sys.stderr, "Preferences via --setpref must be in key=value format"
             sys.exit(1)
         kwargs['extra_prefs'] = [tuple(prefarg.split('=', 1)) for prefarg in
                                  kwargs['extra_prefs']]
 
     if kwargs["reftest_internal"] is None:
         kwargs["reftest_internal"] = True
 
+    if kwargs["lsan_dir"] is None:
+        kwargs["lsan_dir"] = kwargs["prefs_root"]
+
     return kwargs
 
 
 def check_args_update(kwargs):
     set_from_config(kwargs)
 
     if kwargs["product"] is None:
         kwargs["product"] = "firefox"
--- a/third_party/rust/encoding_rs/.cargo-checksum.json
+++ b/third_party/rust/encoding_rs/.cargo-checksum.json
@@ -1,1 +1,1 @@
-{"files":{"CONTRIBUTING.md":"06c26277e8dbd3f57be2eb51b5e3285dc1cbbf8c11326df413868ae702e6a61c","COPYRIGHT":"8b98376eb373dcf81950474efe34b5576a8171460dff500cc58a1ed8d160cd57","Cargo.toml":"e5b9b399e5735753af58e4931032c869e59b7a13a8c36cfc9c20bd068fbdf0a4","Ideas.md":"b7452893f500163868d8de52c09addaf91e1632454ed02e892c467ed7ec39dbd","LICENSE-APACHE":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","LICENSE-MIT":"f2ad48641d9c997d9ae3b95d93d1cd6e1ab12ab4c44de89937c7bfabbd076a4a","README.md":"c1e52d69d2dca1cd645d0e2782f868fb2874c3b9e8e38356f7aab8275f7f7ca7","build.rs":"f5defca2c68b73e8723f489a9279af4fbe9724abc6e9abf58d32542e8a459e26","doc/Big5.txt":"f73a2edc5cb6c2d140ba6e07f4542e1c4a234950378acde1df93480f0ca0be0b","doc/EUC-JP.txt":"ee2818b907d0137f40a9ab9fd525fc700a44dbdddb6cf0c157a656566bae4bf1","doc/EUC-KR.txt":"71d9e2ccf3b124e8bdfb433c8cf2773fd878077038d0cec3c7237a50f4a78a30","doc/GBK.txt":"c1b522b5a799884e5001da661f42c5a8f4d0acb9ef1d74b206f22b5f65365606","doc/IBM866.txt":"a5a433e804d0f83af785015179fbc1d9b0eaf1f7960efcd04093e136b51fbd0e","doc/ISO-2022-JP.txt":"af86684f5a8f0e2868d7b2c292860140c3d2e5527530ca091f1b28198e8e2fe6","doc/ISO-8859-10.txt":"6d3949ad7c81ca176895101ed81a1db7df1060d64e262880b94bd31bb344ab4d","doc/ISO-8859-13.txt":"3951dd89cf93f7729148091683cf8511f4529388b7dc8dcd0d62eaed55be93fa","doc/ISO-8859-14.txt":"3d330784a0374fd255a38b47949675cc7168c800530534b0a01cac6edc623adc","doc/ISO-8859-15.txt":"24b1084aab5127a85aab99153f86e24694d0a3615f53b5ce23683f97cf66c47a","doc/ISO-8859-16.txt":"ce0272559b92ba76d7a7e476f6424ae4a5cc72e75b183611b08392e44add4d25","doc/ISO-8859-2.txt":"18ceff88c13d1b5ba455a3919b1e3de489045c4c3d2dd7e8527c125c75d54aad","doc/ISO-8859-3.txt":"21798404c68f4f5db59223362f24999da96968c0628427321fccce7d2849a130","doc/ISO-8859-4.txt":"d27f6520c6c5bfbcc19176b71d081cdb3bccde1622bb3e420d5680e812632d53","doc/ISO-8859-5.txt":"a10ec8d6ea7a78ad15da7275f6cb1a3365118527e28f9af6d0d5830501303f3a","doc/ISO-8859-6.txt":"ccda8a2efc96115336bdd77776637b9712425e44fbcf745353b9057fbef144e7","doc/ISO-8859-7.txt":"17900fa1f27a445958f0a77d7d9056be375a6bd7ee4492aa680c7c1500bab85e","doc/ISO-8859-8-I.txt":"8357555646d54265a9b9ffa3e68b08d132312f1561c60108ff9b8b1167b6ecf2","doc/ISO-8859-8.txt":"72cd6f3afb7b4a9c16a66a362473315770b7755d72c86c870e52fc3eba86c8af","doc/KOI8-R.txt":"839cf19a38da994488004ed7814b1f6151640156a9a2af02bf2efca745fb5966","doc/KOI8-U.txt":"0cc76624ed1f024183e2298b7e019957da2c70c8ca06e0fc4e6f353f50a5054f","doc/Shift_JIS.txt":"34c49141818cb9ddbcf59cc858f78a79be8ad148d563f26415108ae1f148443f","doc/UTF-16BE.txt":"e2e280d8acbaa6d2a6b3569d60e17500a285f2baa0df3363dd85537cd5a1ef8f","doc/UTF-16LE.txt":"70bdc170e3fc5298ba68f10125fb5eeb8b077036cc96bb4416c4de396f6d76c1","doc/UTF-8.txt":"ea7bae742e613010ced002cf4b601a737d2203fad65e115611451bc4428f548a","doc/gb18030.txt":"dc71378a8f07a2d8659f69ee81fb8791fef56ba86f124b429978285237bb4a7b","doc/macintosh.txt":"57491e53866711b4672d9b9ff35380b9dac9e0d8e3d6c20bdd6140603687c023","doc/replacement.txt":"4b6c3bbd7999d9d4108a281594bd02d13607e334a95465afff8c2c08d395f0e4","doc/windows-1250.txt":"61296bb6a21cdab602300d32ecfba434cb82de5ac3bc88d58710d2f125e28d39","doc/windows-1251.txt":"7deea1c61dea1485c8ff02db2c7d578db7a9aab63ab1cfd02ec04b515864689e","doc/windows-1252.txt":"933ef3bdddfce5ee132b9f1a1aa8b47423d2587bbe475b19028d0a6d38e180b6","doc/windows-1253.txt":"1a38748b88e99071a5c7b3d5456ead4caedeabab50d50d658be105bc113714de","doc/windows-1254.txt":"f8372f86c6f8d642563cd6ddc025260553292a39423df1683a98670bd7bf2b47","doc/windows-1255.txt":"4e5852494730054e2da258a74e1b9d780abbcdd8ce22ebc218ca2efe9e90493d","doc/windows-1256.txt":"c0879c5172abedead302a406e8f60d9cd9598694a0ffa4fd288ffe4fef7b8ea1","doc/windows-1257.txt":"c28a0c9f964fcb2b46d21f537c402446501a2800670481d6abf9fd9e9018d523","doc/windows-1258.txt":"5019ae4d61805c79aacbf17c93793342dbb098d65a1837783bc3e2c6d6a23602","doc/windows-874.txt":"4ef0e4501c5feba8b17aee1818602ed44b36ca8475db771ce2fc16d392cabecc","doc/x-mac-cyrillic.txt":"58be154d8a888ca3d484b83b44f749823ef339ab27f14d90ca9a856f5050a8bd","doc/x-user-defined.txt":"f9cd07c4321bf5cfb0be4bdddd251072999b04a6cf7a6f5bc63709a84e2c1ffc","generate-encoding-data.py":"11f92c0df48ce12919b5b71cbd03bd5e14a34dcdcdfbb20e6c7b327d203a9659","rustfmt.toml":"85c1a3b4382fd89e991cbb81b70fb52780472edc064c963943cdaaa56e0a2030","src/ascii.rs":"72ba111e4d4a0f81d11be7c8597f904006f45a33adace3b0d36b29cf54d51c06","src/big5.rs":"1c94b35813314775c3fa1b10923cf8e8f8eba8c465d9833ad4293594e16c17f2","src/data.rs":"664e87f88c4558458b09d6c600f48bac287c5db29a98a47ac097281573608dcf","src/euc_jp.rs":"0842e4f564a36051c6b85c47bbb652efae2f2926e91491daf77e4ceeecb18163","src/euc_kr.rs":"8e68590efa65485583bf57cae44ebf6de535bac1d37232e7f0307a38425fb992","src/gb18030.rs":"d269efb5e5d175f9d2ecf01d5606955a284b6f00749bb0ee23d3412c83aa3d59","src/handles.rs":"a3d953b0bc88bfd07f9c12d1b5cd85904d0d8f0fe3bee19e7fad74d4f3d3b4fa","src/iso_2022_jp.rs":"9db1cc612787ab0a874f904306d4c516bc82440183f9a6a045f601aab070ed18","src/lib.rs":"a59e12a4b71ef7dadee4fa3792ca9d0d66e5c89226365cec3265c912432c75d8","src/macros.rs":"c7a019fd81d31de77569036ac36fd4e404b3f20144bbf79747faf4ea21538d09","src/mem.rs":"4791afe98b47321f8a5dcfca114aabb3f4b60bb9b7efd89d749880d3e95be704","src/replacement.rs":"182c2093a6edb162183ca5990554fd7b199d3011924a8d80d894ba98ee7c479e","src/shift_jis.rs":"1c0c69ba6c123fcf720276646074660193bf9e6fa4327fe0d739a3e67874e081","src/simd_funcs.rs":"565ceeffe81173b85700c55c396ab72068751ef809bea8e1cb1e6c7919f5a905","src/single_byte.rs":"383d325dedbf3295acd50d880db1cecc29b69efe332ae2a37367cf40bf138ac4","src/test_data/big5_in.txt":"4c5a8691f8dc717311889c63894026d2fb62725a86c4208ca274a9cc8d42a503","src/test_data/big5_in_ref.txt":"99d399e17750cf9c7cf30bb253dbfe35b81c4fcbdead93cfa48b1429213473c7","src/test_data/big5_out.txt":"6193ca97c297aa20e09396038d18e938bb7ea331c26f0f2454097296723a0b13","src/test_data/big5_out_ref.txt":"36567691f557df144f6cc520015a87038dfa156f296fcf103b56ae9a718be1fc","src/test_data/euc_kr_in.txt":"c86a7224f3215fa0d04e685622a752fdc72763e8ae076230c7fd62de57ec4074","src/test_data/euc_kr_in_ref.txt":"1f419f4ca47d708b54c73c461545a022ae2e20498fdbf8005a483d752a204883","src/test_data/euc_kr_out.txt":"e7f32e026f70be1e1b58e0047baf7d3d2c520269c4f9b9992e158b4decb0a1a3","src/test_data/euc_kr_out_ref.txt":"c9907857980b20b8e9e3b584482ed6567a2be6185d72237b6322f0404944924e","src/test_data/gb18030_in.txt":"ab7231b2d3e9afacdbd7d7f3b9e5361a7ff9f7e1cfdb4f3bd905b9362b309e53","src/test_data/gb18030_in_ref.txt":"dc5069421adca2043c55f5012b55a76fdff651d22e6e699fd0978f8d5706815c","src/test_data/gb18030_out.txt":"f0208d527f5ca63de7d9a0323be8d5cf12d8a104b2943d92c2701f0c3364dac1","src/test_data/gb18030_out_ref.txt":"6819fe47627e4ea01027003fc514b9f21a1322e732d7f1fb92cc6c5455bc6c07","src/test_data/iso_2022_jp_in.txt":"cd24bbdcb1834e25db54646fbf4c41560a13dc7540f6be3dba4f5d97d44513af","src/test_data/iso_2022_jp_in_ref.txt":"3dc4e6a5e06471942d086b16c9440945e78415f6f3f47e43717e4bc2eac2cdf5","src/test_data/iso_2022_jp_out.txt":"9b6f015329dda6c3f9ee5ce6dbd6fa9c89acc21283e886836c78b8d833480c21","src/test_data/iso_2022_jp_out_ref.txt":"78cb260093a20116ad9a42f43b05d1848c5ab100b6b9a850749809e943884b35","src/test_data/jis0208_in.txt":"6df3030553ffb0a6615bb33dc8ea9dca6d9623a9028e2ffec754ce3c3da824cc","src/test_data/jis0208_in_ref.txt":"3dc4e6a5e06471942d086b16c9440945e78415f6f3f47e43717e4bc2eac2cdf5","src/test_data/jis0208_out.txt":"4ec24477e1675ce750733bdc3c5add1cd27b6bd4ce1f09289564646e9654e857","src/test_data/jis0208_out_ref.txt":"c3e1cef5032b2b1d93a406f31ff940c4e2dfe8859b8b17ca2761fee7a75a0e48","src/test_data/jis0212_in.txt":"c011f0dd72bd7c8cd922df9374ef8d2769a77190514c77f6c62b415852eeb9fe","src/test_data/jis0212_in_ref.txt":"7d9458b3d2f73e7092a7f505c08ce1d233dde18aa679fbcf9889256239cc9e06","src/test_data/shift_jis_in.txt":"02e389ccef0dd2122e63f503899402cb7f797912c2444cc80ab93131116c5524","src/test_data/shift_jis_in_ref.txt":"512f985950ca902e643c88682dba9708b7c38d3c5ec2925168ab00ac94ab19f9","src/test_data/shift_jis_out.txt":"5fbc44da7bf639bf6cfe0fa1fd3eba7102b88f81919c9ea991302712f69426fb","src/test_data/shift_jis_out_ref.txt":"466322c6fed8286c64582731755290c2296508efdd258826e6279686649b481f","src/test_labels_names.rs":"c962c7aeac3d9ef2aca70c9e21983b231d4cf998cb06879374b0401e5149d1da","src/testing.rs":"b299d27055f3b068de66cc10a75c024b881c48bc093627c01e0b1f8bd7d94666","src/utf_16.rs":"1ec4e1c8ed7e42e4de401c6d0f64c2835bd80c2a306f358959957d30e6ff1501","src/utf_8.rs":"3839b19071bed07efc70c837f07c78600ff3c9eaef5c81c03672e0c17874ec30","src/variant.rs":"619a8e604d2febe6a874e3ad73cddf3ef9e6011480aecf86f23708b313415251","src/x_user_defined.rs":"3f1956b698eb27bc7b323f853b188726bb5f62d15d899a7b08590ba5c26d9d45"},"package":"1a8fa54e6689eb2549c4efed8d00d7f3b2b994a064555b0e8df4ae3764bcc4be"}
\ No newline at end of file
+{"files":{"CONTRIBUTING.md":"06c26277e8dbd3f57be2eb51b5e3285dc1cbbf8c11326df413868ae702e6a61c","COPYRIGHT":"8b98376eb373dcf81950474efe34b5576a8171460dff500cc58a1ed8d160cd57","Cargo.toml":"f4c9b33981fe222ef322d640f5ef680828d75dcd534b8aa2bfdd576598deea64","Ideas.md":"b7452893f500163868d8de52c09addaf91e1632454ed02e892c467ed7ec39dbd","LICENSE-APACHE":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","LICENSE-MIT":"f2ad48641d9c997d9ae3b95d93d1cd6e1ab12ab4c44de89937c7bfabbd076a4a","README.md":"ad140c9178067c8bdba8ae43ddffd0506d70d49474731247a050ff99a3ff7832","build.rs":"f5defca2c68b73e8723f489a9279af4fbe9724abc6e9abf58d32542e8a459e26","doc/Big5.txt":"f73a2edc5cb6c2d140ba6e07f4542e1c4a234950378acde1df93480f0ca0be0b","doc/EUC-JP.txt":"ee2818b907d0137f40a9ab9fd525fc700a44dbdddb6cf0c157a656566bae4bf1","doc/EUC-KR.txt":"71d9e2ccf3b124e8bdfb433c8cf2773fd878077038d0cec3c7237a50f4a78a30","doc/GBK.txt":"c1b522b5a799884e5001da661f42c5a8f4d0acb9ef1d74b206f22b5f65365606","doc/IBM866.txt":"a5a433e804d0f83af785015179fbc1d9b0eaf1f7960efcd04093e136b51fbd0e","doc/ISO-2022-JP.txt":"af86684f5a8f0e2868d7b2c292860140c3d2e5527530ca091f1b28198e8e2fe6","doc/ISO-8859-10.txt":"6d3949ad7c81ca176895101ed81a1db7df1060d64e262880b94bd31bb344ab4d","doc/ISO-8859-13.txt":"3951dd89cf93f7729148091683cf8511f4529388b7dc8dcd0d62eaed55be93fa","doc/ISO-8859-14.txt":"3d330784a0374fd255a38b47949675cc7168c800530534b0a01cac6edc623adc","doc/ISO-8859-15.txt":"24b1084aab5127a85aab99153f86e24694d0a3615f53b5ce23683f97cf66c47a","doc/ISO-8859-16.txt":"ce0272559b92ba76d7a7e476f6424ae4a5cc72e75b183611b08392e44add4d25","doc/ISO-8859-2.txt":"18ceff88c13d1b5ba455a3919b1e3de489045c4c3d2dd7e8527c125c75d54aad","doc/ISO-8859-3.txt":"21798404c68f4f5db59223362f24999da96968c0628427321fccce7d2849a130","doc/ISO-8859-4.txt":"d27f6520c6c5bfbcc19176b71d081cdb3bccde1622bb3e420d5680e812632d53","doc/ISO-8859-5.txt":"a10ec8d6ea7a78ad15da7275f6cb1a3365118527e28f9af6d0d5830501303f3a","doc/ISO-8859-6.txt":"ccda8a2efc96115336bdd77776637b9712425e44fbcf745353b9057fbef144e7","doc/ISO-8859-7.txt":"17900fa1f27a445958f0a77d7d9056be375a6bd7ee4492aa680c7c1500bab85e","doc/ISO-8859-8-I.txt":"8357555646d54265a9b9ffa3e68b08d132312f1561c60108ff9b8b1167b6ecf2","doc/ISO-8859-8.txt":"72cd6f3afb7b4a9c16a66a362473315770b7755d72c86c870e52fc3eba86c8af","doc/KOI8-R.txt":"839cf19a38da994488004ed7814b1f6151640156a9a2af02bf2efca745fb5966","doc/KOI8-U.txt":"0cc76624ed1f024183e2298b7e019957da2c70c8ca06e0fc4e6f353f50a5054f","doc/Shift_JIS.txt":"34c49141818cb9ddbcf59cc858f78a79be8ad148d563f26415108ae1f148443f","doc/UTF-16BE.txt":"e2e280d8acbaa6d2a6b3569d60e17500a285f2baa0df3363dd85537cd5a1ef8f","doc/UTF-16LE.txt":"70bdc170e3fc5298ba68f10125fb5eeb8b077036cc96bb4416c4de396f6d76c1","doc/UTF-8.txt":"ea7bae742e613010ced002cf4b601a737d2203fad65e115611451bc4428f548a","doc/gb18030.txt":"dc71378a8f07a2d8659f69ee81fb8791fef56ba86f124b429978285237bb4a7b","doc/macintosh.txt":"57491e53866711b4672d9b9ff35380b9dac9e0d8e3d6c20bdd6140603687c023","doc/replacement.txt":"4b6c3bbd7999d9d4108a281594bd02d13607e334a95465afff8c2c08d395f0e4","doc/windows-1250.txt":"61296bb6a21cdab602300d32ecfba434cb82de5ac3bc88d58710d2f125e28d39","doc/windows-1251.txt":"7deea1c61dea1485c8ff02db2c7d578db7a9aab63ab1cfd02ec04b515864689e","doc/windows-1252.txt":"933ef3bdddfce5ee132b9f1a1aa8b47423d2587bbe475b19028d0a6d38e180b6","doc/windows-1253.txt":"1a38748b88e99071a5c7b3d5456ead4caedeabab50d50d658be105bc113714de","doc/windows-1254.txt":"f8372f86c6f8d642563cd6ddc025260553292a39423df1683a98670bd7bf2b47","doc/windows-1255.txt":"4e5852494730054e2da258a74e1b9d780abbcdd8ce22ebc218ca2efe9e90493d","doc/windows-1256.txt":"c0879c5172abedead302a406e8f60d9cd9598694a0ffa4fd288ffe4fef7b8ea1","doc/windows-1257.txt":"c28a0c9f964fcb2b46d21f537c402446501a2800670481d6abf9fd9e9018d523","doc/windows-1258.txt":"5019ae4d61805c79aacbf17c93793342dbb098d65a1837783bc3e2c6d6a23602","doc/windows-874.txt":"4ef0e4501c5feba8b17aee1818602ed44b36ca8475db771ce2fc16d392cabecc","doc/x-mac-cyrillic.txt":"58be154d8a888ca3d484b83b44f749823ef339ab27f14d90ca9a856f5050a8bd","doc/x-user-defined.txt":"f9cd07c4321bf5cfb0be4bdddd251072999b04a6cf7a6f5bc63709a84e2c1ffc","generate-encoding-data.py":"92ddec35a834b6bc815fffffe6d07d9938a90d3c4526298637d8624410d83078","rustfmt.toml":"85c1a3b4382fd89e991cbb81b70fb52780472edc064c963943cdaaa56e0a2030","src/ascii.rs":"800cfbe3036d0c97ce27e07a4fd05edbcb7354ebec20903d81c76136d734931c","src/big5.rs":"1c94b35813314775c3fa1b10923cf8e8f8eba8c465d9833ad4293594e16c17f2","src/data.rs":"9544c019c7360a669bd3adaa90b70331124abd1df59841db66e74912bcdb96a5","src/euc_jp.rs":"0842e4f564a36051c6b85c47bbb652efae2f2926e91491daf77e4ceeecb18163","src/euc_kr.rs":"8e68590efa65485583bf57cae44ebf6de535bac1d37232e7f0307a38425fb992","src/gb18030.rs":"d269efb5e5d175f9d2ecf01d5606955a284b6f00749bb0ee23d3412c83aa3d59","src/handles.rs":"71aa7de1c5236a34ea0a8bb85332987751d2466b756fca6b3f6ac0da765cf91e","src/iso_2022_jp.rs":"3adc380736f24a5de36bc1cf81049bbe64473de10e6f12774195e6213c27c322","src/lib.rs":"e786de9e92e5652bc200266cf318753eea869e8971857cc0caa65a3cfe687545","src/macros.rs":"c7a019fd81d31de77569036ac36fd4e404b3f20144bbf79747faf4ea21538d09","src/mem.rs":"f412f60f2d4afb7e32ffba94dc5f93716e6ae9f065799ca17bb1f1b2145f6ee4","src/replacement.rs":"182c2093a6edb162183ca5990554fd7b199d3011924a8d80d894ba98ee7c479e","src/shift_jis.rs":"1c0c69ba6c123fcf720276646074660193bf9e6fa4327fe0d739a3e67874e081","src/simd_funcs.rs":"565ceeffe81173b85700c55c396ab72068751ef809bea8e1cb1e6c7919f5a905","src/single_byte.rs":"383d325dedbf3295acd50d880db1cecc29b69efe332ae2a37367cf40bf138ac4","src/test_data/big5_in.txt":"4c5a8691f8dc717311889c63894026d2fb62725a86c4208ca274a9cc8d42a503","src/test_data/big5_in_ref.txt":"99d399e17750cf9c7cf30bb253dbfe35b81c4fcbdead93cfa48b1429213473c7","src/test_data/big5_out.txt":"6193ca97c297aa20e09396038d18e938bb7ea331c26f0f2454097296723a0b13","src/test_data/big5_out_ref.txt":"36567691f557df144f6cc520015a87038dfa156f296fcf103b56ae9a718be1fc","src/test_data/euc_kr_in.txt":"c86a7224f3215fa0d04e685622a752fdc72763e8ae076230c7fd62de57ec4074","src/test_data/euc_kr_in_ref.txt":"1f419f4ca47d708b54c73c461545a022ae2e20498fdbf8005a483d752a204883","src/test_data/euc_kr_out.txt":"e7f32e026f70be1e1b58e0047baf7d3d2c520269c4f9b9992e158b4decb0a1a3","src/test_data/euc_kr_out_ref.txt":"c9907857980b20b8e9e3b584482ed6567a2be6185d72237b6322f0404944924e","src/test_data/gb18030_in.txt":"ab7231b2d3e9afacdbd7d7f3b9e5361a7ff9f7e1cfdb4f3bd905b9362b309e53","src/test_data/gb18030_in_ref.txt":"dc5069421adca2043c55f5012b55a76fdff651d22e6e699fd0978f8d5706815c","src/test_data/gb18030_out.txt":"f0208d527f5ca63de7d9a0323be8d5cf12d8a104b2943d92c2701f0c3364dac1","src/test_data/gb18030_out_ref.txt":"6819fe47627e4ea01027003fc514b9f21a1322e732d7f1fb92cc6c5455bc6c07","src/test_data/iso_2022_jp_in.txt":"cd24bbdcb1834e25db54646fbf4c41560a13dc7540f6be3dba4f5d97d44513af","src/test_data/iso_2022_jp_in_ref.txt":"3dc4e6a5e06471942d086b16c9440945e78415f6f3f47e43717e4bc2eac2cdf5","src/test_data/iso_2022_jp_out.txt":"9b6f015329dda6c3f9ee5ce6dbd6fa9c89acc21283e886836c78b8d833480c21","src/test_data/iso_2022_jp_out_ref.txt":"78cb260093a20116ad9a42f43b05d1848c5ab100b6b9a850749809e943884b35","src/test_data/jis0208_in.txt":"6df3030553ffb0a6615bb33dc8ea9dca6d9623a9028e2ffec754ce3c3da824cc","src/test_data/jis0208_in_ref.txt":"3dc4e6a5e06471942d086b16c9440945e78415f6f3f47e43717e4bc2eac2cdf5","src/test_data/jis0208_out.txt":"4ec24477e1675ce750733bdc3c5add1cd27b6bd4ce1f09289564646e9654e857","src/test_data/jis0208_out_ref.txt":"c3e1cef5032b2b1d93a406f31ff940c4e2dfe8859b8b17ca2761fee7a75a0e48","src/test_data/jis0212_in.txt":"c011f0dd72bd7c8cd922df9374ef8d2769a77190514c77f6c62b415852eeb9fe","src/test_data/jis0212_in_ref.txt":"7d9458b3d2f73e7092a7f505c08ce1d233dde18aa679fbcf9889256239cc9e06","src/test_data/shift_jis_in.txt":"02e389ccef0dd2122e63f503899402cb7f797912c2444cc80ab93131116c5524","src/test_data/shift_jis_in_ref.txt":"512f985950ca902e643c88682dba9708b7c38d3c5ec2925168ab00ac94ab19f9","src/test_data/shift_jis_out.txt":"5fbc44da7bf639bf6cfe0fa1fd3eba7102b88f81919c9ea991302712f69426fb","src/test_data/shift_jis_out_ref.txt":"466322c6fed8286c64582731755290c2296508efdd258826e6279686649b481f","src/test_labels_names.rs":"c962c7aeac3d9ef2aca70c9e21983b231d4cf998cb06879374b0401e5149d1da","src/testing.rs":"b299d27055f3b068de66cc10a75c024b881c48bc093627c01e0b1f8bd7d94666","src/utf_16.rs":"1ec4e1c8ed7e42e4de401c6d0f64c2835bd80c2a306f358959957d30e6ff1501","src/utf_8.rs":"f639fc5dccd5dcc2458936baa942237d0fd58ac398c83ea3f48e51dceb5b6a81","src/variant.rs":"619a8e604d2febe6a874e3ad73cddf3ef9e6011480aecf86f23708b313415251","src/x_user_defined.rs":"ab26ea900c8f7b7a4d1172872b7ca4bc573bc60b7b1979c93aafdfb86b2c2235"},"package":"a69d152eaa438a291636c1971b0a370212165ca8a75759eb66818c5ce9b538f7"}
\ No newline at end of file
--- a/third_party/rust/encoding_rs/Cargo.toml
+++ b/third_party/rust/encoding_rs/Cargo.toml
@@ -7,17 +7,17 @@
 #
 # If you believe there's an error in this file please file an
 # issue against the rust-lang/cargo repository. If you're
 # editing this file be aware that the upstream Cargo.toml
 # will likely look very different (and much more reasonable)
 
 [package]
 name = "encoding_rs"
-version = "0.8.13"
+version = "0.8.14"
 authors = ["Henri Sivonen <hsivonen@hsivonen.fi>"]
 description = "A Gecko-oriented implementation of the Encoding Standard"
 homepage = "https://docs.rs/encoding_rs/"
 documentation = "https://docs.rs/encoding_rs/"
 readme = "README.md"
 keywords = ["encoding", "web", "unicode", "charset"]
 categories = ["text-processing", "encoding", "web-programming", "internationalization"]
 license = "MIT/Apache-2.0"
--- a/third_party/rust/encoding_rs/README.md
+++ b/third_party/rust/encoding_rs/README.md
@@ -76,38 +76,49 @@ a `std::io::Read`, decode it into UTF-8 
 crate provides that capability.
 
 ## Decoding Email
 
 For decoding character encodings that occur in email, use the
 [`charset`](https://crates.io/crates/charset) crate instead of using this
 one directly. (It wraps this crate and adds UTF-7 decoding.)
 
+## Windows Code Page Identifier Mappings
+
+For mappings to and from Windows code page identifiers, use the
+[`codepage`](https://crates.io/crates/codepage) crate.
+
 ## Licensing
 
 Please see the file named
 [COPYRIGHT](https://github.com/hsivonen/encoding_rs/blob/master/COPYRIGHT).
 
-## API Documentation
+## Documentation
 
 Generated [API documentation](https://docs.rs/encoding_rs/) is available
 online.
 
+There is a [long-form write-up](https://hsivonen.fi/encoding_rs/) about the
+design and internals of the crate.
+
 ## C and C++ bindings
 
 An FFI layer for encoding_rs is available as a
 [separate crate](https://github.com/hsivonen/encoding_c). The crate comes
 with a [demo C++ wrapper](https://github.com/hsivonen/encoding_c/blob/master/include/encoding_rs_cpp.h)
 using the C++ standard library and [GSL](https://github.com/Microsoft/GSL/) types.
 
 For the Gecko context, there's a
 [C++ wrapper using the MFBT/XPCOM types](https://searchfox.org/mozilla-central/source/intl/Encoding.h#100).
 
 These bindings do not cover the `mem` module.
 
+There's a [write-up](https://hsivonen.fi/modern-cpp-in-rust/) about the C++
+wrappers.
+
 ## Sample programs
 
 * [Rust](https://github.com/hsivonen/recode_rs)
 * [C](https://github.com/hsivonen/recode_c)
 * [C++](https://github.com/hsivonen/recode_cpp)
 
 ## Optional features
 
@@ -321,16 +332,18 @@ used in Firefox.
 
 ## Regenerating Generated Code
 
 To regenerate the generated code:
 
  * Have Python 2 installed.
  * Clone [`https://github.com/hsivonen/encoding_c`](https://github.com/hsivonen/encoding_c)
    next to the `encoding_rs` directory.
+ * Clone [`https://github.com/hsivonen/codepage`](https://github.com/hsivonen/codepage)
+   next to the `encoding_rs` directory.
  * Clone [`https://github.com/whatwg/encoding`](https://github.com/whatwg/encoding)
    next to the `encoding_rs` directory.
  * Checkout revision `f381389` of the `encoding` repo.
  * With the `encoding_rs` directory as the working directory, run
    `python generate-encoding-data.py`.
 
 ## Roadmap
 
@@ -360,25 +373,30 @@ To regenerate the generated code:
       range per encoding.
 - [x] Replace uconv with encoding_rs in Gecko.
 - [x] Implement the rust-encoding API in terms of encoding_rs.
 - [x] Add SIMD acceleration for Aarch64.
 - [x] Investigate the use of NEON on 32-bit ARM.
 - [ ] ~Investigate Björn Höhrmann's lookup table acceleration for UTF-8 as
       adapted to Rust in rust-encoding.~
 - [x] Add actually fast CJK encode options.
-- [ ] Investigate [Bob Steagall's lookup table acceleration for UTF-8](https://github.com/BobSteagall/CppNow2018/blob/master/FastConversionFromUTF-8/Fast%20Conversion%20From%20UTF-8%20with%20C%2B%2B%2C%20DFAs%2C%20and%20SSE%20Intrinsics%20-%20Bob%20Steagall%20-%20C%2B%2BNow%202018.pdf).
+- [ ] ~Investigate [Bob Steagall's lookup table acceleration for UTF-8](https://github.com/BobSteagall/CppNow2018/blob/master/FastConversionFromUTF-8/Fast%20Conversion%20From%20UTF-8%20with%20C%2B%2B%2C%20DFAs%2C%20and%20SSE%20Intrinsics%20-%20Bob%20Steagall%20-%20C%2B%2BNow%202018.pdf).~
 
 ## Release Notes
 
+### 0.8.14
+
+* Made UTF-16 to UTF-8 encode conversion fill the output buffer as
+  closely as possible.
+
 ### 0.8.13
 
-* Made the UTF-8 to UTF-16 compare the number of code units written with the
-  length of the right slice (the output slice) to fix a panic introduced in
-  0.8.11.
+* Made the UTF-8 to UTF-16 decoder compare the number of code units written
+  with the length of the right slice (the output slice) to fix a panic
+  introduced in 0.8.11.
 
 ### 0.8.12
 
 * Removed the `clippy::` prefix from clippy lint names.
 
 ### 0.8.11
 
 * Changed minimum Rust requirement to 1.29.0 (for the ability to refer
--- a/third_party/rust/encoding_rs/generate-encoding-data.py
+++ b/third_party/rust/encoding_rs/generate-encoding-data.py
@@ -17,16 +17,20 @@ import os.path
 if (not os.path.isfile("../encoding/encodings.json")) or (not os.path.isfile("../encoding/indexes.json")):
   sys.stderr.write("This script needs a clone of https://github.com/whatwg/encoding/ (preferably at revision f381389) next to the encoding_rs directory.\n");
   sys.exit(-1)
 
 if not os.path.isfile("../encoding_c/src/lib.rs"):
   sys.stderr.write("This script also writes the generated parts of the encoding_c crate and needs a clone of https://github.com/hsivonen/encoding_c next to the encoding_rs directory.\n");
   sys.exit(-1)
 
+if not os.path.isfile("../codepage/src/lib.rs"):
+  sys.stderr.write("This script also writes the generated parts of the codepage crate and needs a clone of https://github.com/hsivonen/codepage next to the encoding_rs directory.\n");
+  sys.exit(-1)
+
 def cmp_from_end(one, other):
   c = cmp(len(one), len(other))
   if c != 0:
     return c
   i = len(one) - 1
   while i >= 0:
     c = cmp(one[i], other[i])
     if c != 0:
@@ -114,34 +118,78 @@ labels = []
 data = json.load(open("../encoding/encodings.json", "r"))
 
 indexes = json.load(open("../encoding/indexes.json", "r"))
 
 single_byte = []
 
 multi_byte = []
 
-code_pages = []
-
 def to_camel_name(name):
   if name == u"iso-8859-8-i":
     return u"Iso8I"
   if name.startswith(u"iso-8859-"):
     return name.replace(u"iso-8859-", u"Iso")
   return name.title().replace(u"X-", u"").replace(u"-", u"").replace(u"_", u"")
 
 def to_constant_name(name):
   return name.replace(u"-", u"_").upper()
 
 def to_snake_name(name):
   return name.replace(u"-", u"_").lower()
 
 def to_dom_name(name):
   return name
 
+# Guestimate based on
+# https://w3techs.com/technologies/overview/character_encoding/all
+# whose methodology is known to be bogus, but the results are credible for
+# this purpose. UTF-16LE lifted up due to prevalence on Windows and
+# "ANSI codepages" prioritized.
+encodings_by_code_page_frequency = [
+  "UTF-8",    
+  "UTF-16LE",
+  "windows-1252",
+  "windows-1251",
+  "GBK",
+  "Shift_JIS",
+  "EUC-KR",
+  "windows-1250",
+  "windows-1256",
+  "windows-1254",
+  "Big5",
+  "windows-874",
+  "windows-1255",
+  "windows-1253",
+  "windows-1257",
+  "windows-1258",
+  "EUC-JP",
+  "ISO-8859-2",
+  "ISO-8859-15",
+  "ISO-8859-7",
+  "KOI8-R",
+  "gb18030",
+  "ISO-8859-5",
+  "ISO-8859-8-I",
+  "ISO-8859-4",
+  "ISO-8859-6",
+  "ISO-2022-JP",
+  "KOI8-U",
+  "ISO-8859-13",
+  "ISO-8859-3",
+  "UTF-16BE",
+  "IBM866",
+  "ISO-8859-10",
+  "ISO-8859-8",
+  "macintosh",
+  "x-mac-cyrillic",
+  "ISO-8859-14",
+  "ISO-8859-16",
+]
+
 encodings_by_code_page = {
   932: "Shift_JIS",
   936: "GBK",
   949: "EUC-KR",
   950: "Big5",
   866: "IBM866",
   874: "windows-874",
   1200: "UTF-16LE",
@@ -180,28 +228,46 @@ encodings_by_code_page = {
 
 code_pages_by_encoding = {}
 
 for code_page, encoding in encodings_by_code_page.iteritems():
   code_pages_by_encoding[encoding] = code_page
 
 encoding_by_alias_code_page = {
   951: "Big5",
+  10007: "x-mac-cyrillic",
   20936: "GBK",
   20949: "EUC-KR",
+  21010: "UTF-16LE", # Undocumented; needed by calamine for Excel compat
   28591: "windows-1252",
   28599: "windows-1254",
-  28601: "windows-847",
+  28601: "windows-874",
   50220: "ISO-2022-JP",
   50222: "ISO-2022-JP",
+  50225: "replacement", # ISO-2022-KR
+  50227: "replacement", # ISO-2022-CN
   51949: "EUC-JP",
   51936: "GBK",
   51949: "EUC-KR",
+  52936: "replacement", # HZ
 }
 
+code_pages = []
+
+for name in encodings_by_code_page_frequency:
+  code_pages.append(code_pages_by_encoding[name])
+
+encodings_by_code_page.update(encoding_by_alias_code_page)
+
+temp_keys = encodings_by_code_page.keys()
+temp_keys.sort()
+for code_page in temp_keys:
+  if not code_page in code_pages:
+    code_pages.append(code_page)
+
 # The position in the index (0 is the first index entry,
 # i.e. byte value 0x80) that starts the longest run of
 # consecutive code points. Must not be in the first
 # quadrant. If the character to be encoded is not in this
 # run, the part of the index after the run is searched
 # forward. Then the part of the index from 32 to the start
 # of the run. The first quadrant is searched last.
 #
@@ -1801,9 +1867,76 @@ jis0212_in_ref_file.write(TEST_HEADER)
 for pointer in range(0, len(index)):
   code_point = index[pointer]
   if code_point:
     jis0212_in_ref_file.write((u"%s\n" % unichr(code_point)).encode("utf-8"))
   else:
     jis0212_in_ref_file.write(u"\uFFFD\n".encode("utf-8"))
 jis0212_in_ref_file.close()
 
+(codepage_begin, codepage_end) = read_non_generated("../codepage/src/lib.rs")
+
+codepage_file = open("../codepage/src/lib.rs", "w")
+
+codepage_file.write(codepage_begin)
+codepage_file.write("""
+// Instead, please regenerate using generate-encoding-data.py
+
+/// Supported code page numbers in estimated order of usage frequency
+static CODE_PAGES: [u16; %d] = [
+""" % len(code_pages))
+
+for code_page in code_pages:
+  codepage_file.write("    %d,\n" % code_page)
+
+codepage_file.write("""];
+
+/// Encodings corresponding to the code page numbers in the same order
+static ENCODINGS: [&'static Encoding; %d] = [
+""" % len(code_pages))
+
+for code_page in code_pages:
+  name = encodings_by_code_page[code_page]
+  codepage_file.write("    &%s_INIT,\n" % to_constant_name(name))
+
+codepage_file.write("""];
+
+""")
+
+codepage_file.write(codepage_end)
+codepage_file.close()
+
+(codepage_test_begin, codepage_test_end) = read_non_generated("../codepage/src/tests.rs")
+
+codepage_test_file = open("../codepage/src/tests.rs", "w")
+
+codepage_test_file.write(codepage_test_begin)
+codepage_test_file.write("""
+// Instead, please regenerate using generate-encoding-data.py
+
+#[test]
+fn test_to_encoding() {
+    assert_eq!(to_encoding(0), None);
+
+""")
+
+for code_page in code_pages:
+  codepage_test_file.write("    assert_eq!(to_encoding(%d), Some(%s));\n" % (code_page, to_constant_name(encodings_by_code_page[code_page])))  
+
+codepage_test_file.write("""}
+
+#[test]
+fn test_from_encoding() {
+""")
+
+for name in preferred:
+  if code_pages_by_encoding.has_key(name):
+    codepage_test_file.write("    assert_eq!(from_encoding(%s), Some(%d));\n" % (to_constant_name(name), code_pages_by_encoding[name]))
+  else:
+    codepage_test_file.write("    assert_eq!(from_encoding(%s), None);\n" % to_constant_name(name))
+
+codepage_test_file.write("""}
+""")
+
+codepage_test_file.write(codepage_test_end)
+codepage_test_file.close()
+
 subprocess.call(["cargo", "fmt"])
--- a/third_party/rust/encoding_rs/src/ascii.rs
+++ b/third_party/rust/encoding_rs/src/ascii.rs
@@ -26,17 +26,17 @@
     any(
         target_feature = "sse2",
         all(target_endian = "little", target_arch = "aarch64"),
         all(target_endian = "little", target_feature = "neon")
     )
 ))]
 use simd_funcs::*;
 
-cfg_if!{
+cfg_if! {
     if #[cfg(feature = "simd-accel")] {
         #[allow(unused_imports)]
         use ::std::intrinsics::unlikely;
         #[allow(unused_imports)]
         use ::std::intrinsics::likely;
     } else {
         #[allow(dead_code)]
         #[inline(always)]
@@ -85,20 +85,17 @@ macro_rules! ascii_naive {
 }
 
 #[allow(unused_macros)]
 macro_rules! ascii_alu {
     ($name:ident,
      $src_unit:ty,
      $dst_unit:ty,
      $stride_fn:ident) => {
-        #[cfg_attr(
-            feature = "cargo-clippy",
-            allow(never_loop, cast_ptr_alignment)
-        )]
+        #[cfg_attr(feature = "cargo-clippy", allow(never_loop, cast_ptr_alignment))]
         #[inline(always)]
         pub unsafe fn $name(
             src: *const $src_unit,
             dst: *mut $dst_unit,
             len: usize,
         ) -> Option<($src_unit, usize)> {
             let mut offset = 0usize;
             // This loop is only broken out of as a `goto` forward
@@ -181,21 +178,17 @@ macro_rules! ascii_alu {
 #[allow(unused_macros)]
 macro_rules! basic_latin_alu {
     ($name:ident,
      $src_unit:ty,
      $dst_unit:ty,
      $stride_fn:ident) => {
         #[cfg_attr(
             feature = "cargo-clippy",
-            allow(
-                never_loop,
-                cast_ptr_alignment,
-                cast_lossless
-            )
+            allow(never_loop, cast_ptr_alignment, cast_lossless)
         )]
         #[inline(always)]
         pub unsafe fn $name(
             src: *const $src_unit,
             dst: *mut $dst_unit,
             len: usize,
         ) -> Option<($src_unit, usize)> {
             let mut offset = 0usize;
@@ -280,21 +273,17 @@ macro_rules! basic_latin_alu {
     };
 }
 
 #[allow(unused_macros)]
 macro_rules! latin1_alu {
     ($name:ident, $src_unit:ty, $dst_unit:ty, $stride_fn:ident) => {
         #[cfg_attr(
             feature = "cargo-clippy",
-            allow(
-                never_loop,
-                cast_ptr_alignment,
-                cast_lossless
-            )
+            allow(never_loop, cast_ptr_alignment, cast_lossless)
         )]
         #[inline(always)]
         pub unsafe fn $name(src: *const $src_unit, dst: *mut $dst_unit, len: usize) {
             let mut offset = 0usize;
             // This loop is only broken out of as a `goto` forward
             loop {
                 let mut until_alignment = {
                     if ::std::mem::size_of::<$src_unit>() < ::std::mem::size_of::<$dst_unit>() {
--- a/third_party/rust/encoding_rs/src/data.rs
+++ b/third_party/rust/encoding_rs/src/data.rs
@@ -29493,20 +29493,17 @@ const IBM_SYMBOL_POINTER_START: usize = 
 pub static JIS0208_RANGE_TRIPLES: [u16; 54] = [
     0x00CB, 0x000A, 0xFF10, 0x00DC, 0x001A, 0xFF21, 0x00FC, 0x001A, 0xFF41, 0x01D6, 0x0011, 0x0391,
     0x01E7, 0x0007, 0x03A3, 0x01F6, 0x0011, 0x03B1, 0x0207, 0x0007, 0x03C3, 0x0234, 0x0006, 0x0410,
     0x023A, 0x0001, 0x0401, 0x023B, 0x001A, 0x0416, 0x0264, 0x0006, 0x0430, 0x026A, 0x0001, 0x0451,
     0x026B, 0x001A, 0x0436, 0x0468, 0x0014, 0x2460, 0x047C, 0x000A, 0x2160, 0x21BA, 0x000A, 0x2170,
     0x29DC, 0x000A, 0x2170, 0x29E6, 0x000A, 0x2160,
 ];
 
-#[cfg(all(
-    feature = "less-slow-kanji-encode",
-    not(feature = "fast-kanji-encode")
-))]
+#[cfg(all(feature = "less-slow-kanji-encode", not(feature = "fast-kanji-encode")))]
 static JIS0208_LEVEL1_KANJI_CODE_POINTS: [u16; 2965] = [
     0x4E00, 0x4E01, 0x4E03, 0x4E07, 0x4E08, 0x4E09, 0x4E0A, 0x4E0B, 0x4E0D, 0x4E0E, 0x4E11, 0x4E14,
     0x4E16, 0x4E18, 0x4E19, 0x4E1E, 0x4E21, 0x4E26, 0x4E2D, 0x4E32, 0x4E38, 0x4E39, 0x4E3B, 0x4E43,
     0x4E45, 0x4E4B, 0x4E4D, 0x4E4E, 0x4E4F, 0x4E57, 0x4E59, 0x4E5D, 0x4E5E, 0x4E5F, 0x4E71, 0x4E73,
     0x4E7E, 0x4E80, 0x4E86, 0x4E88, 0x4E89, 0x4E8B, 0x4E8C, 0x4E91, 0x4E92, 0x4E94, 0x4E95, 0x4E98,
     0x4E99, 0x4E9B, 0x4E9C, 0x4EA1, 0x4EA4, 0x4EA5, 0x4EA6, 0x4EA8, 0x4EAB, 0x4EAC, 0x4EAD, 0x4EAE,
     0x4EBA, 0x4EC0, 0x4EC1, 0x4EC7, 0x4ECA, 0x4ECB, 0x4ECF, 0x4ED4, 0x4ED5, 0x4ED6, 0x4ED8, 0x4ED9,
     0x4EE3, 0x4EE4, 0x4EE5, 0x4EEE, 0x4EF0, 0x4EF2, 0x4EF6, 0x4EFB, 0x4F01, 0x4F0A, 0x4F0D, 0x4F0E,
@@ -29748,20 +29745,17 @@ static JIS0208_LEVEL1_KANJI_CODE_POINTS:
     0x9B92, 0x9BAA, 0x9BAB, 0x9BAD, 0x9BAE, 0x9BC9, 0x9BD6, 0x9BDB, 0x9BE8, 0x9BF5, 0x9C0D, 0x9C10,
     0x9C2D, 0x9C2F, 0x9C39, 0x9C3B, 0x9C48, 0x9C52, 0x9C57, 0x9CE5, 0x9CE9, 0x9CF3, 0x9CF4, 0x9CF6,
     0x9D07, 0x9D0E, 0x9D1B, 0x9D28, 0x9D2B, 0x9D2C, 0x9D3B, 0x9D5C, 0x9D60, 0x9D61, 0x9D6C, 0x9D8F,
     0x9DB4, 0x9DF2, 0x9DF9, 0x9DFA, 0x9E78, 0x9E7F, 0x9E93, 0x9E97, 0x9E9F, 0x9EA6, 0x9EB9, 0x9EBA,
     0x9EBB, 0x9EBF, 0x9EC4, 0x9ECD, 0x9ED2, 0x9ED9, 0x9EDB, 0x9F0E, 0x9F13, 0x9F20, 0x9F3B, 0x9F62,
     0x9F8D,
 ];
 
-#[cfg(all(
-    feature = "less-slow-kanji-encode",
-    not(feature = "fast-kanji-encode")
-))]
+#[cfg(all(feature = "less-slow-kanji-encode", not(feature = "fast-kanji-encode")))]
 static JIS0208_LEVEL1_KANJI_SHIFT_JIS_BYTES: [[u8; 2]; 2965] = [
     [0x88, 0xEA],
     [0x92, 0x9A],
     [0x8E, 0xB5],
     [0x96, 0x9C],
     [0x8F, 0xE4],
     [0x8E, 0x4F],
     [0x8F, 0xE3],
@@ -113962,20 +113956,17 @@ pub fn jis0208_kanji_shift_jis_encode(bm
     let trail = pair[1];
     if lead == 0 && trail == 0 {
         return None;
     }
     // Always set the high bit on lead. (It's zero for IBM Kanji.)
     Some((lead | 0x80, trail))
 }
 
-#[cfg(any(
-    feature = "less-slow-kanji-encode",
-    feature = "fast-kanji-encode"
-))]
+#[cfg(any(feature = "less-slow-kanji-encode", feature = "fast-kanji-encode"))]
 #[inline(always)]
 fn shift_jis_to_euc_jp(tuple: (u8, u8)) -> (u8, u8) {
     let (shift_jis_lead, shift_jis_trail) = tuple;
     let mut lead = shift_jis_lead as usize;
     if shift_jis_lead >= 0xA0 {
         lead -= 0xC1 - 0x81;
     }
     // The next line would overflow u8. Letting it go over allows us to
@@ -114007,20 +113998,17 @@ pub fn jis0208_kanji_euc_jp_encode(bmp: 
         let pos = position(&IBM_KANJI[..], bmp).unwrap();
         let lead = (pos / 94) + 0xF9;
         let trail = (pos % 94) + 0xA1;
         return Some((lead as u8, trail as u8));
     }
     Some(shift_jis_to_euc_jp((lead, trail)))
 }
 
-#[cfg(any(
-    feature = "less-slow-kanji-encode",
-    feature = "fast-kanji-encode"
-))]
+#[cfg(any(feature = "less-slow-kanji-encode", feature = "fast-kanji-encode"))]
 #[inline(always)]
 fn shift_jis_to_iso_2022_jp(tuple: (u8, u8)) -> (u8, u8) {
     let (shift_jis_lead, shift_jis_trail) = tuple;
     let mut lead = shift_jis_lead as usize;
     if shift_jis_lead >= 0xA0 {
         lead -= 0xC1 - 0x81;
     }
     // The next line would overflow u8. Letting it go over allows us to
@@ -114052,86 +114040,68 @@ pub fn jis0208_kanji_iso_2022_jp_encode(
         let pos = position(&IBM_KANJI[..], bmp).unwrap();
         let lead = (pos / 94) + (0xF9 - 0x80);
         let trail = (pos % 94) + 0x21;
         return Some((lead as u8, trail as u8));
     }
     Some(shift_jis_to_iso_2022_jp((lead, trail)))
 }
 
-#[cfg(not(any(
-    feature = "less-slow-kanji-encode",
-    feature = "fast-kanji-encode"
-)))]
+#[cfg(not(any(feature = "less-slow-kanji-encode", feature = "fast-kanji-encode")))]
 #[inline(always)]
 pub fn jis0208_level1_kanji_shift_jis_encode(bmp: u16) -> Option<(u8, u8)> {
     position(&JIS0208_LEVEL1_KANJI[..], bmp).map(|kanji_pointer| {
         let pointer = 1410 + kanji_pointer;
         let lead = pointer / 188;
         let lead_offset = if lead < 0x1F { 0x81 } else { 0xC1 };
         let trail = pointer % 188;
         let trail_offset = if trail < 0x3F { 0x40 } else { 0x41 };
         ((lead + lead_offset) as u8, (trail + trail_offset) as u8)
     })
 }
 
-#[cfg(all(
-    feature = "less-slow-kanji-encode",
-    not(feature = "fast-kanji-encode")
-))]
+#[cfg(all(feature = "less-slow-kanji-encode", not(feature = "fast-kanji-encode")))]
 #[inline(always)]
 pub fn jis0208_level1_kanji_shift_jis_encode(bmp: u16) -> Option<(u8, u8)> {
     match JIS0208_LEVEL1_KANJI_CODE_POINTS.binary_search(&bmp) {
         Ok(i) => {
             let pair = &JIS0208_LEVEL1_KANJI_SHIFT_JIS_BYTES[i];
             Some((pair[0], pair[1]))
         }
         Err(_) => None,
     }
 }
 
-#[cfg(not(any(
-    feature = "less-slow-kanji-encode",
-    feature = "fast-kanji-encode"
-)))]
+#[cfg(not(any(feature = "less-slow-kanji-encode", feature = "fast-kanji-encode")))]
 #[inline(always)]
 pub fn jis0208_level1_kanji_euc_jp_encode(bmp: u16) -> Option<(u8, u8)> {
     position(&JIS0208_LEVEL1_KANJI[..], bmp).map(|kanji_pointer| {
         let lead = (kanji_pointer / 94) + 0xB0;
         let trail = (kanji_pointer % 94) + 0xA1;
         (lead as u8, trail as u8)
     })
 }
 
-#[cfg(all(
-    feature = "less-slow-kanji-encode",
-    not(feature = "fast-kanji-encode")
-))]
+#[cfg(all(feature = "less-slow-kanji-encode", not(feature = "fast-kanji-encode")))]
 #[inline(always)]
 pub fn jis0208_level1_kanji_euc_jp_encode(bmp: u16) -> Option<(u8, u8)> {
     jis0208_level1_kanji_shift_jis_encode(bmp).map(shift_jis_to_euc_jp)
 }
 
-#[cfg(not(any(
-    feature = "less-slow-kanji-encode",
-    feature = "fast-kanji-encode"
-)))]
+#[cfg(not(any(feature = "less-slow-kanji-encode", feature = "fast-kanji-encode")))]
 #[inline(always)]
 pub fn jis0208_level1_kanji_iso_2022_jp_encode(bmp: u16) -> Option<(u8, u8)> {
     position(&JIS0208_LEVEL1_KANJI[..], bmp).map(|kanji_pointer| {
         let lead = (kanji_pointer / 94) + (0xB0 - 0x80);
         let trail = (kanji_pointer % 94) + 0x21;
         (lead as u8, trail as u8)
     })
 }
 
-#[cfg(all(
-    feature = "less-slow-kanji-encode",
-    not(feature = "fast-kanji-encode")
-))]
+#[cfg(all(feature = "less-slow-kanji-encode", not(feature = "fast-kanji-encode")))]
 #[inline(always)]
 pub fn jis0208_level1_kanji_iso_2022_jp_encode(bmp: u16) -> Option<(u8, u8)> {
     jis0208_level1_kanji_shift_jis_encode(bmp).map(shift_jis_to_iso_2022_jp)
 }
 
 #[cfg(not(feature = "fast-kanji-encode"))]
 #[inline(always)]
 pub fn jis0208_level2_and_additional_kanji_encode(bmp: u16) -> Option<usize> {
--- a/third_party/rust/encoding_rs/src/handles.rs
+++ b/third_party/rust/encoding_rs/src/handles.rs
@@ -277,17 +277,17 @@ fn convert_unaligned_utf16_to_utf8<E: En
             &mut dst[dst_pos..],
         ) {
             CopyAsciiResult::GoOn((unit, read_written)) => {
                 src_pos += read_written;
                 dst_pos += read_written;
                 unit
             }
             CopyAsciiResult::Stop(read_written) => {
-                return (src_pos + read_written, dst_pos + read_written, false)
+                return (src_pos + read_written, dst_pos + read_written, false);
             }
         };
         if dst_pos >= dst_len_minus_three {
             break 'outer;
         }
         // We have enough destination space to commit to
         // having read `non_ascii`.
         src_pos += 1;
--- a/third_party/rust/encoding_rs/src/iso_2022_jp.rs
+++ b/third_party/rust/encoding_rs/src/iso_2022_jp.rs
@@ -362,20 +362,17 @@ fn is_kanji_mapped(bmp: u16) -> bool {
     // Use the shift_jis variant, because we don't care about the
     // byte values here.
     jis0208_kanji_shift_jis_encode(bmp).is_some()
 }
 
 #[cfg(not(feature = "fast-kanji-encode"))]
 #[cfg_attr(
     feature = "cargo-clippy",
-    allow(
-        if_let_redundant_pattern_matching,
-        if_same_then_else
-    )
+    allow(if_let_redundant_pattern_matching, if_same_then_else)
 )]
 #[inline(always)]
 fn is_kanji_mapped(bmp: u16) -> bool {
     if 0x4EDD == bmp {
         true
     } else if let Some(_) = jis0208_level1_kanji_shift_jis_encode(bmp) {
         // Use the shift_jis variant, because we don't care about the
         // byte values here.
@@ -386,20 +383,17 @@ fn is_kanji_mapped(bmp: u16) -> bool {
         true
     } else {
         false
     }
 }
 
 #[cfg_attr(
     feature = "cargo-clippy",
-    allow(
-        if_let_redundant_pattern_matching,
-        if_same_then_else
-    )
+    allow(if_let_redundant_pattern_matching, if_same_then_else)
 )]
 fn is_mapped_for_two_byte_encode(bmp: u16) -> bool {
     // The code below uses else after return to
     // keep the same structure as in EUC-JP.
     // Lunde says 60% Hiragana, 30% Kanji, 10% Katakana
     let bmp_minus_hiragana = bmp.wrapping_sub(0x3041);
     if bmp_minus_hiragana < 0x53 {
         true
--- a/third_party/rust/encoding_rs/src/lib.rs
+++ b/third_party/rust/encoding_rs/src/lib.rs
@@ -4,39 +4,38 @@
 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
 #![cfg_attr(
     feature = "cargo-clippy",
-    allow(
-        doc_markdown,
-        inline_always,
-        new_ret_no_self
-    )
+    allow(doc_markdown, inline_always, new_ret_no_self)
 )]
-#![doc(html_root_url = "https://docs.rs/encoding_rs/0.8.13")]
+#![doc(html_root_url = "https://docs.rs/encoding_rs/0.8.14")]
 
 //! encoding_rs is a Gecko-oriented Free Software / Open Source implementation
 //! of the [Encoding Standard](https://encoding.spec.whatwg.org/) in Rust.
 //! Gecko-oriented means that converting to and from UTF-16 is supported in
 //! addition to converting to and from UTF-8, that the performance and
 //! streamability goals are browser-oriented, and that FFI-friendliness is a
 //! goal.
 //!
 //! Additionally, the `mem` module provides functions that are useful for
 //! applications that need to be able to deal with legacy in-memory
 //! representations of Unicode.
 //!
 //! For expectation setting, please be sure to read the sections
 //! [_UTF-16LE, UTF-16BE and Unicode Encoding Schemes_](#utf-16le-utf-16be-and-unicode-encoding-schemes),
 //! [_ISO-8859-1_](#iso-8859-1) and [_Web / Browser Focus_](#web--browser-focus) below.
 //!
+//! There is a [long-form write-up](https://hsivonen.fi/encoding_rs/) about the
+//! design and internals of the crate.
+//!
 //! # Availability
 //!
 //! The code is available under the
 //! [Apache license, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
 //! or the [MIT license](https://opensource.org/licenses/MIT), at your option.
 //! See the
 //! [`COPYRIGHT`](https://github.com/hsivonen/encoding_rs/blob/master/COPYRIGHT)
 //! file for details.
@@ -235,17 +234,19 @@
 //! performance, the decoder for ISO-2022-JP optimizes for ease/clarity
 //! of implementation.
 //!
 //! Despite the browser focus, the hope is that non-browser applications
 //! that wish to consume Web content or submit Web forms in a Web-compatible
 //! way will find encoding_rs useful. While encoding_rs does not try to match
 //! Windows behavior, many of the encodings are close enough to legacy
 //! encodings implemented by Windows that applications that need to consume
-//! data in legacy Windows encodins may find encoding_rs useful.
+//! data in legacy Windows encodins may find encoding_rs useful. The
+//! [codepage](https://crates.io/crates/codepage) crate maps from Windows
+//! code page identifiers onto encoding_rs `Encoding`s and vice versa.
 //!
 //! For decoding email, UTF-7 support is needed (unfortunately) in additition
 //! to the encodings defined in the Encoding Standard. The
 //! [charset](https://crates.io/crates/charset) wraps encoding_rs and adds
 //! UTF-7 decoding for email purposes.
 //!
 //! # Streaming & Non-Streaming; Rust & C/C++
 //!
@@ -659,20 +660,17 @@
 //! <tr><td>ISO-8859-9</td><td>windows-1254</td></tr>
 //! <tr><td>TIS-620</td><td>windows-874</td></tr>
 //! </tbody>
 //! </table>
 //!
 //! See the section [_UTF-16LE, UTF-16BE and Unicode Encoding Schemes_](#utf-16le-utf-16be-and-unicode-encoding-schemes)
 //! for discussion about the UTF-16 family.
 
-#![cfg_attr(
-    feature = "simd-accel",
-    feature(platform_intrinsics, core_intrinsics)
-)]
+#![cfg_attr(feature = "simd-accel", feature(platform_intrinsics, core_intrinsics))]
 
 #[macro_use]
 extern crate cfg_if;
 
 #[cfg(all(
     feature = "simd-accel",
     any(
         target_feature = "sse2",
@@ -3648,17 +3646,17 @@ impl Decoder {
     pub fn max_utf8_buffer_length(&self, byte_length: usize) -> Option<usize> {
         // Need to consider a) the decoder morphing due to the BOM and b) a partial
         // BOM getting pushed to the underlying decoder.
         match self.life_cycle {
             DecoderLifeCycle::Converting
             | DecoderLifeCycle::AtUtf8Start
             | DecoderLifeCycle::AtUtf16LeStart
             | DecoderLifeCycle::AtUtf16BeStart => {
-                return self.variant.max_utf8_buffer_length(byte_length)
+                return self.variant.max_utf8_buffer_length(byte_length);
             }
             DecoderLifeCycle::AtStart => {
                 if let Some(utf8_bom) = checked_add(3, byte_length.checked_mul(3)) {
                     if let Some(utf16_bom) = checked_add(
                         1,
                         checked_mul(3, checked_div(byte_length.checked_add(1), 2)),
                     ) {
                         let utf_bom = std::cmp::max(utf8_bom, utf16_bom);
@@ -3740,17 +3738,17 @@ impl Decoder {
         // BOM getting pushed to the underlying decoder.
         match self.life_cycle {
             DecoderLifeCycle::Converting
             | DecoderLifeCycle::AtUtf8Start
             | DecoderLifeCycle::AtUtf16LeStart
             | DecoderLifeCycle::AtUtf16BeStart => {
                 return self
                     .variant
-                    .max_utf8_buffer_length_without_replacement(byte_length)
+                    .max_utf8_buffer_length_without_replacement(byte_length);
             }
             DecoderLifeCycle::AtStart => {
                 if let Some(utf8_bom) = byte_length.checked_add(3) {
                     if let Some(utf16_bom) = checked_add(
                         1,
                         checked_mul(3, checked_div(byte_length.checked_add(1), 2)),
                     ) {
                         let utf_bom = std::cmp::max(utf8_bom, utf16_bom);
@@ -4056,17 +4054,17 @@ impl Decoder {
     pub fn max_utf16_buffer_length(&self, byte_length: usize) -> Option<usize> {
         // Need to consider a) the decoder morphing due to the BOM and b) a partial
         // BOM getting pushed to the underlying decoder.
         match self.life_cycle {
             DecoderLifeCycle::Converting
             | DecoderLifeCycle::AtUtf8Start
             | DecoderLifeCycle::AtUtf16LeStart
             | DecoderLifeCycle::AtUtf16BeStart => {
-                return self.variant.max_utf16_buffer_length(byte_length)
+                return self.variant.max_utf16_buffer_length(byte_length);
             }
             DecoderLifeCycle::AtStart => {
                 if let Some(utf8_bom) = byte_length.checked_add(1) {
                     if let Some(utf16_bom) =
                         checked_add(1, checked_div(byte_length.checked_add(1), 2))
                     {
                         let utf_bom = std::cmp::max(utf8_bom, utf16_bom);
                         let encoding = self.encoding();
@@ -5295,23 +5293,21 @@ mod tests {
                 Cow::Owned(_) => unreachable!(),
             },
             None => unreachable!(),
         }
     }
 
     #[test]
     fn test_decode_bomful_invalid_utf8_to_cow_without_bom_handling_and_without_replacement() {
-        assert!(
-            UTF_8
-                .decode_without_bom_handling_and_without_replacement(
-                    b"\xEF\xBB\xBF\xE2\x82\xAC\x80\xC3\xA4"
-                )
-                .is_none()
-        );
+        assert!(UTF_8
+            .decode_without_bom_handling_and_without_replacement(
+                b"\xEF\xBB\xBF\xE2\x82\xAC\x80\xC3\xA4"
+            )
+            .is_none());
     }
 
     #[test]
     fn test_decode_valid_windows_1257_to_cow_without_bom_handling_and_without_replacement() {
         match WINDOWS_1257.decode_without_bom_handling_and_without_replacement(b"abc\x80\xE4") {
             Some(cow) => match cow {
                 Cow::Borrowed(_) => unreachable!(),
                 Cow::Owned(s) => {
@@ -5319,21 +5315,19 @@ mod tests {
                 }
             },
             None => unreachable!(),
         }
     }
 
     #[test]
     fn test_decode_invalid_windows_1257_to_cow_without_bom_handling_and_without_replacement() {
-        assert!(
-            WINDOWS_1257
-                .decode_without_bom_handling_and_without_replacement(b"abc\x80\xA1\xE4")
-                .is_none()
-        );
+        assert!(WINDOWS_1257
+            .decode_without_bom_handling_and_without_replacement(b"abc\x80\xA1\xE4")
+            .is_none());
     }
 
     #[test]
     fn test_decode_ascii_only_windows_1257_to_cow_without_bom_handling_and_without_replacement() {
         match WINDOWS_1257.decode_without_bom_handling_and_without_replacement(b"abc") {
             Some(cow) => match cow {
                 Cow::Borrowed(s) => {
                     assert_eq!(s, "abc");
--- a/third_party/rust/encoding_rs/src/mem.rs
+++ b/third_party/rust/encoding_rs/src/mem.rs
@@ -24,30 +24,35 @@
 use std::borrow::Cow;
 
 use super::in_inclusive_range16;
 use super::in_inclusive_range32;
 use super::in_inclusive_range8;
 use super::in_range16;
 use super::in_range32;
 use super::DecoderResult;
-use super::EncoderResult;
 use ascii::*;
 use utf_8::*;
 
 macro_rules! non_fuzz_debug_assert {
     ($($arg:tt)*) => (if !cfg!(fuzzing) { debug_assert!($($arg)*); })
 }
 
-cfg_if!{
+cfg_if! {
     if #[cfg(feature = "simd-accel")] {
+        use ::std::intrinsics::likely;
         use ::std::intrinsics::unlikely;
     } else {
         #[inline(always)]
         // Unsafe to match the intrinsic, which is needlessly unsafe.
+        unsafe fn likely(b: bool) -> bool {
+            b
+        }
+        #[inline(always)]
+        // Unsafe to match the intrinsic, which is needlessly unsafe.
         unsafe fn unlikely(b: bool) -> bool {
             b
         }
     }
 }
 
 /// Classification of text as Latin1 (all code points are below U+0100),
 /// left-to-right with some non-Latin1 characters or as containing at least
@@ -215,17 +220,17 @@ macro_rules! by_unit_check_simd {
             for &unit in &buffer[offset..] {
                 accu |= unit as usize;
             }
             accu < $bound
         }
     };
 }
 
-cfg_if!{
+cfg_if! {
     if #[cfg(all(feature = "simd-accel", any(target_feature = "sse2", all(target_endian = "little", target_arch = "aarch64"), all(target_endian = "little", target_feature = "neon"))))] {
         use simd_funcs::*;
         use simd::u8x16;
         use simd::u16x8;
 
         const SIMD_ALIGNMENT: usize = 16;
 
         const SIMD_ALIGNMENT_MASK: usize = 15;
@@ -347,17 +352,17 @@ fn utf16_valid_up_to_alu(buffer: &[u16])
             }
             // Unpaired, fall through
         }
         // Unpaired surrogate
         return (offset, false);
     }
 }
 
-cfg_if!{
+cfg_if! {
     if #[cfg(all(feature = "simd-accel", any(target_feature = "sse2", all(target_endian = "little", target_arch = "aarch64"), all(target_endian = "little", target_feature = "neon"))))] {
         #[inline(always)]
         fn is_str_latin1_impl(buffer: &str) -> Option<usize> {
             let mut offset = 0usize;
             let bytes = buffer.as_bytes();
             let len = bytes.len();
             if len >= SIMD_STRIDE_SIZE {
                 let src = bytes.as_ptr();
@@ -436,17 +441,17 @@ fn is_utf8_latin1_impl(buffer: &[u8]) ->
                 return Some(total);
             }
         } else {
             return None;
         }
     }
 }
 
-cfg_if!{
+cfg_if! {
     if #[cfg(all(feature = "simd-accel", any(target_feature = "sse2", all(target_endian = "little", target_arch = "aarch64"), all(target_endian = "little", target_feature = "neon"))))] {
         #[inline(always)]
         fn is_utf16_bidi_impl(buffer: &[u16]) -> bool {
             let mut offset = 0usize;
             let len = buffer.len();
             if len >= SIMD_STRIDE_SIZE / 2 {
                 let src = buffer.as_ptr();
                 let mut until_alignment = ((SIMD_ALIGNMENT - ((src as usize) & SIMD_ALIGNMENT_MASK)) &
@@ -486,17 +491,17 @@ cfg_if!{
                     return true;
                 }
             }
             false
         }
     }
 }
 
-cfg_if!{
+cfg_if! {
     if #[cfg(all(feature = "simd-accel", any(target_feature = "sse2", all(target_endian = "little", target_arch = "aarch64"), all(target_endian = "little", target_feature = "neon"))))] {
         #[inline(always)]
         fn check_utf16_for_latin1_and_bidi_impl(buffer: &[u16]) -> Latin1Bidi {
             let mut offset = 0usize;
             let len = buffer.len();
             if len >= SIMD_STRIDE_SIZE / 2 {
                 let src = buffer.as_ptr();
                 let mut until_alignment = ((SIMD_ALIGNMENT - ((src as usize) & SIMD_ALIGNMENT_MASK)) &
@@ -682,20 +687,17 @@ pub fn is_utf16_latin1(buffer: &[u16]) -
 /// for. Control characters that are technically bidi controls but do not
 /// cause right-to-left behavior without the presence of right-to-left
 /// characters or right-to-left controls are not checked for. As a special
 /// case, U+FEFF is excluded from Arabic Presentation Forms-B.
 ///
 /// Returns `true` if the input is invalid UTF-8 or the input contains an
 /// RTL character. Returns `false` if the input is valid UTF-8 and contains
 /// no RTL characters.
-#[cfg_attr(
-    feature = "cargo-clippy",
-    allow(collapsible_if, cyclomatic_complexity)
-)]
+#[cfg_attr(feature = "cargo-clippy", allow(collapsible_if, cyclomatic_complexity))]
 #[inline]
 pub fn is_utf8_bidi(buffer: &[u8]) -> bool {
     // As of rustc 1.25.0-nightly (73ac5d6a8 2018-01-11), this is faster
     // than UTF-8 validation followed by `is_str_bidi()` for German,
     // Russian and Japanese. However, this is considerably slower for Thai.
     // Chances are that the compiler makes some branch predictions that are
     // unfortunate for Thai. Not spending the time to manually optimize
     // further at this time, since it's unclear if this variant even has
@@ -766,32 +768,36 @@ pub fn is_utf8_bidi(buffer: &[u8]) -> bo
                             }
                             read += 2;
                         }
                         // two-byte starting with 0xD7 and above is bidi
                         0xE1 | 0xE3...0xEC | 0xEE => {
                             // Three-byte normal
                             let second = unsafe { *(src.get_unchecked(read + 1)) };
                             let third = unsafe { *(src.get_unchecked(read + 2)) };
-                            if ((UTF8_DATA.table[usize::from(second)] & unsafe {
-                                *(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
-                            }) | (third >> 6))
+                            if ((UTF8_DATA.table[usize::from(second)]
+                                & unsafe {
+                                    *(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
+                                })
+                                | (third >> 6))
                                 != 2
                             {
                                 return true;
                             }
                             read += 3;
                         }
                         0xE2 => {
                             // Three-byte normal, potentially bidi
                             let second = unsafe { *(src.get_unchecked(read + 1)) };
                             let third = unsafe { *(src.get_unchecked(read + 2)) };
-                            if ((UTF8_DATA.table[usize::from(second)] & unsafe {
-                                *(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
-                            }) | (third >> 6))
+                            if ((UTF8_DATA.table[usize::from(second)]
+                                & unsafe {
+                                    *(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
+                                })
+                                | (third >> 6))
                                 != 2
                             {
                                 return true;
                             }
                             if second == 0x80 {
                                 if third == 0x8F || third == 0xAB || third == 0xAE {
                                     return true;
                                 }
@@ -801,19 +807,21 @@ pub fn is_utf8_bidi(buffer: &[u8]) -> bo
                                 }
                             }
                             read += 3;
                         }
                         0xEF => {
                             // Three-byte normal, potentially bidi
                             let second = unsafe { *(src.get_unchecked(read + 1)) };
                             let third = unsafe { *(src.get_unchecked(read + 2)) };
-                            if ((UTF8_DATA.table[usize::from(second)] & unsafe {
-                                *(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
-                            }) | (third >> 6))
+                            if ((UTF8_DATA.table[usize::from(second)]
+                                & unsafe {
+                                    *(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
+                                })
+                                | (third >> 6))
                                 != 2
                             {
                                 return true;
                             }
                             if in_inclusive_range8(second, 0xAC, 0xB7) {
                                 if second == 0xAC {
                                     if third > 0x9C {
                                         return true;
@@ -835,36 +843,40 @@ pub fn is_utf8_bidi(buffer: &[u8]) -> bo
                                 }
                             }
                             read += 3;
                         }
                         0xE0 => {
                             // Three-byte special lower bound, potentially bidi
                             let second = unsafe { *(src.get_unchecked(read + 1)) };
                             let third = unsafe { *(src.get_unchecked(read + 2)) };
-                            if ((UTF8_DATA.table[usize::from(second)] & unsafe {
-                                *(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
-                            }) | (third >> 6))
+                            if ((UTF8_DATA.table[usize::from(second)]
+                                & unsafe {
+                                    *(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
+                                })
+                                | (third >> 6))
                                 != 2
                             {
                                 return true;
                             }
                             // XXX can this be folded into the above validity check
                             if second < 0xA4 {
                                 return true;
                             }
                             read += 3;
                         }
                         0xED => {
                             // Three-byte special upper bound
                             let second = unsafe { *(src.get_unchecked(read + 1)) };
                             let third = unsafe { *(src.get_unchecked(read + 2)) };
-                            if ((UTF8_DATA.table[usize::from(second)] & unsafe {
-                                *(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
-                            }) | (third >> 6))
+                            if ((UTF8_DATA.table[usize::from(second)]
+                                & unsafe {
+                                    *(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
+                                })
+                                | (third >> 6))
                                 != 2
                             {
                                 return true;
                             }
                             read += 3;
                         }
                         0xF1...0xF4 => {
                             // Four-byte normal
@@ -1591,58 +1603,73 @@ pub fn convert_str_to_utf16(src: &str, d
 }
 
 /// Converts potentially-invalid UTF-16 to valid UTF-8 with errors replaced
 /// with the REPLACEMENT CHARACTER with potentially insufficient output
 /// space.
 ///
 /// Returns the number of code units read and the number of bytes written.
 ///
+/// Guarantees that the bytes in the destination beyond the number of
+/// bytes claimed as written by the second item of the return tuple
+/// are left unmodified.
+///
 /// Not all code units are read if there isn't enough output space.
 ///
 /// Note  that this method isn't designed for general streamability but for
 /// not allocating memory for the worst case up front. Specifically,
 /// if the input starts with or ends with an unpaired surrogate, those are
 /// replaced with the REPLACEMENT CHARACTER.
 ///
+/// Matches the semantics of `TextEncoder.encodeInto()` from the
+/// Encoding Standard.
+///
 /// # Safety
 ///
-/// Note that this function may write garbage beyond the number of bytes
-/// indicated by the return value, so using a `&mut str` interpreted as
-/// `&mut [u8]` as the destination is not safe. If you want to convert into
-/// a `&mut str`, use `convert_utf16_to_str()` instead of this function.
-#[inline]
+/// If you want to convert into a `&mut str`, use
+/// `convert_utf16_to_str_partial()` instead of using this function
+/// together with the `unsafe` method `as_bytes_mut()` on `&mut str`.
+#[inline(always)]
 pub fn convert_utf16_to_utf8_partial(src: &[u16], dst: &mut [u8]) -> (usize, usize) {
-    let mut encoder = Utf8Encoder;
-    let (result, read, written) = encoder.encode_from_utf16_raw(src, dst, true);
-    debug_assert!(result == EncoderResult::OutputFull || read == src.len());
-    (read, written)
+    // The two functions called below are marked `inline(never)` to make
+    // transitions from the hot part (first function) into the cold part
+    // (second function) go through a return and another call to discouge
+    // the CPU from speculating from the hot code into the cold code.
+    // Letting the transitions be mere intra-function jumps, even to
+    // basic blocks out-of-lined to the end of the function would wipe
+    // away a quarter of Arabic encode performance on Haswell!
+    let (read, written) = convert_utf16_to_utf16_partial_inner(src, dst);
+    if unsafe { likely(read == src.len()) } {
+        return (read, written);
+    }
+    let (tail_read, tail_written) =
+        convert_utf16_to_utf16_partial_tail(&src[read..], &mut dst[written..]);
+    (read + tail_read, written + tail_written)
 }
 
 /// Converts potentially-invalid UTF-16 to valid UTF-8 with errors replaced
 /// with the REPLACEMENT CHARACTER.
 ///
 /// The length of the destination buffer must be at least the length of the
-/// source buffer times three _plus one_.
+/// source buffer times three.
 ///
 /// Returns the number of bytes written.
 ///
 /// # Panics
 ///
 /// Panics if the destination buffer is shorter than stated above.
 ///
 /// # Safety
 ///
-/// Note that this function may write garbage beyond the number of bytes
-/// indicated by the return value, so using a `&mut str` interpreted as
-/// `&mut [u8]` as the destination is not safe. If you want to convert into
-/// a `&mut str`, use `convert_utf16_to_str()` instead of this function.
-#[inline]
+/// If you want to convert into a `&mut str`, use `convert_utf16_to_str()`
+/// instead of using this function together with the `unsafe` method
+/// `as_bytes_mut()` on `&mut str`.
+#[inline(always)]
 pub fn convert_utf16_to_utf8(src: &[u16], dst: &mut [u8]) -> usize {
-    assert!(dst.len() > src.len() * 3);
+    assert!(dst.len() >= src.len() * 3);
     let (read, written) = convert_utf16_to_utf8_partial(src, dst);
     debug_assert_eq!(read, src.len());
     written
 }
 
 /// Converts potentially-invalid UTF-16 to valid UTF-8 with errors replaced
 /// with the REPLACEMENT CHARACTER such that the validity of the output is
 /// signaled using the Rust type system with potentially insufficient output
@@ -1657,43 +1684,38 @@ pub fn convert_utf16_to_utf8(src: &[u16]
 /// if the input starts with or ends with an unpaired surrogate, those are
 /// replaced with the REPLACEMENT CHARACTER.
 #[inline]
 pub fn convert_utf16_to_str_partial(src: &[u16], dst: &mut str) -> (usize, usize) {
     let bytes: &mut [u8] = unsafe { dst.as_bytes_mut() };
     let (read, written) = convert_utf16_to_utf8_partial(src, bytes);
     let len = bytes.len();
     let mut trail = written;
-    let max = ::std::cmp::min(len, trail + MAX_STRIDE_SIZE);
-    while trail < max {
-        bytes[trail] = 0;
-        trail += 1;
-    }
     while trail < len && ((bytes[trail] & 0xC0) == 0x80) {
         bytes[trail] = 0;
         trail += 1;
     }
     (read, written)
 }
 
 /// Converts potentially-invalid UTF-16 to valid UTF-8 with errors replaced
 /// with the REPLACEMENT CHARACTER such that the validity of the output is
 /// signaled using the Rust type system.
 ///
 /// The length of the destination buffer must be at least the length of the
-/// source buffer times three _plus one_.
+/// source buffer times three.
 ///
 /// Returns the number of bytes written.
 ///
 /// # Panics
 ///
 /// Panics if the destination buffer is shorter than stated above.
-#[inline]
+#[inline(always)]
 pub fn convert_utf16_to_str(src: &[u16], dst: &mut str) -> usize {
-    assert!(dst.len() > src.len() * 3);
+    assert!(dst.len() >= src.len() * 3);
     let (read, written) = convert_utf16_to_str_partial(src, dst);
     debug_assert_eq!(read, src.len());
     written
 }
 
 /// Converts bytes whose unsigned value is interpreted as Unicode code point
 /// (i.e. U+0000 to U+00FF, inclusive) to UTF-16.
 ///
--- a/third_party/rust/encoding_rs/src/utf_8.rs
+++ b/third_party/rust/encoding_rs/src/utf_8.rs
@@ -7,19 +7,20 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
 use super::*;
 use ascii::ascii_to_basic_latin;
 use ascii::basic_latin_to_ascii;
 use ascii::validate_ascii;
 use handles::*;
+use mem::convert_utf16_to_utf8_partial;
 use variant::*;
 
-cfg_if!{
+cfg_if! {
     if #[cfg(feature = "simd-accel")] {
         use ::std::intrinsics::unlikely;
         use ::std::intrinsics::likely;
     } else {
         #[inline(always)]
         // Unsafe to match the intrinsic, which is needlessly unsafe.
         unsafe fn unlikely(b: bool) -> bool {
             b
@@ -228,20 +229,17 @@ pub fn utf8_valid_up_to(src: &[u8]) -> u
                 break 'outer;
             }
             break 'outer;
         }
     }
     read
 }
 
-#[cfg_attr(
-    feature = "cargo-clippy",
-    allow(never_loop, cyclomatic_complexity)
-)]
+#[cfg_attr(feature = "cargo-clippy", allow(never_loop, cyclomatic_complexity))]
 pub fn convert_utf8_to_utf16_up_to_invalid(src: &[u8], dst: &mut [u16]) -> (usize, usize) {
     // This algorithm differs from the UTF-8 validation algorithm, but making
     // this one consistent with that one makes this slower for reasons I don't
     // understand.
     let mut read = 0;
     let mut written = 0;
     'outer: loop {
         let mut byte = {
@@ -607,181 +605,270 @@ impl Utf8Decoder {
         source,
         b,
         destination_handle,
         unread_handle,
         check_space_astral
     );
 }
 
+#[cfg_attr(feature = "cargo-clippy", allow(never_loop))]
+#[inline(never)]
+pub fn convert_utf16_to_utf16_partial_inner(src: &[u16], dst: &mut [u8]) -> (usize, usize) {
+    let mut read = 0;
+    let mut written = 0;
+    'outer: loop {
+        let mut unit = {
+            let src_remaining = &src[read..];
+            let dst_remaining = &mut dst[written..];
+            let length = if dst_remaining.len() < src_remaining.len() {
+                dst_remaining.len()
+            } else {
+                src_remaining.len()
+            };
+            match unsafe {
+                basic_latin_to_ascii(src_remaining.as_ptr(), dst_remaining.as_mut_ptr(), length)
+            } {
+                None => {
+                    read += length;
+                    written += length;
+                    return (read, written);
+                }
+                Some((non_ascii, consumed)) => {
+                    read += consumed;
+                    written += consumed;
+                    non_ascii
+                }
+            }
+        };
+        'inner: loop {
+            // The following loop is only broken out of as a goto forward.
+            loop {
+                // Unfortunately, this check isn't enough for the compiler to elide
+                // the bound checks on writes to dst, which is why they are manually
+                // elided, which makes a measurable difference.
+                if written.checked_add(4).unwrap() > dst.len() {
+                    return (read, written);
+                }
+                read += 1;
+                if unit < 0x800 {
+                    unsafe {
+                        *(dst.get_unchecked_mut(written)) = (unit >> 6) as u8 | 0xC0u8;
+                        written += 1;
+                        *(dst.get_unchecked_mut(written)) = (unit & 0x3F) as u8 | 0x80u8;
+                        written += 1;
+                    }
+                    break;
+                }
+                let unit_minus_surrogate_start = unit.wrapping_sub(0xD800);
+                if unsafe { likely(unit_minus_surrogate_start > (0xDFFF - 0xD800)) } {
+                    unsafe {
+                        *(dst.get_unchecked_mut(written)) = (unit >> 12) as u8 | 0xE0u8;
+                        written += 1;
+                        *(dst.get_unchecked_mut(written)) = ((unit & 0xFC0) >> 6) as u8 | 0x80u8;
+                        written += 1;
+                        *(dst.get_unchecked_mut(written)) = (unit & 0x3F) as u8 | 0x80u8;
+                        written += 1;
+                    }
+                    break;
+                }
+                if unsafe { likely(unit_minus_surrogate_start <= (0xDBFF - 0xD800)) } {
+                    // high surrogate
+                    // read > src.len() is impossible, but using
+                    // >= instead of == allows the compiler to elide a bound check.
+                    if read >= src.len() {
+                        debug_assert_eq!(read, src.len());
+                        // Unpaired surrogate at the end of the buffer.
+                        unsafe {
+                            *(dst.get_unchecked_mut(written)) = 0xEFu8;
+                            written += 1;
+                            *(dst.get_unchecked_mut(written)) = 0xBFu8;
+                            written += 1;
+                            *(dst.get_unchecked_mut(written)) = 0xBDu8;
+                            written += 1;
+                        }
+                        return (read, written);
+                    }
+                    let second = src[read];
+                    let second_minus_low_surrogate_start = second.wrapping_sub(0xDC00);
+                    if unsafe { likely(second_minus_low_surrogate_start <= (0xDFFF - 0xDC00)) } {
+                        // The next code unit is a low surrogate. Advance position.
+                        read += 1;
+                        let astral = (u32::from(unit) << 10) + u32::from(second)
+                            - (((0xD800u32 << 10) - 0x10000u32) + 0xDC00u32);
+                        unsafe {
+                            *(dst.get_unchecked_mut(written)) = (astral >> 18) as u8 | 0xF0u8;
+                            written += 1;
+                            *(dst.get_unchecked_mut(written)) =
+                                ((astral & 0x3F000u32) >> 12) as u8 | 0x80u8;
+                            written += 1;
+                            *(dst.get_unchecked_mut(written)) =
+                                ((astral & 0xFC0u32) >> 6) as u8 | 0x80u8;
+                            written += 1;
+                            *(dst.get_unchecked_mut(written)) = (astral & 0x3F) as u8 | 0x80u8;
+                            written += 1;
+                        }
+                        break;
+                    }
+                    // The next code unit is not a low surrogate. Don't advance
+                    // position and treat the high surrogate as unpaired.
+                    // Fall through
+                }
+                // Unpaired low surrogate
+                unsafe {
+                    *(dst.get_unchecked_mut(written)) = 0xEFu8;
+                    written += 1;
+                    *(dst.get_unchecked_mut(written)) = 0xBFu8;
+                    written += 1;
+                    *(dst.get_unchecked_mut(written)) = 0xBDu8;
+                    written += 1;
+                }
+                break;
+            }
+            // Now see if the next unit is Basic Latin
+            // read > src.len() is impossible, but using
+            // >= instead of == allows the compiler to elide a bound check.
+            if read >= src.len() {
+                debug_assert_eq!(read, src.len());
+                return (read, written);
+            }
+            unit = src[read];
+            if unsafe { unlikely(unit < 0x80) } {
+                // written > dst.len() is impossible, but using
+                // >= instead of == allows the compiler to elide a bound check.
+                if written >= dst.len() {
+                    debug_assert_eq!(written, dst.len());
+                    return (read, written);
+                }
+                dst[written] = unit as u8;
+                read += 1;
+                written += 1;
+                // Mysteriously, adding a punctuation check here makes
+                // the expected benificiary cases *slower*!
+                continue 'outer;
+            }
+            continue 'inner;
+        }
+    }
+}
+
+#[inline(never)]
+pub fn convert_utf16_to_utf16_partial_tail(src: &[u16], dst: &mut [u8]) -> (usize, usize) {
+    // Everything below is cold code!
+    let mut read = 0;
+    let mut written = 0;
+    let mut unit = src[read];
+    // We now have up to 3 output slots, so an astral character
+    // will not fit.
+    if unit < 0x800 {
+        loop {
+            if unit < 0x80 {
+                if written >= dst.len() {
+                    return (read, written);
+                }
+                read += 1;
+                dst[written] = unit as u8;
+                written += 1;
+            } else if unit < 0x800 {
+                if written + 2 > dst.len() {
+                    return (read, written);
+                }
+                read += 1;
+                dst[written] = (unit >> 6) as u8 | 0xC0u8;
+                written += 1;
+                dst[written] = (unit & 0x3F) as u8 | 0x80u8;
+                written += 1;
+            } else {
+                return (read, written);
+            }
+            // read > src.len() is impossible, but using
+            // >= instead of == allows the compiler to elide a bound check.
+            if read >= src.len() {
+                debug_assert_eq!(read, src.len());
+                return (read, written);
+            }
+            unit = src[read];
+        }
+    }
+    // Could be an unpaired surrogate, but we'll need 3 output
+    // slots in any case.
+    if written + 3 > dst.len() {
+        return (read, written);
+    }
+    read += 1;
+    let unit_minus_surrogate_start = unit.wrapping_sub(0xD800);
+    if unit_minus_surrogate_start <= (0xDFFF - 0xD800) {
+        // Got surrogate
+        if unit_minus_surrogate_start <= (0xDBFF - 0xD800) {
+            // Got high surrogate
+            if read >= src.len() {
+                // Unpaired high surrogate
+                unit = 0xFFFD;
+            } else {
+                let second = src[read];
+                if in_inclusive_range16(second, 0xDC00, 0xDFFF) {
+                    // Valid surrogate pair, but we know it won't fit.
+                    read -= 1;
+                    return (read, written);
+                }
+                // Unpaired high
+                unit = 0xFFFD;
+            }
+        } else {
+            // Unpaired low
+            unit = 0xFFFD;
+        }
+    }
+    dst[written] = (unit >> 12) as u8 | 0xE0u8;
+    written += 1;
+    dst[written] = ((unit & 0xFC0) >> 6) as u8 | 0x80u8;
+    written += 1;
+    dst[written] = (unit & 0x3F) as u8 | 0x80u8;
+    written += 1;
+    debug_assert_eq!(written, dst.len());
+    (read, written)
+}
+
 pub struct Utf8Encoder;
 
 impl Utf8Encoder {
     pub fn new(encoding: &'static Encoding) -> Encoder {
         Encoder::new(encoding, VariantEncoder::Utf8(Utf8Encoder))
     }
 
     pub fn max_buffer_length_from_utf16_without_replacement(
         &self,
         u16_length: usize,
     ) -> Option<usize> {
-        checked_add(1, u16_length.checked_mul(3))
+        u16_length.checked_mul(3)
     }
 
     pub fn max_buffer_length_from_utf8_without_replacement(
         &self,
         byte_length: usize,
     ) -> Option<usize> {
         Some(byte_length)
     }
 
-    #[cfg_attr(feature = "cargo-clippy", allow(never_loop))]
     pub fn encode_from_utf16_raw(
         &mut self,
         src: &[u16],
         dst: &mut [u8],
         _last: bool,
     ) -> (EncoderResult, usize, usize) {
-        let mut read = 0;
-        let mut written = 0;
-        'outer: loop {
-            let mut unit = {
-                let src_remaining = &src[read..];
-                let dst_remaining = &mut dst[written..];
-                let (pending, length) = if dst_remaining.len() < src_remaining.len() {
-                    (EncoderResult::OutputFull, dst_remaining.len())
-                } else {
-                    (EncoderResult::InputEmpty, src_remaining.len())
-                };
-                match unsafe {
-                    basic_latin_to_ascii(src_remaining.as_ptr(), dst_remaining.as_mut_ptr(), length)
-                } {
-                    None => {
-                        read += length;
-                        written += length;
-                        return (pending, read, written);
-                    }
-                    Some((non_ascii, consumed)) => {
-                        read += consumed;
-                        written += consumed;
-                        non_ascii
-                    }
-                }
-            };
-            'inner: loop {
-                // The following loop is only broken out of as a goto forward.
-                loop {
-                    // Unfortunately, this check isn't enough for the compiler to elide
-                    // the bound checks on writes to dst, which is why they are manually
-                    // elided, which makes a measurable difference.
-                    if written.checked_add(4).unwrap() > dst.len() {
-                        return (EncoderResult::OutputFull, read, written);
-                    }
-                    read += 1;
-                    if unit < 0x800 {
-                        unsafe {
-                            *(dst.get_unchecked_mut(written)) = (unit >> 6) as u8 | 0xC0u8;
-                            written += 1;
-                            *(dst.get_unchecked_mut(written)) = (unit & 0x3F) as u8 | 0x80u8;
-                            written += 1;
-                        }
-                        break;
-                    }
-                    let unit_minus_surrogate_start = unit.wrapping_sub(0xD800);
-                    if unsafe { likely(unit_minus_surrogate_start > (0xDFFF - 0xD800)) } {
-                        unsafe {
-                            *(dst.get_unchecked_mut(written)) = (unit >> 12) as u8 | 0xE0u8;
-                            written += 1;
-                            *(dst.get_unchecked_mut(written)) =
-                                ((unit & 0xFC0) >> 6) as u8 | 0x80u8;
-                            written += 1;
-                            *(dst.get_unchecked_mut(written)) = (unit & 0x3F) as u8 | 0x80u8;
-                            written += 1;
-                        }
-                        break;
-                    }
-                    if unsafe { likely(unit_minus_surrogate_start <= (0xDBFF - 0xD800)) } {
-                        // high surrogate
-                        // read > src.len() is impossible, but using
-                        // >= instead of == allows the compiler to elide a bound check.
-                        if read >= src.len() {
-                            debug_assert_eq!(read, src.len());
-                            // Unpaired surrogate at the end of the buffer.
-                            unsafe {
-                                *(dst.get_unchecked_mut(written)) = 0xEFu8;
-                                written += 1;
-                                *(dst.get_unchecked_mut(written)) = 0xBFu8;
-                                written += 1;
-                                *(dst.get_unchecked_mut(written)) = 0xBDu8;
-                                written += 1;
-                            }
-                            return (EncoderResult::InputEmpty, read, written);
-                        }
-                        let second = src[read];
-                        let second_minus_low_surrogate_start = second.wrapping_sub(0xDC00);
-                        if unsafe { likely(second_minus_low_surrogate_start <= (0xDFFF - 0xDC00)) }
-                        {
-                            // The next code unit is a low surrogate. Advance position.
-                            read += 1;
-                            let astral = (u32::from(unit) << 10) + u32::from(second)
-                                - (((0xD800u32 << 10) - 0x10000u32) + 0xDC00u32);
-                            unsafe {
-                                *(dst.get_unchecked_mut(written)) = (astral >> 18) as u8 | 0xF0u8;
-                                written += 1;
-                                *(dst.get_unchecked_mut(written)) =
-                                    ((astral & 0x3F000u32) >> 12) as u8 | 0x80u8;
-                                written += 1;
-                                *(dst.get_unchecked_mut(written)) =
-                                    ((astral & 0xFC0u32) >> 6) as u8 | 0x80u8;
-                                written += 1;
-                                *(dst.get_unchecked_mut(written)) = (astral & 0x3F) as u8 | 0x80u8;
-                                written += 1;
-                            }
-                            break;
-                        }
-                        // The next code unit is not a low surrogate. Don't advance
-                        // position and treat the high surrogate as unpaired.
-                        // Fall through
-                    }
-                    // Unpaired low surrogate
-                    unsafe {
-                        *(dst.get_unchecked_mut(written)) = 0xEFu8;
-                        written += 1;
-                        *(dst.get_unchecked_mut(written)) = 0xBFu8;
-                        written += 1;
-                        *(dst.get_unchecked_mut(written)) = 0xBDu8;
-                        written += 1;
-                    }
-                    break;
-                }
-                // Now see if the next unit is Basic Latin
-                // read > src.len() is impossible, but using
-                // >= instead of == allows the compiler to elide a bound check.
-                if read >= src.len() {
-                    debug_assert_eq!(read, src.len());
-                    return (EncoderResult::InputEmpty, read, written);
-                }
-                unit = src[read];
-                if unsafe { unlikely(unit < 0x80) } {
-                    // written > dst.len() is impossible, but using
-                    // >= instead of == allows the compiler to elide a bound check.
-                    if written >= dst.len() {
-                        debug_assert_eq!(written, dst.len());
-                        return (EncoderResult::OutputFull, read, written);
-                    }
-                    dst[written] = unit as u8;
-                    read += 1;
-                    written += 1;
-                    // Mysteriously, adding a punctuation check here makes
-                    // the expected benificiary cases *slower*!
-                    continue 'outer;
-                }
-                continue 'inner;
-            }
-        }
+        let (read, written) = convert_utf16_to_utf8_partial(src, dst);
+        (
+            if read == src.len() {
+                EncoderResult::InputEmpty
+            } else {
+                EncoderResult::OutputFull
+            },
+            read,
+            written,
+        )
     }
 
     pub fn encode_from_utf8_raw(
         &mut self,
         src: &str,
         dst: &mut [u8],
         _last: bool,
     ) -> (EncoderResult, usize, usize) {
@@ -824,16 +911,49 @@ mod tests {
     fn encode_utf8_from_utf16(string: &[u16], expect: &[u8]) {
         encode_from_utf16(UTF_8, string, expect);
     }
 
     fn encode_utf8_from_utf8(string: &str, expect: &[u8]) {
         encode_from_utf8(UTF_8, string, expect);
     }
 
+    fn encode_utf8_from_utf16_with_output_limit(
+        string: &[u16],
+        expect: &str,
+        limit: usize,
+        expect_result: EncoderResult,
+    ) {
+        let mut dst = Vec::new();
+        {
+            dst.resize(limit, 0u8);
+            let mut encoder = UTF_8.new_encoder();
+            let (result, read, written) =
+                encoder.encode_from_utf16_without_replacement(string, &mut dst, false);
+            assert_eq!(result, expect_result);
+            if expect_result == EncoderResult::InputEmpty {
+                assert_eq!(read, string.len());
+            }
+            assert_eq!(&dst[..written], expect.as_bytes());
+        }
+        {
+            dst.resize(64, 0u8);
+            for (i, elem) in dst.iter_mut().enumerate() {
+                *elem = i as u8;
+            }
+            let mut encoder = UTF_8.new_encoder();
+            let (_, _, mut j) =
+                encoder.encode_from_utf16_without_replacement(string, &mut dst, false);
+            while j < dst.len() {
+                assert_eq!(usize::from(dst[j]), j);
+                j += 1;
+            }
+        }
+    }
+
     #[test]
     fn test_utf8_decode() {
         // Empty
         decode_valid_utf8("");
         // ASCII
         decode_valid_utf8("ab");
         // Low BMP
         decode_valid_utf8("a\u{E4}Z");
@@ -1009,16 +1129,414 @@ mod tests {
         encode_utf8_from_utf16(&[0xE000], "\u{E000}".as_bytes());
         encode_utf8_from_utf16(&[0xFFFF], "\u{FFFF}".as_bytes());
         encode_utf8_from_utf16(&[0xD800, 0xDC00], "\u{10000}".as_bytes());
         encode_utf8_from_utf16(&[0xDBFF, 0xDFFF], "\u{10FFFF}".as_bytes());
         encode_utf8_from_utf16(&[0xDC00, 0xDEDE], "\u{FFFD}\u{FFFD}".as_bytes());
     }
 
     #[test]
+    fn test_encode_utf8_from_utf16_with_output_limit() {
+        encode_utf8_from_utf16_with_output_limit(&[0x0062], "\u{62}", 1, EncoderResult::InputEmpty);
+        encode_utf8_from_utf16_with_output_limit(&[0x00A7], "\u{A7}", 2, EncoderResult::InputEmpty);
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x2603],
+            "\u{2603}",
+            3,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0xD83D, 0xDCA9],
+            "\u{1F4A9}",
+            4,
+            EncoderResult::InputEmpty,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(&[0x00A7], "", 1, EncoderResult::OutputFull);
+        encode_utf8_from_utf16_with_output_limit(&[0x2603], "", 2, EncoderResult::OutputFull);
+        encode_utf8_from_utf16_with_output_limit(
+            &[0xD83D, 0xDCA9],
+            "",
+            3,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x0062],
+            "\u{63}\u{62}",
+            2,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00A7],
+            "\u{63}\u{A7}",
+            3,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x2603],
+            "\u{63}\u{2603}",
+            4,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0xD83D, 0xDCA9],
+            "\u{63}\u{1F4A9}",
+            5,
+            EncoderResult::InputEmpty,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00A7],
+            "\u{63}",
+            2,
+            EncoderResult::OutputFull,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x2603],
+            "\u{63}",
+            3,
+            EncoderResult::OutputFull,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0xD83D, 0xDCA9],
+            "\u{63}",
+            4,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x00B6, 0x0062],
+            "\u{B6}\u{62}",
+            3,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x00B6, 0x00A7],
+            "\u{B6}\u{A7}",
+            4,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x00B6, 0x2603],
+            "\u{B6}\u{2603}",
+            5,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x00B6, 0xD83D, 0xDCA9],
+            "\u{B6}\u{1F4A9}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x00B6, 0x00A7],
+            "\u{B6}",
+            3,
+            EncoderResult::OutputFull,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x00B6, 0x2603],
+            "\u{B6}",
+            4,
+            EncoderResult::OutputFull,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x00B6, 0xD83D, 0xDCA9],
+            "\u{B6}",
+            5,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x0062],
+            "\u{263A}\u{62}",
+            4,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x00A7],
+            "\u{263A}\u{A7}",
+            5,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x2603],
+            "\u{263A}\u{2603}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0xD83D, 0xDCA9],
+            "\u{263A}\u{1F4A9}",
+            7,
+            EncoderResult::InputEmpty,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x00A7],
+            "\u{263A}",
+            4,
+            EncoderResult::OutputFull,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x2603],
+            "\u{263A}",
+            5,
+            EncoderResult::OutputFull,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0xD83D, 0xDCA9],
+            "\u{263A}",
+            6,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0xD83D, 0xDE0E, 0x0062],
+            "\u{1F60E}\u{62}",
+            5,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0xD83D, 0xDE0E, 0x00A7],
+            "\u{1F60E}\u{A7}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0xD83D, 0xDE0E, 0x2603],
+            "\u{1F60E}\u{2603}",
+            7,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0xD83D, 0xDE0E, 0xD83D, 0xDCA9],
+            "\u{1F60E}\u{1F4A9}",
+            8,
+            EncoderResult::InputEmpty,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0xD83D, 0xDE0E, 0x00A7],
+            "\u{1F60E}",
+            5,
+            EncoderResult::OutputFull,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0xD83D, 0xDE0E, 0x2603],
+            "\u{1F60E}",
+            6,
+            EncoderResult::OutputFull,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0xD83D, 0xDE0E, 0xD83D, 0xDCA9],
+            "\u{1F60E}",
+            7,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0x0062, 0x0062],
+            "\u{63}\u{B6}\u{62}\u{62}",
+            5,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0x0062, 0x0062],
+            "\u{63}\u{B6}\u{62}",
+            4,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0x0062, 0x0062, 0x0062],
+            "\u{63}\u{B6}\u{62}\u{62}\u{62}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0x0062, 0x0062, 0x0062],
+            "\u{63}\u{B6}\u{62}\u{62}",
+            5,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x0062, 0x0062],
+            "\u{263A}\u{62}\u{62}",
+            5,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x0062, 0x0062],
+            "\u{263A}\u{62}",
+            4,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x0062, 0x0062, 0x0062],
+            "\u{263A}\u{62}\u{62}\u{62}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x0062, 0x0062, 0x0062],
+            "\u{263A}\u{62}\u{62}",
+            5,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0x00A7],
+            "\u{63}\u{B6}\u{A7}",
+            5,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0x00A7],
+            "\u{63}\u{B6}",
+            4,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0x00A7, 0x0062],
+            "\u{63}\u{B6}\u{A7}\u{62}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0x00A7, 0x0062],
+            "\u{63}\u{B6}\u{A7}",
+            5,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x00A7, 0x0062],
+            "\u{263A}\u{A7}\u{62}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x00A7, 0x0062],
+            "\u{263A}\u{A7}",
+            5,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0x0062, 0x00A7],
+            "\u{63}\u{B6}\u{62}\u{A7}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0x0062, 0x00A7],
+            "\u{63}\u{B6}\u{62}",
+            5,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x0062, 0x00A7],
+            "\u{263A}\u{62}\u{A7}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x0062, 0x00A7],
+            "\u{263A}\u{62}",
+            5,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0x2603],
+            "\u{63}\u{B6}\u{2603}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0x2603],
+            "\u{63}\u{B6}",
+            5,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x2603],
+            "\u{263A}\u{2603}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0x2603],
+            "\u{263A}",
+            5,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0xD83D],
+            "\u{63}\u{B6}\u{FFFD}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0xD83D],
+            "\u{63}\u{B6}",
+            5,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0xD83D],
+            "\u{263A}\u{FFFD}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0xD83D],
+            "\u{263A}",
+            5,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0xDCA9],
+            "\u{63}\u{B6}\u{FFFD}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x0063, 0x00B6, 0xDCA9],
+            "\u{63}\u{B6}",
+            5,
+            EncoderResult::OutputFull,
+        );
+
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0xDCA9],
+            "\u{263A}\u{FFFD}",
+            6,
+            EncoderResult::InputEmpty,
+        );
+        encode_utf8_from_utf16_with_output_limit(
+            &[0x263A, 0xDCA9],
+            "\u{263A}",
+            5,
+            EncoderResult::OutputFull,
+        );
+    }
+
+    #[test]
     fn test_utf8_max_length_from_utf16() {
         let mut encoder = UTF_8.new_encoder();
         let mut output = [0u8; 13];
         let input = &[0x2C9Fu16, 0x2CA9u16, 0x2CA3u16, 0x2C9Fu16];
         let needed = encoder
             .max_buffer_length_from_utf16_without_replacement(input.len())
             .unwrap();
         let (result, _, _) =
@@ -1109,12 +1627,11 @@ mod tests {
             let (result, read, written, had_errors) =
                 decoder.decode_to_utf16("\u{E4}a".as_bytes(), &mut output[..], false);
             assert_eq!(result, CoderResult::OutputFull);
             assert_eq!(read, 2);
             assert_eq!(written, 1);
             assert!(!had_errors);
             assert_eq!(output[0], 0x00E4);
         }
-
     }
 
 }
--- a/third_party/rust/encoding_rs/src/x_user_defined.rs
+++ b/third_party/rust/encoding_rs/src/x_user_defined.rs
@@ -6,17 +6,17 @@
 // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
 use super::*;
 use handles::*;
 use variant::*;
 
-cfg_if!{
+cfg_if! {
     if #[cfg(feature = "simd-accel")] {
         use simd_funcs::*;
         use simd::u16x8;
 
         #[inline(always)]
         fn shift_upper(unpacked: u16x8) -> u16x8 {
             let highest_ascii = u16x8::splat(0x7F);
             let offset = u16x8::splat(0xF700);
--- a/widget/nsXPLookAndFeel.cpp
+++ b/widget/nsXPLookAndFeel.cpp
@@ -192,21 +192,24 @@ const char nsXPLookAndFeel::sColorPrefs[
     "ui.-moz-mac-vibrant-titlebar-dark",
     "ui.-moz-mac-menupopup",
     "ui.-moz-mac-menuitem",
     "ui.-moz-mac-active-menuitem",
     "ui.-moz-mac-source-list",
     "ui.-moz-mac-source-list-selection",
     "ui.-moz-mac-active-source-list-selection",
     "ui.-moz-mac-tooltip",
+    "ui.-moz-win-accentcolor",
+    "ui.-moz-win-accentcolortext",
     "ui.-moz-win-mediatext",
     "ui.-moz-win-communicationstext",
     "ui.-moz-nativehyperlinktext",
     "ui.-moz-comboboxtext",
-    "ui.-moz-combobox"};
+    "ui.-moz-combobox",
+    "ui.-moz-gtk-info-bar-text"};
 
 int32_t nsXPLookAndFeel::sCachedColors[LookAndFeel::eColorID_LAST_COLOR] = {0};
 int32_t nsXPLookAndFeel::sCachedColorBits[COLOR_CACHE_SIZE] = {0};
 
 bool nsXPLookAndFeel::sInitialized = false;
 bool nsXPLookAndFeel::sUseNativeColors = true;
 bool nsXPLookAndFeel::sUseStandinsForNativeColors = false;
 bool nsXPLookAndFeel::sFindbarModalHighlight = false;
--- a/xpcom/rust/nsstring/src/conversions.rs
+++ b/xpcom/rust/nsstring/src/conversions.rs
@@ -14,18 +14,18 @@ use super::Gecko_FallibleAssignCString;
 use super::Latin1StringLike;
 
 use conversions::encoding_rs::mem::*;
 use conversions::encoding_rs::Encoding;
 
 /// Required math stated in the docs of
 /// `convert_utf16_to_utf8()`.
 #[inline(always)]
-fn times_three_plus_one(a: usize) -> Option<usize> {
-    a.checked_mul(3)?.checked_add(1)
+fn times_three(a: usize) -> Option<usize> {
+    a.checked_mul(3)
 }
 
 #[inline(always)]
 fn identity(a: usize) -> Option<usize> {
     Some(a)
 }
 
 #[inline(always)]
@@ -316,17 +316,17 @@ impl nsACString {
         other: &[u16],
         old_len: usize,
     ) -> Result<BulkWriteOk, ()> {
         // We first size the buffer for ASCII if the first two cache lines are ASCII. If that turns out
         // not to be enough, we size for the worst case given the length of the remaining input at that
         // point. BUT if the worst case fits inside the inline capacity of an autostring, we skip
         // the ASCII stuff.
         let worst_case_needed = if let Some(inline_capacity) = self.inline_capacity() {
-            let worst_case = times_three_plus_one(other.len()).ok_or(())?;
+            let worst_case = times_three(other.len()).ok_or(())?;
             if worst_case <= inline_capacity {
                 Some(worst_case)
             } else {
                 None
             }
         } else {
             None
         };
@@ -335,28 +335,28 @@ impl nsACString {
             let new_len_with_ascii = old_len.checked_add(other.len()).ok_or(())?;
             let mut handle = unsafe { self.bulk_write(new_len_with_ascii, old_len, false)? };
             let (read, written) = convert_utf16_to_utf8_partial(other, &mut handle.as_mut_slice()[old_len..]);
             let left = other.len() - read;
             if left == 0 {
                 return Ok(handle.finish(old_len + written, true));
             }
             let filled = old_len + written;
-            let needed = times_three_plus_one(left).ok_or(())?;
+            let needed = times_three(left).ok_or(())?;
             let new_len = filled.checked_add(needed).ok_or(())?;
             unsafe {
                 handle.restart_bulk_write(new_len, filled, false)?;
             }
             (filled, read, handle)
         } else {
             // Started with non-ASCII. Compute worst case
             let needed = if let Some(n) = worst_case_needed {
                 n
             } else {
-                times_three_plus_one(other.len()).ok_or(())?
+                times_three(other.len()).ok_or(())?
             };
             let new_len = old_len.checked_add(needed).ok_or(())?;
             let mut handle = unsafe { self.bulk_write(new_len, old_len, false)? };
             (old_len, 0, handle)
         };
         let written =
             convert_utf16_to_utf8(&other[read..], &mut handle.as_mut_slice()[filled..]);
         Ok(handle.finish(filled + written, true))
--- a/xpcom/string/nsReadableUtils.cpp
+++ b/xpcom/string/nsReadableUtils.cpp
@@ -46,23 +46,23 @@ char* ToNewCString(const nsAString& aSou
   return dest;
 }
 
 char* ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count) {
   auto len = aSource.Length();
   // The uses of this function seem temporary enough that it's not
   // worthwhile to be fancy about the allocation size. Let's just use
   // the worst case.
-  // Times 3 plus 2, because ConvertUTF16toUTF8 requires times 3 plus 1 and
+  // Times 3 plus 1, because ConvertUTF16toUTF8 requires times 3 and
   // then we have the terminator.
   // Using CheckedInt<uint32_t>, because aUTF8Count is uint32_t* for
   // historical reasons.
   mozilla::CheckedInt<uint32_t> destLen(len);
   destLen *= 3;
-  destLen += 2;
+  destLen += 1;
   if (!destLen.isValid()) {
     return nullptr;
   }
   size_t destLenVal = destLen.value();
   char* dest = static_cast<char*>(moz_xmalloc(destLenVal));
 
   size_t written = ConvertUTF16toUTF8(aSource, MakeSpan(dest, destLenVal));
   dest[written] = 0;
--- a/xpcom/string/nsReadableUtils.h
+++ b/xpcom/string/nsReadableUtils.h
@@ -124,18 +124,17 @@ inline void ConvertLatin1toUTF16(mozilla
                                  mozilla::Span<char16_t> aDest) {
   encoding_mem_convert_latin1_to_utf16(aSource.Elements(), aSource.Length(),
                                        aDest.Elements(), aDest.Length());
 }
 
 /**
  * Lone surrogates are replaced with the REPLACEMENT CHARACTER.
  *
- * The length of aDest must be at least the length of aSource times three
- * _plus one_.
+ * The length of aDest must be at least the length of aSource times three.
  *
  * Returns the number of code units written.
  */
 inline size_t ConvertUTF16toUTF8(mozilla::Span<const char16_t> aSource,
                                  mozilla::Span<char> aDest) {
   return encoding_mem_convert_utf16_to_utf8(
       aSource.Elements(), aSource.Length(), aDest.Elements(), aDest.Length());
 }