Merge autoland to mozilla-central. a=merge
authorTiberius Oros <toros@mozilla.com>
Tue, 06 Mar 2018 11:52:14 +0200
changeset 461715 a007dd56b9947a93c276e82275d7065db1949c9e
parent 461714 709eae4e54ffa3f3518745516dd5d27a05255af2 (current diff)
parent 461711 575562183a3bc245c50dcf45d8dc21043b4565ae (diff)
child 461716 af9d5a9c1e28fd790adc5eef0d0e335a99072e9c
child 461736 7aef35642e5facf784d9a41b96e54cf35f6fb5a3
child 461787 ae94c562fe0417960b1365a36b87f60ec97005c4
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.0a1
first release with
nightly linux32
a007dd56b994 / 60.0a1 / 20180306100123 / files
nightly linux64
a007dd56b994 / 60.0a1 / 20180306100123 / files
nightly mac
a007dd56b994 / 60.0a1 / 20180306100123 / files
nightly win32
a007dd56b994 / 60.0a1 / 20180306100123 / files
nightly win64
a007dd56b994 / 60.0a1 / 20180306100123 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
editor/libeditor/EditorBase.cpp
editor/libeditor/HTMLEditor.cpp
--- a/browser/components/enterprisepolicies/EnterprisePolicies.js
+++ b/browser/components/enterprisepolicies/EnterprisePolicies.js
@@ -28,16 +28,19 @@ const MAGIC_TEST_ROOT_PREFIX  = "<test-r
 const PREF_TEST_ROOT          = "mochitest.testRoot";
 
 // This pref is meant to be temporary: it will only be used while we're
 // testing this feature without rolling it out officially. When the
 // policy engine is released, this pref should be removed.
 const PREF_ENABLED            = "browser.policies.enabled";
 const PREF_LOGLEVEL           = "browser.policies.loglevel";
 
+// To force disallowing enterprise-only policies during tests
+const PREF_DISALLOW_ENTERPRISE = "browser.policies.testing.disallowEnterprise";
+
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm", {});
   return new ConsoleAPI({
     prefix: "Enterprise Policies",
     // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
     // messages during development. See LOG_LEVELS in Console.jsm for details.
     maxLogLevel: "error",
     maxLogLevelPref: PREF_LOGLEVEL,
@@ -122,16 +125,21 @@ EnterprisePoliciesManager.prototype = {
       let policySchema = schema.properties[policyName];
       let policyParameters = unparsedPolicies[policyName];
 
       if (!policySchema) {
         log.error(`Unknown policy: ${policyName}`);
         continue;
       }
 
+      if (policySchema.enterprise_only && !areEnterpriseOnlyPoliciesAllowed()) {
+        log.error(`Policy ${policyName} is only allowed on ESR`);
+        continue;
+      }
+
       let [parametersAreValid, parsedParameters] =
         PoliciesValidator.validateAndParseParameters(policyParameters,
                                                      policySchema);
 
       if (!parametersAreValid) {
         log.error(`Invalid parameters specified for ${policyName}.`);
         continue;
       }
@@ -300,16 +308,43 @@ EnterprisePoliciesManager.prototype = {
 
   isAllowed: function BG_sanitize(feature) {
     return !(feature in DisallowedFeatures);
   },
 };
 
 let DisallowedFeatures = {};
 
+/**
+ * areEnterpriseOnlyPoliciesAllowed
+ *
+ * Checks whether the policies marked as enterprise_only in the
+ * schema are allowed to run on this browser.
+ *
+ * This is meant to only allow policies to run on ESR, but in practice
+ * we allow it to run on channels different than release, to allow
+ * these policies to be tested on pre-release channels.
+ *
+ * @returns {Bool} Whether the policy can run.
+ */
+function areEnterpriseOnlyPoliciesAllowed() {
+  if (Services.prefs.getBoolPref(PREF_DISALLOW_ENTERPRISE, false)) {
+    // This is used as an override to test the "enterprise_only"
+    // functionality itself on tests, which would always return
+    // true due to the Cu.isInAutomation check below.
+    return false;
+  }
+
+  if (AppConstants.MOZ_UPDATE_CHANNEL != "release" ||
+      Cu.isInAutomation) {
+    return true;
+  }
+
+  return false;
+}
 
 /*
  * JSON PROVIDER OF POLICIES
  *
  * This is a platform-agnostic provider which looks for
  * policies specified through a policies.json file stored
  * in the installation's distribution folder.
  */
--- a/browser/components/enterprisepolicies/tests/browser/browser.ini
+++ b/browser/components/enterprisepolicies/tests/browser/browser.ini
@@ -2,16 +2,17 @@
 prefs =
   browser.policies.enabled=true
 support-files =
   head.js
   config_popups_cookies_addons_flash.json
   config_broken_json.json
 
 [browser_policies_broken_json.js]
+[browser_policies_enterprise_only.js]
 [browser_policies_notice_in_aboutpreferences.js]
 [browser_policies_popups_cookies_addons_flash.js]
 [browser_policies_runOnce_helper.js]
 [browser_policies_setAndLockPref_API.js]
 [browser_policies_simple_policies.js]
 [browser_policies_sorted_alphabetically.js]
 [browser_policies_validate_and_parse_API.js]
 [browser_policy_app_update.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_enterprise_only.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const PREF_DISALLOW_ENTERPRISE = "browser.policies.testing.disallowEnterprise";
+
+add_task(async function test_enterprise_only_policies() {
+  let { Policies } = ChromeUtils.import("resource:///modules/policies/Policies.jsm", {});
+
+  let normalPolicyRan = false, enterprisePolicyRan = false;
+
+  Policies.NormalPolicy = {
+    onProfileAfterChange(manager, param) {
+      normalPolicyRan = true;
+    }
+  };
+
+  Policies.EnterpriseOnlyPolicy = {
+    onProfileAfterChange(manager, param) {
+      enterprisePolicyRan = true;
+    }
+  };
+
+  Services.prefs.setBoolPref(PREF_DISALLOW_ENTERPRISE, true);
+
+  await setupPolicyEngineWithJson(
+    // policies.json
+    {
+      "policies": {
+        "NormalPolicy": true,
+        "EnterpriseOnlyPolicy": true
+      }
+    },
+
+    // custom schema
+    {
+      properties: {
+        "NormalPolicy": {
+          "type": "boolean"
+        },
+
+        "EnterpriseOnlyPolicy": {
+          "type": "boolean",
+          "enterprise_only": true
+        },
+      }
+    }
+  );
+
+  is(Services.policies.status, Ci.nsIEnterprisePolicies.ACTIVE, "Engine is active");
+  is(normalPolicyRan, true, "Normal policy ran as expected");
+  is(enterprisePolicyRan, false, "Enterprise-only policy was prevented from running");
+
+  // Clean-up
+  delete Policies.NormalPolicy;
+  delete Policies.EnterpriseOnlyPolicy;
+  Services.prefs.clearUserPref(PREF_DISALLOW_ENTERPRISE);
+});
--- a/dom/animation/test/chrome.ini
+++ b/dom/animation/test/chrome.ini
@@ -11,11 +11,10 @@ support-files =
 [chrome/test_keyframe_effect_xrays.html]
 [chrome/test_animation_observers_async.html]
 [chrome/test_animation_observers_sync.html]
 [chrome/test_animation_performance_warning.html]
 [chrome/test_animation_properties.html]
 [chrome/test_cssanimation_missing_keyframes.html]
 [chrome/test_generated_content_getAnimations.html]
 [chrome/test_running_on_compositor.html]
-skip-if = os == 'android' # bug 1442150
 [chrome/test_simulate_compute_values_failure.html]
 skip-if = !debug
--- a/dom/animation/test/chrome/test_running_on_compositor.html
+++ b/dom/animation/test/chrome/test_running_on_compositor.html
@@ -57,651 +57,661 @@ function assert_animation_is_running_on_
                 desc + ' at ' + animation.currentTime + 'ms');
 }
 
 function assert_animation_is_not_running_on_compositor(animation, desc) {
   assert_equals(animation.isRunningOnCompositor, false,
                 desc + ' at ' + animation.currentTime + 'ms');
 }
 
-promise_test(t => {
+promise_test(async t => {
   // FIXME: When we implement Element.animate, use that here instead of CSS
   // so that we remove any dependency on the CSS mapping.
   var div = addDiv(t, { style: 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-       'Animation reports that it is running on the compositor'
-       + ' during playback');
+  // If the animation starts at the current timeline time, we need to wait for
+  // one more frame to avoid receiving the fake timer-based MozAfterPaint event.
+  // FIXME: Bug 1419226: Drop this 'animation.ready' and 'waitForFrame'.  Once
+  // MozAfterPaint is fired reliably, we just need to wait for a MozAfterPaint
+  // here.
+  await animation.ready;
 
-    div.style.animationPlayState = 'paused';
+  if (animationStartsRightNow(animation)) {
+    await waitForFrame();
+  }
 
-    return animation.ready;
-  }).then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-       'Animation reports that it is NOT running on the compositor'
-       + ' when paused');
-  });
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+     'Animation reports that it is running on the compositor'
+     + ' during playback');
+
+  div.style.animationPlayState = 'paused';
+
+  await animation.ready;
+
+  assert_animation_is_not_running_on_compositor(animation,
+     'Animation reports that it is NOT running on the compositor'
+     + ' when paused');
 }, '');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: 'animation: background 100s' });
   var animation = div.getAnimations()[0];
 
-  return waitForPaints().then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-       'Animation reports that it is NOT running on the compositor'
-       + ' for animation of "background"');
-  });
+  await waitForPaints();
+
+  assert_animation_is_not_running_on_compositor(animation,
+     'Animation reports that it is NOT running on the compositor'
+     + ' for animation of "background"');
 }, 'isRunningOnCompositor is false for animation of "background"');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: 'animation: background_and_translate 100s' });
   var animation = div.getAnimations()[0];
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-       'Animation reports that it is running on the compositor'
-        + ' when the animation has two properties, where one can run'
-        + ' on the compositor, the other cannot');
-  });
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+     'Animation reports that it is running on the compositor'
+      + ' when the animation has two properties, where one can run'
+      + ' on the compositor, the other cannot');
 }, 'isRunningOnCompositor is true if the animation has at least one ' +
    'property can run on compositor');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
 
-  return waitForPaints().then(() => {
-    animation.pause();
-    return animation.ready;
-  }).then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-       'Animation reports that it is NOT running on the compositor'
-       + ' when animation.pause() is called');
-  });
+  await waitForPaints();
+
+  animation.pause();
+  await animation.ready;
+
+  assert_animation_is_not_running_on_compositor(animation,
+     'Animation reports that it is NOT running on the compositor'
+     + ' when animation.pause() is called');
 }, 'isRunningOnCompositor is false when the animation.pause() is called');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
 
-  return waitForPaints().then(() => {
-    animation.finish();
-    assert_animation_is_not_running_on_compositor(animation,
-       'Animation reports that it is NOT running on the compositor'
-       + ' immediately after animation.finish() is called');
-    // Check that we don't set the flag back again on the next tick.
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-       'Animation reports that it is NOT running on the compositor'
-       + ' on the next tick after animation.finish() is called');
-  });
+  await waitForPaints();
+
+  animation.finish();
+  assert_animation_is_not_running_on_compositor(animation,
+     'Animation reports that it is NOT running on the compositor'
+     + ' immediately after animation.finish() is called');
+  // Check that we don't set the flag back again on the next tick.
+  await waitForFrame();
+
+  assert_animation_is_not_running_on_compositor(animation,
+     'Animation reports that it is NOT running on the compositor'
+     + ' on the next tick after animation.finish() is called');
 }, 'isRunningOnCompositor is false when the animation.finish() is called');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
 
-  return waitForPaints().then(() => {
-    animation.currentTime = 100 * MS_PER_SEC;
-    assert_animation_is_not_running_on_compositor(animation,
-       'Animation reports that it is NOT running on the compositor'
-       + ' immediately after manually seeking the animation to the end');
-    // Check that we don't set the flag back again on the next tick.
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-       'Animation reports that it is NOT running on the compositor'
-       + ' on the next tick after manually seeking the animation to the end');
-  });
+  await waitForPaints();
+
+  animation.currentTime = 100 * MS_PER_SEC;
+  assert_animation_is_not_running_on_compositor(animation,
+     'Animation reports that it is NOT running on the compositor'
+     + ' immediately after manually seeking the animation to the end');
+  // Check that we don't set the flag back again on the next tick.
+  await waitForFrame();
+
+  assert_animation_is_not_running_on_compositor(animation,
+     'Animation reports that it is NOT running on the compositor'
+     + ' on the next tick after manually seeking the animation to the end');
 }, 'isRunningOnCompositor is false when manually seeking the animation to ' +
    'the end');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
 
-  return waitForPaints().then(() => {
-    animation.cancel();
-    assert_animation_is_not_running_on_compositor(animation,
-       'Animation reports that it is NOT running on the compositor'
-       + ' immediately after animation.cancel() is called');
-    // Check that we don't set the flag back again on the next tick.
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-       'Animation reports that it is NOT running on the compositor'
-       + ' on the next tick after animation.cancel() is called');
-  });
+  await waitForPaints();
+
+  animation.cancel();
+  assert_animation_is_not_running_on_compositor(animation,
+     'Animation reports that it is NOT running on the compositor'
+     + ' immediately after animation.cancel() is called');
+  // Check that we don't set the flag back again on the next tick.
+  await waitForFrame();
+
+  assert_animation_is_not_running_on_compositor(animation,
+     'Animation reports that it is NOT running on the compositor'
+     + ' on the next tick after animation.cancel() is called');
 }, 'isRunningOnCompositor is false when animation.cancel() is called');
 
 // This is to test that we don't simply clobber the flag when ticking
 // animations and then set it again during painting.
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
 
-  return waitForPaints().then(() => {
-    return new Promise(resolve => {
-      window.requestAnimationFrame(() => {
-        t.step(() => {
-          assert_animation_is_running_on_compositor(animation,
-            'Animation reports that it is running on the compositor'
-             + ' in requestAnimationFrame callback');
-        });
+  await waitForPaints();
 
-        resolve();
+  await new Promise(resolve => {
+    window.requestAnimationFrame(() => {
+      t.step(() => {
+        assert_animation_is_running_on_compositor(animation,
+          'Animation reports that it is running on the compositor'
+           + ' in requestAnimationFrame callback');
       });
+
+      resolve();
     });
   });
 }, 'isRunningOnCompositor is true in requestAnimationFrame callback');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
 
-  return waitForPaints().then(() => {
-    return new Promise(resolve => {
-      var observer = new MutationObserver(records => {
-        var changedAnimation;
+  await waitForPaints();
+
+  await new Promise(resolve => {
+    var observer = new MutationObserver(records => {
+      var changedAnimation;
 
-        records.forEach(record => {
-          changedAnimation =
-            record.changedAnimations.find(changedAnim => {
-              return changedAnim == animation;
-            });
-        });
+      records.forEach(record => {
+        changedAnimation =
+          record.changedAnimations.find(changedAnim => {
+            return changedAnim == animation;
+          });
+      });
 
-        t.step(() => {
-          assert_true(!!changedAnimation, 'The animation should be recorded '
-            + 'as one of the changedAnimations');
-
-          assert_animation_is_running_on_compositor(animation,
-            'Animation reports that it is running on the compositor'
-             + ' in MutationObserver callback');
-        });
+      t.step(() => {
+        assert_true(!!changedAnimation, 'The animation should be recorded '
+          + 'as one of the changedAnimations');
 
-        resolve();
+        assert_animation_is_running_on_compositor(animation,
+          'Animation reports that it is running on the compositor'
+           + ' in MutationObserver callback');
       });
-      observer.observe(div, { animations: true, subtree: false });
-      t.add_cleanup(() => {
-        observer.disconnect();
-      });
-      div.style.animationDuration = "200s";
+
+      resolve();
     });
+    observer.observe(div, { animations: true, subtree: false });
+    t.add_cleanup(() => {
+      observer.disconnect();
+    });
+    div.style.animationDuration = "200s";
   });
 }, 'isRunningOnCompositor is true in MutationObserver callback');
 
 // This is to test that we don't temporarily clear the flag when forcing
 // an unthrottled sample.
-promise_test(t => {
+promise_test(async t => {
   // Needs scrollbars to cause overflow.
-  return SpecialPowers.pushPrefEnv({ set: [["ui.showHideScrollbars", 1]] })
-  .then(() => {
-    var div = addDiv(t, { style: 'animation: rotate 100s' });
-    var animation = div.getAnimations()[0];
+  await SpecialPowers.pushPrefEnv({ set: [["ui.showHideScrollbars", 1]] })
+
+  var div = addDiv(t, { style: 'animation: rotate 100s' });
+  var animation = div.getAnimations()[0];
+
+  await waitForPaints();
 
-    return waitForPaints().then(() => {
-      return new Promise(resolve => {
-        var timeAtStart = window.performance.now();
-        function handleFrame() {
-          t.step(() => {
-            assert_animation_is_running_on_compositor(animation,
-              'Animation reports that it is running on the compositor'
-               + ' in requestAnimationFrame callback');
-          });
+  await new Promise(resolve => {
+    var timeAtStart = window.performance.now();
+    function handleFrame() {
+      t.step(() => {
+        assert_animation_is_running_on_compositor(animation,
+          'Animation reports that it is running on the compositor'
+           + ' in requestAnimationFrame callback');
+      });
 
-          // we have to wait at least 200ms because this animation is
-          // unthrottled on every 200ms.
-          // See https://hg.mozilla.org/mozilla-central/file/cafb1c90f794/layout/style/AnimationCommon.cpp#l863
-          if (window.performance.now() - timeAtStart > 200) {
-            resolve();
-            return;
-          }
-          window.requestAnimationFrame(handleFrame);
-        }
-        window.requestAnimationFrame(handleFrame);
-      });
-    });
+      // we have to wait at least 200ms because this animation is
+      // unthrottled on every 200ms.
+      // See https://hg.mozilla.org/mozilla-central/file/cafb1c90f794/layout/style/AnimationCommon.cpp#l863
+      if (window.performance.now() - timeAtStart > 200) {
+        resolve();
+        return;
+      }
+      window.requestAnimationFrame(handleFrame);
+    }
+    window.requestAnimationFrame(handleFrame);
   });
 }, 'isRunningOnCompositor remains true in requestAnimationFrameCallback for ' +
    'overflow animation');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: 'transition: opacity 100s; opacity: 1' });
 
   getComputedStyle(div).opacity;
 
   div.style.opacity = 0;
   var animation = div.getAnimations()[0];
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-       'Transition reports that it is running on the compositor'
-       + ' during playback for opacity transition');
-  });
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+     'Transition reports that it is running on the compositor'
+     + ' during playback for opacity transition');
 }, 'isRunningOnCompositor for transitions');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: 'animation: rotate-and-opacity 100s; ' +
                                'backface-visibility: hidden; ' +
                                'transform: none !important;' });
   var animation = div.getAnimations()[0];
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-       'If an animation has a property that can run on the compositor and a '
-       + 'property that cannot (due to Gecko limitations) but where the latter'
-       + 'property is overridden in the CSS cascade, the animation should '
-       + 'still report that it is running on the compositor');
-  });
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+     'If an animation has a property that can run on the compositor and a '
+     + 'property that cannot (due to Gecko limitations) but where the latter'
+     + 'property is overridden in the CSS cascade, the animation should '
+     + 'still report that it is running on the compositor');
 }, 'isRunningOnCompositor is true when a property that would otherwise block ' +
    'running on the compositor is overridden in the CSS cascade');
 
-promise_test(t => {
+promise_test(async t => {
   var animation = addDivAndAnimate(t,
                                    {},
                                    { opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-      'Animation reports that it is running on the compositor');
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+    'Animation reports that it is running on the compositor');
 
-    animation.currentTime = 150 * MS_PER_SEC;
-    animation.effect.timing.duration = 100 * MS_PER_SEC;
+  animation.currentTime = 150 * MS_PER_SEC;
+  animation.effect.timing.duration = 100 * MS_PER_SEC;
 
-    assert_animation_is_not_running_on_compositor(animation,
-       'Animation reports that it is NOT running on the compositor'
-       + ' when the animation is set a shorter duration than current time');
-  });
+  assert_animation_is_not_running_on_compositor(animation,
+     'Animation reports that it is NOT running on the compositor'
+     + ' when the animation is set a shorter duration than current time');
 }, 'animation is immediately removed from compositor' +
    'when timing.duration is made shorter than the current time');
 
-promise_test(t => {
+promise_test(async t => {
   var animation = addDivAndAnimate(t,
                                    {},
                                    { opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-      'Animation reports that it is running on the compositor');
+  await waitForPaints();
 
-    animation.currentTime = 500 * MS_PER_SEC;
+  assert_animation_is_running_on_compositor(animation,
+    'Animation reports that it is running on the compositor');
+
+  animation.currentTime = 500 * MS_PER_SEC;
 
-    assert_animation_is_not_running_on_compositor(animation,
-      'Animation reports that it is NOT running on the compositor'
-      + ' when finished');
+  assert_animation_is_not_running_on_compositor(animation,
+    'Animation reports that it is NOT running on the compositor'
+    + ' when finished');
 
-    animation.effect.timing.duration = 1000 * MS_PER_SEC;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_running_on_compositor(animation,
-      'Animation reports that it is running on the compositor'
-      + ' when restarted');
-  });
+  animation.effect.timing.duration = 1000 * MS_PER_SEC;
+  await waitForFrame();
+
+  assert_animation_is_running_on_compositor(animation,
+    'Animation reports that it is running on the compositor'
+    + ' when restarted');
 }, 'animation is added to compositor' +
    ' when timing.duration is made longer than the current time');
 
-promise_test(t => {
+promise_test(async t => {
   var animation = addDivAndAnimate(t,
                                    {},
                                    { opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-      'Animation reports that it is running on the compositor');
+  await waitForPaints();
 
-    animation.effect.timing.endDelay = 100 * MS_PER_SEC;
+  assert_animation_is_running_on_compositor(animation,
+    'Animation reports that it is running on the compositor');
+
+  animation.effect.timing.endDelay = 100 * MS_PER_SEC;
 
-    assert_animation_is_running_on_compositor(animation,
-      'Animation reports that it is running on the compositor'
-      + ' when endDelay is changed');
+  assert_animation_is_running_on_compositor(animation,
+    'Animation reports that it is running on the compositor'
+    + ' when endDelay is changed');
 
-    animation.currentTime = 110 * MS_PER_SEC;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-      'Animation reports that it is NOT running on the compositor'
-      + ' when currentTime is during endDelay');
-  });
+  animation.currentTime = 110 * MS_PER_SEC;
+  await waitForFrame();
+
+  assert_animation_is_not_running_on_compositor(animation,
+    'Animation reports that it is NOT running on the compositor'
+    + ' when currentTime is during endDelay');
 }, 'animation is removed from compositor' +
    ' when current time is made longer than the duration even during endDelay');
 
-promise_test(t => {
+promise_test(async t => {
   var animation = addDivAndAnimate(t,
                                    {},
                                    { opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-      'Animation reports that it is running on the compositor');
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+    'Animation reports that it is running on the compositor');
 
-    animation.effect.timing.endDelay = -200 * MS_PER_SEC;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-      'Animation reports that it is NOT running on the compositor'
-      + ' when endTime is negative value');
-  });
+  animation.effect.timing.endDelay = -200 * MS_PER_SEC;
+  await waitForFrame();
+
+  assert_animation_is_not_running_on_compositor(animation,
+    'Animation reports that it is NOT running on the compositor'
+    + ' when endTime is negative value');
 }, 'animation is removed from compositor' +
    ' when endTime is negative value');
 
-promise_test(t => {
+promise_test(async t => {
   var animation = addDivAndAnimate(t,
                                    {},
                                    { opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-      'Animation reports that it is running on the compositor');
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+    'Animation reports that it is running on the compositor');
+
+  animation.effect.timing.endDelay = -100 * MS_PER_SEC;
+  await waitForFrame();
 
-    animation.effect.timing.endDelay = -100 * MS_PER_SEC;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_running_on_compositor(animation,
-      'Animation reports that it is running on the compositor'
-      + ' when endTime is positive and endDelay is negative');
-    animation.currentTime = 110 * MS_PER_SEC;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-      'Animation reports that it is NOT running on the compositor'
-      + ' when currentTime is after endTime');
-  });
+  assert_animation_is_running_on_compositor(animation,
+    'Animation reports that it is running on the compositor'
+    + ' when endTime is positive and endDelay is negative');
+  animation.currentTime = 110 * MS_PER_SEC;
+  await waitForFrame();
+
+  assert_animation_is_not_running_on_compositor(animation,
+    'Animation reports that it is NOT running on the compositor'
+    + ' when currentTime is after endTime');
 }, 'animation is NOT running on compositor' +
    ' when endTime is positive and endDelay is negative');
 
-promise_test(t => {
+promise_test(async t => {
   var effect = new KeyframeEffect(null,
                                   { opacity: [ 0, 1 ] },
                                   100 * MS_PER_SEC);
   var animation = new Animation(effect, document.timeline);
   animation.play();
 
   var div = addDiv(t);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-                  'Animation with null target reports that it is not running ' +
-                  'on the compositor');
+  await waitForPaints();
+
+  assert_animation_is_not_running_on_compositor(animation,
+                'Animation with null target reports that it is not running ' +
+                'on the compositor');
 
-    animation.effect.target = div;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_running_on_compositor(animation,
-                  'Animation reports that it is running on the compositor ' +
-                  'after setting a valid target');
-  });
+  animation.effect.target = div;
+  await waitForFrame();
+
+  assert_animation_is_running_on_compositor(animation,
+                'Animation reports that it is running on the compositor ' +
+                'after setting a valid target');
 }, 'animation is added to the compositor when setting a valid target');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t);
   var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-                  'Animation reports that it is running on the compositor');
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+                'Animation reports that it is running on the compositor');
 
-    animation.effect.target = null;
-    assert_animation_is_not_running_on_compositor(animation,
-                  'Animation reports that it is NOT running on the ' +
-                  'compositor after setting null target');
-  });
+  animation.effect.target = null;
+  assert_animation_is_not_running_on_compositor(animation,
+                'Animation reports that it is NOT running on the ' +
+                'compositor after setting null target');
 }, 'animation is removed from the compositor when setting null target');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t);
   var animation = div.animate({ opacity: [ 0, 1 ] },
                               { duration: 100 * MS_PER_SEC,
                                 delay: 100 * MS_PER_SEC,
                                 fill: 'backwards' });
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-                  'Animation with fill:backwards in delay phase reports ' +
-                  'that it is running on the compositor');
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+                'Animation with fill:backwards in delay phase reports ' +
+                'that it is running on the compositor');
 
-    animation.currentTime = 100 * MS_PER_SEC;
-    return waitForFrame();
- }).then(() => {
-    assert_animation_is_running_on_compositor(animation,
-                  'Animation with fill:backwards in delay phase reports ' +
-                  'that it is running on the compositor after delay phase');
-  });
+  animation.currentTime = 100 * MS_PER_SEC;
+  await waitForFrame();
+
+  assert_animation_is_running_on_compositor(animation,
+      'Animation with fill:backwards in delay phase reports ' +
+      'that it is running on the compositor after delay phase');
 }, 'animation with fill:backwards in delay phase is running on the ' +
    ' main-thread while it is in delay phase');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t);
   var animation = div.animate([{ opacity: 1, offset: 0 },
                                { opacity: 1, offset: 0.99 },
                                { opacity: 0, offset: 1 }], 100 * MS_PER_SEC);
 
   var another = addDiv(t);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-                  'Opacity animation on a 100% opacity keyframe reports ' +
-                  'that it is running on the compositor from the begining');
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+                'Opacity animation on a 100% opacity keyframe reports ' +
+                'that it is running on the compositor from the begining');
 
-    animation.effect.target = another;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_running_on_compositor(animation,
-                  'Opacity animation on a 100% opacity keyframe keeps ' +
-                  'running on the compositor after changing the target ' +
-                  'element');
-  });
+  animation.effect.target = another;
+  await waitForFrame();
+
+  assert_animation_is_running_on_compositor(animation,
+                'Opacity animation on a 100% opacity keyframe keeps ' +
+                'running on the compositor after changing the target ' +
+                'element');
 }, '100% opacity animations with keeps running on the ' +
    'compositor after changing the target element');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t);
   var animation = div.animate({ color: ['red', 'black'] }, 100 * MS_PER_SEC);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-                  'Color animation reports that it is not running on the ' +
-                  'compositor');
+  await waitForPaints();
+
+  assert_animation_is_not_running_on_compositor(animation,
+                'Color animation reports that it is not running on the ' +
+                'compositor');
 
-    animation.effect.setKeyframes([{ opacity: 1, offset: 0 },
-                                   { opacity: 1, offset: 0.99 },
-                                   { opacity: 0, offset: 1 }]);
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_running_on_compositor(animation,
-                  '100% opacity animation set by using setKeyframes reports ' +
-                  'that it is running on the compositor');
-  });
+  animation.effect.setKeyframes([{ opacity: 1, offset: 0 },
+                                 { opacity: 1, offset: 0.99 },
+                                 { opacity: 0, offset: 1 }]);
+  await waitForFrame();
+
+  assert_animation_is_running_on_compositor(animation,
+                '100% opacity animation set by using setKeyframes reports ' +
+                'that it is running on the compositor');
 }, '100% opacity animation set up by converting an existing animation with ' +
    'cannot be run on the compositor, is running on the compositor');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t);
   var animation = div.animate({ color: ['red', 'black'] }, 100 * MS_PER_SEC);
   var effect = new KeyframeEffect(div,
                                   [{ opacity: 1, offset: 0 },
                                    { opacity: 1, offset: 0.99 },
                                    { opacity: 0, offset: 1 }],
                                   100 * MS_PER_SEC);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-                  'Color animation reports that it is not running on the ' +
-                  'compositor');
+  await waitForPaints();
+
+  assert_animation_is_not_running_on_compositor(animation,
+                'Color animation reports that it is not running on the ' +
+                'compositor');
 
-    animation.effect = effect;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_running_on_compositor(animation,
-                  '100% opacity animation set up by changing effects reports ' +
-                  'that it is running on the compositor');
-  });
+  animation.effect = effect;
+  await waitForFrame();
+
+  assert_animation_is_running_on_compositor(animation,
+                '100% opacity animation set up by changing effects reports ' +
+                'that it is running on the compositor');
 }, '100% opacity animation set up by changing the effects on an existing ' +
    'animation which cannot be run on the compositor, is running on the ' +
    'compositor');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: "opacity: 1 ! important" });
 
   var animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-      'Opacity animation on an element which has 100% opacity style with ' +
-      '!important flag reports that it is not running on the compositor');
-    // Clear important flag from the opacity style on the target element.
-    div.style.setProperty("opacity", "1", "");
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_running_on_compositor(animation,
-      'Opacity animation reports that it is running on the compositor after '
-      + 'clearing the !important flag');
-  });
+  await waitForPaints();
+
+  assert_animation_is_not_running_on_compositor(animation,
+    'Opacity animation on an element which has 100% opacity style with ' +
+    '!important flag reports that it is not running on the compositor');
+  // Clear important flag from the opacity style on the target element.
+  div.style.setProperty("opacity", "1", "");
+  await waitForFrame();
+
+  assert_animation_is_running_on_compositor(animation,
+    'Opacity animation reports that it is running on the compositor after '
+    + 'clearing the !important flag');
 }, 'Clearing *important* opacity style on the target element sends the ' +
    'animation to the compositor');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t);
   var lowerAnimation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC);
   var higherAnimation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(higherAnimation,
-                  'A higher-priority opacity animation on an element ' +
-                  'reports that it is running on the compositor');
-    assert_animation_is_running_on_compositor(lowerAnimation,
-                  'A lower-priority opacity animation on the same ' +
-                  'element also reports that it is running on the compositor');
-  });
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(higherAnimation,
+                'A higher-priority opacity animation on an element ' +
+                'reports that it is running on the compositor');
+  assert_animation_is_running_on_compositor(lowerAnimation,
+                'A lower-priority opacity animation on the same ' +
+                'element also reports that it is running on the compositor');
 }, 'Opacity animations on the same element run on the compositor');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: 'transition: opacity 100s; opacity: 1' });
 
   getComputedStyle(div).opacity;
 
   div.style.opacity = 0;
   getComputedStyle(div).opacity;
 
   var transition = div.getAnimations()[0];
   var animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-                  'An opacity animation on an element reports that' +
-                  'that it is running on the compositor');
-    assert_animation_is_running_on_compositor(transition,
-                  'An opacity transition on the same element reports that ' +
-                  'it is running on the compositor');
-  });
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+                'An opacity animation on an element reports that' +
+                'that it is running on the compositor');
+  assert_animation_is_running_on_compositor(transition,
+                'An opacity transition on the same element reports that ' +
+                'it is running on the compositor');
 }, 'Both of transition and script animation on the same element run on the ' +
    'compositor');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t);
   var importantOpacityElement = addDiv(t, { style: "opacity: 1 ! important" });
 
   var animation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-                  'Opacity animation on an element reports ' +
-                  'that it is running on the compositor');
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+                'Opacity animation on an element reports ' +
+                'that it is running on the compositor');
+
+  animation.effect.target = null;
+  await waitForFrame();
 
-    animation.effect.target = null;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-                  'Animation is no longer running on the compositor after ' +
-                  'removing from the element');
-    animation.effect.target = importantOpacityElement;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-                  'Animation is NOT running on the compositor even after ' +
-                  'being applied to a different element which has an ' +
-                  '!important opacity declaration');
-  });
+  assert_animation_is_not_running_on_compositor(animation,
+                'Animation is no longer running on the compositor after ' +
+                'removing from the element');
+  animation.effect.target = importantOpacityElement;
+  await waitForFrame();
+
+  assert_animation_is_not_running_on_compositor(animation,
+                'Animation is NOT running on the compositor even after ' +
+                'being applied to a different element which has an ' +
+                '!important opacity declaration');
 }, 'Animation continues not running on the compositor after being ' +
    'applied to an element which has an important declaration and ' +
    'having previously been temporarily associated with no target element');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t);
   var another = addDiv(t);
 
   var lowerAnimation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC);
   var higherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(lowerAnimation,
-                  'An opacity animation on an element reports that ' +
-                  'it is running on the compositor');
-    assert_animation_is_running_on_compositor(higherAnimation,
-                  'Opacity animation on a different element reports ' +
-                  'that it is running on the compositor');
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(lowerAnimation,
+                'An opacity animation on an element reports that ' +
+                'it is running on the compositor');
+  assert_animation_is_running_on_compositor(higherAnimation,
+                'Opacity animation on a different element reports ' +
+                'that it is running on the compositor');
+
+  lowerAnimation.effect.target = null;
+  await waitForFrame();
 
-    lowerAnimation.effect.target = null;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_not_running_on_compositor(lowerAnimation,
-                  'Animation is no longer running on the compositor after ' +
-                  'being removed from the element');
-    lowerAnimation.effect.target = another;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_running_on_compositor(lowerAnimation,
-                  'A lower-priority animation begins running ' +
-                  'on the compositor after being applied to an element ' +
-                  'which has a higher-priority animation');
-    assert_animation_is_running_on_compositor(higherAnimation,
-                  'A higher-priority animation continues to run on the ' +
-                  'compositor even after a lower-priority animation is ' +
-                  'applied to the same element');
-  });
+  assert_animation_is_not_running_on_compositor(lowerAnimation,
+                'Animation is no longer running on the compositor after ' +
+                'being removed from the element');
+  lowerAnimation.effect.target = another;
+  await waitForFrame();
+
+  assert_animation_is_running_on_compositor(lowerAnimation,
+                'A lower-priority animation begins running ' +
+                'on the compositor after being applied to an element ' +
+                'which has a higher-priority animation');
+  assert_animation_is_running_on_compositor(higherAnimation,
+                'A higher-priority animation continues to run on the ' +
+                'compositor even after a lower-priority animation is ' +
+                'applied to the same element');
 }, 'Animation begins running on the compositor after being applied ' +
    'to an element which has a higher-priority animation and after ' +
    'being temporarily associated with no target element');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t);
   var another = addDiv(t);
 
   var lowerAnimation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC);
   var higherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(lowerAnimation,
-                  'An opacity animation on an element reports that ' +
-                  'it is running on the compositor');
-    assert_animation_is_running_on_compositor(higherAnimation,
-                  'Opacity animation on a different element reports ' +
-                  'that it is running on the compositor');
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(lowerAnimation,
+                'An opacity animation on an element reports that ' +
+                'it is running on the compositor');
+  assert_animation_is_running_on_compositor(higherAnimation,
+                'Opacity animation on a different element reports ' +
+                'that it is running on the compositor');
+
+  higherAnimation.effect.target = null;
+  await waitForFrame();
 
-    higherAnimation.effect.target = null;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_not_running_on_compositor(higherAnimation,
-                  'Animation is no longer running on the compositor after ' +
-                  'being removed from the element');
-    higherAnimation.effect.target = div;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_running_on_compositor(lowerAnimation,
-                  'Animation continues running on the compositor after ' +
-                  'a higher-priority animation applied to the same element');
-    assert_animation_is_running_on_compositor(higherAnimation,
-                  'A higher-priority animation begins to running on the ' +
-                  'compositor after being applied to an element which has ' +
-                  'a lower-priority-animation');
-  });
+  assert_animation_is_not_running_on_compositor(higherAnimation,
+                'Animation is no longer running on the compositor after ' +
+                'being removed from the element');
+  higherAnimation.effect.target = div;
+  await waitForFrame();
+
+  assert_animation_is_running_on_compositor(lowerAnimation,
+                'Animation continues running on the compositor after ' +
+                'a higher-priority animation applied to the same element');
+  assert_animation_is_running_on_compositor(higherAnimation,
+                'A higher-priority animation begins to running on the ' +
+                'compositor after being applied to an element which has ' +
+                'a lower-priority-animation');
 }, 'Animation begins running on the compositor after being applied ' +
    'to an element which has a lower-priority animation once after ' +
    'disassociating with an element');
 
 var delayPhaseTests = [
   {
     desc: 'script animation of opacity',
     setupAnimation: t => {
@@ -750,24 +760,24 @@ var delayPhaseTests = [
 
       div.style.transform = 'translateX(100px)';
       return div.getAnimations()[0];
     },
   },
 ];
 
 delayPhaseTests.forEach(test => {
-  promise_test(t => {
+  promise_test(async t => {
     var animation = test.setupAnimation(t);
 
-    return waitForPaints().then(() => {
-      assert_animation_is_running_on_compositor(animation,
-         test.desc + ' reports that it is running on the '
-         + 'compositor even though it is in the delay phase');
-    });
+    await waitForPaints();
+
+    assert_animation_is_running_on_compositor(animation,
+       test.desc + ' reports that it is running on the '
+       + 'compositor even though it is in the delay phase');
   }, 'isRunningOnCompositor for ' + test.desc + ' is true even though ' +
      'it is in the delay phase');
 });
 
 // The purpose of thie test cases is to check that
 // NS_FRAME_MAY_BE_TRANSFORMED flag on the associated nsIFrame persists
 // after transform style on the frame is removed.
 var delayPhaseWithTransformStyleTests = [
@@ -796,32 +806,32 @@ var delayPhaseWithTransformStyleTests = 
 
       div.style.transform = 'translateX(100px)';
       return div.getAnimations()[0];
     },
   },
 ];
 
 delayPhaseWithTransformStyleTests.forEach(test => {
-  promise_test(t => {
+  promise_test(async t => {
     var animation = test.setupAnimation(t);
 
-    return waitForPaints().then(() => {
-      assert_animation_is_running_on_compositor(animation,
-         test.desc + ' reports that it is running on the '
-         + 'compositor even though it is in the delay phase');
-    }).then(() => {
-      // Remove the initial transform style during delay phase.
-      animation.effect.target.style.transform = 'none';
-      return animation.ready;
-    }).then(() => {
-      assert_animation_is_running_on_compositor(animation,
-         test.desc + ' reports that it keeps running on the '
-         + 'compositor after removing the initial transform style');
-    });
+    await waitForPaints();
+
+    assert_animation_is_running_on_compositor(animation,
+       test.desc + ' reports that it is running on the '
+       + 'compositor even though it is in the delay phase');
+
+    // Remove the initial transform style during delay phase.
+    animation.effect.target.style.transform = 'none';
+    await animation.ready;
+
+    assert_animation_is_running_on_compositor(animation,
+       test.desc + ' reports that it keeps running on the '
+       + 'compositor after removing the initial transform style');
   }, 'isRunningOnCompositor for ' + test.desc + ' is true after removing ' +
      'the initial transform style during the delay phase');
 });
 
 var startsWithNoneTests = [
   {
     desc: 'script animation of transform starts with transform:none segment',
     setupAnimation: t => {
@@ -835,130 +845,130 @@ var startsWithNoneTests = [
       return addDiv(t,
         { style: 'animation: transform-starts-with-none 100s 100s' })
           .getAnimations()[0];
     },
   },
 ];
 
 startsWithNoneTests.forEach(test => {
-  promise_test(t => {
+  promise_test(async t => {
     var animation = test.setupAnimation(t);
 
-    return waitForPaints().then(() => {
-      assert_animation_is_running_on_compositor(animation,
-         test.desc + ' reports that it is running on the '
-         + 'compositor even though it is in transform:none segment');
-    });
+    await waitForPaints();
+
+    assert_animation_is_running_on_compositor(animation,
+       test.desc + ' reports that it is running on the '
+       + 'compositor even though it is in transform:none segment');
   }, 'isRunningOnCompositor for ' + test.desc + ' is true even though ' +
      'it is in transform:none segment');
 });
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: 'opacity: 1 ! important' });
 
   var animation = div.animate(
     { opacity: [0, 1] },
     { delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC });
 
-  return waitForPaints().then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-      'Opacity animation on an element which has opacity:1 important style'
-      + 'reports that it is not running on the compositor');
-    // Clear the opacity style on the target element.
-    div.style.setProperty("opacity", "1", "");
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_running_on_compositor(animation,
-      'Opacity animations reports that it is running on the compositor after '
-      + 'clearing the opacity style on the element');
-  });
+  await waitForPaints();
+
+  assert_animation_is_not_running_on_compositor(animation,
+    'Opacity animation on an element which has opacity:1 important style'
+    + 'reports that it is not running on the compositor');
+  // Clear the opacity style on the target element.
+  div.style.setProperty("opacity", "1", "");
+  await waitForFrame();
+
+  assert_animation_is_running_on_compositor(animation,
+    'Opacity animations reports that it is running on the compositor after '
+    + 'clearing the opacity style on the element');
 }, 'Clearing *important* opacity style on the target element sends the ' +
    'animation to the compositor even if the animation is in the delay phase');
 
-promise_test(t => {
+promise_test(async t => {
   var opaqueDiv = addDiv(t, { style: 'opacity: 1 ! important' });
   var anotherDiv = addDiv(t);
 
   var animation = opaqueDiv.animate(
     { opacity: [0, 1] },
     { delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC });
 
-  return waitForPaints().then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-      'Opacity animation on an element which has opacity:1 important style'
-      + 'reports that it is not running on the compositor');
-    // Changing target element to another element which has no opacity style.
-    animation.effect.target = anotherDiv;
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_running_on_compositor(animation,
-      'Opacity animations reports that it is running on the compositor after '
-      + 'changing the target element to another elemenent having no '
-      + 'opacity style');
-  });
+  await waitForPaints();
+
+  assert_animation_is_not_running_on_compositor(animation,
+    'Opacity animation on an element which has opacity:1 important style'
+    + 'reports that it is not running on the compositor');
+  // Changing target element to another element which has no opacity style.
+  animation.effect.target = anotherDiv;
+  await waitForFrame();
+
+  assert_animation_is_running_on_compositor(animation,
+    'Opacity animations reports that it is running on the compositor after '
+    + 'changing the target element to another elemenent having no '
+    + 'opacity style');
 }, 'Changing target element of opacity animation sends the animation to the ' +
    'the compositor even if the animation is in the delay phase');
 
-promise_test(t => {
+promise_test(async t => {
   var animation =
     addDivAndAnimate(t,
                      {},
                      { width: ['100px', '200px'] },
                      { duration: 100 * MS_PER_SEC, delay: 100 * MS_PER_SEC });
 
-  return waitForPaints().then(() => {
-    assert_animation_is_not_running_on_compositor(animation,
-      'Width animation reports that it is not running on the compositor '
-      + 'in the delay phase');
-    // Changing to property runnable on the compositor.
-    animation.effect.setKeyframes({ opacity: [0, 1] });
-    return waitForFrame();
-  }).then(() => {
-    assert_animation_is_running_on_compositor(animation,
-      'Opacity animation reports that it is running on the compositor '
-      + 'after changing the property from width property in the delay phase');
-  });
+  await waitForPaints();
+
+  assert_animation_is_not_running_on_compositor(animation,
+    'Width animation reports that it is not running on the compositor '
+    + 'in the delay phase');
+  // Changing to property runnable on the compositor.
+  animation.effect.setKeyframes({ opacity: [0, 1] });
+  await waitForFrame();
+
+  assert_animation_is_running_on_compositor(animation,
+    'Opacity animation reports that it is running on the compositor '
+    + 'after changing the property from width property in the delay phase');
 }, 'Dynamic change to a property runnable on the compositor ' +
    'in the delay phase');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: 'transition: opacity 100s; ' +
                                'opacity: 0 !important' });
   getComputedStyle(div).opacity;
 
   div.style.setProperty('opacity', '1', 'important');
   getComputedStyle(div).opacity;
 
   var animation = div.getAnimations()[0];
 
-  return waitForPaints().then(() => {
-    assert_animation_is_running_on_compositor(animation,
-       'Transition reports that it is running on the compositor even if the ' +
-       'property is overridden by an !important rule');
-  });
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+     'Transition reports that it is running on the compositor even if the ' +
+     'property is overridden by an !important rule');
 }, 'Transitions override important rules');
 
-promise_test(t => {
+promise_test(async t => {
   var div = addDiv(t, { style: 'transition: opacity 100s; ' +
                                'opacity: 0 !important' });
   getComputedStyle(div).opacity;
 
   div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
 
   div.style.setProperty('opacity', '1', 'important');
   getComputedStyle(div).opacity;
 
   var [transition, animation] = div.getAnimations();
 
-  return waitForPaints().then(() => {
-    assert_animation_is_not_running_on_compositor(transition,
-       'Transition suppressed by an animation which is overridden by an ' +
-       '!important rule reports that it is NOT running on the compositor');
-    assert_animation_is_not_running_on_compositor(animation,
-       'Animation overridden by an !important rule reports that it is ' +
-       'NOT running on the compositor');
-  });
+  await waitForPaints();
+
+  assert_animation_is_not_running_on_compositor(transition,
+     'Transition suppressed by an animation which is overridden by an ' +
+     '!important rule reports that it is NOT running on the compositor');
+  assert_animation_is_not_running_on_compositor(animation,
+     'Animation overridden by an !important rule reports that it is ' +
+     'NOT running on the compositor');
 }, 'Neither transition nor animation does run on the compositor if the ' +
    'property is overridden by an !important rule');
 
 </script>
 </body>
--- a/dom/animation/test/mozilla/file_restyles.html
+++ b/dom/animation/test/mozilla/file_restyles.html
@@ -91,26 +91,16 @@ function waitForWheelEvent(aTarget) {
 
     sendWheelAndPaintNoFlush(aTarget, centerX, centerY,
                              { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
                                deltaY: targetRect.height },
                              resolve);
   });
 }
 
-// Returns true if |aAnimation| begins at the current timeline time.  We
-// sometimes need to detect this case because if we started an animation
-// asynchronously (e.g. using play()) and then ended up running the next frame
-// at precisely the time the animation started (due to aligning with vsync
-// refresh rate) then we won't end up restyling in that frame.
-function startsRightNow(aAnimation) {
-  return aAnimation.startTime === aAnimation.timeline.currentTime &&
-         aAnimation.currentTime === 0;
-}
-
 function tweakExpectedRestyleCount(aAnimation, aExpectedRestyleCount) {
   // Normally we expect one restyling for each requestAnimationFrame (as
   // called by observeRestyling) PLUS one for the last frame because of bug
   // 1193394.  However, we won't observe that initial restyling unless BOTH of
   // the following two conditions hold:
   //
   // 1. We are running *before* restyling happens.
   // 2. The animation actually needs a restyle because it started prior to
@@ -118,20 +108,20 @@ function tweakExpectedRestyleCount(aAnim
   //    the refresh driver, the animation fame in which the ready promise is
   //    resolved happens to coincide perfectly with the start time of the
   //    animation.  In this case no restyling is needed so we won't observe
   //    an additional restyle.
   if (hasConformantPromiseHandling) {
     // If we have the conformant Promise handling and |aAnimation| begins at
     // the current timeline time, we will not process restyling in the initial
     // frame.
-    if (startsRightNow(aAnimation)) {
+    if (animationStartsRightNow(aAnimation)) {
       return aExpectedRestyleCount - 1;
     }
-  } else if (!startsRightNow(aAnimation)) {
+  } else if (!animationStartsRightNow(aAnimation)) {
     // If we don't have the conformant Promise handling and |aAnimation|
     // doesn't begin at the current timeline time, we will see an additional
     // restyling in the last frame.
     return aExpectedRestyleCount + 1;
   }
   return aExpectedRestyleCount;
 }
 
--- a/dom/animation/test/testcommon.js
+++ b/dom/animation/test/testcommon.js
@@ -414,8 +414,19 @@ function getDistance(target, prop, v1, v
  * A promise wrapper for waiting MozAfterPaint.
  */
 function waitForPaints() {
   // FIXME: Bug 1415065. Instead waiting for two requestAnimationFrames, we
   // should wait for MozAfterPaint once after MozAfterPaint is fired properly
   // (bug 1341294).
   return waitForAnimationFrames(2);
 }
+
+// Returns true if |aAnimation| begins at the current timeline time.  We
+// sometimes need to detect this case because if we started an animation
+// asynchronously (e.g. using play()) and then ended up running the next frame
+// at precisely the time the animation started (due to aligning with vsync
+// refresh rate) then we won't end up restyling in that frame.
+function animationStartsRightNow(aAnimation) {
+  return aAnimation.startTime === aAnimation.timeline.currentTime &&
+         aAnimation.currentTime === 0;
+}
+
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -5084,30 +5084,33 @@ EditorBase::HandleInlineSpellCheck(EditA
 
 already_AddRefed<nsIContent>
 EditorBase::FindSelectionRoot(nsINode* aNode)
 {
   nsCOMPtr<nsIContent> rootContent = GetRoot();
   return rootContent.forget();
 }
 
+void
+EditorBase::InitializeSelectionAncestorLimit(Selection& aSelection,
+                                             nsIContent& aAncestorLimit)
+{
+  aSelection.SetAncestorLimiter(&aAncestorLimit);
+}
+
 nsresult
 EditorBase::InitializeSelection(nsIDOMEventTarget* aFocusEventTarget)
 {
   nsCOMPtr<nsINode> targetNode = do_QueryInterface(aFocusEventTarget);
   NS_ENSURE_TRUE(targetNode, NS_ERROR_INVALID_ARG);
   nsCOMPtr<nsIContent> selectionRootContent = FindSelectionRoot(targetNode);
   if (!selectionRootContent) {
     return NS_OK;
   }
 
-  bool isTargetDoc =
-    targetNode->NodeType() == nsINode::DOCUMENT_NODE &&
-    targetNode->HasFlag(NODE_IS_EDITABLE);
-
   RefPtr<Selection> selection = GetSelection();
   NS_ENSURE_STATE(selection);
 
   nsCOMPtr<nsIPresShell> presShell = GetPresShell();
   NS_ENSURE_TRUE(presShell, NS_ERROR_NOT_INITIALIZED);
 
   nsCOMPtr<nsISelectionController> selectionController =
     GetSelectionController();
@@ -5125,34 +5128,28 @@ EditorBase::InitializeSelection(nsIDOMEv
 
   // Init selection
   selectionController->SetDisplaySelection(
                          nsISelectionController::SELECTION_ON);
   selectionController->SetSelectionFlags(
                          nsISelectionDisplay::DISPLAY_ALL);
   selectionController->RepaintSelection(
                          nsISelectionController::SELECTION_NORMAL);
+
   // If the computed selection root isn't root content, we should set it
   // as selection ancestor limit.  However, if that is root element, it means
   // there is not limitation of the selection, then, we must set nullptr.
   // NOTE: If we set a root element to the ancestor limit, some selection
   // methods don't work fine.
   if (selectionRootContent->GetParent()) {
-    selection->SetAncestorLimiter(selectionRootContent);
+    InitializeSelectionAncestorLimit(*selection, *selectionRootContent);
   } else {
     selection->SetAncestorLimiter(nullptr);
   }
 
-  // XXX What case needs this?
-  if (isTargetDoc) {
-    if (!selection->RangeCount()) {
-      BeginningOfDocument();
-    }
-  }
-
   // If there is composition when this is called, we may need to restore IME
   // selection because if the editor is reframed, this already forgot IME
   // selection and the transaction.
   if (mComposition && mComposition->IsMovingToNewTextNode()) {
     // We need to look for the new text node from current selection.
     // XXX If selection is changed during reframe, this doesn't work well!
     nsRange* firstRange = selection->GetRangeAt(0);
     if (NS_WARN_IF(!firstRange)) {
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -728,16 +728,29 @@ protected:
    * placeholder transaction to wrap up any further transaction while the
    * batch is open.  The advantage of this is that placeholder transactions
    * can later merge, if needed.  Merging is unavailable between transaction
    * manager batches.
    */
   void BeginPlaceholderTransaction(nsAtom* aTransactionName);
   void EndPlaceholderTransaction();
 
+  /**
+   * InitializeSelectionAncestorLimit() is called by InitializeSelection().
+   * When this is called, each implementation has to call
+   * aSelection.SetAncestorLimiter() with aAnotherLimit.
+   *
+   * @param aSelection          The selection.
+   * @param aAncestorLimit      New ancestor limit of aSelection.  This always
+   *                            has parent node.  So, it's always safe to
+   *                            call SetAncestorLimit() with this node.
+   */
+  virtual void InitializeSelectionAncestorLimit(Selection& aSelection,
+                                                nsIContent& aAncestorLimit);
+
 public:
   /**
    * All editor operations which alter the doc should be prefaced
    * with a call to StartOperation, naming the action and direction.
    */
   NS_IMETHOD StartOperation(EditAction opID,
                             nsIEditor::EDirection aDirection);
 
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -503,90 +503,182 @@ HTMLEditor::InitRules()
     mRules = new HTMLEditRules();
   }
   return mRules->Init(this);
 }
 
 NS_IMETHODIMP
 HTMLEditor::BeginningOfDocument()
 {
+  return MaybeCollapseSelectionAtFirstEditableNode(false);
+}
+
+void
+HTMLEditor::InitializeSelectionAncestorLimit(Selection& aSelection,
+                                             nsIContent& aAncestorLimit)
+{
+  // Hack for initializing selection.
+  // HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode() will try to
+  // collapse selection at first editable text node or inline element which
+  // cannot have text nodes as its children.  However, selection has already
+  // set into the new editing host by user, we should not change it.  For
+  // solving this issue, we should do nothing if selection range is in active
+  // editing host except it's not collapsed at start of the editing host since
+  // aSelection.SetAncestorLimiter(aAncestorLimit) will collapse selection
+  // at start of the new limiter if focus node of aSelection is outside of the
+  // editing host.  However, we need to check here if selection is already
+  // collapsed at start of the editing host because it's possible JS to do it.
+  // In such case, we should not modify selection with calling
+  // MaybeCollapseSelectionAtFirstEditableNode().
+
+  // Basically, we should try to collapse selection at first editable node
+  // in HTMLEditor.
+  bool tryToCollapseSelectionAtFirstEditableNode = true;
+  if (aSelection.RangeCount() == 1 && aSelection.IsCollapsed()) {
+    Element* editingHost = GetActiveEditingHost();
+    nsRange* range = aSelection.GetRangeAt(0);
+    if (range->GetStartContainer() == editingHost &&
+        !range->StartOffset()) {
+      // JS or user operation has already collapsed selection at start of
+      // the editing host.  So, we don't need to try to change selection
+      // in this case.
+      tryToCollapseSelectionAtFirstEditableNode = false;
+    }
+  }
+
+  EditorBase::InitializeSelectionAncestorLimit(aSelection, aAncestorLimit);
+
+  // XXX Do we need to check if we still need to change selection?  E.g.,
+  //     we could have already lost focus while we're changing the ancestor
+  //     limiter because it may causes "selectionchange" event.
+  if (tryToCollapseSelectionAtFirstEditableNode) {
+    MaybeCollapseSelectionAtFirstEditableNode(true);
+  }
+}
+
+nsresult
+HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(
+              bool aIgnoreIfSelectionInEditingHost)
+{
   // XXX Why doesn't this check if the document is alive?
   if (!IsInitialized()) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
-  // Get the selection
   RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
-
-  // Get the root element.
-  nsCOMPtr<Element> rootElement = GetRoot();
-  if (!rootElement) {
-    NS_WARNING("GetRoot() returned a null pointer (mRootElement is null)");
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  // Use editing host.  If you use root element here, selection may be
+  // moved to <head> element, e.g., if there is a text node in <script>
+  // element.  So, we should use active editing host.
+  RefPtr<Element> editingHost = GetActiveEditingHost();
+  if (NS_WARN_IF(!editingHost)) {
     return NS_OK;
   }
 
-  // Find first editable thingy
-  bool done = false;
-  nsCOMPtr<nsINode> curNode = rootElement.get(), selNode;
-  int32_t curOffset = 0, selOffset = 0;
-  while (!done) {
-    WSRunObject wsObj(this, curNode, curOffset);
+  // If selection range is already in the editing host and the range is not
+  // start of the editing host, we shouldn't reset selection.  E.g., window
+  // is activated when the editor had focus before inactivated.
+  if (aIgnoreIfSelectionInEditingHost && selection->RangeCount() == 1) {
+    nsRange* range = selection->GetRangeAt(0);
+    if (!range->Collapsed() ||
+        range->GetStartContainer() != editingHost.get() ||
+        range->StartOffset()) {
+      return NS_OK;
+    }
+  }
+
+  // Find first editable and visible node.
+  EditorRawDOMPoint pointToPutCaret(editingHost, 0);
+  for (;;) {
+    WSRunObject wsObj(this, pointToPutCaret.GetContainer(),
+                      pointToPutCaret.Offset());
     int32_t visOffset = 0;
     WSType visType;
     nsCOMPtr<nsINode> visNode;
-    wsObj.NextVisibleNode(curNode, curOffset, address_of(visNode), &visOffset,
-                          &visType);
+    wsObj.NextVisibleNode(pointToPutCaret.GetContainer(),
+                          pointToPutCaret.Offset(),
+                          address_of(visNode), &visOffset, &visType);
+
+    // If we meet a non-editable node first, we should move caret to start of
+    // the editing host (perhaps, user may want to insert something before
+    // the first non-editable node? Chromium behaves so).
+    if (visNode && !visNode->IsEditable()) {
+      pointToPutCaret.Set(editingHost, 0);
+      break;
+    }
+
+    // WSRunObject::NextVisibleNode() returns WSType::special and the "special"
+    // node when it meets empty inline element.  In this case, we should go to
+    // next sibling.  For example, if current editor is:
+    // <div contenteditable><span></span><b><br></b></div>
+    // then, we should put caret at the <br> element.  So, let's check if
+    // found node is an empty inline container element.
+    if (visType == WSType::special && visNode &&
+        TagCanContainTag(*visNode->NodeInfo()->NameAtom(),
+                         *nsGkAtoms::textTagName)) {
+      pointToPutCaret.Set(visNode);
+      DebugOnly<bool> advanced = pointToPutCaret.AdvanceOffset();
+      NS_WARNING_ASSERTION(advanced,
+        "Failed to advance offset from found empty inline container element");
+      continue;
+    }
+
+    // If there is editable and visible text node, move caret at start of it.
     if (visType == WSType::normalWS || visType == WSType::text) {
-      selNode = visNode;
-      selOffset = visOffset;
-      done = true;
-    } else if (visType == WSType::br || visType == WSType::special) {
-      selNode = visNode->GetParentNode();
-      selOffset = selNode ? selNode->ComputeIndexOf(visNode) : -1;
-      done = true;
-    } else if (visType == WSType::otherBlock) {
-      // By definition of WSRunObject, a block element terminates a
-      // whitespace run. That is, although we are calling a method that is
-      // named "NextVisibleNode", the node returned might not be
-      // visible/editable!
-      //
-      // If the given block does not contain any visible/editable items, we
-      // want to skip it and continue our search.
-
-      if (!IsContainer(visNode)) {
-        // However, we were given a block that is not a container.  Since the
-        // block can not contain anything that's visible, such a block only
-        // makes sense if it is visible by itself, like a <hr>.  We want to
-        // place the caret in front of that block.
-        selNode = visNode->GetParentNode();
-        selOffset = selNode ? selNode->ComputeIndexOf(visNode) : -1;
-        done = true;
-      } else {
-        bool isEmptyBlock;
-        if (NS_SUCCEEDED(IsEmptyNode(visNode, &isEmptyBlock)) &&
-            isEmptyBlock) {
-          // Skip the empty block
-          curNode = visNode->GetParentNode();
-          curOffset = curNode ? curNode->ComputeIndexOf(visNode) : -1;
-          curOffset++;
-        } else {
-          curNode = visNode;
-          curOffset = 0;
-        }
-        // Keep looping
-      }
+      pointToPutCaret.Set(visNode, visOffset);
+      break;
+    }
+
+    // If there is editable <br> or something inline special element like
+    // <img>, <input>, etc, move caret before it.
+    if (visType == WSType::br || visType == WSType::special) {
+      pointToPutCaret.Set(visNode);
+      break;
+    }
+
+    // If there is no visible/editable node except another block element in
+    // current editing host, we should move caret to very first of the editing
+    // host.
+    // XXX This may not make sense, but Chromium behaves so.  Therefore, the
+    //     reason why we do this is just compatibility with Chromium.
+    if (visType != WSType::otherBlock) {
+      pointToPutCaret.Set(editingHost, 0);
+      break;
+    }
+
+    // By definition of WSRunObject, a block element terminates a whitespace
+    // run. That is, although we are calling a method that is named
+    // "NextVisibleNode", the node returned might not be visible/editable!
+
+    // However, we were given a block that is not a container.  Since the
+    // block can not contain anything that's visible, such a block only
+    // makes sense if it is visible by itself, like a <hr>.  We want to
+    // place the caret in front of that block.
+    if (!IsContainer(visNode)) {
+      pointToPutCaret.Set(visNode);
+      break;
+    }
+
+    // If the given block does not contain any visible/editable items, we want
+    // to skip it and continue our search.
+    bool isEmptyBlock;
+    if (NS_SUCCEEDED(IsEmptyNode(visNode, &isEmptyBlock)) && isEmptyBlock) {
+      // Skip the empty block
+      pointToPutCaret.Set(visNode);
+      DebugOnly<bool> advanced = pointToPutCaret.AdvanceOffset();
+      NS_WARNING_ASSERTION(advanced,
+        "Failed to advance offset from the found empty block node");
     } else {
-      // Else we found nothing useful
-      selNode = curNode;
-      selOffset = curOffset;
-      done = true;
+      pointToPutCaret.Set(visNode, 0);
     }
   }
-  return selection->Collapse(selNode, selOffset);
+  return selection->Collapse(pointToPutCaret);
 }
 
 nsresult
 HTMLEditor::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent)
 {
   // NOTE: When you change this method, you should also change:
   //   * editor/libeditor/tests/test_htmleditor_keyevent_handling.html
 
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -322,16 +322,20 @@ public:
   }
 
 protected:
   virtual ~HTMLEditor();
 
   using EditorBase::IsBlockNode;
   virtual bool IsBlockNode(nsINode *aNode) override;
 
+  virtual void
+  InitializeSelectionAncestorLimit(Selection& aSelection,
+                                   nsIContent& aAncestorLimit) override;
+
 public:
   // XXX Why don't we move following methods above for grouping by the origins?
   NS_IMETHOD SetFlags(uint32_t aFlags) override;
 
   NS_IMETHOD Paste(int32_t aSelectionType) override;
   NS_IMETHOD CanPaste(int32_t aSelectionType, bool* aCanPaste) override;
 
   NS_IMETHOD PasteTransferable(nsITransferable* aTransferable) override;
@@ -535,16 +539,42 @@ public:
 
   /**
    * Modifies the table containing the selection according to the
    * activation of an inline table editing UI element
    * @param aUIAnonymousElement [IN] the inline table editing UI element
    */
   nsresult DoInlineTableEditingAction(const Element& aUIAnonymousElement);
 
+  /**
+   * MaybeCollapseSelectionAtFirstEditableNode() may collapse selection at
+   * proper position to staring to edit.  If there is a non-editable node
+   * before any editable text nodes or inline elements which can have text
+   * nodes as their children, collapse selection at start of the editing
+   * host.  If there is an editable text node which is not collapsed, collapses
+   * selection at the start of the text node.  If there is an editable inline
+   * element which cannot have text nodes as its child, collapses selection at
+   * before the element node.  Otherwise, collapses selection at start of the
+   * editing host.
+   *
+   * @param aIgnoreIfSelectionInEditingHost
+   *                        This method does nothing if selection is in the
+   *                        editing host except if it's collapsed at start of
+   *                        the editing host.
+   *                        Note that if selection ranges were outside of
+   *                        current selection limiter, selection was collapsed
+   *                        at the start of the editing host therefore, if
+   *                        you call this with setting this to true, you can
+   *                        keep selection ranges if user has already been
+   *                        changed.
+   */
+  nsresult
+  MaybeCollapseSelectionAtFirstEditableNode(
+    bool aIgnoreIfSelectionInEditingHost);
+
 protected:
   class BlobReader final : public nsIEditorBlobListener
   {
   public:
     BlobReader(dom::BlobImpl* aBlob, HTMLEditor* aHTMLEditor,
                bool aIsSafe, nsIDOMDocument* aSourceDoc,
                nsIDOMNode* aDestinationNode, int32_t aDestOffset,
                bool aDoDeleteSelection);
--- a/editor/libeditor/tests/test_contenteditable_focus.html
+++ b/editor/libeditor/tests/test_contenteditable_focus.html
@@ -96,42 +96,42 @@ function runTestsInternal()
   editor.focus();
   is(SpecialPowers.unwrap(fm.focusedElement), editor,
      "editor didn't get focus");
   is(selection.rangeCount, 1,
      "there is no selection range when editor has focus");
   var range = selection.getRangeAt(0);
   ok(range.collapsed, "the selection range isn't collapsed");
   var startNode = range.startContainer;
-  is(startNode.nodeType, 1, "the caret isn't set to the div node");
-  is(startNode, editor, "the caret isn't set to the editor");
+  is(startNode.nodeType, Node.TEXT_NODE, "the caret isn't set to the first text node");
+  is(startNode, editor.firstChild, "the caret isn't set to the editor");
   ok(selCon.caretVisible, "caret isn't visible in the editor");
   // Move focus to other editor
   otherEditor.focus();
   is(SpecialPowers.unwrap(fm.focusedElement), otherEditor,
      "the other editor didn't get focus");
   is(selection.rangeCount, 1,
      "there is no selection range when the other editor has focus");
   range = selection.getRangeAt(0);
   ok(range.collapsed, "the selection range isn't collapsed");
   var startNode = range.startContainer;
-  is(startNode.nodeType, 1, "the caret isn't set to the div node");
-  is(startNode, otherEditor, "the caret isn't set to the other editor");
+  is(startNode.nodeType, Node.TEXT_NODE, "the caret isn't set to the text node");
+  is(startNode, otherEditor.firstChild, "the caret isn't set to the other editor");
   ok(selCon.caretVisible, "caret isn't visible in the other editor");
   // Move focus to inputTextInEditor
   inputTextInEditor.focus();
   is(SpecialPowers.unwrap(fm.focusedElement), inputTextInEditor,
      "inputTextInEditor didn't get focus #2");
   is(selection.rangeCount, 1, "selection range is lost from the document");
   range = selection.getRangeAt(0);
   ok(range.collapsed, "the selection range isn't collapsed");
   var startNode = range.startContainer;
-  is(startNode.nodeType, 1, "the caret isn't set to the div node");
+  is(startNode.nodeType, Node.TEXT_NODE, "the caret isn't set to the first text node");
   // XXX maybe, the caret can stay on the other editor if it's better.
-  is(startNode, editor,
+  is(startNode, editor.firstChild,
      "the caret should stay on the other editor");
   ok(selCon.caretVisible,
      "caret isn't visible in the inputTextInEditor");
   // Move focus to the other editor again
   otherEditor.focus();
   is(SpecialPowers.unwrap(fm.focusedElement), otherEditor,
      "the other editor didn't get focus #2");
   // Set selection to the span element in the editor.
--- a/editor/nsIEditor.idl
+++ b/editor/nsIEditor.idl
@@ -325,17 +325,28 @@ interface nsIEditor  : nsISupports
     */
   boolean canPasteTransferable([optional] in nsITransferable aTransferable);
 
   /* ------------ Selection methods -------------- */
 
   /** sets the document selection to the entire contents of the document */
   void selectAll();
 
-  /** sets the document selection to the beginning of the document */
+  /**
+   * Collapses selection at start of the document.  If it's an HTML editor,
+   * collapses selection at start of current editing host (<body> element if
+   * it's in designMode) instead.  If there is a non-editable node before any
+   * editable text nodes or inline elements which can have text nodes as their
+   * children, collapses selection at start of the editing host.  If there is
+   * an editable text node which is not collapsed, collapses selection at
+   * start of the text node.  If there is an editable inline element which
+   * cannot have text nodes as its child, collapses selection at before the
+   * element node.  Otherwise, collapses selection at start of the editing
+   * host.
+   */
   void beginningOfDocument();
 
   /** sets the document selection to the end of the document */
   void endOfDocument();
 
   /* ------------ Node manipulation methods -------------- */
 
   /**
new file mode 100644
--- /dev/null
+++ b/intl/l10n/docs/fluent_tutorial.rst
@@ -0,0 +1,683 @@
+.. role:: html(code)
+   :language: html
+
+.. role:: js(code)
+   :language: javascript
+
+=============================
+Fluent for Firefox Developers
+=============================
+
+
+This tutorial is intended for Firefox engineers already familiar with the previous
+localization systems offered by Gecko - `DTD`_ and  `StringBundle`_ - and assumes
+prior experience with those systems.
+
+
+Using Fluent in Gecko
+=====================
+
+`Fluent`_ is a modern localization system currently being progressively introduced into
+the Gecko platform with a focus on quality, performance, maintenance and completeness.
+
+In order to ensure that Fluent is ready for engineers to work with, the initial
+migrations are performed manually with a lot of oversight from the involved
+stakeholders.
+
+In this initial phase, `Firefox Preferences`_ is being migrated as the first target
+and as a result, the first bindings to be stabilized are for chrome-privileged
+XUL context.
+
+From there we plan to focus on two areas:
+
+ - `Unprivileged Contexts`_
+ - `System Add-ons`_
+
+The end goal is replacing all uses of DTD and StringBundle within Firefox's codebase.
+
+If you want to use Fluent and your code involves one of the areas currently unsupported,
+we'd like to work with you on getting Fluent ready for your code.
+
+
+Getting a Review
+----------------
+
+If you end up working on any patch which touches FTL files, we have a temporary
+hook in place that will reject your patch unless you get an r+ from one of the following
+L10n Drivers:
+
+  - Francesco Lodolo (:flod)
+  - Zibi Braniecki (:gandalf)
+  - Axel Hecht (:pike)
+  - Stas Malolepszy (:stas)
+
+
+Major Benefits
+==============
+
+Not only was the previous system designed over 20 years ago using file formats
+never intended for localization, but also the Web stack which Fluent ties into has
+completely changed over the same period, and the domain of internationalization
+got a powerful foundation in the form of `Unicode`_, `CLDR`_ and `ICU`_ which Fluent tightly
+`interoperates with`__.
+
+__ https://github.com/projectfluent/fluent/wiki/Fluent-and-Standards
+
+While it is beyond the scope of this document to cover all the benefits of Fluent in detail,
+below is an attempt to select some most observable changes for each group of consumers.
+
+
+Developers
+----------
+
+ - Support for XUL, XHTML, HTML, Web Components, React, JS, Python and Rust
+ - Strings are available in a single, unified localization context available for both DOM and runtime code
+ - Full internationalization (i18n) support: date and time formatting, number formatting, plurals, genders etc.
+ - Strong focus on `declarative API via DOM attributes`__
+ - Extensible with custom formatters, Mozilla-specific APIs etc.
+ - `Separation of concerns`__: localization details, and the added complexity of some languages, don't leak onto the source code and are no concern for developers
+ - Compound messages link a single translation unit to a single UI element
+ - `DOM Overlays`__ allow for localization of DOM fragments
+ - Simplified build system model
+ - No need for pre-processing instructions
+
+__ https://github.com/projectfluent/fluent/wiki/Get-Started
+__ https://github.com/projectfluent/fluent/wiki/Design-Principles
+__ https://github.com/projectfluent/fluent.js/wiki/DOM-Overlays
+
+
+Product Quality
+------------------
+
+ - A robust, multilevel, `error fallback system`__ prevents XML errors and runtime errors
+ - Simplified l10n API reduces the amount of l10n specific code and resulting bugs
+ - Runtime localization allows for dynamic language changes and updates over-the-air
+ - DOM Overlays increase localization security
+
+Many other smaller improvements will be noticed by the users of the system over time
+and, with the new foundation, the Fluent team is `currently working`__ on multiple highly
+requested features which will further improve the experience of developing
+localizable UIs.
+
+__ https://github.com/projectfluent/fluent/wiki/Error-Handling
+__ https://github.com/projectfluent/fluent/wiki/Roadmap
+
+
+Fluent Translation List - FTL
+=============================
+
+Fluent introduces a new localization format designed specifically for easy readability
+and localization features offered by the system.
+
+At first glance the format resembles `.properties` file. It may look like this:
+
+.. code-block:: properties
+
+  home-page-header = Home Page
+
+  # The label of a button opening a new tab
+  new-tab-open = Open New Tab
+
+But the FTL file format is significantly more powerful and the additional features
+quickly add up. In order to familiarize yourself with the basic features,
+consider reading through the `Fluent Syntax Guide`_ to understand
+a more complex example like:
+
+.. code-block:: properties
+
+  ### These messages correspond to security and privacy user interface.
+  ###
+  ### Please, choose simple and non-threatening language when localizing
+  ### to help user feel in control when interacting with the UI.
+
+  ## General Section
+
+  -brand-short-name = Firefox
+      .gender = masculine
+  
+  pref-pane =
+      .title =
+          { PLATFORM() ->
+              [windows] Options
+             *[other] Preferences
+          }
+      .accesskey = C
+  
+  # Variables:
+  #   $tabCount (Number) - number of container tabs to be closed
+  containers-disable-alert-ok-button =
+      { $tabCount ->
+          [one] Close { $tabCount } Container Tab
+         *[other] Close { $tabCount } Container Tabs
+      }
+  
+  update-application-info =
+      You are using { -brand-short-name } Version: { $version }.
+      <span>Please, read the <a>privacy policy</a>.</span>
+
+The above, of course, is a particular selection of complex strings intended to exemplify
+the new features and concepts introduced by Fluent.
+
+In order to ensure the quality of the output, a lot of new checks and tooling
+has been added to the build system.
+`Pontoon`_, the main localization tool used to translate Firefox, has been rebuilding
+its user experience to support localizers in their work.
+
+
+Social Contract
+===============
+
+Fluent uses the concept of a `social contract` between developer and localizers.
+This contract is established by the selection of a unique identifier, called :js:`l10n-id`,
+which carries a promise of being used in a particular place to carry a particular meaning.
+
+The use of unique identifiers is not new for Firefox engineers, but it is important
+to recognize that Fluent formalizes this relationship.
+
+.. important::
+
+  An important part of the contract is that the developer commits to treat the
+  localization output as `opaque`. That means that no concatenations, replacements
+  or splitting should happen after the translation is completed to generate the
+  desired output.
+
+In return, localizers enter the social contract by promising to provide an accurate
+and clean translation of the messages that match the request.
+
+In previous localization systems, developers were responsible for differentiating
+string variant based on a platform via pre-processing instructions, or
+selecting which strings should be formatted using `PluralForms.jsm`.
+
+In Fluent, the developer is not to be bothered with inner logic and complexity that the
+localization will use to construct the response. Whether `declensions`__ or other
+variant selection techniques are used is up to a localizer and their particular translation.
+From the developer perspective, Fluent returns a final string to be presented to
+the user, with no l10n logic required in the running code.
+
+__ https://en.wikipedia.org/wiki/Declension
+
+
+Markup Localization
+===================
+
+Fluent fully replaces the use of `DTD`_ in localization.
+
+To localize an element in Fluent, the developer adds a new message to
+an FTL file and then has to associate an :js:`l10n-id` with the element
+by defining a :js:`data-l10n-id` attribute:
+
+.. code-block:: html
+
+  <h1 data-l10n-id="home-page-header" />
+
+  <button data-l10n-id="pref-pane" />
+
+Fluent will take care of the rest, populating the element with the message value
+in its content and all localizable attributes if defined.
+
+The difference compared to the use of DTD is that the developer provides only a single
+message to localize the whole element, rather than a separate entity for
+the value and each of the attributes.
+
+The other change is that the developer can localize a whole fragment of DOM:
+
+.. code-block:: html
+
+  <p data-l10n-id="update-application-info" data-l10n-args="{'version': '60.0'}">
+    <span class="bold">
+      <a href="http://www.mozilla.org/privacy" />
+    </span>
+  </p>
+
+.. code-block:: properties
+
+  -brand-short-name = Firefox
+  update-application-info =
+      You are using { -brand-short-name } Version: { $version }.
+      <span>Please, read the <a>privacy policy</a>.</span>
+
+
+Fluent will overlay the translation onto the source fragment preserving attributes like
+:code:`class` and :code:`href` from the source and adding translations for the elements
+inside. The end result will look like this:
+
+.. code-block:: html
+
+  <p data-l10n-id="update-application-info" data-l10n-args="{'version': '60.0'}">
+    You are using Firefox Version: 60.0.
+    <span class="bold">
+      Please, read the <a href="http://www.mozilla.org/privacy">privacy policy</a>.
+    </span>
+  </p>
+
+
+This operation is sanitized, and Fluent takes care of selecting which elements and
+attributes can be safely provided by the localization.
+The list of allowed elements and attributes is `maintained by the W3C`__, and if
+the developer needs to allow for localization of additional attributes, they can
+whitelist them using :code:`data-l10n-attrs` list:
+
+.. code-block:: html
+
+  <label data-l10n-id="search-input" data-l10n-attrs="style" />
+
+The above example adds an attribute :code:`style` to be allowed on this
+particular :code:`label` element.
+
+
+External Arguments
+------------------
+
+Notice in the previous example the attribute :code:`data-l10n-args`, which is
+a JSON object storing variables exposed by the developer to the localizer.
+
+This is the main channel for the developer to provide additional variables
+to be used in the localization.
+
+It is very rare that the arguments are needed for localizations which previously
+used DTD, because such variables will usually have to be computed from the runtime code,
+but it is worth understanding that when the :code:`l10n-args` are set in
+the runtime code, they are in fact encoded via JSON and stored together with
+:code:`l10n-id` as an attribute on the element.
+
+__ https://www.w3.org/TR/2011/WD-html5-20110525/text-level-semantics.html
+
+
+Runtime Localization
+====================
+
+Fluent fully replaces the use of `StringBundle`_ in localization.
+
+In almost every case the JS runtime code will operate on a particular document, either
+XUL, XHTML or HTML.
+
+If the document has its markup already localized, then Fluent exposes a new
+attribute on the :js:`document` element - :js:`document.l10n`.
+
+This property is an object of type :js:`DOMLocalization` which maintains the main
+localization context for this document and exposes it to runtime code as well.
+
+With a focus on `declarative localization`__, the primary method of localization is
+to alter the localization attributes in the DOM. Fluent provides a method to facilitate this:
+
+.. code-block:: javascript
+
+  document.l10n.setAttributes(element, "new-panel-header");
+
+This will set the :code:`data-l10n-id` on the element and translate it before the next
+animation frame.
+
+The reason to use this API over manually setting the attribute is that it also
+facilitates encoding l10n arguments as JSON:
+
+.. code-block:: javascript
+
+  document.l10n.setAttributes(element "containers-disable-alert-ok-button", {
+    tabCount: 5
+  }
+
+__ https://github.com/projectfluent/fluent/wiki/Good-Practices-for-Developers
+
+
+Non-Markup Localization
+-----------------------
+
+In rare cases, when the runtime code needs to retrieve the translation and not
+apply it onto the DOM, Fluent provides an API to retrieve it:
+
+.. code-block:: javascript
+
+  let [ msg ] = await document.l10n.formatValues([
+    ["remove-containers-description"]
+  ]);
+
+  alert(msg);
+
+This model is heavily discouraged and should be used only in cases where the
+DOM annotation is not possible.
+
+.. note::
+
+  This API is currently only available as asynchronous. In case of Firefox,
+  the only non-DOM localizable calls are used where the output goes to
+  a third-party like Bluetooth, Notifications etc.
+  All those cases should already be asynchronous.
+
+
+Internationalization
+====================
+
+The majority of internationalization issues are implicitly handled by Fluent without
+any additional requirement. Full Unicode support, `bidirectionality`__, and
+correct number formatting work without any action required from either
+developer or localizer.
+
+__ https://github.com/projectfluent/fluent/wiki/BiDi-in-Fluent
+
+.. code-block:: javascript
+
+  document.l10n.setAttributes(element, "welcome-message", {
+    userName: "اليسع",
+    count: 5
+  });
+
+A message like this localized to American English will correctly wrap the user
+name in directionality marks allowing the layout engine to determine how to
+display the bidirectional text.
+
+On the other hand, the same message localized to Arabic will use the Eastern Arabic
+numeral for number "5".
+
+
+Plural Rules
+------------
+
+The most common localization feature is the ability to provide different variants
+of the same string depending on plural categories.
+
+Fluent replaces the use of the proprietary :code:`PluralForms.jsm` with a Unicode CLDR
+standard called `Plural Rules`_.
+
+In order to allow localizers to use it, all the developer has to do is to pass
+an external argument number:
+
+.. code-block:: javascript
+
+  document.l10n.setAttributes(element, "unread-warning", { unreadCount: 5 });
+
+Localizers can use the argument to build a multi variant message if their
+language requires that:
+
+.. code-block:: properties
+
+  unread-warning =
+      { $unreadCount ->
+          [one] You have { $unreadCount } unread message
+         *[other] You have { $unreadCount } unread messages
+      }
+
+Fluent guesses that since the variant selection is performed based on a number,
+its `plural category`__ should be retrieved.
+
+If the given translation doesn't need pluralization for the string (for example
+Japanese often will not), the localizer can replace it with:
+
+.. code-block:: properties
+
+  unread-warning = You have { $unreadCount } unread messages
+
+and the message will preserve the social contract.
+
+One additional feature is that the localizer can further improve the message by
+specifying variants for particular values:
+
+.. code-block:: properties
+
+  unread-warning =
+      { $unreadCount ->
+          [0] You have no unread messages
+          [1] You have one unread message
+         *[other] You have { $unreadCount } unread messages
+      }
+
+The advantage here is that per-locale choices don't leak onto the source code
+and the developer is not affected.
+
+
+.. note::
+
+  There is an important distinction between a variant keyed on plural category
+  `one` and digit `1`. Although in English the two are synonymous, in other
+  languages category `one` may be used for other numbers.
+  For example in `Bosnian`__, category `one` is used for numbers like `1`, `21`, `31`
+  and so on, and also for fractional numbers like `0.1`.
+
+__ https://unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
+__ https://unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#bs
+
+Partial Arguments
+-----------------
+
+When it comes to formatting data, Fluent allows the developer to provide
+a set of parameters for the formatter, and the localizer can fine tune some of them.
+This technique is called `partial arguments`__.
+
+For example, when formatting a date, the developer can just pass a JS :js:`Date` object,
+but its default formatting will be pretty expressive. In most cases, the developer
+may want to use some of the :js:`Intl.DateTimeFormat` options to select the default
+representation of the date in string:
+
+.. code-block:: javascript
+
+  document.l10n.setAttributes(element, "welcome-message", {
+  startDate: FluentDateTime(new Date(), {
+      year: "numeric",
+      month: "long",
+      day: "numeric"
+    })
+  });
+
+.. code-block:: properties
+
+  welcome-message = Your session will start date: { $startDate }
+
+In most cases, that will be enough and the date would get formatted in the current
+Firefox as `February 28, 2018`.
+
+But if in some other locale the string would get too long, the localizer can fine
+tune the options as well:
+
+.. code-block:: properties
+
+  welcome-message = Początek Twojej sesji: { DATETIME($startDate, month="short") }
+
+This will adjust the length of the month token in the message to short and get formatted
+in Polish as `28 lut 2018`.
+
+At the moment Fluent supports two formatters that match JS Intl API counterparts:
+
+ * **NUMBER**: `Intl.NumberFormat`__
+ * **DATETIME**: `Intl.DateTimeFormat`__
+
+With time more formatters will be added.
+
+__ http://projectfluent.org/fluent/guide/functions.html#partial-arguments
+__ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
+__ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
+
+Registering New L10n Files
+==========================
+
+In the previous system, a new localization file had to be registered in order to
+add it in the `jar.mn` file for packaging.
+
+Fluent uses a wildcard statement packaging all localization resources into
+their component's `/localization/` directory.
+
+That means that, if a new file is added to a component of Firefox already
+covered by Fluent like `browser`, it's enough to add the new file to the
+repository in a path like `browser/locales/en-US/browser/component/file.ftl` and
+the toolchain will package it into `browser/localization/browser/component/file.ftl`.
+
+At runtime Firefox uses a special registry for all localization data. It will
+register the browser's `/localization/` directory and make all files inside it
+available to be references.
+
+To make the document localized using Fluent, all the developer has to do is add
+a single polyfill for the Fluent API to the source and list the resources
+that will be used:
+
+.. code-block:: html
+
+  <link rel="localization" href="branding/brand.ftl"/>
+  <link rel="localization" href="browser/preferences/preferences.ftl"/>
+  <script src="chrome://global/content/l10n.js"></script>
+
+For performance reasons the :html:`<link/>` elements have to be specified above the
+:html:`<script/>` and the :html:`<script/>` itself has to be synchronous in order to ensure
+that the localization happens before first paint.
+
+This allows Fluent to trigger asynchronous resource loading early enough to
+perform the initial DOM translation before the initial layout.
+
+The URI provided to the :html:`<link/>` element are relative paths within the localization
+system.
+
+Notice that only the registration of the script is synchronous. All the I/O and
+translation happen asynchronously.
+
+
+Custom Contexts
+===============
+
+The above method creates a single localization context per document.
+In almost all scenarios that's sufficient.
+
+In rare edge cases where the developer needs to fetch additional resources, or
+the same resources in another language, it is possible to create additional
+contexts manually using `Localization` class:
+
+.. code-block:: javascript
+
+	const { Localization } =
+		ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
+
+
+	const myL10n = new Localization([
+		"branding/brand.ftl",
+		"browser/preferences/preferences.ftl"
+	]);
+
+
+	let [isDefaultMsg, isNotDefaultMsg] =
+		myL10n.formatValues(["is-default", "is-not-default"]);
+
+
+.. admonition:: Example
+
+  An example of a use case is the Preferences UI in Firefox which uses the
+  main context to localize the UI but also to build a search index.
+
+  It is common to build such search index both in a current langauge and additionally
+  in English since a lot of documentation and online help exists in that language.
+
+  A developer may create manually a new context with the same resources as the main one
+  uses, but hardcode it to `en-US` and then build the search index using both contexts.
+
+Designing Localizable APIs
+==========================
+
+When designing localizable APIs, the most important rule is to resolve localization as
+late as possible. That means that instead of resolving strings somewhere deep in the
+codebase and then passing them on or even caching, it is highly recommended to pass
+around :code:`l10n-id` or :code:`[l10n-id, l10n-args]` pairs until the top-most code
+resolves them or applies them onto the DOM element.
+
+
+Testing
+=======
+
+When writing tests that involve both I18n and L10n, the general rule is that
+result strings are opaque. That means that the developer should not assume any particular
+value and should never test against it.
+
+In case of raw i18n the :js:`resolvedOptions` method on all :js:`Intl.*` formatters
+makes it relatively easy. In case of localization, the recommended way is to test that
+the code sets the right :code:`l10n-id`/:code:`l10n-args` attributes like this:
+
+.. code-block:: javascript
+  
+  testedFunction();
+  
+  const l10nAttrs = document.l10n.getAttributes(element);
+  
+  deepEquals(l10nAttrs, {
+		id: "my-expected-id",
+		args: {
+			unreadCount: 5
+		}
+  });
+
+If the code really has to test for particular values in the localized UI, it is
+always better to scan for a variable:
+
+.. code-block:: javascript
+
+  testedFunction();
+  
+  equals(element.textContent.contains("John"));
+
+.. important::
+
+  Testing against whole values is brittle and will break when we insert Unicode
+  bidirectionality marks into the result string or adapt the output in other ways.
+
+
+Inner Structure of Fluent
+=========================
+
+The inner structure of Fluent in Gecko is out of scope of this tutorial, but
+since the class and file names may show up during debugging or profiling,
+below is a list of major components, each with a corresponding file in `/intl/l10n`
+modules in Gecko.
+
+
+MessageContext
+--------------
+
+MessageContext is the lowest level API. It's fully synchronous, contains a parser for the
+FTL file format and a resolver for the logic. It is not meant to be used by
+consumers directly.
+
+In the future we intend to offer this layer for standardization and it may become
+part of the :js:`mozIntl.*` or even :js:`Intl.*` API sets.
+
+That part of the codebase is also the first that we'll be looking to port to Rust.
+
+
+Localization
+------------
+
+Localization is a higher level API which uses :js:`MessageContext` internally but
+provides a full layer of compound message formatting and robust error fall-backing.
+
+It is intended for use in runtime code and contains all fundamental localization
+methods.
+
+
+DOMLocalization
+---------------
+
+DOMLocalization extends :js:`Localization` with functionality to operate on HTML, XUL
+and the DOM directly including DOM Overlays and Mutation Observers.
+
+
+l10n.js
+-------
+
+l10n.js is a small runtime code which fetches the :html:`<link>` elements specified
+in the document and initializes the main :js:`DOMLocalization` context
+on :js:`document.l10n`.
+
+
+L10nRegistry
+------------
+
+L10nRegistry is our resource management service. It replaces :js:`ChromeRegistry` and
+maintains the state of resources packaged into the build and language packs,
+providing an asynchronous iterator of :js:`MessageContext` objects for a given locale set
+and resources that the :js:`Localization` class uses.
+
+
+.. _Fluent: http://projectfluent.org/
+.. _DTD: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Tutorial/Localization
+.. _StringBundle: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Tutorial/Property_Files
+.. _Firefox Preferences: https://bugzilla.mozilla.org/show_bug.cgi?id=1415730
+.. _Unprivileged Contexts: https://bugzilla.mozilla.org/show_bug.cgi?id=1407418
+.. _System Add-ons: https://bugzilla.mozilla.org/show_bug.cgi?id=1425104
+.. _CLDR: http://cldr.unicode.org/
+.. _ICU: http://site.icu-project.org/
+.. _Unicode: https://www.unicode.org/
+.. _Fluent Syntax Guide: http://projectfluent.org/fluent/guide/
+.. _Pontoon: https://pontoon.mozilla.org/
+.. _Plural Rules: http://cldr.unicode.org/index/cldr-spec/plural-rules
new file mode 100644
--- /dev/null
+++ b/intl/l10n/docs/index.rst
@@ -0,0 +1,23 @@
+======
+Fluent
+======
+
+`Fluent`_ is a new localization system, developed by Mozilla, which aims to replace
+all existing localization models currently used at Mozilla.
+
+In case of Firefox it directly superseeds DTD and StringBundle systems providing
+a large number of new features and improvements over them both, for developers
+and localizers.
+
+.. toctree::
+   :maxdepth: 2
+
+   fluent_tutorial
+
+Other resources:
+
+ * `Fluent Syntax Guide <http://projectfluent.org/fluent/guide/>`_
+ * `Fluent Wiki <https://github.com/projectfluent/fluent/wiki>`_
+ * `Fluent.js Wiki <https://github.com/projectfluent/fluent.js/wiki>`_
+
+.. _Fluent: http://projectfluent.org/
--- a/intl/l10n/moz.build
+++ b/intl/l10n/moz.build
@@ -12,9 +12,11 @@ EXTRA_JS_MODULES += [
 ]
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
 
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
 JAR_MANIFESTS += ['jar.mn']
 
+SPHINX_TREES['l10n'] = 'docs'
+
 FINAL_LIBRARY = 'xul'
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -210,20 +210,20 @@ dependencies = [
 
 [[package]]
 name = "blurdroid"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "blurmac"
-version = "0.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+version = "0.1.0"
+source = "git+https://github.com/servo/devices#1069d67cbacb28b77a3d5dd7f211171c05f32c62"
+dependencies = [
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "objc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "blurmock"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -688,20 +688,20 @@ name = "deny_public_fields_tests"
 version = "0.0.1"
 dependencies = [
  "deny_public_fields 0.0.1",
 ]
 
 [[package]]
 name = "device"
 version = "0.0.1"
-source = "git+https://github.com/servo/devices#c3b012b0ac4fbc47d1ebc9bd3fc308f599be4ee4"
+source = "git+https://github.com/servo/devices#1069d67cbacb28b77a3d5dd7f211171c05f32c62"
 dependencies = [
  "blurdroid 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "blurmac 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "blurmac 0.1.0 (git+https://github.com/servo/devices)",
  "blurmock 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "blurz 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "devtools"
 version = "0.0.1"
 dependencies = [
@@ -3655,17 +3655,17 @@ dependencies = [
 "checksum binary-space-partition 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88ceb0d16c4fd0e42876e298d7d3ce3780dd9ebdcbe4199816a32c77e08597ff"
 "checksum bincode 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9a6301db0b49fb63551bc15b5ae348147101cdf323242b93ec7546d5002ff1af"
 "checksum bindgen 0.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1657d607dd7a8e10b3181149f60f3b27ea0eac81058c09a1c791b8f6ead91f19"
 "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
 "checksum bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5cde24d1b2e2216a726368b2363a273739c91f4e3eb4e0dd12d672d396ad989"
 "checksum bitreader 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "80b13e2ab064ff3aa0bdbf1eff533f9822dc37899821f5f98c67f263eab51707"
 "checksum block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
 "checksum blurdroid 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d7daba519d29beebfc7d302795af88a16b43f431b9b268586926ac61cc655a68"
-"checksum blurmac 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72af3718b3f652fb2026bf9d9dd5f92332cd287884283c343f03fff16cbb0172"
+"checksum blurmac 0.1.0 (git+https://github.com/servo/devices)" = "<none>"
 "checksum blurmock 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "68dd72da3a3bb40f3d3bdd366c4cf8e2b1d208c366304f382c80cef8126ca8da"
 "checksum blurz 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e73bda0f4c71c63a047351070097f3f507e6718e86b9ee525173371ef7b94b73"
 "checksum brotli 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fe87b40996b84fdc56e57c165d93079f4b50cb806598118e692ddfaa3d3c57c0"
 "checksum brotli-decompressor 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "313f4b6cc0b365d6b88eda5aa40175ee34ac6efa9a79e0b3b8202eca90247ba8"
 "checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23"
 "checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27"
 "checksum caseless 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3261638034d9db4f94a666ebb16494846341ae5a8456c05c1616d66980cf39a"
 "checksum cc 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2c674f0870e3dbd4105184ea035acb1c32c8ae69939c9e228d2b11bbfe29efad"
--- a/testing/web-platform/moz.build
+++ b/testing/web-platform/moz.build
@@ -58,16 +58,19 @@ with Files("mozilla/README"):
     BUG_COMPONENT = ("Testing", "web-platform-tests")
 
 with Files("mozilla/meta/**"):
     BUG_COMPONENT = ("Testing", "web-platform-tests")
 
 with Files("mozilla/tests/dom/**"):
     BUG_COMPONENT = ("Core", "DOM")
 
+with Files("mozilla/tests/editor/**"):
+    BUG_COMPONENT = ("Core", "Editor")
+
 with Files("mozilla/tests/fetch/**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 with Files("mozilla/tests/focus/**"):
     BUG_COMPONENT = ("Core", "Editor")
 
 with Files("mozilla/tests/html/**"):
     BUG_COMPONENT = ("Core", "DOM")
--- a/testing/web-platform/mozilla/meta/MANIFEST.json
+++ b/testing/web-platform/mozilla/meta/MANIFEST.json
@@ -464,16 +464,22 @@
     ]
    ],
    "dom/throttling/throttling-ws.window.js": [
     [
      "/_mozilla/dom/throttling/throttling-ws.window.html",
      {}
     ]
    ],
+   "editor/initial_selection_on_focus.html": [
+    [
+     "/_mozilla/editor/initial_selection_on_focus.html",
+     {}
+    ]
+   ],
    "fetch/api/redirect/redirect-referrer.https.html": [
     [
      "/_mozilla/fetch/api/redirect/redirect-referrer.https.html",
      {}
     ]
    ],
    "focus/Range_collapse.html": [
     [
@@ -1017,16 +1023,20 @@
   "dom/throttling/throttling-webrtc.window.js": [
    "02e6acec2ff275e0e935cb6d903d348f98d5d437",
    "testharness"
   ],
   "dom/throttling/throttling-ws.window.js": [
    "67a981ba2a4d08b684947ed42aba6648dcd262b4",
    "testharness"
   ],
+  "editor/initial_selection_on_focus.html": [
+   "da3d0ff5305658e18f51a4f19b34927fb2691e60",
+   "testharness"
+  ],
   "fetch/api/redirect/redirect-referrer-mixed-content.js": [
    "f9d7ec9cf9fa8c847e45664b05482e3f8c191385",
    "support"
   ],
   "fetch/api/redirect/redirect-referrer.https.html": [
    "99cbd16b78771f35e075e4012d8dbc5dce3209c0",
    "testharness"
   ],
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/editor/initial_selection_on_focus.html
@@ -0,0 +1,360 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>initial selection on focus of contenteditable</title>
+<!-- if you move this file into cross-browser's directly, you should include
+     editing/include/tests.js for using addBrackets() and get rid of it from
+     this file. -->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<p id="staticText">out of editor</p>
+<div id="editor" contenteditable style="min-height: 1em;"></div>
+<script>
+"use strict";
+
+(function() {
+  var tests = [
+    { description: "empty editor should set focus to start of it",
+      content: "{}",
+    },
+    { description: "editor should set selection to start of the text node",
+      content: "[]abc",
+    },
+    { description: "editor should set selection to before the <br> node",
+      content: "{}<br>",
+    },
+    { description: "editor should set selection to before the first <br> node",
+      content: "{}<br><br>",
+    },
+
+    { description: "editor should set selection to start of the text node in the <p> node",
+      content: "<p>[]abc</p>",
+    },
+    { description: "editor should set selection to before the <br> node in the <p> node",
+      content: "<p>{}<br></p>",
+    },
+    { description: "editor should set selection to before the first <br> node in the <p> node",
+      content: "<p>{}<br><br></p>",
+    },
+
+    { description: "editor should set selection to start of the text node in the <span> node",
+      content: "<span>[]abc</span>",
+    },
+    { description: "editor should set selection to before the <br> node in the <span> node",
+      content: "<span>{}<br></span>",
+    },
+    { description: "editor should set selection to before the first <br> node in the <span> node",
+      content: "<span>{}<br><br></span>",
+    },
+
+    { description: "editor should set selection to before the empty <span> node",
+      content: "{}<span></span>",
+    },
+    { description: "editor should set selection to before the empty <b> node",
+      content: "{}<b></b>",
+    },
+    { description: "editor should set selection to before the empty <i> node",
+      content: "{}<i></i>",
+    },
+    { description: "editor should set selection to before the empty <u> node",
+      content: "{}<u></u>",
+    },
+    { description: "editor should set selection to before the empty <s> node",
+      content: "{}<s></s>",
+    },
+    { description: "editor should set selection to before the empty <code> node",
+      content: "{}<code></code>",
+    },
+    { description: "editor should set selection to before the empty <a> node",
+      content: "{}<a href=\"foo.html\"></a>",
+    },
+    { description: "editor should set selection to before the empty <foobar> node",
+      content: "{}<foobar></foobar>",
+    },
+    { description: "editor should set selection to before the <input> node",
+      content: "{}<input>",
+    },
+    { description: "editor should set selection to before the <img> node",
+      content: "{}<img alt=\"foo\">",
+    },
+
+    { description: "editor should set selection to start of the text node in the second <span> node",
+      content: "<span></span><span>[]abc</span>",
+    },
+    { description: "editor should set selection to before the <br> node in the second <span> node",
+      content: "<span></span><span>{}<br></span>",
+    },
+    { description: "editor should set selection to start of the text node in the first <span> node #1",
+      content: "<span>[]abc</span><span>abc</span>",
+    },
+    { description: "editor should set selection to start of the text node in the first <span> node #2",
+      content: "<span>[]abc</span><span><br></span>",
+    },
+    { description: "editor should set selection to before the <br> node in the first <span> node #1",
+      content: "<span>{}<br></span><span><br></span>",
+    },
+    { description: "editor should set selection to before the <br> node in the first <span> node #2",
+      content: "<span>{}<br></span><span>abc</span>",
+    },
+
+    { description: "editor should set selection to start of the text node in the second <span> node since the text node in the first <span> node is only whitespaces",
+      content: "<span> </span><span>[]abc</span>",
+    },
+    { description: "editor should set selection to before the <br> node in the second <span> node since the text node in the first <span> node is only whitespaces",
+      content: "<span> </span><span>{}<br></span>",
+    },
+    { description: "editor should set selection to start of the text node in the second <span> node even if there is a whitespace only text node before the first <span> node",
+      content: " <span></span><span>[]abc</span>",
+    },
+    { description: "editor should set selection to before the <br> node in the second <span> node even if there is a whitespace only text node before the first <span> node",
+      content: " <span></span><span>{}<br></span>",
+    },
+
+    { description: "editor should set selection to start of the text node in the second <p> node",
+      content: "<p></p><p>[]abc</p>",
+    },
+    { description: "editor should set selection to before the <br> node in the second <p> node",
+      content: "<p></p><p>{}<br></p>",
+    },
+    { description: "editor should set selection to start of the text node in the first <p> node #1",
+      content: "<p>[]abc</p><p>abc</p>",
+    },
+    { description: "editor should set selection to start of the text node in the first <p> node #2",
+      content: "<p>[]abc</p><p><br></p>",
+    },
+    { description: "editor should set selection to before the <br> node in the first <p> node #1",
+      content: "<p>{}<br></p><p><br></p>",
+    },
+    { description: "editor should set selection to before the <br> node in the first <p> node #2",
+      content: "<p>{}<br></p><p>abc</p>",
+    },
+
+    { description: "editor should set selection to start of the text node in the second <p> node since the text node in the first <p> node is only whitespaces",
+      content: "<p> </p><p>[]abc</p>",
+    },
+    { description: "editor should set selection to before the <br> node in the second <p> node since the text node in the first <p> node is only whitespaces",
+      content: "<p> </p><p>{}<br></p>",
+    },
+    { description: "editor should set selection to start of the text node in the second <p> node even if there is a whitespace only text node before the first <p> node",
+      content: " <p></p><p>[]abc</p>",
+    },
+    { description: "editor should set selection to before the <br> node in the second <p> node even if there is a whitespace only text node before the first <p> node",
+      content: " <p></p><p>{}<br></p>",
+    },
+
+    { description: "editor should set selection to start of the text node in the <span> node in the second <p> node",
+      content: "<p><span></span></p><p><span>[]abc</span></p>",
+    },
+    { description: "editor should set selection to before the <br> node in the <span> node in the second <p> node",
+      content: "<p><span></span></p><p><span>{}<br></span></p>",
+    },
+    { description: "editor should set selection to start of the text node in the <span> node in the first <p> node #1",
+      content: "<p><span>[]abc</span></p><p><span>abc</span></p>",
+    },
+    { description: "editor should set selection to start of the text node in the <span> node in the first <p> node #2",
+      content: "<p><span>[]abc</span></p><p><span><br></span></p>",
+    },
+    { description: "editor should set selection to before the <br> node in the <span> node in the first <p> node #1",
+      content: "<p><span>{}<br></span></p><p><span><br></span></p>",
+    },
+    { description: "editor should set selection to before the <br> node in the <span> node in the first <p> node #2",
+      content: "<p><span>{}<br></span></p><p><span>abc</span></p>",
+    },
+
+    { description: "editor should set focus to before the non-editable <span> node",
+      content: "{}<span contenteditable=\"false\"></span>",
+    },
+    { description: "editor should set focus to before the non-editable <span> node even if it has a text node",
+      content: "{}<span contenteditable=\"false\">abc</span>",
+    },
+    { description: "editor should set focus to before the non-editable <span> node even if it has a <br> node",
+      content: "{}<span contenteditable=\"false\"><br></span>",
+    },
+
+    { description: "editor should set focus to before the non-editable empty <span> node followed by a text node",
+      content: "{}<span contenteditable=\"false\"></span><span>abc</span>",
+    },
+    { description: "editor should set focus to before the non-editable <span> node having a text node and followed by another text node",
+      content: "{}<span contenteditable=\"false\">abc</span><span>def</span>",
+    },
+    { description: "editor should set focus to before the non-editable <span> node having a <br> node and followed by a text node",
+      content: "{}<span contenteditable=\"false\"><br></span><span>abc</span>",
+    },
+    { description: "editor should set focus to before the non-editable empty <span> node followed by a <br> node",
+      content: "{}<span contenteditable=\"false\"></span><span><br></span>",
+    },
+    { description: "editor should set focus to before the non-editable <span> node having text node and followed by a <br> node",
+      content: "{}<span contenteditable=\"false\">abc</span><span><br></span>",
+    },
+    { description: "editor should set focus to before the non-editable <span> node having a <br> node and followed by another <br> node",
+      content: "{}<span contenteditable=\"false\"><br></span><span><br></span>",
+    },
+
+    { description: "editor should set focus to before the non-editable empty <p> node followed by a text node",
+      content: "{}<p contenteditable=\"false\"></p><p>abc</p>",
+    },
+    { description: "editor should set focus to before the non-editable <p> node having a text node and followed by another text node",
+      content: "{}<p contenteditable=\"false\">abc</p><p>def</p>",
+    },
+    { description: "editor should set focus to before the non-editable <p> node having a <br> node and followed by a text node",
+      content: "{}<p contenteditable=\"false\"><br></p><p>abc</p>",
+    },
+    { description: "editor should set focus to before the non-editable empty <p> node followed by a <br> node",
+      content: "{}<p contenteditable=\"false\"></p><p><br></p>",
+    },
+    { description: "editor should set focus to before the non-editable <p> node having text node and followed by a <br> node",
+      content: "{}<p contenteditable=\"false\">abc</p><p><br></p>",
+    },
+    { description: "editor should set focus to before the non-editable <p> node having a <br> node and followed by another <br> node",
+      content: "{}<p contenteditable=\"false\"><br></p><p><br></p>",
+    },
+
+    { description: "editor should set focus to start of it if there is non-editable node before first editable text node",
+      content: "{}<span></span><span contenteditable=\"false\"></span><span>abc</span>",
+    },
+    { description: "editor should set focus to start of it if there is non-editable node having a text node before first editable text node",
+      content: "{}<span></span><span contenteditable=\"false\">abc</span><span>def</span>",
+    },
+    { description: "editor should set focus to start of it if there is non-editable node having a <br> node before first editable text node",
+      content: "{}<span></span><span contenteditable=\"false\"><br></span><span>abc</span>",
+    },
+    { description: "editor should set focus to start of it if there is non-editable node before first editable <br> node",
+      content: "{}<span></span><span contenteditable=\"false\"></span><span><br></span>",
+    },
+    { description: "editor should set focus to start of it if there is non-editable node having a text node before first editable <br> node",
+      content: "{}<span></span><span contenteditable=\"false\">abc</span><span><br></span>",
+    },
+    { description: "editor should set focus to start of it if there is non-editable node having a <br> node before first editable <br> node",
+      content: "{}<span></span><span contenteditable=\"false\"><br></span><span><br></span>",
+    },
+
+    { description: "editor should set focus to the first editable text node in the first <span> node even if followed by a non-editable node",
+      content: "<span>[]abc</span><span contenteditable=\"false\"></span>",
+    },
+    { description: "editor should set focus to the first editable text node in the first <span> node even if followed by a non-editable node having another text node",
+      content: "<span>[]abc</span><span contenteditable=\"false\">def</span>",
+    },
+    { description: "editor should set focus to the first editable text node in the first <span> node even if followed by a non-editable node having a <br> node",
+      content: "<span>[]abc</span><span contenteditable=\"false\"><br></span>",
+    },
+    { description: "editor should set focus to the first editable <br> node in the first <span> node even if followed by a non-editable node",
+      content: "<span>{}<br></span><span contenteditable=\"false\"></span>",
+    },
+    { description: "editor should set focus to the first editable <br> node in the first <span> node even if followed by a non-editable node having a text node",
+      content: "<span>{}<br></span><span contenteditable=\"false\">abc</span>",
+    },
+    { description: "editor should set focus to the first editable <br> node in the first <span> node even if followed by a non-editable node having a <br> node",
+      content: "<span>{}<br></span><span contenteditable=\"false\"><br></span>",
+    },
+
+    { description: "editor should set focus to the first editable text node in the first <p> node even if followed by a non-editable node",
+      content: "<p>[]abc</p><p contenteditable=\"false\"></p>",
+    },
+    { description: "editor should set focus to the first editable text node in the first <p> node even if followed by a non-editable node having another text node",
+      content: "<p>[]abc</p><p contenteditable=\"false\">def</p>",
+    },
+    { description: "editor should set focus to the first editable text node in the first <p> node even if followed by a non-editable node having a <br> node",
+      content: "<p>[]abc</p><p contenteditable=\"false\"><br></p>",
+    },
+    { description: "editor should set focus to the first editable <br> node in the first <p> node even if followed by a non-editable node",
+      content: "<p>{}<br></p><p contenteditable=\"false\"></p>",
+    },
+    { description: "editor should set focus to the first editable <br> node in the first <p> node even if followed by a non-editable node having a text node",
+      content: "<p>{}<br></p><p contenteditable=\"false\">abc</p>",
+    },
+    { description: "editor should set focus to the first editable <br> node in the first <p> node even if followed by a non-editable node having a <br> node",
+      content: "<p>{}<br></p><p contenteditable=\"false\"><br></p>",
+    },
+  ];
+
+  // This function is copied from editing/include/tests.js
+  function addBrackets(range) {
+    // Handle the collapsed case specially, to avoid confusingly getting the
+    // markers backwards in some cases
+    if (range.startContainer.nodeType == Node.TEXT_NODE ||
+        range.startContainer.nodeType == Node.COMMENT_NODE) {
+      if (range.collapsed) {
+        range.startContainer.insertData(range.startOffset, "[]");
+      } else {
+        range.startContainer.insertData(range.startOffset, "[");
+      }
+    } else {
+      var marker = range.collapsed ? "{}" : "{";
+      if (range.startOffset != range.startContainer.childNodes.length &&
+          range.startContainer.childNodes[range.startOffset].nodeType == Node.TEXT_NODE) {
+        range.startContainer.childNodes[range.startOffset].insertData(0, marker);
+      } else if (range.startOffset != 0 &&
+                 range.startContainer.childNodes[range.startOffset - 1].nodeType == Node.TEXT_NODE) {
+        range.startContainer.childNodes[range.startOffset - 1].appendData(marker);
+      } else {
+        // Seems to serialize as I'd want even for tables . . . IE doesn't
+        // allow undefined to be passed as the second argument (it throws
+        // an exception), so we have to explicitly check the number of
+        // children and pass null.
+        range.startContainer.insertBefore(document.createTextNode(marker),
+          range.startContainer.childNodes.length == range.startOffset ?
+            null : range.startContainer.childNodes[range.startOffset]);
+      }
+    }
+    if (range.collapsed) {
+      return;
+    }
+    if (range.endContainer.nodeType == Node.TEXT_NODE ||
+        range.endContainer.nodeType == Node.COMMENT_NODE) {
+      range.endContainer.insertData(range.endOffset, "]");
+    } else {
+      if (range.endOffset != range.endContainer.childNodes.length &&
+          range.endContainer.childNodes[range.endOffset].nodeType == Node.TEXT_NODE) {
+        range.endContainer.childNodes[range.endOffset].insertData(0, "}");
+      } else if (range.endOffset != 0 &&
+                 range.endContainer.childNodes[range.endOffset - 1].nodeType == Node.TEXT_NODE) {
+        range.endContainer.childNodes[range.endOffset - 1].appendData("}");
+      } else {
+        range.endContainer.insertBefore(document.createTextNode("}"),
+          range.endContainer.childNodes.length == range.endOffset ?
+            null : range.endContainer.childNodes[range.endOffset]);
+      }
+    }
+  }
+
+  var editor = document.getElementById("editor");
+  var textInP = document.getElementById("staticText").firstChild;
+  var selection = document.getSelection();
+  for (var i = 0; i < tests.length; i++) {
+    test(function() {
+      // Select outside the editor.
+      editor.blur();
+      selection.collapse(textInP);
+
+      // Initialize the editor content.
+      editor.innerHTML = tests[i].content.replace(/[{}\[\]]/g, "");
+
+      // Make the editor focused.
+      editor.focus();
+
+      assert_equals(selection.rangeCount, 1);
+      if (selection.rangeCount) {
+        addBrackets(selection.getRangeAt(0));
+        assert_equals(editor.innerHTML, tests[i].content);
+      }
+    }, tests[i].description);
+  }
+
+  test(function() {
+    // Check if selection is initialized after temporarily blurred.
+    editor.innerHTML = "<p>abc</p><p>def</p>";
+    editor.focus();
+    // Move selection to the second paragraph.
+    selection.collapse(editor.firstChild.nextSibling.firstChild);
+    // Reset focus.
+    editor.blur();
+    editor.focus();
+    // Then, selection should still be in the second paragraph.
+    assert_equals(selection.rangeCount, 1);
+    if (selection.rangeCount) {
+      addBrackets(selection.getRangeAt(0));
+      assert_equals(editor.innerHTML, "<p>abc</p><p>[]def</p>");
+    }
+  }, "editor shouldn't reset selection when it gets focus again");
+})();
+</script>