Merge mozilla-central to inbound. a=merge CLOSED TREE
authorCsoregi Natalia <ncsoregi@mozilla.com>
Thu, 07 Jun 2018 01:02:21 +0300
changeset 475907 5e2b8c50e708e20fb90ea6486eb254b4f51479de
parent 475906 13251c6d1ee246f50771d4c2c73ad4d7177d1448 (current diff)
parent 475847 1ab062fd31db7d4367a479fedb350dc6fcee7a3f (diff)
child 475908 99f438f07d891447f42a7710f5e63bb4763b8c00
push id9374
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:43:20 +0000
treeherdermozilla-beta@160e085dfb0b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
gfx/webrender/res/cs_clip_border.glsl
gfx/webrender/res/ps_border_corner.glsl
gfx/webrender/res/ps_border_edge.glsl
gfx/webrender/res/shared_border.glsl
mobile/android/app/src/main/res/drawable-nodpi/firstrun_tabqueue_off.png
mobile/android/app/src/main/res/drawable-nodpi/firstrun_tabqueue_on.webp
mobile/android/base/java/org/mozilla/gecko/firstrun/TabQueuePanel.java
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -45,17 +45,17 @@
   --toolbar-bgimage: none;
 
   --toolbox-border-bottom-color: rgba(0,0,0,.3);
 
   --panel-separator-color: hsla(210,4%,10%,.14);
 }
 
 :root[lwt-popup-brighttext] {
-  --panel-separator-color: rgba(249,249,250,.2);
+  --panel-separator-color: rgba(249,249,250,.1);
 
   --arrowpanel-dimmed: rgba(249,249,250,.1);
   --arrowpanel-dimmed-further: rgba(249,249,250,.15);
   --arrowpanel-dimmed-even-further: rgba(249,249,250,.2);
 }
 
 #menubar-items {
   -moz-box-orient: vertical; /* for flex hack */
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -36,17 +36,17 @@
 :root:-moz-lwtheme {
   --toolbar-bgcolor: rgba(255,255,255,.4);
   --toolbar-bgimage: none;
 
   --toolbox-border-bottom-color: rgba(0,0,0,.3);
 }
 
 :root[lwt-popup-brighttext] {
-  --panel-separator-color: rgba(249,249,250,.2);
+  --panel-separator-color: rgba(249,249,250,.1);
 
   --arrowpanel-dimmed: rgba(249,249,250,.1);
   --arrowpanel-dimmed-further: rgba(249,249,250,.15);
   --arrowpanel-dimmed-even-further: rgba(249,249,250,.2);
 }
 
 #navigator-toolbox {
   --tabs-border-color: rgba(0,0,0,.3);
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -67,17 +67,17 @@
   --toolbar-bgimage: none;
 
   --toolbox-border-bottom-color: rgba(0,0,0,.3);
 
   --panel-separator-color: hsla(210,4%,10%,.14);
 }
 
 :root[lwt-popup-brighttext] {
-  --panel-separator-color: rgba(249,249,250,.2);
+  --panel-separator-color: rgba(249,249,250,.1);
 
   --arrowpanel-dimmed: rgba(249,249,250,.1);
   --arrowpanel-dimmed-further: rgba(249,249,250,.15);
   --arrowpanel-dimmed-even-further: rgba(249,249,250,.2);
 }
 
 #navigator-toolbox:-moz-lwtheme {
   --tabs-border-color: rgba(0,0,0,.3);
--- a/devtools/client/application/src/components/WorkerListEmpty.js
+++ b/devtools/client/application/src/components/WorkerListEmpty.js
@@ -66,17 +66,19 @@ class WorkerListEmpty extends Component 
         Localized({
           id: "serviceworker-empty-suggestions-debugger",
           a: a({ className: "link", onClick: () => this.switchToDebugger() })
         },
           li({ className: "worker-list-empty__tips__item" })
         ),
         Localized({
           id: "serviceworker-empty-suggestions-aboutdebugging",
-          a: a({ className: "link", onClick: () => this.openAboutDebugging() })
+          a: a({
+            className: "link js-trusted-link",
+            onClick: () => this.openAboutDebugging() })
         },
           li({ className: "worker-list-empty__tips__item" })
         ),
       )
     );
   }
 }
 
--- a/devtools/client/application/test/browser.ini
+++ b/devtools/client/application/test/browser.ini
@@ -16,8 +16,9 @@ support-files =
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_application_panel_debug-service-worker.js]
 [browser_application_panel_list-domain-workers.js]
 [browser_application_panel_list-several-workers.js]
 [browser_application_panel_list-single-worker.js]
 [browser_application_panel_list-unicode.js]
+[browser_application_panel_open-links.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/application/test/browser_application_panel_open-links.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { Toolbox } = require("devtools/client/framework/toolbox");
+
+/**
+ * Check that links work when the devtools are detached in a separate window.
+ */
+
+const TAB_URL = URL_ROOT + "service-workers/empty.html";
+
+add_task(async function() {
+  await enableApplicationPanel();
+
+  const {
+    panel,
+    toolbox
+  } = await openNewTabAndApplicationPanel(TAB_URL);
+
+  const doc = panel.panelWin.document;
+
+  // detach devtools in a separate window
+  await toolbox.switchHost(Toolbox.HostType.WINDOW);
+
+  // click on the link and wait for the new tab to open
+  const onTabLoaded = BrowserTestUtils
+    .waitForNewTab(gBrowser, "about:debugging#workers");
+  doc.querySelector(".js-trusted-link").click();
+  info("Opening link in a new tab.");
+  const newTab = await onTabLoaded;
+
+  // We only need to check that newTab is truthy since
+  // BrowserTestUtils.waitForNewTab checks the URL.
+  ok(newTab, "The expected tab was opened.");
+
+  // close the tab
+  info("Closing the tab.");
+  await BrowserTestUtils.removeTab(newTab);
+});
--- a/dom/media/mediasource/test/.eslintrc.js
+++ b/dom/media/mediasource/test/.eslintrc.js
@@ -14,16 +14,17 @@ module.exports = {
     "loadSegment": false,
     "must_not_reject": false,
     "must_not_throw": false,
     "must_reject": false,
     "must_throw": false,
     "once": false,
     "range": false,
     "runWithMSE": false,
+    "wait": false,
     "waitUntilTime": false
   },
   // Use const/let instead of var for tighter scoping, avoiding redeclaration
   "rules": {
     "array-bracket-spacing": ["error", "never"],
     "no-var": "error",
     "prefer-const": "error"
   }
--- a/dom/media/mediasource/test/mediasource.js
+++ b/dom/media/mediasource/test/mediasource.js
@@ -30,32 +30,27 @@ async function runWithMSE(testFunction) 
   try {
     await testFunction(ms, el);
   } catch (e) {
     ok(false, `${testFunction.name} failed with error ${e.name}`);
     throw e;
   }
 }
 
-async function fetchWithXHR(uri, onLoadFunction) {
-  let result = await new Promise(resolve => {
+async function fetchWithXHR(uri) {
+  return new Promise(resolve => {
     const xhr = new XMLHttpRequest();
     xhr.open("GET", uri, true);
     xhr.responseType = "arraybuffer";
     xhr.addEventListener("load", function() {
       is(xhr.status, 200, "fetchWithXHR load uri='" + uri + "' status=" + xhr.status);
       resolve(xhr.response);
     });
     xhr.send();
   });
-
-  if (onLoadFunction) {
-    result = await onLoadFunction(result);
-  }
-  return result;
 }
 
 function range(start, end) {
   const rv = [];
   for (let i = start; i < end; ++i) {
     rv.push(i);
   }
   return rv;
@@ -84,25 +79,23 @@ async function must_reject(f, msg, error
     if (error === true) {
       ok(false, `Please provide name of expected error! Got ${e.name}: ${e.message}.`);
     } else if (e.name != error) {
       throw e;
     }
   }
 }
 
+const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
+
 const must_not_throw = (f, msg) => must_throw(f, msg, false);
 const must_not_reject = (f, msg) => must_reject(f, msg, false);
 
-async function once(target, name, cb) {
-  let result = await new Promise(r => target.addEventListener(name, r, {once: true}));
-  if (cb) {
-    result = await cb();
-  }
-  return result;
+async function once(target, name) {
+  return new Promise(r => target.addEventListener(name, r, {once: true}));
 }
 
 function timeRangeToString(r) {
   let str = "TimeRanges: ";
   for (let i = 0; i < r.length; i++) {
     str += "[" + r.start(i) + ", " + r.end(i) + ")";
   }
   return str;
--- a/dom/media/mediasource/test/test_AppendPartialInitSegment.html
+++ b/dom/media/mediasource/test/test_AppendPartialInitSegment.html
@@ -7,39 +7,37 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/webm");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/webm");
 
-    fetchWithXHR("seek.webm", async function(arrayBuffer) {
-      // init segment is total 236 bytes.
-      info("- append partial init segment -");
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 100));
+  const arrayBuffer = await fetchWithXHR("seek.webm");
+  // init segment is total 236 bytes.
+  info("- append partial init segment -");
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 100));
 
-      info("- wait for updateend -");
-      await once(sb, "updateend");
+  info("- wait for updateend -");
+  await once(sb, "updateend");
 
-      info("- append remaining init segment -");
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 100, 136));
+  info("- append remaining init segment -");
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 100, 136));
 
-      info("- wait for metadata -");
-      await once(v, "loadedmetadata");
-      is(v.videoWidth, 320, "videoWidth has correct initial value");
-      is(v.videoHeight, 240, "videoHeight has correct initial value");
+  info("- wait for metadata -");
+  await once(v, "loadedmetadata");
+  is(v.videoWidth, 320, "videoWidth has correct initial value");
+  is(v.videoHeight, 240, "videoHeight has correct initial value");
 
-      info("- wait for updateend -");
-      await once(sb, "updateend");
-      SimpleTest.finish();
-    });
-  });
+  info("- wait for updateend -");
+  await once(sb, "updateend");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_AutoRevocation.html
+++ b/dom/media/mediasource/test/test_AutoRevocation.html
@@ -12,22 +12,22 @@
 
 SimpleTest.waitForExplicitFinish();
 
 runWithMSE(function() {
   const ms = new MediaSource();
   const o = URL.createObjectURL(ms);
   const v = document.createElement("video");
 
-  v.addEventListener("error", function(e) {
+  v.addEventListener("error", () => {
     ok(true, "ObjectURL should be auto-revoked");
     SimpleTest.finish();
   });
 
-  v.addEventListener("stalled", function(e) {
+  v.addEventListener("stalled", () => {
     ok(false, "If auto-revocation is gone, please turn on TODOs in browser_mediaSourceURL.js");
     SimpleTest.finish();
   });
 
   setTimeout(function() {
     v.src = o;
     v.preload = "auto";
     document.body.appendChild(v);
--- a/dom/media/mediasource/test/test_BufferedSeek.html
+++ b/dom/media/mediasource/test/test_BufferedSeek.html
@@ -7,42 +7,38 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/webm");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/webm");
 
-    fetchWithXHR("seek.webm", function(arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer));
-    });
+  sb.appendBuffer(new Uint8Array(await fetchWithXHR("seek.webm")));
 
-    const target = 2;
+  const target = 2;
 
-    v.addEventListener("loadedmetadata", function() {
-      ok(true, "received loadedmetadata");
-      v.currentTime = target;
-    });
+  v.addEventListener("loadedmetadata", () => {
+    ok(true, "received loadedmetadata");
+    v.currentTime = target;
+  });
 
-    let wasSeeking = false;
+  let wasSeeking = false;
 
-    v.addEventListener("seeking", function() {
-      wasSeeking = true;
-      is(v.currentTime, target, "Video currentTime at target");
-    });
+  v.addEventListener("seeking", () => {
+    wasSeeking = true;
+    is(v.currentTime, target, "Video currentTime at target");
+  });
 
-    v.addEventListener("seeked", function() {
-      ok(wasSeeking, "Received expected seeking and seeked events");
-      is(v.currentTime, target, "Video currentTime at target");
-      SimpleTest.finish();
-    });
-  });
+  await once(v, "seeked");
+  ok(wasSeeking, "Received expected seeking and seeked events");
+  is(v.currentTime, target, "Video currentTime at target");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_BufferedSeek_mp4.html
+++ b/dom/media/mediasource/test/test_BufferedSeek_mp4.html
@@ -7,42 +7,37 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/mp4");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/mp4");
+
+  sb.appendBuffer(new Uint8Array(await fetchWithXHR("bipbop/bipbop2s.mp4")));
 
-    fetchWithXHR("bipbop/bipbop2s.mp4", function(arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer));
-    });
+  const target = 1.3;
 
-    const target = 1.3;
+  await once(v, "loadedmetadata");
+  ok(true, "received loadedmetadata");
+  v.currentTime = target;
 
-    v.addEventListener("loadedmetadata", function() {
-      ok(true, "received loadedmetadata");
-      v.currentTime = target;
-    });
-
-    let wasSeeking = false;
+  let wasSeeking = false;
 
-    v.addEventListener("seeking", function() {
-      wasSeeking = true;
-      is(v.currentTime, target, "Video currentTime at target");
-    });
+  v.addEventListener("seeking", () => {
+    wasSeeking = true;
+    is(v.currentTime, target, "Video currentTime at target");
+  });
 
-    v.addEventListener("seeked", function() {
-      ok(wasSeeking, "Received expected seeking and seeked events");
-      is(v.currentTime, target, "Video currentTime at target");
-      SimpleTest.finish();
-    });
-  });
+  await once(v, "seeked");
+  ok(wasSeeking, "Received expected seeking and seeked events");
+  is(v.currentTime, target, "Video currentTime at target");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_ChangeType.html
+++ b/dom/media/mediasource/test/test_ChangeType.html
@@ -31,17 +31,17 @@ runWithMSE(function(ms, el) {
 
     ok(true, "Receive a sourceopen event");
 
     const videosb = ms.addSourceBuffer("video/mp4");
     if (typeof videosb.changeType === "undefined") {
       info("changeType API is not available");
     }
 
-    el.addEventListener("error", function(e) {
+    el.addEventListener("error", e => {
       ok(false, "should not fire '" + e.type + "' event");
       SimpleTest.finish();
     });
     is(el.readyState, el.HAVE_NOTHING, "readyState is HAVE_NOTHING");
     const loadedmetadataPromises = [];
     loadedmetadataPromises.push(fetchAndLoad(videosb, "bipbop/bipbop", ["init"], ".mp4"));
     loadedmetadataPromises.push(once(el, "loadedmetadata"));
     Promise.all(loadedmetadataPromises)
--- a/dom/media/mediasource/test/test_EndOfStream.html
+++ b/dom/media/mediasource/test/test_EndOfStream.html
@@ -7,44 +7,23 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function() {
-  const ms = new MediaSource();
-
-  const v = document.createElement("video");
-  v.src = URL.createObjectURL(ms);
-  document.body.appendChild(v);
-
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/webm");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/webm");
 
-    fetchWithXHR("seek.webm", function(arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 88966));
-      let count = 0;
-      sb.addEventListener("updateend", function() {
-        ++count;
-        if (count == 1) {
-          setTimeout(function() {
-            let fail = false;
-            try {
-              ms.endOfStream();
-            } catch (e) {
-              fail = true;
-            }
-            ok(!fail, "MediaSource.endOfStream succeeded");
-            SimpleTest.finish();
-          }, 0);
-        }
-      });
-    });
-  });
+  sb.appendBuffer(new Uint8Array(await fetchWithXHR("seek.webm"), 0, 88966));
+  await once(sb, "updateend");
+  await wait(0);
+  must_not_throw(() => ms.endOfStream(), "MediaSource.endOfStream succeeded");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_EndOfStream_mp4.html
+++ b/dom/media/mediasource/test/test_EndOfStream_mp4.html
@@ -7,44 +7,23 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function() {
-  const ms = new MediaSource();
-
-  const v = document.createElement("video");
-  v.src = URL.createObjectURL(ms);
-  document.body.appendChild(v);
-
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/mp4");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/mp4");
 
-    fetchWithXHR("bipbop/bipbop2s.mp4", function(arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer));
-      let count = 0;
-      sb.addEventListener("updateend", function() {
-        ++count;
-        if (count == 1) {
-          setTimeout(function() {
-            let fail = false;
-            try {
-              ms.endOfStream();
-            } catch (e) {
-              fail = true;
-            }
-            ok(!fail, "MediaSource.endOfStream succeeded");
-            SimpleTest.finish();
-          }, 0);
-        }
-      });
-    });
-  });
+  sb.appendBuffer(new Uint8Array(await fetchWithXHR("bipbop/bipbop2s.mp4")));
+  await once(sb, "updateend");
+  await wait(0);
+  must_not_throw(() => ms.endOfStream(), "MediaSource.endOfStream succeeded");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_FrameSelection_mp4.html
+++ b/dom/media/mediasource/test/test_FrameSelection_mp4.html
@@ -16,17 +16,17 @@ SimpleTest.waitForExplicitFinish();
 
 runWithMSE(async (ms, v) => {
   await once(ms, "sourceopen");
   ok(true, "Receive a sourceopen event");
   ms.addEventListener("sourceopen", () => ok(false, "No more sourceopen"));
   const sb = ms.addSourceBuffer("video/mp4");
   ok(sb, "Create a SourceBuffer");
   logEvents(v);
-  sb.addEventListener("error", function(e) {
+  sb.addEventListener("error", e => {
     ok(false, `should not fire ${e.type} event`);
     SimpleTest.finish();
   });
   await fetchAndLoad(sb, "bipbop/bipbop", ["init"], ".mp4");
   const p = once(v, "loadeddata");
   await fetchAndLoad(sb, "bipbop/bipbop", range(1, 3), ".m4s");
   await p;
   is(sb.buffered.length, 1, "continuous range");
--- a/dom/media/mediasource/test/test_HaveMetadataUnbufferedSeek.html
+++ b/dom/media/mediasource/test/test_HaveMetadataUnbufferedSeek.html
@@ -7,40 +7,32 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/webm");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/webm");
 
-    fetchWithXHR("seek.webm", function(arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 67833));
-    });
+  const arrayBuffer = await fetchWithXHR("seek.webm");
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 67833));
 
-    const target = 2;
+  const target = 2;
 
-    v.addEventListener("loadeddata", function() {
-      ok(v.readyState >= v.HAVE_CURRENT_DATA, "readyState is >= CURRENT_DATA");
-      v.currentTime = target;
-    }, {once: true});
+  await once(v, "loadeddata");
+  ok(v.readyState >= v.HAVE_CURRENT_DATA, "readyState is >= CURRENT_DATA");
+  v.currentTime = target;
 
-    v.addEventListener("seeking", function() {
-      is(v.readyState, v.HAVE_METADATA, "readyState is HAVE_METADATA");
-      fetchWithXHR("seek.webm", function(arrayBuffer) {
-        sb.appendBuffer(new Uint8Array(arrayBuffer, 67833));
-      });
-    });
-
-    v.addEventListener("seeked", function() {
-      SimpleTest.finish();
-    });
-  });
+  await once(v, "seeking");
+  is(v.readyState, v.HAVE_METADATA, "readyState is HAVE_METADATA");
+  sb.appendBuffer(new Uint8Array(await fetchWithXHR("seek.webm"), 67833));
+  await once(v, "seeked");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_HaveMetadataUnbufferedSeek_mp4.html
+++ b/dom/media/mediasource/test/test_HaveMetadataUnbufferedSeek_mp4.html
@@ -7,45 +7,36 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/mp4");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/mp4");
 
-    fetchWithXHR("bipbop/bipbop2s.mp4", function(arrayBuffer) {
-      // 25819 is the offset of the first media segment's end
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 25819));
-    });
+  const arrayBuffer = await fetchWithXHR("bipbop/bipbop2s.mp4");
+  // 25819 is the offset of the first media segment's end
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 25819));
 
-    const target = 1.3;
-
-    v.addEventListener("loadeddata", function() {
-      ok(v.readyState >= v.HAVE_CURRENT_DATA, "readyState is >= CURRENT_DATA");
-      v.currentTime = target;
-    }, {once: true});
+  const target = 1.3;
 
-    v.addEventListener("seeking", function() {
-      is(v.readyState, v.HAVE_METADATA, "readyState is HAVE_METADATA");
-      fetchWithXHR("bipbop/bipbop2s.mp4", function(arrayBuffer) {
-        // 25819 is the offset of the first media segment's end
-        sb.addEventListener("updateend", function() {
-          ms.endOfStream();
-        });
-        sb.appendBuffer(new Uint8Array(arrayBuffer, 25819));
-      });
-    });
+  await once(v, "loadeddata");
+  ok(v.readyState >= v.HAVE_CURRENT_DATA, "readyState is >= CURRENT_DATA");
+  v.currentTime = target;
 
-    v.addEventListener("seeked", function() {
-      SimpleTest.finish();
-    });
-  });
+  await once(v, "seeking");
+  is(v.readyState, v.HAVE_METADATA);
+  // 25819 is the offset of the first media segment's end
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 25819));
+  await once(sb, "updateend");
+  ms.endOfStream();
+  await once(v, "seeked");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_LoadedDataFired_mp4.html
+++ b/dom/media/mediasource/test/test_LoadedDataFired_mp4.html
@@ -9,22 +9,22 @@
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 runWithMSE(async (ms, el) => {
   el.controls = true;
-  el.addEventListener("loadeddata", function() {
+  el.addEventListener("loadeddata", () => {
     ok(el.buffered.length > 0, "data is buffered");
     is(el.buffered.start(0), 0, "must fire loadeddata when data has been loaded");
     is(el.currentTime, 0, "must fire loadeddata at start");
   });
-  el.addEventListener("playing", function() {
+  el.addEventListener("playing", () => {
     ok(el.buffered.length > 0, "data is buffered");
     is(el.buffered.start(0), 0, "must fire playing when data has been loaded");
     ok(el.currentTime >= 0, "must have started playback");
   });
   await once(ms, "sourceopen");
   ok(true, "Receive a sourceopen event");
   const videosb = ms.addSourceBuffer("video/mp4");
   is(el.readyState, el.HAVE_NOTHING, "readyState is HAVE_NOTHING");
--- a/dom/media/mediasource/test/test_LoadedMetadataFired.html
+++ b/dom/media/mediasource/test/test_LoadedMetadataFired.html
@@ -7,31 +7,25 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/webm");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/webm");
 
-    v.addEventListener("loadedmetadata", function() {
-      ok(true, "Got loadedmetadata event");
-      is(v.videoWidth, 320, "videoWidth has correct initial value");
-      is(v.videoHeight, 240, "videoHeight has correct initial value");
-      SimpleTest.finish();
-    });
-
-    fetchWithXHR("seek.webm", function(arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 318));
-      v.play();
-    });
-  });
-
+  sb.appendBuffer(new Uint8Array(await fetchWithXHR("seek.webm"), 0, 318));
+  v.play();
+  await once(v, "loadedmetadata");
+  ok(true, "Got loadedmetadata event");
+  is(v.videoWidth, 320, "videoWidth has correct initial value");
+  is(v.videoHeight, 240, "videoHeight has correct initial value");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_LoadedMetadataFired_mp4.html
+++ b/dom/media/mediasource/test/test_LoadedMetadataFired_mp4.html
@@ -7,31 +7,25 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/mp4");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/mp4");
 
-    v.addEventListener("loadedmetadata", function() {
-      ok(true, "Got loadedmetadata event");
-      is(v.videoWidth, 400, "videoWidth has correct initial value");
-      is(v.videoHeight, 300, "videoHeight has correct initial value");
-      SimpleTest.finish();
-    });
-
-    fetchWithXHR("bipbop/bipbop2s.mp4", function(arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 1395));
-      v.play();
-    });
-  });
-
+  sb.appendBuffer(new Uint8Array(await fetchWithXHR("bipbop/bipbop2s.mp4"), 0, 1395));
+  v.play();
+  await once(v, "loadedmetadata");
+  ok(true, "Got loadedmetadata event");
+  is(v.videoWidth, 400, "videoWidth has correct initial value");
+  is(v.videoHeight, 300, "videoHeight has correct initial value");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_MediaSource.html
+++ b/dom/media/mediasource/test/test_MediaSource.html
@@ -7,104 +7,86 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function() {
+runWithMSE(async (ms, v) => {
   SimpleTest.doesThrow(() => new SourceBuffer, "new SourceBuffer should fail");
   SimpleTest.doesThrow(() => new SourceBufferList, "new SourceBufferList direct should fail");
 
-  const ms = new MediaSource();
-  ok(ms, "Create a MediaSource object");
   ok(ms instanceof EventTarget, "MediaSource must be an EventTarget");
   is(ms.readyState, "closed", "New MediaSource must be in closed state");
 
   // Wrapper creation, tests for leaks.
   SpecialPowers.wrap(ms);
 
   // Set an expando to force wrapper creation, tests for leaks.
   ms.foo = null;
 
-  const o = URL.createObjectURL(ms);
-  ok(o, "Create an objectURL from the MediaSource");
-
-  const v = document.createElement("video");
-  v.preload = "auto";
-  v.src = o;
-  document.body.appendChild(v);
+  ok(URL.createObjectURL(ms), "Create an objectURL from the MediaSource");
 
   let loadedmetadataCount = 0;
   let updatestartCount = 0;
   let updateendCount = 0;
   let updateCount = 0;
 
   ok(MediaSource.isTypeSupported("video/webm; codecs=vp8"), "VP8 MSE is always supported");
-  ok(MediaSource.isTypeSupported("audio/webm", "Audio MSE is always supported"));
+  ok(MediaSource.isTypeSupported("audio/webm"), "Audio MSE is always supported");
 
-  ms.addEventListener("sourceopen", function() {
-    ok(true, "Receive a sourceopen event");
-    is(ms.readyState, "open", "MediaSource must be in open state after sourceopen");
-    const sb = ms.addSourceBuffer("video/webm");
-    ok(sb, "Create a SourceBuffer");
-    is(ms.sourceBuffers.length, 1, "MediaSource.sourceBuffers is expected length");
-    is(ms.sourceBuffers[0], sb, "SourceBuffer in list matches our SourceBuffer");
-    is(ms.activeSourceBuffers.length, 0, "MediaSource.activeSourceBuffers is expected length");
-
-
-    fetchWithXHR("seek.webm", function(arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer));
-      is(sb.updating, true, "SourceBuffer.updating is expected value after appendBuffer");
-    });
+  await once(ms, "sourceopen");
+  ok(true, "Receive a sourceopen event");
+  is(ms.readyState, "open", "MediaSource must be in open state after sourceopen");
+  const sb = ms.addSourceBuffer("video/webm");
+  ok(sb, "Create a SourceBuffer");
+  is(ms.sourceBuffers.length, 1, "MediaSource.sourceBuffers is expected length");
+  is(ms.sourceBuffers[0], sb, "SourceBuffer in list matches our SourceBuffer");
+  is(ms.activeSourceBuffers.length, 0, "MediaSource.activeSourceBuffers is expected length");
 
-    sb.addEventListener("update", function() {
-      is(sb.updating, false, "SourceBuffer.updating is expected value in update event");
-      updateCount++;
-      /* Ensure that we endOfStream on the first update event only as endOfStream can
-         raise more if the duration of the last buffered range and the intial duration
-         differ. See bug 1065207 */
-      if (updateCount == 1) {
-        ms.endOfStream();
-      }
-    });
+  sb.appendBuffer(new Uint8Array(await fetchWithXHR("seek.webm")));
+  is(sb.updating, true, "SourceBuffer.updating is expected value after appendBuffer");
 
-    sb.addEventListener("updatestart", function() {
-      updatestartCount++;
-    });
-
-    sb.addEventListener("updateend", function() {
-      is(ms.activeSourceBuffers[0], sb, "SourceBuffer in active list matches our SourceBuffer");
-      is(sb.updating, false, "SourceBuffer.updating is expected value in updateend event");
-      updateendCount++;
-      v.play();
-    });
+  sb.addEventListener("update", () => {
+    is(sb.updating, false, "SourceBuffer.updating is expected value in update event");
+    updateCount++;
+    /* Ensure that we endOfStream on the first update event only as endOfStream can
+       raise more if the duration of the last buffered range and the intial duration
+       differ. See bug 1065207 */
+    if (updateCount == 1) {
+      ms.endOfStream();
+    }
   });
 
-  ms.addEventListener("sourceended", function() {
+  sb.addEventListener("updatestart", () => updatestartCount++);
+
+  sb.addEventListener("updateend", () => {
+    is(ms.activeSourceBuffers[0], sb, "SourceBuffer in active list matches our SourceBuffer");
+    is(sb.updating, false, "SourceBuffer.updating is expected value in updateend event");
+    updateendCount++;
+    v.play();
+  });
+
+  ms.addEventListener("sourceended", () => {
     ok(true, "Receive a sourceended event");
     is(ms.readyState, "ended", "MediaSource must be in ended state after sourceended");
   });
 
-  v.addEventListener("loadedmetadata", function() {
-    loadedmetadataCount++;
-  });
+  v.addEventListener("loadedmetadata", () => loadedmetadataCount++);
 
-  v.addEventListener("ended", function() {
-    // XXX: Duration should be exactly 4.0, see bug 1065207.
-    ok(Math.abs(v.duration - 4) <= 0.002, "Video has correct duration");
-    ok(Math.abs(v.currentTime - 4) <= 0.002, "Video has played to end");
-    // XXX: 2 update events can be received dueto duration differences, see bug 1065207.
-    ok(updateCount == 1 || updateCount == 2, "update event received");
-    ok(updateendCount == 1 || updateendCount == 2, "updateend event received");
-    ok(updatestartCount == 1 || updatestartCount == 2, "updatestart event received");
-    is(loadedmetadataCount, 1, "loadedmetadata event received");
-    v.remove();
-    SimpleTest.finish();
-  });
+  await once(v, "ended");
+  // XXX: Duration should be exactly 4.0, see bug 1065207.
+  ok(Math.abs(v.duration - 4) <= 0.002, "Video has correct duration");
+  ok(Math.abs(v.currentTime - 4) <= 0.002, "Video has played to end");
+  // XXX: 2 update events can be received dueto duration differences, see bug 1065207.
+  ok(updateCount == 1 || updateCount == 2, "update event received");
+  ok(updateendCount == 1 || updateendCount == 2, "updateend event received");
+  ok(updatestartCount == 1 || updatestartCount == 2, "updatestart event received");
+  is(loadedmetadataCount, 1, "loadedmetadata event received");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_MediaSource_mp4.html
+++ b/dom/media/mediasource/test/test_MediaSource_mp4.html
@@ -7,102 +7,84 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function() {
+runWithMSE(async (ms, v) => {
   SimpleTest.doesThrow(() => new SourceBuffer, "new SourceBuffer should fail");
   SimpleTest.doesThrow(() => new SourceBufferList, "new SourceBufferList direct should fail");
 
-  const ms = new MediaSource();
-  ok(ms, "Create a MediaSource object");
   ok(ms instanceof EventTarget, "MediaSource must be an EventTarget");
   is(ms.readyState, "closed", "New MediaSource must be in closed state");
 
   // Wrapper creation, tests for leaks.
   SpecialPowers.wrap(ms);
 
   // Set an expando to force wrapper creation, tests for leaks.
   ms.foo = null;
 
-  const o = URL.createObjectURL(ms);
-  ok(o, "Create an objectURL from the MediaSource");
-
-  const v = document.createElement("video");
-  v.preload = "auto";
-  v.src = o;
-  document.body.appendChild(v);
+  ok(URL.createObjectURL(ms), "Create an objectURL from the MediaSource");
 
   let loadedmetadataCount = 0;
   let updatestartCount = 0;
   let updateendCount = 0;
   let updateCount = 0;
 
-  ms.addEventListener("sourceopen", function() {
-    ok(true, "Receive a sourceopen event");
-    is(ms.readyState, "open", "MediaSource must be in open state after sourceopen");
-    const sb = ms.addSourceBuffer("video/mp4");
-    ok(sb, "Create a SourceBuffer");
-    is(ms.sourceBuffers.length, 1, "MediaSource.sourceBuffers is expected length");
-    is(ms.sourceBuffers[0], sb, "SourceBuffer in list matches our SourceBuffer");
-    is(ms.activeSourceBuffers.length, 0, "MediaSource.activeSourceBuffers is expected length");
-
-
-    fetchWithXHR("bipbop/bipbop2s.mp4", function(arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer));
-      is(sb.updating, true, "SourceBuffer.updating is expected value after appendBuffer");
-    });
+  await once(ms, "sourceopen");
+  ok(true, "Receive a sourceopen event");
+  is(ms.readyState, "open", "MediaSource must be in open state after sourceopen");
+  const sb = ms.addSourceBuffer("video/mp4");
+  ok(sb, "Create a SourceBuffer");
+  is(ms.sourceBuffers.length, 1, "MediaSource.sourceBuffers is expected length");
+  is(ms.sourceBuffers[0], sb, "SourceBuffer in list matches our SourceBuffer");
+  is(ms.activeSourceBuffers.length, 0, "MediaSource.activeSourceBuffers is expected length");
 
-    sb.addEventListener("update", function() {
-      is(sb.updating, false, "SourceBuffer.updating is expected value in update event");
-      updateCount++;
-      /* Ensure that we endOfStream on the first update event only as endOfStream can
-         raise more if the duration of the last buffered range and the intial duration
-         differ. See bug 1065207 */
-      if (updateCount == 1) {
-        ms.endOfStream();
-      }
-    });
+  sb.appendBuffer(new Uint8Array(await fetchWithXHR("bipbop/bipbop2s.mp4")));
+  is(sb.updating, true, "SourceBuffer.updating is expected value after appendBuffer");
 
-    sb.addEventListener("updatestart", function() {
-      updatestartCount++;
-    });
-
-    sb.addEventListener("updateend", function() {
-      is(ms.activeSourceBuffers[0], sb, "SourceBuffer in active list matches our SourceBuffer");
-      is(sb.updating, false, "SourceBuffer.updating is expected value in updateend event");
-      updateendCount++;
-      v.play();
-    });
+  sb.addEventListener("update", () => {
+    is(sb.updating, false, "SourceBuffer.updating is expected value in update event");
+    updateCount++;
+    /* Ensure that we endOfStream on the first update event only as endOfStream can
+       raise more if the duration of the last buffered range and the intial duration
+       differ. See bug 1065207 */
+    if (updateCount == 1) {
+      ms.endOfStream();
+    }
   });
 
-  ms.addEventListener("sourceended", function() {
+  sb.addEventListener("updatestart", () => updatestartCount++);
+
+  sb.addEventListener("updateend", () => {
+    is(ms.activeSourceBuffers[0], sb, "SourceBuffer in active list matches our SourceBuffer");
+    is(sb.updating, false, "SourceBuffer.updating is expected value in updateend event");
+    updateendCount++;
+    v.play();
+  });
+
+  ms.addEventListener("sourceended", () => {
     ok(true, "Receive a sourceended event");
     is(ms.readyState, "ended", "MediaSource must be in ended state after sourceended");
   });
 
-  v.addEventListener("loadedmetadata", function() {
-    loadedmetadataCount++;
-  });
+  v.addEventListener("loadedmetadata", () => loadedmetadataCount++);
 
-  v.addEventListener("ended", function() {
-    // The bipbop video doesn't start at 0. The old MSE code adjust the
-    // timestamps and ignore the audio track. The new one doesn't.
-    isfuzzy(v.duration, 1.696, 0.166, "Video has correct duration");
-    isfuzzy(v.currentTime, 1.696, 0.166, "Video has correct duration");
-    // XXX: 2 update events can be received dueto duration differences, see bug 1065207.
-    ok(updateCount == 1 || updateCount == 2, "update event received");
-    ok(updateendCount == 1 || updateendCount == 2, "updateend event received");
-    ok(updatestartCount == 1 || updatestartCount == 2, "updatestart event received");
-    is(loadedmetadataCount, 1, "loadedmetadata event received");
-    v.remove();
-    SimpleTest.finish();
-  });
+  await once(v, "ended");
+  // The bipbop video doesn't start at 0. The old MSE code adjust the
+  // timestamps and ignore the audio track. The new one doesn't.
+  isfuzzy(v.duration, 1.696, 0.166, "Video has correct duration");
+  isfuzzy(v.currentTime, 1.696, 0.166, "Video has correct duration");
+  // XXX: 2 update events can be received dueto duration differences, see bug 1065207.
+  ok(updateCount == 1 || updateCount == 2, "update event received");
+  ok(updateendCount == 1 || updateendCount == 2, "updateend event received");
+  ok(updatestartCount == 1 || updatestartCount == 2, "updatestart event received");
+  is(loadedmetadataCount, 1, "loadedmetadata event received");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStream.html
+++ b/dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStream.html
@@ -7,50 +7,48 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/webm");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/webm");
 
-    fetchWithXHR("seek.webm", async function(arrayBuffer) {
-      info("- append buffer -");
-      sb.appendBuffer(new Uint8Array(arrayBuffer));
+  const arrayBuffer = await fetchWithXHR("seek.webm");
+  info("- append first buffer -");
+  sb.appendBuffer(new Uint8Array(arrayBuffer));
 
-      info("- wait for metadata -");
-      await once(v, "loadedmetadata");
+  info("- wait for metadata -");
+  await once(v, "loadedmetadata");
 
-      info("- wait for updateend -");
-      await once(sb, "updateend");
+  info("- wait for updateend -");
+  await once(sb, "updateend");
 
-      info("- check seekable -");
-      const target = 2;
-      ok(v.seekable.length, "Resource is seekable");
-      is(v.seekable.start(0), 0, "Seekable's start point is correct");
-      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
-      ok(v.seekable.length &&
-         target >= v.seekable.start(0) &&
-         target < v.seekable.end(0), "Target is within seekable range");
+  info("- check seekable -");
+  const target = 2;
+  ok(v.seekable.length, "Resource is seekable");
+  is(v.seekable.start(0), 0, "Seekable's start point is correct");
+  is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
+  ok(v.seekable.length &&
+     target >= v.seekable.start(0) &&
+     target < v.seekable.end(0), "Target is within seekable range");
 
-      info("- call end of stream -");
-      ms.endOfStream();
-      await once(ms, "sourceended");
+  info("- call end of stream -");
+  ms.endOfStream();
+  await once(ms, "sourceended");
 
-      info("- check seekable -");
-      ok(v.seekable.length, "Resource is seekable");
-      is(v.seekable.start(0), 0, "Seekable's start point is correct");
-      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
-      ok(v.seekable.length &&
-         target >= v.seekable.start(0) &&
-         target < v.seekable.end(0), "Target is within seekable range");
-      SimpleTest.finish();
-    });
-  });
+  info("- check seekable -");
+  ok(v.seekable.length, "Resource is seekable");
+  is(v.seekable.start(0), 0, "Seekable's start point is correct");
+  is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
+  ok(v.seekable.length &&
+     target >= v.seekable.start(0) &&
+     target < v.seekable.end(0), "Target is within seekable range");
+  SimpleTest.finish();
 });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStreamSplit.html
+++ b/dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStreamSplit.html
@@ -7,56 +7,54 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/webm");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/webm");
 
-    fetchWithXHR("seek.webm", async function(arrayBuffer) {
-      info("- append first buffer -");
-      // 25523 is the offset of the first media segment's end
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 25523));
+  const arrayBuffer = await fetchWithXHR("seek.webm");
+  info("- append first buffer -");
+  // 25523 is the offset of the first media segment's end
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 25523));
 
-      info("- wait for metadata -");
-      await once(v, "loadedmetadata");
+  info("- wait for metadata -");
+  await once(v, "loadedmetadata");
 
-      info("- wait for updateend -");
-      await once(sb, "updateend");
+  info("- wait for updateend -");
+  await once(sb, "updateend");
 
-      info("- append second buffer -");
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 25523));
-      await once(sb, "updateend");
+  info("- append second buffer -");
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 25523));
+  await once(sb, "updateend");
 
-      info("- check seekable -");
-      const target = 2;
-      ok(v.seekable.length, "Resource is seekable");
-      is(v.seekable.start(0), 0, "Seekable's start point is correct");
-      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
-      ok(v.seekable.length &&
-         target >= v.seekable.start(0) &&
-         target < v.seekable.end(0), "Target is within seekable range");
+  info("- check seekable -");
+  const target = 2;
+  ok(v.seekable.length, "Resource is seekable");
+  is(v.seekable.start(0), 0, "Seekable's start point is correct");
+  is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
+  ok(v.seekable.length &&
+     target >= v.seekable.start(0) &&
+     target < v.seekable.end(0), "Target is within seekable range");
 
-      info("- call end of stream -");
-      ms.endOfStream();
-      await once(ms, "sourceended");
+  info("- call end of stream -");
+  ms.endOfStream();
+  await once(ms, "sourceended");
 
-      info("- check seekable -");
-      ok(v.seekable.length, "Resource is seekable");
-      is(v.seekable.start(0), 0, "Seekable's start point is correct");
-      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
-      ok(v.seekable.length &&
-         target >= v.seekable.start(0) &&
-         target < v.seekable.end(0), "Target is within seekable range");
-      SimpleTest.finish();
-    });
-  });
+  info("- check seekable -");
+  ok(v.seekable.length, "Resource is seekable");
+  is(v.seekable.start(0), 0, "Seekable's start point is correct");
+  is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
+  ok(v.seekable.length &&
+     target >= v.seekable.start(0) &&
+     target < v.seekable.end(0), "Target is within seekable range");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStreamSplit_mp4.html
+++ b/dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStreamSplit_mp4.html
@@ -7,56 +7,54 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/mp4");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/mp4");
 
-    fetchWithXHR("bipbop/bipbop2s.mp4", async function(arrayBuffer) {
-      info("- append first buffer -");
-      // 25819 is the offset of the first media segment's end
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 25819));
+  const arrayBuffer = await fetchWithXHR("bipbop/bipbop2s.mp4");
+  info("- append first buffer -");
+  // 25819 is the offset of the first media segment's end
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 25819));
 
-      info("- wait for metadata -");
-      await once(v, "loadedmetadata");
+  info("- wait for metadata -");
+  await once(v, "loadedmetadata");
 
-      info("- wait for updateend -");
-      await once(sb, "updateend");
+  info("- wait for updateend -");
+  await once(sb, "updateend");
 
-      info("- append second buffer -");
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 25819));
-      await once(sb, "updateend");
+  info("- append second buffer -");
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 25819));
+  await once(sb, "updateend");
 
-      info("- check seekable -");
-      const target = 1.3;
-      ok(v.seekable.length, "Resource is seekable");
-      is(v.seekable.start(0), 0, "Seekable's start point is correct");
-      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
-      ok(v.seekable.length &&
-         target >= v.seekable.start(0) &&
-         target < v.seekable.end(0), "Target is within seekable range");
+  info("- check seekable -");
+  const target = 1.3;
+  ok(v.seekable.length, "Resource is seekable");
+  is(v.seekable.start(0), 0, "Seekable's start point is correct");
+  is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
+  ok(v.seekable.length &&
+     target >= v.seekable.start(0) &&
+     target < v.seekable.end(0), "Target is within seekable range");
 
-      info("- call end of stream -");
-      ms.endOfStream();
-      await once(ms, "sourceended");
+  info("- call end of stream -");
+  ms.endOfStream();
+  await once(ms, "sourceended");
 
-      info("- check seekable -");
-      ok(v.seekable.length, "Resource is seekable");
-      is(v.seekable.start(0), 0, "Seekable's start point is correct");
-      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
-      ok(v.seekable.length &&
-         target >= v.seekable.start(0) &&
-         target < v.seekable.end(0), "Target is within seekable range");
-      SimpleTest.finish();
-    });
-  });
+  info("- check seekable -");
+  ok(v.seekable.length, "Resource is seekable");
+  is(v.seekable.start(0), 0, "Seekable's start point is correct");
+  is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
+  ok(v.seekable.length &&
+     target >= v.seekable.start(0) &&
+     target < v.seekable.end(0), "Target is within seekable range");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStream_mp4.html
+++ b/dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStream_mp4.html
@@ -7,51 +7,49 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/mp4");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/mp4");
 
-    fetchWithXHR("bipbop/bipbop2s.mp4", async function(arrayBuffer) {
-      info("- append buffer -");
-      sb.appendBuffer(new Uint8Array(arrayBuffer));
+  const arrayBuffer = await fetchWithXHR("bipbop/bipbop2s.mp4");
+  info("- append buffer -");
+  sb.appendBuffer(new Uint8Array(arrayBuffer));
 
-      info("- wait for metadata -");
-      await once(v, "loadedmetadata");
+  info("- wait for metadata -");
+  await once(v, "loadedmetadata");
 
-      info("- wait for updateend -");
-      await once(sb, "updateend");
+  info("- wait for updateend -");
+  await once(sb, "updateend");
 
-      info("- check seekable -");
-      const target = 1.3;
-      ok(v.seekable.length, "Resource is seekable");
-      is(v.seekable.start(0), 0, "Seekable's start point is correct");
-      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
-      ok(v.seekable.length &&
-         target >= v.seekable.start(0) &&
-         target < v.seekable.end(0), "Target is within seekable range");
+  info("- check seekable -");
+  const target = 1.3;
+  ok(v.seekable.length, "Resource is seekable");
+  is(v.seekable.start(0), 0, "Seekable's start point is correct");
+  is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
+  ok(v.seekable.length &&
+     target >= v.seekable.start(0) &&
+     target < v.seekable.end(0), "Target is within seekable range");
 
-      info("- call end of stream -");
-      ms.endOfStream();
-      await once(ms, "sourceended");
+  info("- call end of stream -");
+  ms.endOfStream();
+  await once(ms, "sourceended");
 
-      info("- check seekable -");
-      ok(v.seekable.length, "Resource is seekable");
-      is(v.seekable.start(0), 0, "Seekable's start point is correct");
-      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
-      ok(v.seekable.length &&
-         target >= v.seekable.start(0) &&
-         target < v.seekable.end(0), "Target is within seekable range");
-      SimpleTest.finish();
-    });
-  });
+  info("- check seekable -");
+  ok(v.seekable.length, "Resource is seekable");
+  is(v.seekable.start(0), 0, "Seekable's start point is correct");
+  is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
+  ok(v.seekable.length &&
+     target >= v.seekable.start(0) &&
+     target < v.seekable.end(0), "Target is within seekable range");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_SetModeThrows.html
+++ b/dom/media/mediasource/test/test_SetModeThrows.html
@@ -9,17 +9,17 @@
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 // MSE supports setting mode now. make sure it does not throw.
 runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
+  ms.addEventListener("sourceopen", () => {
     const sb = ms.addSourceBuffer("video/webm");
 
     sb.mode = "segments";
     ok("true", "Setting to segments does not throw");
     try {
       sb.mode = "sequence";
       ok("true", "Setting to sequence does not throw");
     } catch (e) { ok(false, "Should not throw setting mode to sequence: " + e); }
--- a/dom/media/mediasource/test/test_SplitAppend.html
+++ b/dom/media/mediasource/test/test_SplitAppend.html
@@ -7,40 +7,30 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-let updateCount = 0;
-
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/webm");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/webm");
 
-    fetchWithXHR("seek.webm", function(arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 318));
-      sb.addEventListener("updateend", function() {
-        updateCount++;
-        if (updateCount == 1) {
-          sb.appendBuffer(new Uint8Array(arrayBuffer, 318));
-        } else if (updateCount == 2) {
-          ms.endOfStream();
-        }
-      });
-      v.play();
-    });
-  });
-
-  v.addEventListener("ended", function() {
-    // XXX: Duration should be exactly 4.0, see bug 1065207.
-    ok(Math.abs(v.duration - 4) <= 0.002, "Video has correct duration");
-    ok(Math.abs(v.currentTime - 4) <= 0.002, "Video has played to end");
-    SimpleTest.finish();
-  });
+  const arrayBuffer = await fetchWithXHR("seek.webm");
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 318));
+  v.play();
+  await once(sb, "updateend");
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 318));
+  await once(sb, "updateend");
+  ms.endOfStream();
+  await once(v, "ended");
+  // XXX: Duration should be exactly 4.0, see bug 1065207.
+  ok(Math.abs(v.duration - 4) <= 0.002, "Video has correct duration");
+  ok(Math.abs(v.currentTime - 4) <= 0.002, "Video has played to end");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_SplitAppendDelay.html
+++ b/dom/media/mediasource/test/test_SplitAppendDelay.html
@@ -8,42 +8,31 @@
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 SimpleTest.requestFlakyTimeout("untriaged");
 
-let updateCount = 0;
-
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/webm");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/webm");
 
-    fetchWithXHR("seek.webm", function(arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 318));
-      sb.addEventListener("updateend", function() {
-        updateCount++;
-        if (updateCount == 1) {
-          window.setTimeout(function() {
-            sb.appendBuffer(new Uint8Array(arrayBuffer, 318));
-          }, 1000);
-        } else if (updateCount == 2) {
-          ms.endOfStream();
-        }
-      });
-      v.play();
-    });
-  });
-
-  v.addEventListener("ended", function() {
-    // XXX: Duration should be exactly 4.0, see bug 1065207.
-    ok(Math.abs(v.duration - 4) <= 0.002, "Video has correct duration");
-    ok(Math.abs(v.currentTime - 4) <= 0.002, "Video has played to end");
-    SimpleTest.finish();
-  });
+  const arrayBuffer = await fetchWithXHR("seek.webm");
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 318));
+  v.play();
+  await once(sb, "updateend");
+  await wait(1000);
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 318));
+  await once(sb, "updateend");
+  ms.endOfStream();
+  await once(v, "ended");
+  // XXX: Duration should be exactly 4.0, see bug 1065207.
+  ok(Math.abs(v.duration - 4) <= 0.002, "Video has correct duration");
+  ok(Math.abs(v.currentTime - 4) <= 0.002, "Video has played to end");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_SplitAppendDelay_mp4.html
+++ b/dom/media/mediasource/test/test_SplitAppendDelay_mp4.html
@@ -8,43 +8,32 @@
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 SimpleTest.requestFlakyTimeout("untriaged");
 
-let updateCount = 0;
-
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/mp4");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/mp4");
 
-    fetchWithXHR("bipbop/bipbop2s.mp4", function(arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 1395));
-      sb.addEventListener("updateend", function() {
-        updateCount++;
-        if (updateCount == 1) {
-          window.setTimeout(function() {
-            sb.appendBuffer(new Uint8Array(arrayBuffer, 1395));
-          }, 1000);
-        } else if (updateCount == 2) {
-          ms.endOfStream();
-        }
-      });
-      v.play();
-    });
-  });
-
-  v.addEventListener("ended", function() {
-    // The bipbop video doesn't start at 0. The old MSE code adjust the
-    // timestamps and ignore the audio track. The new one doesn't.
-    isfuzzy(v.duration, 1.696, 0.166, "Video has correct duration");
-    isfuzzy(v.currentTime, 1.696, 0.166, "Video has played to end");
-    SimpleTest.finish();
-  });
+  const arrayBuffer = await fetchWithXHR("bipbop/bipbop2s.mp4");
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 1395));
+  v.play();
+  await once(sb, "updateend");
+  await wait(1000);
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 1395));
+  await once(sb, "updateend");
+  ms.endOfStream();
+  await once(v, "ended");
+  // The bipbop video doesn't start at 0. The old MSE code adjust the
+  // timestamps and ignore the audio track. The new one doesn't.
+  isfuzzy(v.duration, 1.696, 0.166, "Video has correct duration");
+  isfuzzy(v.currentTime, 1.696, 0.166, "Video has played to end");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_SplitAppend_mp4.html
+++ b/dom/media/mediasource/test/test_SplitAppend_mp4.html
@@ -7,41 +7,32 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-let updateCount = 0;
-
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/mp4");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/mp4");
 
-    fetchWithXHR("bipbop/bipbop2s.mp4", function(arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 1395));
-      sb.addEventListener("updateend", function() {
-        updateCount++;
-        if (updateCount == 1) {
-          sb.appendBuffer(new Uint8Array(arrayBuffer, 1395));
-        } else if (updateCount == 2) {
-          ms.endOfStream();
-        }
-      });
-      v.play();
-    });
-  });
+  const arrayBuffer = await fetchWithXHR("bipbop/bipbop2s.mp4");
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 1395));
+  v.play();
+  await once(sb, "updateend");
+  sb.appendBuffer(new Uint8Array(arrayBuffer, 1395));
+  await once(sb, "updateend");
+  ms.endOfStream();
 
-  v.addEventListener("ended", function() {
-    // The bipbop video doesn't start at 0. The old MSE code adjust the
-    // timestamps and ignore the audio track. The new one doesn't.
-    isfuzzy(v.duration, 1.696, 0.166, "Video has correct duration");
-    isfuzzy(v.currentTime, 1.696, 0.166, "Video has played to end");
-    SimpleTest.finish();
-  });
+  await once(v, "ended");
+  // The bipbop video doesn't start at 0. The old MSE code adjust the
+  // timestamps and ignore the audio track. The new one doesn't.
+  isfuzzy(v.duration, 1.696, 0.166, "Video has correct duration");
+  isfuzzy(v.currentTime, 1.696, 0.166, "Video has played to end");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_WaitingOnMissingDataEnded_mp4.html
+++ b/dom/media/mediasource/test/test_WaitingOnMissingDataEnded_mp4.html
@@ -10,17 +10,17 @@
 <pre id="test"><script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 runWithMSE(async (ms, el) => {
   el.controls = true;
   await once(ms, "sourceopen");
   ok(true, "Receive a sourceopen event");
-  el.addEventListener("ended", function() {
+  el.addEventListener("ended", () => {
     ok(false, "ended should never fire");
     SimpleTest.finish();
   });
   const videosb = ms.addSourceBuffer("video/mp4");
   await fetchAndLoad(videosb, "bipbop/bipbop_video", ["init"], ".mp4");
   await fetchAndLoad(videosb, "bipbop/bipbop_video", range(1, 5), ".m4s");
   await fetchAndLoad(videosb, "bipbop/bipbop_video", range(6, 8), ".m4s");
   is(el.buffered.length, 2, "discontinuous buffered range");
--- a/dom/media/mediasource/test/test_WebMTagsBeforeCluster.html
+++ b/dom/media/mediasource/test/test_WebMTagsBeforeCluster.html
@@ -9,41 +9,39 @@
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 addMSEPrefs(["media.mediasource.webm.enabled", true]);
 
-runWithMSE(function(ms, v) {
-  ms.addEventListener("sourceopen", function() {
-    const sb = ms.addSourceBuffer("video/webm");
+runWithMSE(async (ms, v) => {
+  await once(ms, "sourceopen");
+  const sb = ms.addSourceBuffer("video/webm");
 
-    fetchWithXHR("tags_before_cluster.webm", async function(arrayBuffer) {
-      info("- append buffer -");
-      sb.appendBuffer(new Uint8Array(arrayBuffer));
+  const arrayBuffer = await fetchWithXHR("tags_before_cluster.webm");
+  info("- append buffer -");
+  sb.appendBuffer(new Uint8Array(arrayBuffer));
 
-      info("- wait for metadata -");
-      await once(v, "loadedmetadata");
-
-      info("- wait for updateend -");
-      await once(sb, "updateend");
+  info("- wait for metadata -");
+  await once(v, "loadedmetadata");
 
-      info("- call end of stream -");
-      ms.endOfStream();
-      await once(ms, "sourceended");
+  info("- wait for updateend -");
+  await once(sb, "updateend");
 
-      info("- check buffered range -");
-      is(sb.buffered.length, 1, "buffered range is not empty.");
+  info("- call end of stream -");
+  ms.endOfStream();
+  await once(ms, "sourceended");
 
-      info("- video is playing -");
-      v.play();
-      await once(v, "timeupdate");
-      SimpleTest.finish();
-    });
-  });
+  info("- check buffered range -");
+  is(sb.buffered.length, 1, "buffered range is not empty.");
+
+  info("- video is playing -");
+  v.play();
+  await once(v, "timeupdate");
+  SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_basicScreenshare.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicScreenshare.html
@@ -22,15 +22,15 @@
     test = new PeerConnectionTest(options);
     var constraints = {
       video: {
          mozMediaSource: "screen",
          mediaSource: "screen"
       },
       fake: false
     };
-    test.setMediaConstraints([constraints], [constraints]);
+    test.setMediaConstraints([constraints], []);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_basicWindowshare.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicWindowshare.html
@@ -22,15 +22,15 @@
     test = new PeerConnectionTest(options);
     var constraints = {
       video: {
          mozMediaSource: "window",
          mediaSource: "window"
       },
       fake: false
     };
-    test.setMediaConstraints([constraints], [constraints]);
+    test.setMediaConstraints([constraints], []);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/gfx/webrender/doc/CLIPPING.md
+++ b/gfx/webrender/doc/CLIPPING.md
@@ -1,97 +1,53 @@
-# Clipping in WebRender
-
-The WebRender display list allows defining clips in two different ways. The
-first is specified directly on each display item and cannot be reused between
-items. The second is specified using the `SpecificDisplayItem::Clip` display item
-and can be reused between items as well as used to define scrollable regions.
-
-## Clips
-
-Clips are defined using the ClipRegion in both cases.
+# Clipping and Positioning in WebRender
 
-```rust
-pub struct ClipRegion {
-    pub main: LayoutRect,
-    pub complex: ItemRange<ComplexClip>,
-    pub image_mask: Option<ImageMask>,
-}
-```
-
-`main` defines a rectangular clip, while the other members make that rectangle
-smaller. `complex`, if it is not empty, defines the boundaries of a rounded
-rectangle. While `image_mask` defines the positioning, repetition, and data of
-a masking image.
+Each non-structural WebRender display list item has
+ * A `ClipId` of a positioning node
+ * A `ClipId` of a clip chain
+ * An item-specific rectangular clip rectangle
 
-## Item Clips
+The positioning node determines how that item is positioned. It's assumed that the
+positioning node and the item are children of the same reference frame. The clip
+chain determines how that item is clipped. This should be fully independent of
+how the node is positioned and items can be clipped by any `ClipChain` regardless
+of the reference frame of their member clips. Finally, the item-specific
+clipping rectangle is applied directly to the item and should never result in the
+creation of a clip mask itself.
 
-Item clips are simply a `ClipRegion` structure defined directly on the
-`DisplayItem`. The important thing to note about these clips is that all the
-coordinate in `ClipRegion` **are in the same coordinate space as the item
-itself**. This different than for clips defined by `SpecificDisplayItem::Clip`.
-
-## Clip Display Items
+# The `ClipScrollTree`
 
-Clip display items allow items to share clips in order to increase performance
-(shared clips are only rasterized once) and to allow for scrolling regions.
-Display items can be assigned a clip display item using the `clip_id`
-field. An item can be assigned any clip that is defined by its parent stacking
-context or any of the ancestors. The behavior of assigning an id outside of
-this hierarchy is undefined, because that situation does not occur in CSS
+The ClipScrollTree contains two sorts of elements. The first sort are nodes
+that affect the positioning of display primitives and other clips. These
+nodes can currently be reference frames, scroll frames, or sticky frames.
+The second sort of node is a clip node, which specifies some combination of
+a clipping rectangle, a collection of rounded clipping rectangles, and an
+optional image mask. These nodes are created and added to the display list
+during display list flattening.
 
-The clip display item has a `ClipRegion` as well as several other fields:
-
-```rust
-pub struct ClipDisplayItem {
-    pub id: ClipId,
-    pub parent_id: ClipId,
-}
-```
+# `ClipChain`s
 
-A `ClipDisplayItem` also gets a clip and bounds from the `BaseDisplayItem`. The
-clip is shared among all items that use this `ClipDisplayItem`. Critically,
-**coordinates in this ClipRegion are defined relative to the bounds of the
-ClipDisplayItem itself**. Additionally, WebRender only supports clip rectangles
-that start at the origin of the `BaseDisplayItem` bounds.
-
-The `BaseDisplayItem` bounds are known as the *content rectangle* of the clip. If
-the content rectangle is larger than *main* clipping rectangle, the clip will
-be a scrolling clip and participate in scrolling event capture and
-transformation.
-
-`ClipDisplayItems` are positioned, like all other items, relatively to their
-containing stacking context, yet they also live in a parallel tree defined by
-their `parent_id`. Child clips also include any clipping and scrolling that
-their ancestors do. In this way, a clip is positioned by a stacking context,
-but that position may be adjusted by any scroll offset of its parent clips.
-
-## Clip ids
+A `ClipChain` represents some collection of `ClipId`s of clipping nodes in the
+`ClipScrollTree`. The collection defines a clip mask which can be applied
+to a given display item primitive. A `ClipChain` is automatically created
+for every clipping node in the `ClipScrollTree` from the particular node
+and every ancestor clipping node. Additionally, the API exposes functionality
+to create a `ClipChain` given an arbitrary list of clipping nodes and the
+`ClipId` of a parent `ClipChain`. These custom `ClipChain`s will not take
+into account ancestor clipping nodes in the `ClipScrollTree` when clipping
+the item.
 
-All clips defined by a `ClipDisplayItem` have an id. It is useful to associate
-an external id with WebRender id in order to allow for tracking and updating
-scroll positions using the WebRender API. In order to make this as cheap as
-possible and to avoid having to create a `HashMap` to map between the two types
-of ids, the WebRender API provides an optional id argument in
-`DisplayListBuilder::define_clip`. The only types of ids that are supported
-here are those created with `ClipId::new(...)`. If this argument is not
-provided `define_clip` will return a uniquely generated id. Thus, the following
-should always be true:
+Important information about `ClipChain`s:
+ * The `ClipId`s in the list must refer to clipping nodes in the `ClipScrollTree`.
+   The list should not contain `ClipId` of positioning nodes or other `ClipChain`s.
+ * The `ClipId` of a clip node serves at the `ClipId` of that node's automatically
+   generated `ClipChain` as well.
+
+# The Future
 
-```rust
-let id = ClipId::new(my_internal_id, pipeline_id);
-let generated_id = define_clip(content_rect, clip, id);
-assert!(id == generated_id);
-```
-
-Note that calling `define_clip` multiple times with the same `clip_id` value
-results in undefined behaviour, and should be avoided. There is a debug mode
-assertion to catch this.
-
-## Pending changes
-1. Normalize the way that clipping coordinates are defined. Having them
-   specified in two different ways makes for a confusing API. This should be
-   fixed.  ([github issue](https://github.com/servo/webrender/issues/1090))
-
-1. It should be possible to specify more than one predefined clip for an item.
-   This is necessary for items that live in a scrolling frame, but are also
-   clipped by a clip that lives outside that frame.
-   ([github issue](https://github.com/servo/webrender/issues/840))
+In general, the clipping API is becoming more and more stable as it has become
+more flexible. Some ideas for improving the API further:
+ * Creating a distinction between ids that refer to `ClipScrollTree` nodes and individual
+  `ClipChain`s. This would make it harder to accidentally misuse the API, but require
+   `DisplayListBuilder::define_clip` to return two different types of ids.
+ * Separate out the clipping nodes from the positioning nodes. Perhaps WebRender only
+   needs an API where `ClipChains` are defined individually. This could potentially
+   prevent unnecessary `ClipChain` creation during display list flattening.
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -90,20 +90,20 @@ void brush_vs(
         local_rect = segment_rect;
         stretch_size = local_rect.size;
 
         // Note: Here we can assume that texels in device
         //       space map to local space, due to how border-image
         //       works. That assumption may not hold if this
         //       is used for other purposes in the future.
         if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X) != 0) {
-            stretch_size.x = texel_rect.z - texel_rect.x;
+            stretch_size.x = (texel_rect.z - texel_rect.x) / uDevicePixelRatio;
         }
         if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y) != 0) {
-            stretch_size.y = texel_rect.w - texel_rect.y;
+            stretch_size.y = (texel_rect.w - texel_rect.y) / uDevicePixelRatio;
         }
 
         uv0 = res.uv_rect.p0 + texel_rect.xy;
         uv1 = res.uv_rect.p0 + texel_rect.zw;
     }
 
     vUv.z = res.layer;
 
--- a/gfx/webrender/res/cs_border_segment.glsl
+++ b/gfx/webrender/res/cs_border_segment.glsl
@@ -8,34 +8,38 @@
 // are the colors of each edge making up the corner.
 flat varying vec4 vColor0[2];
 flat varying vec4 vColor1[2];
 
 // A point + tangent defining the line where the edge
 // transition occurs. Used for corners only.
 flat varying vec4 vColorLine;
 
-// x = segment, y = styles, z = edge axes
-flat varying ivec3 vConfig;
+// x = segment, y = styles, z = edge axes, w = clip mode
+flat varying ivec4 vConfig;
 
 // xy = Local space position of the clip center.
 // zw = Scale the rect origin by this to get the outer
 // corner from the segment rectangle.
 flat varying vec4 vClipCenter_Sign;
 
 // An outer and inner elliptical radii for border
 // corner clipping.
 flat varying vec4 vClipRadii;
 
 // Reference point for determine edge clip lines.
 flat varying vec4 vEdgeReference;
 
 // Stores widths/2 and widths/3 to save doing this in FS.
 flat varying vec4 vPartialWidths;
 
+// Clipping parameters for dot or dash.
+flat varying vec4 vClipParams1;
+flat varying vec4 vClipParams2;
+
 // Local space position
 varying vec2 vPos;
 
 #define SEGMENT_TOP_LEFT        0
 #define SEGMENT_TOP_RIGHT       1
 #define SEGMENT_BOTTOM_RIGHT    2
 #define SEGMENT_BOTTOM_LEFT     3
 #define SEGMENT_LEFT            4
@@ -50,25 +54,31 @@ varying vec2 vPos;
 #define BORDER_STYLE_DOTTED       3
 #define BORDER_STYLE_DASHED       4
 #define BORDER_STYLE_HIDDEN       5
 #define BORDER_STYLE_GROOVE       6
 #define BORDER_STYLE_RIDGE        7
 #define BORDER_STYLE_INSET        8
 #define BORDER_STYLE_OUTSET       9
 
+#define CLIP_NONE   0
+#define CLIP_DASH   1
+#define CLIP_DOT    2
+
 #ifdef WR_VERTEX_SHADER
 
 in vec2 aTaskOrigin;
 in vec4 aRect;
 in vec4 aColor0;
 in vec4 aColor1;
 in int aFlags;
 in vec2 aWidths;
 in vec2 aRadii;
+in vec4 aClipParams1;
+in vec4 aClipParams2;
 
 vec2 get_outer_corner_scale(int segment) {
     vec2 p;
 
     switch (segment) {
         case SEGMENT_TOP_LEFT:
             p = vec2(0.0, 0.0);
             break;
@@ -115,16 +125,17 @@ vec4[2] get_colors_for_side(vec4 color, 
 
     return result;
 }
 
 void main(void) {
     int segment = aFlags & 0xff;
     int style0 = (aFlags >> 8) & 0xff;
     int style1 = (aFlags >> 16) & 0xff;
+    int clip_mode = (aFlags >> 24) & 0xff;
 
     vec2 outer_scale = get_outer_corner_scale(segment);
     vec2 outer = outer_scale * aRect.zw;
     vec2 clip_sign = 1.0 - 2.0 * outer_scale;
 
     // Set some flags used by the FS to determine the
     // orientation of the two edges in this corner.
     ivec2 edge_axis;
@@ -156,30 +167,45 @@ void main(void) {
         case SEGMENT_LEFT:
         case SEGMENT_RIGHT:
         default:
             edge_axis = ivec2(0, 0);
             edge_reference = vec2(0.0);
             break;
     }
 
-    vConfig = ivec3(
+    vConfig = ivec4(
         segment,
         style0 | (style1 << 16),
-        edge_axis.x | (edge_axis.y << 16)
+        edge_axis.x | (edge_axis.y << 16),
+        clip_mode
     );
     vPartialWidths = vec4(aWidths / 3.0, aWidths / 2.0);
     vPos = aRect.zw * aPosition.xy;
 
     vColor0 = get_colors_for_side(aColor0, style0);
     vColor1 = get_colors_for_side(aColor1, style1);
     vClipCenter_Sign = vec4(outer + clip_sign * aRadii, clip_sign);
     vClipRadii = vec4(aRadii, max(aRadii - aWidths, 0.0));
     vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x);
     vEdgeReference = vec4(edge_reference, edge_reference + aWidths);
+    vClipParams1 = aClipParams1;
+    vClipParams2 = aClipParams2;
+
+    // For the case of dot clips, optimize the number of pixels that
+    // are hit to just include the dot itself.
+    // TODO(gw): We should do something similar in the future for
+    //           dash clips!
+    if (clip_mode == CLIP_DOT) {
+        // Expand by a small amount to allow room for AA around
+        // the dot.
+        float expanded_radius = aClipParams1.z + 2.0;
+        vPos = vClipParams1.xy + expanded_radius * (2.0 * aPosition.xy - 1.0);
+        vPos = clamp(vPos, vec2(0.0), aRect.zw);
+    }
 
     gl_Position = uTransform * vec4(aTaskOrigin + aRect.xy + vPos, 0.0, 1.0);
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 vec4 evaluate_color_for_style_in_corner(
     vec2 clip_relative_pos,
@@ -271,32 +297,56 @@ vec4 evaluate_color_for_style_in_edge(
             break;
     }
 
     return color[0];
 }
 
 void main(void) {
     float aa_range = compute_aa_range(vPos);
-    float d = -1.0;
     vec4 color0, color1;
 
     int segment = vConfig.x;
     ivec2 style = ivec2(vConfig.y & 0xffff, vConfig.y >> 16);
     ivec2 edge_axis = ivec2(vConfig.z & 0xffff, vConfig.z >> 16);
+    int clip_mode = vConfig.w;
 
     float mix_factor = 0.0;
     if (edge_axis.x != edge_axis.y) {
         float d_line = distance_to_line(vColorLine.xy, vColorLine.zw, vPos);
         mix_factor = distance_aa(aa_range, -d_line);
     }
 
     // Check if inside corner clip-region
     vec2 clip_relative_pos = vPos - vClipCenter_Sign.xy;
     bool in_clip_region = all(lessThan(vClipCenter_Sign.zw * clip_relative_pos, vec2(0.0)));
+    float d = -1.0;
+
+    switch (clip_mode) {
+        case CLIP_DOT: {
+            // Set clip distance based or dot position and radius.
+            d = distance(vClipParams1.xy, vPos) - vClipParams1.z;
+            break;
+        }
+        case CLIP_DASH: {
+            // Get SDF for the two line/tangent clip lines,
+            // do SDF subtract to get clip distance.
+            float d0 = distance_to_line(vClipParams1.xy,
+                                        vClipParams1.zw,
+                                        vPos);
+            float d1 = distance_to_line(vClipParams2.xy,
+                                        vClipParams2.zw,
+                                        vPos);
+            d = max(d0, -d1);
+            break;
+        }
+        case CLIP_NONE:
+        default:
+            break;
+    }
 
     if (in_clip_region) {
         float d_radii_a = distance_to_ellipse(clip_relative_pos, vClipRadii.xy, aa_range);
         float d_radii_b = distance_to_ellipse(clip_relative_pos, vClipRadii.zw, aa_range);
         float d_radii = max(d_radii_a, -d_radii_b);
         d = max(d, d_radii);
 
         color0 = evaluate_color_for_style_in_corner(
deleted file mode 100644
--- a/gfx/webrender/res/cs_clip_border.glsl
+++ /dev/null
@@ -1,204 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include shared,clip_shared
-
-varying vec3 vPos;
-
-flat varying vec2 vClipCenter;
-
-flat varying vec4 vPoint_Tangent0;
-flat varying vec4 vPoint_Tangent1;
-flat varying vec3 vDotParams;
-flat varying vec2 vAlphaMask;
-flat varying vec4 vTaskRect;
-
-#ifdef WR_VERTEX_SHADER
-
-in vec4 aDashOrDot0;
-in vec4 aDashOrDot1;
-
-// Matches BorderCorner enum in border.rs
-#define CORNER_TOP_LEFT     0
-#define CORNER_TOP_RIGHT    1
-#define CORNER_BOTTOM_LEFT  2
-#define CORNER_BOTTOM_RIGHT 3
-
-// Matches BorderCornerClipKind enum in border.rs
-#define CLIP_MODE_DASH      0
-#define CLIP_MODE_DOT       1
-
-// Header for a border corner clip.
-struct BorderCorner {
-    RectWithSize rect;
-    vec2 clip_center;
-    int corner;
-    int clip_mode;
-};
-
-BorderCorner fetch_border_corner(ivec2 address) {
-    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
-    return BorderCorner(RectWithSize(data[0].xy, data[0].zw),
-                        data[1].xy,
-                        int(data[1].z),
-                        int(data[1].w));
-}
-
-// Per-dash clip information.
-struct BorderClipDash {
-    vec4 point_tangent_0;
-    vec4 point_tangent_1;
-};
-
-BorderClipDash fetch_border_clip_dash(ivec2 address) {
-    return BorderClipDash(aDashOrDot0, aDashOrDot1);
-}
-
-// Per-dot clip information.
-struct BorderClipDot {
-    vec3 center_radius;
-};
-
-BorderClipDot fetch_border_clip_dot(ivec2 address) {
-    return BorderClipDot(aDashOrDot0.xyz);
-}
-
-void main(void) {
-    ClipMaskInstance cmi = fetch_clip_item();
-    ClipArea area = fetch_clip_area(cmi.render_task_address);
-    ClipScrollNode scroll_node = fetch_clip_scroll_node(cmi.scroll_node_id);
-
-    // Fetch the header information for this corner clip.
-    BorderCorner corner = fetch_border_corner(cmi.clip_data_address);
-    vClipCenter = corner.clip_center;
-
-    // Get local vertex position for the corner rect.
-    // TODO(gw): We could reduce the number of pixels written here by calculating a tight
-    // fitting bounding box of the dash itself like we do for dots below.
-    vec2 pos = corner.rect.p0 + aPosition.xy * corner.rect.size;
-
-    if (cmi.segment == 0) {
-        // The first segment is used to zero out the border corner.
-        vAlphaMask = vec2(0.0);
-        vDotParams = vec3(0.0);
-        vPoint_Tangent0 = vec4(1.0);
-        vPoint_Tangent1 = vec4(1.0);
-    } else {
-        vec2 sign_modifier;
-        switch (corner.corner) {
-            case CORNER_TOP_LEFT:
-                sign_modifier = vec2(-1.0);
-                break;
-            case CORNER_TOP_RIGHT:
-                sign_modifier = vec2(1.0, -1.0);
-                break;
-            case CORNER_BOTTOM_RIGHT:
-                sign_modifier = vec2(1.0);
-                break;
-            case CORNER_BOTTOM_LEFT:
-                sign_modifier = vec2(-1.0, 1.0);
-                break;
-            default:
-                sign_modifier = vec2(0.0);
-        };
-
-        switch (corner.clip_mode) {
-            case CLIP_MODE_DASH: {
-                // Fetch the information about this particular dash.
-                BorderClipDash dash = fetch_border_clip_dash(cmi.clip_data_address);
-                vPoint_Tangent0 = dash.point_tangent_0 * sign_modifier.xyxy;
-                vPoint_Tangent1 = dash.point_tangent_1 * sign_modifier.xyxy;
-                vDotParams = vec3(0.0);
-                vAlphaMask = vec2(0.0, 1.0);
-                break;
-            }
-            case CLIP_MODE_DOT: {
-                BorderClipDot cdot = fetch_border_clip_dot(cmi.clip_data_address);
-                vPoint_Tangent0 = vec4(1.0);
-                vPoint_Tangent1 = vec4(1.0);
-                vDotParams = vec3(cdot.center_radius.xy * sign_modifier, cdot.center_radius.z);
-                vAlphaMask = vec2(1.0, 1.0);
-
-                // Generate a tighter bounding rect for dots based on their position. Dot
-                // centers are given relative to clip center, so we need to move the dot
-                // rectangle into the clip space with an origin at the top left. First,
-                // we expand the radius slightly to ensure we get full coverage on all the pixels
-                // of the dots.
-                float expanded_radius = cdot.center_radius.z + 2.0;
-                pos = (vClipCenter + vDotParams.xy - vec2(expanded_radius));
-                pos += (aPosition.xy * vec2(expanded_radius * 2.0));
-                pos = clamp(pos, corner.rect.p0, corner.rect.p0 + corner.rect.size);
-
-                break;
-            }
-            default:
-                vPoint_Tangent0 = vPoint_Tangent1 = vec4(1.0);
-                vDotParams = vec3(0.0);
-                vAlphaMask = vec2(0.0);
-        }
-    }
-
-    // Transform to world pos
-    vec4 world_pos = scroll_node.transform * vec4(pos, 0.0, 1.0);
-    world_pos.xyz /= world_pos.w;
-
-    // Scale into device pixels.
-    vec2 device_pos = world_pos.xy * uDevicePixelRatio;
-
-    // Position vertex within the render task area.
-    vec2 task_rect_origin = area.common_data.task_rect.p0;
-    vec2 final_pos = device_pos - area.screen_origin + task_rect_origin;
-
-    // We pass the task rectangle to the fragment shader so that we can do one last clip
-    // in order to ensure that we don't draw outside the task rectangle.
-    vTaskRect.xy = task_rect_origin;
-    vTaskRect.zw = task_rect_origin + area.common_data.task_rect.size;
-
-    // Calculate the local space position for this vertex.
-    vec4 node_pos = get_node_pos(world_pos.xy, scroll_node);
-    vPos = node_pos.xyw;
-
-    gl_Position = uTransform * vec4(final_pos, 0.0, 1.0);
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-void main(void) {
-    vec2 local_pos = vPos.xy / vPos.z;
-
-    // Get local space position relative to the clip center.
-    vec2 clip_relative_pos = local_pos - vClipCenter;
-
-    // Get the signed distances to the two clip lines.
-    float d0 = distance_to_line(vPoint_Tangent0.xy,
-                                vPoint_Tangent0.zw,
-                                clip_relative_pos);
-    float d1 = distance_to_line(vPoint_Tangent1.xy,
-                                vPoint_Tangent1.zw,
-                                clip_relative_pos);
-
-    // Get AA widths based on zoom / scale etc.
-    float aa_range = compute_aa_range(local_pos);
-
-    // SDF subtract edges for dash clip
-    float dash_distance = max(d0, -d1);
-
-    // Get distance from dot.
-    float dot_distance = distance(clip_relative_pos, vDotParams.xy) - vDotParams.z;
-
-    // Select between dot/dash clip based on mode.
-    float d = mix(dash_distance, dot_distance, vAlphaMask.x);
-
-    // Apply AA.
-    d = distance_aa(aa_range, d);
-
-    // Completely mask out clip if zero'ing out the rect.
-    d = d * vAlphaMask.y;
-
-    // Make sure that we don't draw outside the task rectangle.
-    d = d * point_inside_rect(gl_FragCoord.xy, vTaskRect.xy, vTaskRect.zw);
-
-    oFragColor = vec4(d, 0.0, 0.0, 1.0);
-}
-#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_border_corner.glsl
+++ /dev/null
@@ -1,426 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include shared,prim_shared,shared_border,ellipse
-
-// Edge color transition
-flat varying vec4 vColor00;
-flat varying vec4 vColor01;
-flat varying vec4 vColor10;
-flat varying vec4 vColor11;
-flat varying vec4 vColorEdgeLine;
-
-// Border radius
-flat varying vec2 vClipCenter;
-flat varying vec4 vRadii0;
-flat varying vec4 vRadii1;
-flat varying vec2 vClipSign;
-flat varying vec4 vEdgeDistance;
-flat varying float vSDFSelect;
-
-flat varying float vIsBorderRadiusLessThanBorderWidth;
-
-// Border style
-flat varying float vAlphaSelect;
-
-varying vec2 vLocalPos;
-
-#ifdef WR_VERTEX_SHADER
-// Matches BorderCornerSide enum in border.rs
-#define SIDE_BOTH       0
-#define SIDE_FIRST      1
-#define SIDE_SECOND     2
-
-vec2 get_radii(vec2 radius, vec2 invalid) {
-    if (all(greaterThan(radius, vec2(0.0)))) {
-        return radius;
-    }
-
-    return invalid;
-}
-
-void set_radii(int style,
-               vec2 radii,
-               vec2 widths,
-               vec2 adjusted_widths) {
-    vRadii0.xy = get_radii(radii, 2.0 * widths);
-    vRadii0.zw = get_radii(radii - widths, -widths);
-
-    switch (style) {
-        case BORDER_STYLE_RIDGE:
-        case BORDER_STYLE_GROOVE:
-            vRadii1.xy = radii - adjusted_widths;
-            // See comment in default branch
-            vRadii1.zw = vec2(-100.0);
-            break;
-        case BORDER_STYLE_DOUBLE:
-            vRadii1.xy = get_radii(radii - adjusted_widths, -widths);
-            vRadii1.zw = get_radii(radii - widths + adjusted_widths, -widths);
-            break;
-        default:
-            // These aren't needed, so we set them to some reasonably large
-            // negative value so later computations will discard them. This
-            // avoids branches and numerical issues in the fragment shader.
-            vRadii1.xy = vec2(-100.0);
-            vRadii1.zw = vec2(-100.0);
-            break;
-    }
-}
-
-void set_edge_line(vec2 border_width,
-                   vec2 outer_corner,
-                   vec2 gradient_sign) {
-    vec2 gradient = border_width * gradient_sign;
-    vColorEdgeLine = vec4(outer_corner, vec2(-gradient.y, gradient.x));
-}
-
-void write_color(vec4 color0, vec4 color1, int style, vec2 delta, int instance_kind) {
-    vec4 modulate;
-
-    switch (style) {
-        case BORDER_STYLE_GROOVE:
-            modulate = vec4(1.0 - 0.3 * delta.x,
-                            1.0 + 0.3 * delta.x,
-                            1.0 - 0.3 * delta.y,
-                            1.0 + 0.3 * delta.y);
-
-            break;
-        case BORDER_STYLE_RIDGE:
-            modulate = vec4(1.0 + 0.3 * delta.x,
-                            1.0 - 0.3 * delta.x,
-                            1.0 + 0.3 * delta.y,
-                            1.0 - 0.3 * delta.y);
-            break;
-        default:
-            modulate = vec4(1.0);
-            break;
-    }
-
-    // Optionally mask out one side of the border corner,
-    // depending on the instance kind.
-    switch (instance_kind) {
-        case SIDE_FIRST:
-            color0.a = 0.0;
-            break;
-        case SIDE_SECOND:
-            color1.a = 0.0;
-            break;
-        default: break;
-    }
-
-    vColor00 = vec4(clamp(color0.rgb * modulate.x, vec3(0.0), vec3(color0.a)), color0.a);
-    vColor01 = vec4(clamp(color0.rgb * modulate.y, vec3(0.0), vec3(color0.a)), color0.a);
-    vColor10 = vec4(clamp(color1.rgb * modulate.z, vec3(0.0), vec3(color1.a)), color1.a);
-    vColor11 = vec4(clamp(color1.rgb * modulate.w, vec3(0.0), vec3(color1.a)), color1.a);
-}
-
-int select_style(int color_select, vec2 fstyle) {
-    ivec2 style = ivec2(fstyle);
-
-    switch (color_select) {
-        case SIDE_BOTH:
-        {
-            // TODO(gw): A temporary hack! While we don't support
-            //           border corners that have dots or dashes
-            //           with another style, pretend they are solid
-            //           border corners.
-            bool has_dots = style.x == BORDER_STYLE_DOTTED ||
-                            style.y == BORDER_STYLE_DOTTED;
-            bool has_dashes = style.x == BORDER_STYLE_DASHED ||
-                              style.y == BORDER_STYLE_DASHED;
-            if (style.x != style.y && (has_dots || has_dashes))
-                return BORDER_STYLE_SOLID;
-            return style.x;
-        }
-        case SIDE_FIRST:
-            return style.x;
-        case SIDE_SECOND:
-            return style.y;
-        default:
-            return 0;
-    }
-}
-
-void main(void) {
-    Primitive prim = load_primitive();
-    Border border = fetch_border(prim.specific_prim_address);
-    int sub_part = prim.user_data0;
-    BorderCorners corners = get_border_corners(border, prim.local_rect);
-
-    vec2 p0, p1;
-
-    // TODO(gw): We'll need to pass through multiple styles
-    //           once we support style transitions per corner.
-    int style;
-    vec4 edge_distances;
-    vec4 color0, color1;
-    vec2 color_delta;
-    vec4 edge_mask;
-
-    // TODO(gw): Now that all border styles are supported, the
-    //           statement below can be tidied up quite a bit.
-
-    switch (sub_part) {
-        case 0: {
-            p0 = corners.tl_outer;
-            p1 = corners.tl_inner;
-            color0 = border.colors[0];
-            color1 = border.colors[1];
-            vClipCenter = corners.tl_outer + border.radii[0].xy;
-            vClipSign = vec2(1.0);
-            style = select_style(prim.user_data1, border.style.yx);
-            vec4 adjusted_widths = get_effective_border_widths(border, style);
-            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
-            set_radii(style,
-                      border.radii[0].xy,
-                      border.widths.xy,
-                      adjusted_widths.xy);
-            set_edge_line(border.widths.xy,
-                          corners.tl_outer,
-                          vec2(1.0, 1.0));
-            edge_distances = vec4(p0 + adjusted_widths.xy,
-                                  p0 + inv_adjusted_widths.xy);
-            color_delta = vec2(1.0);
-            vIsBorderRadiusLessThanBorderWidth = any(lessThan(border.radii[0].xy,
-                                                              border.widths.xy)) ? 1.0 : 0.0;
-            edge_mask = vec4(1.0, 1.0, 0.0, 0.0);
-            break;
-        }
-        case 1: {
-            p0 = vec2(corners.tr_inner.x, corners.tr_outer.y);
-            p1 = vec2(corners.tr_outer.x, corners.tr_inner.y);
-            color0 = border.colors[1];
-            color1 = border.colors[2];
-            vClipCenter = corners.tr_outer + vec2(-border.radii[0].z, border.radii[0].w);
-            vClipSign = vec2(-1.0, 1.0);
-            style = select_style(prim.user_data1, border.style.zy);
-            vec4 adjusted_widths = get_effective_border_widths(border, style);
-            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
-            set_radii(style,
-                      border.radii[0].zw,
-                      border.widths.zy,
-                      adjusted_widths.zy);
-            set_edge_line(border.widths.zy,
-                          corners.tr_outer,
-                          vec2(-1.0, 1.0));
-            edge_distances = vec4(p1.x - adjusted_widths.z,
-                                  p0.y + adjusted_widths.y,
-                                  p1.x - border.widths.z + adjusted_widths.z,
-                                  p0.y + inv_adjusted_widths.y);
-            color_delta = vec2(1.0, -1.0);
-            vIsBorderRadiusLessThanBorderWidth = any(lessThan(border.radii[0].zw,
-                                                              border.widths.zy)) ? 1.0 : 0.0;
-            edge_mask = vec4(0.0, 1.0, 1.0, 0.0);
-            break;
-        }
-        case 2: {
-            p0 = corners.br_inner;
-            p1 = corners.br_outer;
-            color0 = border.colors[2];
-            color1 = border.colors[3];
-            vClipCenter = corners.br_outer - border.radii[1].xy;
-            vClipSign = vec2(-1.0, -1.0);
-            style = select_style(prim.user_data1, border.style.wz);
-            vec4 adjusted_widths = get_effective_border_widths(border, style);
-            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
-            set_radii(style,
-                      border.radii[1].xy,
-                      border.widths.zw,
-                      adjusted_widths.zw);
-            set_edge_line(border.widths.zw,
-                          corners.br_outer,
-                          vec2(-1.0, -1.0));
-            edge_distances = vec4(p1.x - adjusted_widths.z,
-                                  p1.y - adjusted_widths.w,
-                                  p1.x - border.widths.z + adjusted_widths.z,
-                                  p1.y - border.widths.w + adjusted_widths.w);
-            color_delta = vec2(-1.0);
-            vIsBorderRadiusLessThanBorderWidth = any(lessThan(border.radii[1].xy,
-                                                              border.widths.zw)) ? 1.0 : 0.0;
-            edge_mask = vec4(0.0, 0.0, 1.0, 1.0);
-            break;
-        }
-        case 3: {
-            p0 = vec2(corners.bl_outer.x, corners.bl_inner.y);
-            p1 = vec2(corners.bl_inner.x, corners.bl_outer.y);
-            color0 = border.colors[3];
-            color1 = border.colors[0];
-            vClipCenter = corners.bl_outer + vec2(border.radii[1].z, -border.radii[1].w);
-            vClipSign = vec2(1.0, -1.0);
-            style = select_style(prim.user_data1, border.style.xw);
-            vec4 adjusted_widths = get_effective_border_widths(border, style);
-            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
-            set_radii(style,
-                      border.radii[1].zw,
-                      border.widths.xw,
-                      adjusted_widths.xw);
-            set_edge_line(border.widths.xw,
-                          corners.bl_outer,
-                          vec2(1.0, -1.0));
-            edge_distances = vec4(p0.x + adjusted_widths.x,
-                                  p1.y - adjusted_widths.w,
-                                  p0.x + inv_adjusted_widths.x,
-                                  p1.y - border.widths.w + adjusted_widths.w);
-            color_delta = vec2(-1.0, 1.0);
-            vIsBorderRadiusLessThanBorderWidth = any(lessThan(border.radii[1].zw,
-                                                              border.widths.xw)) ? 1.0 : 0.0;
-            edge_mask = vec4(1.0, 0.0, 0.0, 1.0);
-            break;
-        }
-        default:
-            p0 = p1 = vec2(0.0);
-            color0 = color1 = vec4(1.0);
-            vClipCenter = vClipSign = vec2(0.0);
-            style = 0;
-            edge_distances = edge_mask = vec4(0.0);
-            color_delta = vec2(0.0);
-            vIsBorderRadiusLessThanBorderWidth = 0.0;
-    }
-
-    switch (style) {
-        case BORDER_STYLE_DOUBLE: {
-            vEdgeDistance = edge_distances;
-            vAlphaSelect = 0.0;
-            vSDFSelect = 0.0;
-            break;
-        }
-        case BORDER_STYLE_GROOVE:
-        case BORDER_STYLE_RIDGE:
-            vEdgeDistance = vec4(edge_distances.xy, 0.0, 0.0);
-            vAlphaSelect = 1.0;
-            vSDFSelect = 1.0;
-            break;
-        case BORDER_STYLE_DOTTED:
-            // Disable normal clip radii for dotted corners, since
-            // all the clipping is handled by the clip mask.
-            vClipSign = vec2(0.0);
-            vEdgeDistance = vec4(0.0);
-            vAlphaSelect = 1.0;
-            vSDFSelect = 0.0;
-            break;
-        default: {
-            vEdgeDistance = vec4(0.0);
-            vAlphaSelect = 1.0;
-            vSDFSelect = 0.0;
-            break;
-        }
-    }
-
-    write_color(color0, color1, style, color_delta, prim.user_data1);
-
-    RectWithSize segment_rect;
-    segment_rect.p0 = p0;
-    segment_rect.size = p1 - p0;
-
-#ifdef WR_FEATURE_TRANSFORM
-    VertexInfo vi = write_transform_vertex(segment_rect,
-                                           prim.local_rect,
-                                           prim.local_clip_rect,
-                                           edge_mask,
-                                           prim.z,
-                                           prim.scroll_node,
-                                           prim.task,
-                                           true);
-#else
-    VertexInfo vi = write_vertex(segment_rect,
-                                 prim.local_clip_rect,
-                                 prim.z,
-                                 prim.scroll_node,
-                                 prim.task,
-                                 prim.local_rect);
-#endif
-
-    vLocalPos = vi.local_pos;
-    write_clip(vi.screen_pos, prim.clip_area);
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-void main(void) {
-    float alpha = 1.0;
-#ifdef WR_FEATURE_TRANSFORM
-    alpha = init_transform_fs(vLocalPos);
-#endif
-
-    alpha *= do_clip();
-
-    float aa_range = compute_aa_range(vLocalPos);
-
-    float distance_for_color;
-    float color_mix_factor;
-
-    // Only apply the clip AA if inside the clip region. This is
-    // necessary for correctness when the border width is greater
-    // than the border radius.
-    if (vIsBorderRadiusLessThanBorderWidth == 0.0 ||
-        all(lessThan(vLocalPos * vClipSign, vClipCenter * vClipSign))) {
-        vec2 p = vLocalPos - vClipCenter;
-
-        // The coordinate system is snapped to pixel boundaries. To sample the distance,
-        // however, we are interested in the center of the pixels which introduces an
-        // error of half a pixel towards the exterior of the curve (See issue #1750).
-        // This error is corrected by offsetting the distance by half a device pixel.
-        // This not entirely correct: it leaves an error that varries between
-        // 0 and (sqrt(2) - 1)/2 = 0.2 pixels but it is hardly noticeable and is better
-        // than the constant sqrt(2)/2 px error without the correction.
-        // To correct this exactly we would need to offset p by half a pixel in the
-        // direction of the center of the ellipse (a different offset for each corner).
-
-        // Get signed distance from the inner/outer clips.
-        float d0 = distance_to_ellipse(p, vRadii0.xy, aa_range);
-        float d1 = distance_to_ellipse(p, vRadii0.zw, aa_range);
-        float d2 = distance_to_ellipse(p, vRadii1.xy, aa_range);
-        float d3 = distance_to_ellipse(p, vRadii1.zw, aa_range);
-
-        // SDF subtract main radii
-        float d_main = max(d0, -d1);
-
-        // SDF subtract inner radii (double style borders)
-        float d_inner = max(d2, -d3);
-
-        // Select how to combine the SDF based on border style.
-        float d = mix(max(d_main, -d_inner), d_main, vSDFSelect);
-
-        // Only apply AA to fragments outside the signed distance field.
-        alpha = min(alpha, distance_aa(aa_range, d));
-
-        // Get the groove/ridge mix factor.
-        color_mix_factor = distance_aa(aa_range, d2);
-    } else {
-        // Handle the case where the fragment is outside the clip
-        // region in a corner. This occurs when border width is
-        // greater than border radius.
-
-        // Get linear distances along horizontal and vertical edges.
-        vec2 d0 = vClipSign.xx * (vLocalPos.xx - vEdgeDistance.xz);
-        vec2 d1 = vClipSign.yy * (vLocalPos.yy - vEdgeDistance.yw);
-        // Apply union to get the outer edge signed distance.
-        float da = min(d0.x, d1.x);
-        // Apply intersection to get the inner edge signed distance.
-        float db = max(-d0.y, -d1.y);
-        // Apply union to get both edges.
-        float d = min(da, db);
-        // Select fragment on/off based on signed distance.
-        // No AA here, since we know we're on a straight edge
-        // and the width is rounded to a whole CSS pixel.
-        alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
-
-        // Get the groove/ridge mix factor.
-        // TODO(gw): Support AA for groove/ridge border edge with transforms.
-        color_mix_factor = mix(0.0, 1.0, da > 0.0);
-    }
-
-    // Mix inner/outer color.
-    vec4 color0 = mix(vColor00, vColor01, color_mix_factor);
-    vec4 color1 = mix(vColor10, vColor11, color_mix_factor);
-
-    // Select color based on side of line. Get distance from the
-    // reference line, and then apply AA along the edge.
-    float ld = distance_to_line(vColorEdgeLine.xy, vColorEdgeLine.zw, vLocalPos);
-    float m = distance_aa(aa_range, -ld);
-    vec4 color = mix(color0, color1, m);
-
-    oFragColor = color * alpha;
-}
-#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_border_edge.glsl
+++ /dev/null
@@ -1,334 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include shared,prim_shared,shared_border
-
-flat varying vec4 vColor0;
-flat varying vec4 vColor1;
-flat varying vec2 vEdgeDistance;
-flat varying float vAxisSelect;
-flat varying float vAlphaSelect;
-flat varying vec4 vClipParams;
-flat varying float vClipSelect;
-
-varying vec2 vLocalPos;
-
-#ifdef WR_VERTEX_SHADER
-void write_edge_distance(float p0,
-                         float original_width,
-                         float adjusted_width,
-                         float style,
-                         float axis_select,
-                         float sign_adjust) {
-    switch (int(style)) {
-        case BORDER_STYLE_DOUBLE:
-            vEdgeDistance = vec2(p0 + adjusted_width,
-                                 p0 + original_width - adjusted_width);
-            break;
-        case BORDER_STYLE_GROOVE:
-        case BORDER_STYLE_RIDGE:
-            vEdgeDistance = vec2(p0 + adjusted_width, sign_adjust);
-            break;
-        default:
-            vEdgeDistance = vec2(0.0);
-            break;
-    }
-
-    vAxisSelect = axis_select;
-}
-
-void write_alpha_select(float style) {
-    switch (int(style)) {
-        case BORDER_STYLE_DOUBLE:
-            vAlphaSelect = 0.0;
-            break;
-        default:
-            vAlphaSelect = 1.0;
-            break;
-    }
-}
-
-// write_color function is duplicated to work around a Mali-T880 GPU driver program link error.
-// See https://github.com/servo/webrender/issues/1403 for more info.
-// TODO: convert back to a single function once the driver issues are resolved, if ever.
-void write_color0(vec4 color, float style, bool flip) {
-    vec2 modulate;
-
-    switch (int(style)) {
-        case BORDER_STYLE_GROOVE:
-        {
-            modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
-            break;
-        }
-        case BORDER_STYLE_RIDGE:
-        {
-            modulate = flip ? vec2(0.7, 1.3) : vec2(1.3, 0.7);
-            break;
-        }
-        default:
-            modulate = vec2(1.0);
-            break;
-    }
-
-    vColor0 = vec4(min(color.rgb * modulate.x, vec3(color.a)), color.a);
-}
-
-void write_color1(vec4 color, float style, bool flip) {
-    vec2 modulate;
-
-    switch (int(style)) {
-        case BORDER_STYLE_GROOVE:
-        {
-            modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
-            break;
-        }
-        case BORDER_STYLE_RIDGE:
-        {
-            modulate = flip ? vec2(0.7, 1.3) : vec2(1.3, 0.7);
-            break;
-        }
-        default:
-            modulate = vec2(1.0);
-            break;
-    }
-
-    vColor1 = vec4(min(color.rgb * modulate.y, vec3(color.a)), color.a);
-}
-
-void write_clip_params(float style,
-                       float border_width,
-                       float edge_length,
-                       float edge_offset,
-                       float center_line,
-                       bool start_corner_has_radius,
-                       bool end_corner_has_radius) {
-    // x = offset
-    // y = dash on + off length
-    // z = dash length
-    // w = center line of edge cross-axis (for dots only)
-    switch (int(style)) {
-        case BORDER_STYLE_DASHED: {
-            float desired_dash_length = border_width * 3.0;
-            // Consider half total length since there is an equal on/off for each dash.
-            float dash_count = ceil(0.5 * edge_length / desired_dash_length);
-            float dash_length = 0.5 * edge_length / dash_count;
-            vClipParams = vec4(edge_offset - 0.5 * dash_length,
-                               2.0 * dash_length,
-                               dash_length,
-                               0.0);
-            vClipSelect = 0.0;
-            break;
-        }
-        case BORDER_STYLE_DOTTED: {
-            float diameter = border_width;
-            float radius = 0.5 * diameter;
-
-            // If this edge connects a corner with a radius to a corner without a radius, we
-            // act as if we have space for one more dot. This will position the dots so that
-            // there is a half dot on one of the ends.
-            float full_edge_length = edge_length +
-                (float(start_corner_has_radius ^^ end_corner_has_radius) * diameter);
-
-            float dot_count = ceil(0.5 * full_edge_length / diameter);
-            float empty_space = full_edge_length - (dot_count * diameter);
-            float distance_between_centers = diameter + empty_space / dot_count;
-
-            // If the starting corner has a radius, we want to position the half dot right
-            // against that edge.
-            float starting_offset =
-                edge_offset + radius + (-diameter * float(start_corner_has_radius));
-
-            vClipParams = vec4(starting_offset,
-                               distance_between_centers,
-                               radius,
-                               center_line);
-
-            vClipSelect = 1.0;
-            break;
-        }
-        default:
-            vClipParams = vec4(1.0);
-            vClipSelect = 0.0;
-            break;
-    }
-}
-
-bool hasRadius(vec2 radius) {
-    return any(notEqual(radius, vec2(0.0)));
-}
-
-void main(void) {
-    Primitive prim = load_primitive();
-    Border border = fetch_border(prim.specific_prim_address);
-    int sub_part = prim.user_data0;
-    BorderCorners corners = get_border_corners(border, prim.local_rect);
-    vec4 color = border.colors[sub_part];
-
-    // TODO(gw): Now that all border styles are supported, the
-    //           statement below can be tidied up quite a bit.
-
-    float style;
-    bool color_flip;
-
-    RectWithSize segment_rect;
-    vec4 edge_mask;
-
-    switch (sub_part) {
-        case 0: {
-            segment_rect.p0 = vec2(corners.tl_outer.x, corners.tl_inner.y);
-            segment_rect.size = vec2(border.widths.x, corners.bl_inner.y - corners.tl_inner.y);
-            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.x));
-            write_edge_distance(segment_rect.p0.x, border.widths.x, adjusted_widths.x, border.style.x, 0.0, 1.0);
-            style = border.style.x;
-            color_flip = false;
-            write_clip_params(border.style.x,
-                              border.widths.x,
-                              segment_rect.size.y,
-                              segment_rect.p0.y,
-                              segment_rect.p0.x + 0.5 * segment_rect.size.x,
-                              hasRadius(border.radii[0].xy),
-                              hasRadius(border.radii[1].zw));
-            edge_mask = vec4(1.0, 0.0, 1.0, 0.0);
-            break;
-        }
-        case 1: {
-            segment_rect.p0 = vec2(corners.tl_inner.x, corners.tl_outer.y);
-            segment_rect.size = vec2(corners.tr_inner.x - corners.tl_inner.x, border.widths.y);
-            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.y));
-            write_edge_distance(segment_rect.p0.y, border.widths.y, adjusted_widths.y, border.style.y, 1.0, 1.0);
-            style = border.style.y;
-            color_flip = false;
-            write_clip_params(border.style.y,
-                              border.widths.y,
-                              segment_rect.size.x,
-                              segment_rect.p0.x,
-                              segment_rect.p0.y + 0.5 * segment_rect.size.y,
-                              hasRadius(border.radii[0].xy),
-                              hasRadius(border.radii[0].zw));
-            edge_mask = vec4(0.0, 1.0, 0.0, 1.0);
-            break;
-        }
-        case 2: {
-            segment_rect.p0 = vec2(corners.tr_outer.x - border.widths.z, corners.tr_inner.y);
-            segment_rect.size = vec2(border.widths.z, corners.br_inner.y - corners.tr_inner.y);
-            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.z));
-            write_edge_distance(segment_rect.p0.x, border.widths.z, adjusted_widths.z, border.style.z, 0.0, -1.0);
-            style = border.style.z;
-            color_flip = true;
-            write_clip_params(border.style.z,
-                              border.widths.z,
-                              segment_rect.size.y,
-                              segment_rect.p0.y,
-                              segment_rect.p0.x + 0.5 * segment_rect.size.x,
-                              hasRadius(border.radii[0].zw),
-                              hasRadius(border.radii[1].xy));
-            edge_mask = vec4(1.0, 0.0, 1.0, 0.0);
-            break;
-        }
-        case 3: {
-            segment_rect.p0 = vec2(corners.bl_inner.x, corners.bl_outer.y - border.widths.w);
-            segment_rect.size = vec2(corners.br_inner.x - corners.bl_inner.x, border.widths.w);
-            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.w));
-            write_edge_distance(segment_rect.p0.y, border.widths.w, adjusted_widths.w, border.style.w, 1.0, -1.0);
-            style = border.style.w;
-            color_flip = true;
-            write_clip_params(border.style.w,
-                              border.widths.w,
-                              segment_rect.size.x,
-                              segment_rect.p0.x,
-                              segment_rect.p0.y + 0.5 * segment_rect.size.y,
-                              hasRadius(border.radii[1].zw),
-                              hasRadius(border.radii[1].xy));
-            edge_mask = vec4(0.0, 1.0, 0.0, 1.0);
-            break;
-        }
-        default:
-            segment_rect.p0 = segment_rect.size = vec2(0.0);
-            style = 0.0;
-            color_flip = false;
-            edge_mask = vec4(0.0);
-    }
-
-    write_alpha_select(style);
-    write_color0(color, style, color_flip);
-    write_color1(color, style, color_flip);
-
-#ifdef WR_FEATURE_TRANSFORM
-    VertexInfo vi = write_transform_vertex(segment_rect,
-                                           prim.local_rect,
-                                           prim.local_clip_rect,
-                                           edge_mask,
-                                           prim.z,
-                                           prim.scroll_node,
-                                           prim.task,
-                                           true);
-#else
-    VertexInfo vi = write_vertex(segment_rect,
-                                 prim.local_clip_rect,
-                                 prim.z,
-                                 prim.scroll_node,
-                                 prim.task,
-                                 prim.local_rect);
-#endif
-
-    vLocalPos = vi.local_pos;
-    write_clip(vi.screen_pos, prim.clip_area);
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-void main(void) {
-    float alpha = 1.0;
-#ifdef WR_FEATURE_TRANSFORM
-    alpha = init_transform_fs(vLocalPos);
-#endif
-
-    alpha *= do_clip();
-
-    // Find the appropriate distance to apply the step over.
-    float aa_range = compute_aa_range(vLocalPos);
-
-    // Applies the math necessary to draw a style: double
-    // border. In the case of a solid border, the vertex
-    // shader sets interpolator values that make this have
-    // no effect.
-
-    // Select the x/y coord, depending on which axis this edge is.
-    vec2 pos = mix(vLocalPos.xy, vLocalPos.yx, vAxisSelect);
-
-    // Get signed distance from each of the inner edges.
-    float d0 = pos.x - vEdgeDistance.x;
-    float d1 = vEdgeDistance.y - pos.x;
-
-    // SDF union to select both outer edges.
-    float d = min(d0, d1);
-
-    // Select fragment on/off based on signed distance.
-    // No AA here, since we know we're on a straight edge
-    // and the width is rounded to a whole CSS pixel.
-    alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
-
-    // Mix color based on first distance.
-    // TODO(gw): Support AA for groove/ridge border edge with transforms.
-    vec4 color = mix(vColor0, vColor1, bvec4(d0 * vEdgeDistance.y > 0.0));
-
-    // Apply dashing / dotting parameters.
-
-    // Get the main-axis position relative to closest dot or dash.
-    float x = mod(pos.y - vClipParams.x, vClipParams.y);
-
-    // Calculate dash alpha (on/off) based on dash length
-    float dash_alpha = step(x, vClipParams.z);
-
-    // Get the dot alpha
-    vec2 dot_relative_pos = vec2(x, pos.x) - vClipParams.zw;
-    float dot_distance = length(dot_relative_pos) - vClipParams.z;
-    float dot_alpha = distance_aa(aa_range, dot_distance);
-
-    // Select between dot/dash alpha based on clip mode.
-    alpha = min(alpha, mix(dash_alpha, dot_alpha, vClipSelect));
-
-    oFragColor = color * alpha;
-}
-#endif
deleted file mode 100644
--- a/gfx/webrender/res/shared_border.glsl
+++ /dev/null
@@ -1,98 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-// Border styles as defined in webrender_api/types.rs
-#define BORDER_STYLE_NONE         0
-#define BORDER_STYLE_SOLID        1
-#define BORDER_STYLE_DOUBLE       2
-#define BORDER_STYLE_DOTTED       3
-#define BORDER_STYLE_DASHED       4
-#define BORDER_STYLE_HIDDEN       5
-#define BORDER_STYLE_GROOVE       6
-#define BORDER_STYLE_RIDGE        7
-#define BORDER_STYLE_INSET        8
-#define BORDER_STYLE_OUTSET       9
-
-#ifdef WR_VERTEX_SHADER
-
-struct Border {
-    vec4 style;
-    vec4 widths;
-    vec4 colors[4];
-    vec4 radii[2];
-};
-
-struct BorderCorners {
-    vec2 tl_outer;
-    vec2 tl_inner;
-    vec2 tr_outer;
-    vec2 tr_inner;
-    vec2 br_outer;
-    vec2 br_inner;
-    vec2 bl_outer;
-    vec2 bl_inner;
-};
-
-vec4 get_effective_border_widths(Border border, int style) {
-    switch (style) {
-        case BORDER_STYLE_DOUBLE:
-            // Calculate the width of a border segment in a style: double
-            // border. Round to the nearest CSS pixel.
-
-            // The CSS spec doesn't define what width each of the segments
-            // in a style: double border should be. It only says that the
-            // sum of the segments should be equal to the total border
-            // width. We pick to make the segments (almost) equal thirds
-            // for now - we can adjust this if we find other browsers pick
-            // different values in some cases.
-            // SEE: https://drafts.csswg.org/css-backgrounds-3/#double
-            return max(floor(0.5 + border.widths / 3.0), 1.0);
-        case BORDER_STYLE_GROOVE:
-        case BORDER_STYLE_RIDGE:
-            return floor(0.5 + border.widths * 0.5);
-        default:
-            return border.widths;
-    }
-}
-
-Border fetch_border(int address) {
-    vec4 data[8] = fetch_from_resource_cache_8(address);
-    return Border(data[0], data[1],
-                  vec4[4](data[2], data[3], data[4], data[5]),
-                  vec4[2](data[6], data[7]));
-}
-
-BorderCorners get_border_corners(Border border, RectWithSize local_rect) {
-    vec2 tl_outer = local_rect.p0;
-    vec2 tl_inner = tl_outer + vec2(max(border.radii[0].x, border.widths.x),
-                                    max(border.radii[0].y, border.widths.y));
-
-    vec2 tr_outer = vec2(local_rect.p0.x + local_rect.size.x,
-                         local_rect.p0.y);
-    vec2 tr_inner = tr_outer + vec2(-max(border.radii[0].z, border.widths.z),
-                                    max(border.radii[0].w, border.widths.y));
-
-    vec2 br_outer = vec2(local_rect.p0.x + local_rect.size.x,
-                         local_rect.p0.y + local_rect.size.y);
-    vec2 br_inner = br_outer - vec2(max(border.radii[1].x, border.widths.z),
-                                    max(border.radii[1].y, border.widths.w));
-
-    vec2 bl_outer = vec2(local_rect.p0.x,
-                         local_rect.p0.y + local_rect.size.y);
-    vec2 bl_inner = bl_outer + vec2(max(border.radii[1].z, border.widths.x),
-                                    -max(border.radii[1].w, border.widths.w));
-
-    return BorderCorners(
-        tl_outer,
-        tl_inner,
-        tr_outer,
-        tr_inner,
-        br_outer,
-        br_inner,
-        bl_outer,
-        bl_inner
-    );
-}
-
-#endif
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -1,33 +1,32 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, ClipMode, DeviceIntRect, DeviceIntSize};
 use api::{DeviceUintRect, DeviceUintPoint, ExternalImageType, FilterOp, ImageRendering, LayoutRect};
 use api::{DeviceIntPoint, YuvColorSpace, YuvFormat};
 use api::{LayoutToWorldTransform, WorldPixel};
-use border::{BorderCornerInstance, BorderCornerSide, BorderEdgeKind};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::{CoordinateSystemId};
 use euclid::{TypedTransform3D, vec3};
 use glyph_rasterizer::GlyphFormat;
-use gpu_cache::{GpuCache, GpuCacheAddress};
-use gpu_types::{BrushFlags, BrushInstance, ClipChainRectIndex, ClipMaskBorderCornerDotDash};
+use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheAddress};
+use gpu_types::{BrushFlags, BrushInstance, ClipChainRectIndex};
 use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex, CompositePrimitiveInstance};
 use gpu_types::{PrimitiveInstance, RasterizationSpace, SimplePrimitiveInstance, ZBufferId};
 use gpu_types::ZBufferIdGenerator;
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Polygon, Splitter};
-use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, CachedGradient, DeferredResolve};
+use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
 use prim_store::{EdgeAaSegmentMask, ImageSource, PictureIndex, PrimitiveIndex, PrimitiveKind};
 use prim_store::{PrimitiveMetadata, PrimitiveRun, PrimitiveStore, VisibleGradientTile};
-use prim_store::{BorderSource, CachedGradientIndex};
+use prim_store::{BorderSource};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind};
 use renderer::{BLOCKS_PER_UV_RECT, ShaderColorMode};
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use scene::FilterOpHelpers;
 use std::{usize, f32, i32};
 use tiling::{RenderTargetContext};
 use util::{MatrixHelpers, TransformedRectKind};
@@ -36,18 +35,16 @@ use util::{MatrixHelpers, TransformedRec
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff);
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum TransformBatchKind {
     TextRun(GlyphFormat),
-    BorderCorner,
-    BorderEdge,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BrushBatchKind {
     Solid,
     Image(ImageBufferKind),
@@ -317,16 +314,34 @@ impl BatchList {
             BlendMode::SubpixelWithBgColor |
             BlendMode::SubpixelDualSource => {
                 self.alpha_batch_list
                     .get_suitable_batch(key, task_relative_bounding_rect)
             }
         }
     }
 
+    // Remove any batches that were added but didn't get any instances
+    // added to them.
+    fn remove_unused_batches(&mut self) {
+        if self.opaque_batch_list
+               .batches
+               .last()
+               .map_or(false, |batch| batch.instances.is_empty()) {
+            self.opaque_batch_list.batches.pop().unwrap();
+        }
+
+        if self.alpha_batch_list
+               .batches
+               .last()
+               .map_or(false, |batch| batch.instances.is_empty()) {
+            self.alpha_batch_list.batches.pop().unwrap();
+        }
+    }
+
     fn finalize(&mut self) {
         self.opaque_batch_list.finalize()
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveBatch {
@@ -625,17 +640,16 @@ impl AlphaBatchBuilder {
         };
 
         let prim_cache_address = if is_multiple_primitives {
             GpuCacheAddress::invalid()
         } else {
             gpu_cache.get_address(&prim_metadata.gpu_location)
         };
 
-        let no_textures = BatchTextures::no_texture();
         let clip_task_address = prim_metadata
             .clip_task_id
             .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
         let base_instance = SimplePrimitiveInstance::new(
             prim_cache_address,
             task_address,
             clip_task_address,
             clip_chain_rect_index,
@@ -1016,56 +1030,53 @@ impl AlphaBatchBuilder {
                                     task_address,
                                     z,
                                     user_data,
                                     tile.edge_flags
                                 );
                             }
                         }
                     }
-                    BrushKind::LinearGradient { gradient_index, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
+                    BrushKind::LinearGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
                         add_gradient_tiles(
                             visible_tiles,
-                            gradient_index,
+                            stops_handle,
                             BrushBatchKind::LinearGradient,
                             specified_blend_mode,
                             &task_relative_bounding_rect,
                             clip_chain_rect_index,
                             scroll_id,
                             task_address,
                             clip_task_address,
                             z,
-                            ctx,
                             gpu_cache,
                             &mut self.batch_list,
                         );
                     }
-                    BrushKind::RadialGradient { gradient_index, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
+                    BrushKind::RadialGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
                         add_gradient_tiles(
                             visible_tiles,
-                            gradient_index,
+                            stops_handle,
                             BrushBatchKind::RadialGradient,
                             specified_blend_mode,
                             &task_relative_bounding_rect,
                             clip_chain_rect_index,
                             scroll_id,
                             task_address,
                             clip_task_address,
                             z,
-                            ctx,
                             gpu_cache,
                             &mut self.batch_list,
                         );
                     }
                     _ => {
                         if let Some((batch_kind, textures, user_data)) = brush.get_batch_params(
                                 ctx.resource_cache,
                                 gpu_cache,
                                 deferred_resolves,
-                                ctx.cached_gradients,
                         ) {
                             self.add_brush_to_batch(
                                 brush,
                                 prim_metadata,
                                 batch_kind,
                                 specified_blend_mode,
                                 non_segmented_blend_mode,
                                 textures,
@@ -1079,77 +1090,16 @@ impl AlphaBatchBuilder {
                                 z,
                                 render_tasks,
                                 user_data,
                             );
                         }
                     }
                 }
             }
-            PrimitiveKind::Border => {
-                let border_cpu =
-                    &ctx.prim_store.cpu_borders[prim_metadata.cpu_prim_index.0];
-                // TODO(gw): Select correct blend mode for edges and corners!!
-
-                if border_cpu.corner_instances.iter().any(|&kind| kind != BorderCornerInstance::None) {
-                    let corner_kind = BatchKind::Transformable(
-                        transform_kind,
-                        TransformBatchKind::BorderCorner,
-                    );
-                    let corner_key = BatchKey::new(corner_kind, non_segmented_blend_mode, no_textures);
-                    let batch = self.batch_list
-                        .get_suitable_batch(corner_key, &task_relative_bounding_rect);
-
-                    for (i, instance_kind) in border_cpu.corner_instances.iter().enumerate() {
-                        let sub_index = i as i32;
-                        match *instance_kind {
-                            BorderCornerInstance::None => {}
-                            BorderCornerInstance::Single => {
-                                batch.push(base_instance.build(
-                                    sub_index,
-                                    BorderCornerSide::Both as i32,
-                                    0,
-                                ));
-                            }
-                            BorderCornerInstance::Double => {
-                                batch.push(base_instance.build(
-                                    sub_index,
-                                    BorderCornerSide::First as i32,
-                                    0,
-                                ));
-                                batch.push(base_instance.build(
-                                    sub_index,
-                                    BorderCornerSide::Second as i32,
-                                    0,
-                                ));
-                            }
-                        }
-                    }
-                }
-
-                if border_cpu.edges.iter().any(|&kind| kind != BorderEdgeKind::None) {
-                    let edge_kind = BatchKind::Transformable(
-                        transform_kind,
-                        TransformBatchKind::BorderEdge,
-                    );
-                    let edge_key = BatchKey::new(edge_kind, non_segmented_blend_mode, no_textures);
-                    let batch = self.batch_list
-                        .get_suitable_batch(edge_key, &task_relative_bounding_rect);
-
-                    for (border_segment, instance_kind) in border_cpu.edges.iter().enumerate() {
-                        match *instance_kind {
-                            BorderEdgeKind::None => {},
-                            BorderEdgeKind::Solid |
-                            BorderEdgeKind::Clip => {
-                                batch.push(base_instance.build(border_segment as i32, 0, 0));
-                            }
-                        }
-                    }
-                }
-            }
             PrimitiveKind::TextRun => {
                 let text_cpu =
                     &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
 
                 let font = text_cpu.get_font(
                     ctx.device_pixel_scale,
                     Some(scroll_node.transform),
                 );
@@ -1374,45 +1324,45 @@ impl AlphaBatchBuilder {
                     blend_mode: non_segmented_blend_mode,
                     kind: BatchKind::Brush(batch_kind),
                     textures,
                 };
                 let batch = self.batch_list.get_suitable_batch(batch_key, task_relative_bounding_rect);
                 batch.push(PrimitiveInstance::from(base_instance));
             }
         }
+
+        self.batch_list.remove_unused_batches();
     }
 }
 
 fn add_gradient_tiles(
     visible_tiles: &[VisibleGradientTile],
-    gradient_index: CachedGradientIndex,
+    stops_handle: &GpuCacheHandle,
     kind: BrushBatchKind,
     blend_mode: BlendMode,
     task_relative_bounding_rect: &DeviceIntRect,
     clip_chain_rect_index: ClipChainRectIndex,
     scroll_id: ClipScrollNodeIndex,
     task_address: RenderTaskAddress,
     clip_task_address: RenderTaskAddress,
     z: ZBufferId,
-    ctx: &RenderTargetContext,
     gpu_cache: &GpuCache,
     batch_list: &mut BatchList,
 ) {
     batch_list.add_bounding_rect(task_relative_bounding_rect);
     let batch = batch_list.get_suitable_batch(
         BatchKey {
             blend_mode: blend_mode,
             kind: BatchKind::Brush(kind),
             textures: BatchTextures::no_texture(),
         },
         task_relative_bounding_rect
     );
 
-    let stops_handle = &ctx.cached_gradients[gradient_index.0].handle;
     let user_data = [stops_handle.as_int(gpu_cache), 0, 0];
 
     let base_instance = BrushInstance {
         picture_address: task_address,
         prim_address: GpuCacheAddress::invalid(),
         clip_chain_rect_index,
         scroll_id,
         clip_task_address,
@@ -1476,17 +1426,16 @@ impl BrushPrimitive {
         }
     }
 
     fn get_batch_params(
         &self,
         resource_cache: &ResourceCache,
         gpu_cache: &mut GpuCache,
         deferred_resolves: &mut Vec<DeferredResolve>,
-        cached_gradients: &[CachedGradient],
     ) -> Option<(BrushBatchKind, BatchTextures, [i32; 3])> {
         match self.kind {
             BrushKind::Image { request, ref source, .. } => {
                 let cache_item = match *source {
                     ImageSource::Default => {
                         resolve_image(
                             request,
                             resource_cache,
@@ -1570,30 +1519,28 @@ impl BrushPrimitive {
             }
             BrushKind::Clear => {
                 Some((
                     BrushBatchKind::Solid,
                     BatchTextures::no_texture(),
                     [0; 3],
                 ))
             }
-            BrushKind::RadialGradient { gradient_index, .. } => {
-                let stops_handle = &cached_gradients[gradient_index.0].handle;
+            BrushKind::RadialGradient { ref stops_handle, .. } => {
                 Some((
                     BrushBatchKind::RadialGradient,
                     BatchTextures::no_texture(),
                     [
                         stops_handle.as_int(gpu_cache),
                         0,
                         0,
                     ],
                 ))
             }
-            BrushKind::LinearGradient { gradient_index, .. } => {
-                let stops_handle = &cached_gradients[gradient_index.0].handle;
+            BrushKind::LinearGradient { ref stops_handle, .. } => {
                 Some((
                     BrushBatchKind::LinearGradient,
                     BatchTextures::no_texture(),
                     [
                         stops_handle.as_int(gpu_cache),
                         0,
                         0,
                     ],
@@ -1663,17 +1610,16 @@ trait AlphaBatchHelpers {
         metadata: &PrimitiveMetadata,
     ) -> BlendMode;
 }
 
 impl AlphaBatchHelpers for PrimitiveStore {
     fn get_blend_mode(&self, metadata: &PrimitiveMetadata) -> BlendMode {
         match metadata.prim_kind {
             // Can only resolve the TextRun's blend mode once glyphs are fetched.
-            PrimitiveKind::Border |
             PrimitiveKind::TextRun => {
                 BlendMode::PremultipliedAlpha
             }
 
             PrimitiveKind::Brush => {
                 let brush = &self.cpu_brushes[metadata.cpu_prim_index.0];
                 match brush.kind {
                     BrushKind::Clear => {
@@ -1824,29 +1770,25 @@ fn make_polygon(
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipBatcher {
     /// Rectangle draws fill up the rectangles with rounded corners.
     pub rectangles: Vec<ClipMaskInstance>,
     /// Image draws apply the image masking.
     pub images: FastHashMap<SourceTexture, Vec<ClipMaskInstance>>,
-    pub border_clears: Vec<ClipMaskBorderCornerDotDash>,
-    pub borders: Vec<ClipMaskBorderCornerDotDash>,
     pub box_shadows: FastHashMap<SourceTexture, Vec<ClipMaskInstance>>,
     pub line_decorations: Vec<ClipMaskInstance>,
 }
 
 impl ClipBatcher {
     pub fn new() -> Self {
         ClipBatcher {
             rectangles: Vec::new(),
             images: FastHashMap::default(),
-            border_clears: Vec::new(),
-            borders: Vec::new(),
             box_shadows: FastHashMap::default(),
             line_decorations: Vec::new(),
         }
     }
 
     pub fn add_clip_region(
         &mut self,
         task_address: RenderTaskAddress,
@@ -1948,40 +1890,16 @@ impl ClipBatcher {
                         }
                     }
                     ClipSource::RoundedRectangle(..) => {
                         self.rectangles.push(ClipMaskInstance {
                             clip_data_address: gpu_address,
                             ..instance
                         });
                     }
-                    ClipSource::BorderCorner(ref source) => {
-                        let instance = ClipMaskBorderCornerDotDash {
-                            clip_mask_instance: ClipMaskInstance {
-                                clip_data_address: gpu_address,
-                                segment: 0,
-                                ..instance
-                            },
-                            dot_dash_data: [0.; 8],
-                        };
-
-                        self.border_clears.push(instance);
-
-                        for data in source.dot_dash_data.iter() {
-                            self.borders.push(ClipMaskBorderCornerDotDash {
-                                clip_mask_instance: ClipMaskInstance {
-                                    // The shader understands segment=0 as the clear, so the
-                                    // segment here just needs to be non-zero.
-                                    segment: 1,
-                                    ..instance.clip_mask_instance
-                                },
-                                dot_dash_data: *data,
-                            })
-                        }
-                    }
                 }
             }
         }
     }
 }
 
 fn get_buffer_kind(texture: SourceTexture) -> ImageBufferKind {
     match texture {
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,47 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ColorF, LayoutPoint};
+use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ColorF};
 use api::{ColorU, DeviceRect, DeviceSize, LayoutSizeAu, LayoutPrimitiveInfo, LayoutToDeviceScale};
-use api::{DevicePoint, DeviceIntSize, LayoutRect, LayoutSize, NormalBorder};
+use api::{DevicePixel, DeviceVector2D, DevicePoint, DeviceIntSize, LayoutRect, LayoutSize, NormalBorder};
 use app_units::Au;
-use clip::ClipSource;
 use ellipse::Ellipse;
 use display_list_flattener::DisplayListFlattener;
 use gpu_types::{BorderInstance, BorderSegment, BrushFlags};
-use gpu_cache::GpuDataRequest;
-use prim_store::{BorderPrimitiveCpu, BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegment, BrushSegmentDescriptor};
+use prim_store::{BrushKind, BrushPrimitive, BrushSegment};
 use prim_store::{BorderSource, EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
-use util::{lerp, pack_as_float, RectHelpers};
-
-#[repr(u8)]
-#[derive(Debug, Copy, Clone, PartialEq)]
-pub enum BorderCornerInstance {
-    None,
-    Single, // Single instance needed - corner styles are same or similar.
-    Double, // Different corner styles. Draw two instances, one per style.
-}
-
-#[repr(C)]
-pub enum BorderCornerSide {
-    Both,
-    First,
-    Second,
-}
-
-#[repr(C)]
-enum BorderCorner {
-    TopLeft,
-    TopRight,
-    BottomLeft,
-    BottomRight,
-}
+use util::{lerp, RectHelpers};
 
 trait AuSizeConverter {
     fn to_au(&self) -> LayoutSizeAu;
 }
 
 impl AuSizeConverter for LayoutSize {
     fn to_au(&self) -> LayoutSizeAu {
         LayoutSizeAu::new(
@@ -123,215 +98,16 @@ pub struct BorderCacheKey {
     pub right: BorderSideAu,
     pub top: BorderSideAu,
     pub bottom: BorderSideAu,
     pub radius: BorderRadiusAu,
     pub widths: BorderWidthsAu,
     pub scale: Au,
 }
 
-#[derive(Clone, Debug, PartialEq)]
-pub enum BorderCornerKind {
-    None,
-    Solid,
-    Clip(BorderCornerInstance),
-    Mask(
-        BorderCornerClipData,
-        LayoutSize,
-        LayoutSize,
-        BorderCornerClipKind,
-    ),
-}
-
-impl BorderCornerKind {
-    fn new_mask(
-        kind: BorderCornerClipKind,
-        width0: f32,
-        width1: f32,
-        corner: BorderCorner,
-        radius: LayoutSize,
-        border_rect: LayoutRect,
-    ) -> BorderCornerKind {
-        let size = LayoutSize::new(width0.max(radius.width), width1.max(radius.height));
-        let (origin, clip_center) = match corner {
-            BorderCorner::TopLeft => {
-                let origin = border_rect.origin;
-                let clip_center = origin + size;
-                (origin, clip_center)
-            }
-            BorderCorner::TopRight => {
-                let origin = LayoutPoint::new(
-                    border_rect.origin.x + border_rect.size.width - size.width,
-                    border_rect.origin.y,
-                );
-                let clip_center = origin + LayoutSize::new(0.0, size.height);
-                (origin, clip_center)
-            }
-            BorderCorner::BottomRight => {
-                let origin = border_rect.origin + (border_rect.size - size);
-                let clip_center = origin;
-                (origin, clip_center)
-            }
-            BorderCorner::BottomLeft => {
-                let origin = LayoutPoint::new(
-                    border_rect.origin.x,
-                    border_rect.origin.y + border_rect.size.height - size.height,
-                );
-                let clip_center = origin + LayoutSize::new(size.width, 0.0);
-                (origin, clip_center)
-            }
-        };
-        let clip_data = BorderCornerClipData {
-            corner_rect: LayoutRect::new(origin, size),
-            clip_center,
-            corner: pack_as_float(corner as u32),
-            kind: pack_as_float(kind as u32),
-        };
-        BorderCornerKind::Mask(clip_data, radius, LayoutSize::new(width0, width1), kind)
-    }
-
-    fn get_radius(&self, original_radius: &LayoutSize) -> LayoutSize {
-        match *self {
-            BorderCornerKind::Solid => *original_radius,
-            BorderCornerKind::Clip(..) => *original_radius,
-            BorderCornerKind::Mask(_, ref radius, _, _) => *radius,
-            BorderCornerKind::None => *original_radius,
-        }
-    }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub enum BorderEdgeKind {
-    None,
-    Solid,
-    Clip,
-}
-
-fn get_corner(
-    edge0: &BorderSide,
-    width0: f32,
-    edge1: &BorderSide,
-    width1: f32,
-    radius: &LayoutSize,
-    corner: BorderCorner,
-    border_rect: &LayoutRect,
-) -> BorderCornerKind {
-    // If both widths are zero, a corner isn't formed.
-    if width0 == 0.0 && width1 == 0.0 {
-        return BorderCornerKind::None;
-    }
-
-    // If both edges are transparent, no corner is formed.
-    if edge0.color.a == 0.0 && edge1.color.a == 0.0 {
-        return BorderCornerKind::None;
-    }
-
-    match (edge0.style, edge1.style) {
-        // If both edges are none or hidden, no corner is needed.
-        (BorderStyle::None, BorderStyle::None) |
-        (BorderStyle::None, BorderStyle::Hidden) |
-        (BorderStyle::Hidden, BorderStyle::None) |
-        (BorderStyle::Hidden, BorderStyle::Hidden) => {
-            BorderCornerKind::None
-        }
-
-        // If one of the edges is none or hidden, we just draw one style.
-        (BorderStyle::None, _) |
-        (_, BorderStyle::None) |
-        (BorderStyle::Hidden, _) |
-        (_, BorderStyle::Hidden) => {
-            BorderCornerKind::Clip(BorderCornerInstance::Single)
-        }
-
-        // If both borders are solid, we can draw them with a simple rectangle if
-        // both the colors match and there is no radius.
-        (BorderStyle::Solid, BorderStyle::Solid) => {
-            if edge0.color == edge1.color && radius.width == 0.0 && radius.height == 0.0 {
-                BorderCornerKind::Solid
-            } else {
-                BorderCornerKind::Clip(BorderCornerInstance::Single)
-            }
-        }
-
-        // Inset / outset borders just modify the color of edges, so can be
-        // drawn with the normal border corner shader.
-        (BorderStyle::Outset, BorderStyle::Outset) |
-        (BorderStyle::Inset, BorderStyle::Inset) |
-        (BorderStyle::Double, BorderStyle::Double) |
-        (BorderStyle::Groove, BorderStyle::Groove) |
-        (BorderStyle::Ridge, BorderStyle::Ridge) => {
-            BorderCornerKind::Clip(BorderCornerInstance::Single)
-        }
-
-        // Dashed and dotted border corners get drawn into a clip mask.
-        (BorderStyle::Dashed, BorderStyle::Dashed) => BorderCornerKind::new_mask(
-            BorderCornerClipKind::Dash,
-            width0,
-            width1,
-            corner,
-            *radius,
-            *border_rect,
-        ),
-        (BorderStyle::Dotted, BorderStyle::Dotted) => {
-            let mut radius = *radius;
-            if radius.width < width0 {
-                radius.width = 0.0;
-            }
-            if radius.height < width1 {
-                radius.height = 0.0;
-            }
-            BorderCornerKind::new_mask(
-                BorderCornerClipKind::Dot,
-                width0,
-                width1,
-                corner,
-                radius,
-                *border_rect,
-             )
-        }
-
-        // Draw border transitions with dots and/or dashes as
-        // solid segments. The old border path didn't support
-        // this anyway, so we might as well start using the new
-        // border path here, since the dashing in the edges is
-        // much higher quality anyway.
-        (BorderStyle::Dotted, _) |
-        (_, BorderStyle::Dotted) |
-        (BorderStyle::Dashed, _) |
-        (_, BorderStyle::Dashed) => BorderCornerKind::Clip(BorderCornerInstance::Single),
-
-        // Everything else can be handled by drawing the corner twice,
-        // where the shader outputs zero alpha for the side it's not
-        // drawing. This is somewhat inefficient in terms of pixels
-        // written, but it's a fairly rare case, and we can optimize
-        // this case later.
-        _ => BorderCornerKind::Clip(BorderCornerInstance::Double),
-    }
-}
-
-fn get_edge(edge: &BorderSide, width: f32, height: f32) -> (BorderEdgeKind, f32) {
-    if width == 0.0 || height <= 0.0 {
-        return (BorderEdgeKind::None, 0.0);
-    }
-
-    match edge.style {
-        BorderStyle::None | BorderStyle::Hidden => (BorderEdgeKind::None, 0.0),
-
-        BorderStyle::Solid | BorderStyle::Inset | BorderStyle::Outset => {
-            (BorderEdgeKind::Solid, width)
-        }
-
-        BorderStyle::Double |
-        BorderStyle::Groove |
-        BorderStyle::Ridge |
-        BorderStyle::Dashed |
-        BorderStyle::Dotted => (BorderEdgeKind::Clip, width),
-    }
-}
-
 pub fn ensure_no_corner_overlap(
     radius: &mut BorderRadius,
     rect: &LayoutRect,
 ) {
     let mut ratio = 1.0;
     let top_left_radius = &mut radius.top_left;
     let top_right_radius = &mut radius.top_right;
     let bottom_right_radius = &mut radius.bottom_right;
@@ -368,354 +144,53 @@ pub fn ensure_no_corner_overlap(
         bottom_left_radius.height *= ratio;
 
         bottom_right_radius.width *= ratio;
         bottom_right_radius.height *= ratio;
     }
 }
 
 impl<'a> DisplayListFlattener<'a> {
-    fn add_normal_border_primitive(
-        &mut self,
-        info: &LayoutPrimitiveInfo,
-        border: &NormalBorder,
-        radius: &BorderRadius,
-        widths: &BorderWidths,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        corner_instances: [BorderCornerInstance; 4],
-        edges: [BorderEdgeKind; 4],
-        clip_sources: Vec<ClipSource>,
-    ) {
-        let left = &border.left;
-        let right = &border.right;
-        let top = &border.top;
-        let bottom = &border.bottom;
-
-        // These colors are used during inset/outset scaling.
-        let left_color = left.border_color(1.0, 2.0 / 3.0, 0.3, 0.7).premultiplied();
-        let top_color = top.border_color(1.0, 2.0 / 3.0, 0.3, 0.7).premultiplied();
-        let right_color = right.border_color(2.0 / 3.0, 1.0, 0.7, 0.3).premultiplied();
-        let bottom_color = bottom.border_color(2.0 / 3.0, 1.0, 0.7, 0.3).premultiplied();
-
-        let prim_cpu = BorderPrimitiveCpu {
-            corner_instances,
-            edges,
-
-            // TODO(gw): In the future, we will build these on demand
-            //           from the deserialized display list, rather
-            //           than creating it immediately.
-            gpu_blocks: [
-                [
-                    pack_as_float(left.style as u32),
-                    pack_as_float(top.style as u32),
-                    pack_as_float(right.style as u32),
-                    pack_as_float(bottom.style as u32),
-                ].into(),
-                [widths.left, widths.top, widths.right, widths.bottom].into(),
-                left_color.into(),
-                top_color.into(),
-                right_color.into(),
-                bottom_color.into(),
-                [
-                    radius.top_left.width,
-                    radius.top_left.height,
-                    radius.top_right.width,
-                    radius.top_right.height,
-                ].into(),
-                [
-                    radius.bottom_right.width,
-                    radius.bottom_right.height,
-                    radius.bottom_left.width,
-                    radius.bottom_left.height,
-                ].into(),
-            ],
-        };
-
-        self.add_primitive(
-            clip_and_scroll,
-            info,
-            clip_sources,
-            PrimitiveContainer::Border(prim_cpu),
-        );
-    }
-
-    // TODO(gw): This allows us to move border types over to the
-    // simplified shader model one at a time. Once all borders
-    // are converted, this can be removed, along with the complex
-    // border code path.
     pub fn add_normal_border(
         &mut self,
         info: &LayoutPrimitiveInfo,
         border: &NormalBorder,
         widths: &BorderWidths,
         clip_and_scroll: ScrollNodeAndClipChain,
     ) {
-        // The border shader is quite expensive. For simple borders, we can just draw
-        // the border with a few rectangles. This generally gives better batching, and
-        // a GPU win in fragment shader time.
-        // More importantly, the software (OSMesa) implementation we run tests on is
-        // particularly slow at running our complex border shader, compared to the
-        // rectangle shader. This has the effect of making some of our tests time
-        // out more often on CI (the actual cause is simply too many Servo processes and
-        // threads being run on CI at once).
-
         let mut border = *border;
         ensure_no_corner_overlap(&mut border.radius, &info.rect);
 
-        let radius = &border.radius;
-        let left = &border.left;
-        let right = &border.right;
-        let top = &border.top;
-        let bottom = &border.bottom;
-
-        let brush_border_supported = [left, top, right, bottom].iter().all(|edge| {
-            match edge.style {
-                BorderStyle::Solid |
-                BorderStyle::Hidden |
-                BorderStyle::None |
-                BorderStyle::Double |
-                BorderStyle::Inset |
-                BorderStyle::Groove |
-                BorderStyle::Ridge |
-                BorderStyle::Outset => {
-                    true
-                }
-
-                BorderStyle::Dotted |
-                BorderStyle::Dashed => {
-                    false
-                }
-            }
-        });
-
-        if brush_border_supported {
-            let prim = BrushPrimitive::new(
-                BrushKind::Border {
-                    source: BorderSource::Border {
-                        border,
-                        widths: *widths,
-                        cache_key: BorderCacheKey {
-                            left: border.left.into(),
-                            top: border.top.into(),
-                            right: border.right.into(),
-                            bottom: border.bottom.into(),
-                            widths: (*widths).into(),
-                            radius: border.radius.into(),
-                            scale: Au::from_f32_px(0.0),
-                        },
-                        task_info: None,
-                        handle: None,
+        let prim = BrushPrimitive::new(
+            BrushKind::Border {
+                source: BorderSource::Border {
+                    border,
+                    widths: *widths,
+                    cache_key: BorderCacheKey {
+                        left: border.left.into(),
+                        top: border.top.into(),
+                        right: border.right.into(),
+                        bottom: border.bottom.into(),
+                        widths: (*widths).into(),
+                        radius: border.radius.into(),
+                        scale: Au::from_f32_px(0.0),
                     },
+                    task_info: None,
+                    handle: None,
                 },
-                None,
-            );
-
-            self.add_primitive(
-                clip_and_scroll,
-                info,
-                Vec::new(),
-                PrimitiveContainer::Brush(prim),
-            );
-            return;
-        }
-
-        let corners = [
-            get_corner(
-                left,
-                widths.left,
-                top,
-                widths.top,
-                &radius.top_left,
-                BorderCorner::TopLeft,
-                &info.rect,
-            ),
-            get_corner(
-                right,
-                widths.right,
-                top,
-                widths.top,
-                &radius.top_right,
-                BorderCorner::TopRight,
-                &info.rect,
-            ),
-            get_corner(
-                right,
-                widths.right,
-                bottom,
-                widths.bottom,
-                &radius.bottom_right,
-                BorderCorner::BottomRight,
-                &info.rect,
-            ),
-            get_corner(
-                left,
-                widths.left,
-                bottom,
-                widths.bottom,
-                &radius.bottom_left,
-                BorderCorner::BottomLeft,
-                &info.rect,
-            ),
-        ];
-
-        let (left_edge, left_len) = get_edge(left, widths.left,
-            info.rect.size.height - radius.top_left.height - radius.bottom_left.height);
-        let (top_edge, top_len) = get_edge(top, widths.top,
-            info.rect.size.width - radius.top_left.width - radius.top_right.width);
-        let (right_edge, right_len) = get_edge(right, widths.right,
-            info.rect.size.height - radius.top_right.height - radius.bottom_right.height);
-        let (bottom_edge, bottom_len) = get_edge(bottom, widths.bottom,
-            info.rect.size.width - radius.bottom_right.width - radius.bottom_left.width);
-
-        let edges = [left_edge, top_edge, right_edge, bottom_edge];
-
-        // Use a simple rectangle case when all edges and corners are either
-        // solid or none.
-        let all_corners_simple = corners.iter().all(|c| {
-            *c == BorderCornerKind::Solid || *c == BorderCornerKind::None
-        });
-        let all_edges_simple = edges.iter().all(|e| {
-            *e == BorderEdgeKind::Solid || *e == BorderEdgeKind::None
-        });
-
-        let has_no_curve = radius.is_zero();
+            },
+            None,
+        );
 
-        if has_no_curve && all_corners_simple && all_edges_simple {
-            let p0 = info.rect.origin;
-            let p1 = LayoutPoint::new(
-                info.rect.origin.x + left_len,
-                info.rect.origin.y + top_len,
-            );
-            let p2 = LayoutPoint::new(
-                info.rect.origin.x + info.rect.size.width - right_len,
-                info.rect.origin.y + info.rect.size.height - bottom_len,
-            );
-            let p3 = info.rect.bottom_right();
-
-            let segment = |x0, y0, x1, y1| BrushSegment::new(
-                LayoutRect::from_floats(x0, y0, x1, y1),
-                true,
-                EdgeAaSegmentMask::all(), // Note: this doesn't seem right, needs revision
-                [0.0; 4],
-                BrushFlags::empty(),
-            );
-
-            // Add a solid rectangle for each visible edge/corner combination.
-            if top_edge == BorderEdgeKind::Solid {
-                let descriptor = BrushSegmentDescriptor {
-                    segments: vec![
-                        segment(p0.x, p0.y, p1.x, p1.y),
-                        segment(p2.x, p0.y, p3.x, p1.y),
-                        segment(p1.x, p0.y, p2.x, p1.y),
-                    ],
-                    clip_mask_kind: BrushClipMaskKind::Unknown,
-                };
-
-                self.add_solid_rectangle(
-                    clip_and_scroll,
-                    info,
-                    border.top.color,
-                    Some(descriptor),
-                    Vec::new(),
-                );
-            }
-
-            if left_edge == BorderEdgeKind::Solid {
-                let descriptor = BrushSegmentDescriptor {
-                    segments: vec![
-                        segment(p0.x, p1.y, p1.x, p2.y),
-                    ],
-                    clip_mask_kind: BrushClipMaskKind::Unknown,
-                };
-
-                self.add_solid_rectangle(
-                    clip_and_scroll,
-                    info,
-                    border.left.color,
-                    Some(descriptor),
-                    Vec::new(),
-                );
-            }
-
-            if right_edge == BorderEdgeKind::Solid {
-                let descriptor = BrushSegmentDescriptor {
-                    segments: vec![
-                        segment(p2.x, p1.y, p3.x, p2.y),
-                    ],
-                    clip_mask_kind: BrushClipMaskKind::Unknown,
-                };
-
-                self.add_solid_rectangle(
-                    clip_and_scroll,
-                    info,
-                    border.right.color,
-                    Some(descriptor),
-                    Vec::new(),
-                );
-            }
-
-            if bottom_edge == BorderEdgeKind::Solid {
-                let descriptor = BrushSegmentDescriptor {
-                    segments: vec![
-                        segment(p1.x, p2.y, p2.x, p3.y),
-                        segment(p2.x, p2.y, p3.x, p3.y),
-                        segment(p0.x, p2.y, p1.x, p3.y),
-                    ],
-                    clip_mask_kind: BrushClipMaskKind::Unknown,
-                };
-
-                self.add_solid_rectangle(
-                    clip_and_scroll,
-                    info,
-                    border.bottom.color,
-                    Some(descriptor),
-                    Vec::new(),
-                );
-            }
-        } else {
-            // Create clip masks for border corners, if required.
-            let mut extra_clips = Vec::new();
-            let mut corner_instances = [BorderCornerInstance::Single; 4];
-
-            let radius = &border.radius;
-            let radius = BorderRadius {
-                top_left: corners[0].get_radius(&radius.top_left),
-                top_right: corners[1].get_radius(&radius.top_right),
-                bottom_right: corners[2].get_radius(&radius.bottom_right),
-                bottom_left: corners[3].get_radius(&radius.bottom_left),
-            };
-
-            for (i, corner) in corners.iter().enumerate() {
-                match *corner {
-                    BorderCornerKind::Mask(corner_data, mut corner_radius, widths, kind) => {
-                        let clip_source =
-                            BorderCornerClipSource::new(corner_data, corner_radius, widths, kind);
-                        extra_clips.push(ClipSource::BorderCorner(clip_source));
-                    }
-                    BorderCornerKind::Clip(instance_kind) => {
-                        corner_instances[i] = instance_kind;
-                    }
-                    BorderCornerKind::Solid => {}
-                    BorderCornerKind::None => {
-                        corner_instances[i] = BorderCornerInstance::None;
-                    }
-                }
-            }
-
-            self.add_normal_border_primitive(
-                info,
-                &border,
-                &radius,
-                widths,
-                clip_and_scroll,
-                corner_instances,
-                edges,
-                extra_clips,
-            );
-        }
+        self.add_primitive(
+            clip_and_scroll,
+            info,
+            Vec::new(),
+            PrimitiveContainer::Brush(prim),
+        );
     }
 }
 
 pub trait BorderSideHelpers {
     fn border_color(
         &self,
         scale_factor_0: f32,
         scale_factor_1: f32,
@@ -751,36 +226,34 @@ impl BorderSideHelpers for BorderSide {
         }
     }
 }
 
 /// The kind of border corner clip.
 #[repr(C)]
 #[derive(Copy, Debug, Clone, PartialEq)]
 pub enum BorderCornerClipKind {
-    Dash,
-    Dot,
+    Dash = 1,
+    Dot = 2,
 }
 
 /// The source data for a border corner clip mask.
 #[derive(Debug, Clone)]
 pub struct BorderCornerClipSource {
-    pub corner_data: BorderCornerClipData,
     pub max_clip_count: usize,
     kind: BorderCornerClipKind,
-    widths: LayoutSize,
-    ellipse: Ellipse,
-    pub dot_dash_data: Vec<[f32; 8]>,
+    widths: DeviceSize,
+    radius: DeviceSize,
+    ellipse: Ellipse<DevicePixel>,
 }
 
 impl BorderCornerClipSource {
     pub fn new(
-        corner_data: BorderCornerClipData,
-        corner_radius: LayoutSize,
-        widths: LayoutSize,
+        corner_radius: DeviceSize,
+        widths: DeviceSize,
         kind: BorderCornerClipKind,
     ) -> BorderCornerClipSource {
         // Work out a dash length (and therefore dash count)
         // based on the width of the border edges. The "correct"
         // dash length is not mentioned in the CSS borders
         // spec. The calculation below is similar, but not exactly
         // the same as what Gecko uses.
         // TODO(gw): Iterate on this to get it closer to what Gecko
@@ -797,17 +270,17 @@ impl BorderCornerClipSource {
                 // Get the ideal number of dashes for that arc length.
                 // This is scaled by 0.5 since there is an on/off length
                 // for each dash.
                 let desired_count = 0.5 * ellipse.total_arc_length / desired_dash_arc_length;
 
                 // Round that up to the nearest integer, so that the dash length
                 // doesn't exceed the ratio above. Add one extra dash to cover
                 // the last half-dash of the arc.
-                (ellipse, 1 + desired_count.ceil() as usize)
+                (ellipse, desired_count.ceil() as usize)
             }
             BorderCornerClipKind::Dot => {
                 let mut corner_radius = corner_radius;
                 if corner_radius.width < (widths.width / 2.0) {
                     corner_radius.width = 0.0;
                 }
                 if corner_radius.height < (widths.height / 2.0) {
                     corner_radius.height = 0.0;
@@ -827,80 +300,130 @@ impl BorderCornerClipSource {
                     let min_diameter = widths.width.min(widths.height);
 
                     // Get the number of circles (assuming spacing of one diameter
                     // between dots).
                     let max_dot_count = 0.5 * ellipse.total_arc_length / min_diameter;
 
                     // Add space for one extra dot since they are centered at the
                     // start of the arc.
-                    (ellipse, 1 + max_dot_count.ceil() as usize)
+                    (ellipse, max_dot_count.ceil() as usize)
                 }
             }
         };
 
         BorderCornerClipSource {
             kind,
-            corner_data,
             max_clip_count,
             ellipse,
             widths,
-            dot_dash_data: Vec::new(),
+            radius: corner_radius,
         }
     }
 
-    pub fn write(&mut self, mut request: GpuDataRequest) {
-        self.corner_data.write(&mut request);
-        assert_eq!(request.close(), 2);
+    // TODO(gw): The naming and structure of BorderCornerClipSource
+    //           don't really make sense. I've left it this way
+    //           for now in order to reduce the size of the
+    //           patch a bit. In the future, when we spent some
+    //           time working on dot/dash placement, we should
+    //           restructure this code to be more consistent
+    //           with how border rendering works now.
+    pub fn write(self, segment: BorderSegment) -> Vec<[f32; 8]> {
+        let mut dot_dash_data = Vec::new();
+
+        let outer_scale = match segment {
+            BorderSegment::TopLeft => DeviceVector2D::new(0.0, 0.0),
+            BorderSegment::TopRight => DeviceVector2D::new(1.0, 0.0),
+            BorderSegment::BottomRight => DeviceVector2D::new(1.0, 1.0),
+            BorderSegment::BottomLeft => DeviceVector2D::new(0.0, 1.0),
+            _ => unreachable!(),
+        };
+        let outer = DevicePoint::new(
+            outer_scale.x * self.radius.width,
+            outer_scale.y * self.radius.height,
+        );
+        let clip_sign = DeviceVector2D::new(
+            1.0 - 2.0 * outer_scale.x,
+            1.0 - 2.0 * outer_scale.y,
+        );
 
         match self.kind {
             BorderCornerClipKind::Dash => {
                 // Get the correct dash arc length.
                 let dash_arc_length =
-                    0.5 * self.ellipse.total_arc_length / (self.max_clip_count - 1) as f32;
-                self.dot_dash_data.clear();
-                let mut current_arc_length = -0.5 * dash_arc_length;
+                    0.5 * self.ellipse.total_arc_length / self.max_clip_count as f32;
+                // Start the first dash at one quarter the length of a single dash
+                // along the arc line. This is arbitrary but looks reasonable in
+                // most cases. We need to spend some time working on a more
+                // sophisticated dash placement algorithm that takes into account
+                // the offset of the dashes along edge segments.
+                let mut current_arc_length = 0.25 * dash_arc_length;
                 for _ in 0 .. self.max_clip_count {
                     let arc_length0 = current_arc_length;
                     current_arc_length += dash_arc_length;
 
                     let arc_length1 = current_arc_length;
                     current_arc_length += dash_arc_length;
 
                     let alpha = self.ellipse.find_angle_for_arc_length(arc_length0);
                     let beta =  self.ellipse.find_angle_for_arc_length(arc_length1);
 
                     let (point0, tangent0) =  self.ellipse.get_point_and_tangent(alpha);
                     let (point1, tangent1) =  self.ellipse.get_point_and_tangent(beta);
 
-                    self.dot_dash_data.push([
-                        point0.x, point0.y, tangent0.x, tangent0.y,
-                        point1.x, point1.y, tangent1.x, tangent1.y
+                    let point0 = DevicePoint::new(
+                        outer.x + clip_sign.x * (self.radius.width - point0.x),
+                        outer.y + clip_sign.y * (self.radius.height - point0.y),
+                    );
+
+                    let tangent0 = DeviceVector2D::new(
+                        -tangent0.x * clip_sign.x,
+                        -tangent0.y * clip_sign.y,
+                    );
+
+                    let point1 = DevicePoint::new(
+                        outer.x + clip_sign.x * (self.radius.width - point1.x),
+                        outer.y + clip_sign.y * (self.radius.height - point1.y),
+                    );
+
+                    let tangent1 = DeviceVector2D::new(
+                        -tangent1.x * clip_sign.x,
+                        -tangent1.y * clip_sign.y,
+                    );
+
+                    dot_dash_data.push([
+                        point0.x,
+                        point0.y,
+                        tangent0.x,
+                        tangent0.y,
+                        point1.x,
+                        point1.y,
+                        tangent1.x,
+                        tangent1.y,
                     ]);
                 }
             }
             BorderCornerClipKind::Dot if self.max_clip_count == 1 => {
                 let dot_diameter = lerp(self.widths.width, self.widths.height, 0.5);
-                self.dot_dash_data.clear();
-                self.dot_dash_data.push([
+                dot_dash_data.push([
                     self.widths.width / 2.0, self.widths.height / 2.0, 0.5 * dot_diameter, 0.,
                     0., 0., 0., 0.,
                 ]);
             }
             BorderCornerClipKind::Dot => {
                 let mut forward_dots = Vec::new();
                 let mut back_dots = Vec::new();
                 let mut leftover_arc_length = 0.0;
 
                 // Alternate between adding dots at the start and end of the
                 // ellipse arc. This ensures that we always end up with an exact
                 // half dot at each end of the arc, to match up with the edges.
-                forward_dots.push(DotInfo::new(0.0, self.widths.width));
+                forward_dots.push(DotInfo::new(self.widths.width, self.widths.width));
                 back_dots.push(DotInfo::new(
-                    self.ellipse.total_arc_length,
+                    self.ellipse.total_arc_length - self.widths.height,
                     self.widths.height,
                 ));
 
                 for dot_index in 0 .. self.max_clip_count {
                     let prev_forward_pos = *forward_dots.last().unwrap();
                     let prev_back_pos = *back_dots.last().unwrap();
 
                     // Select which end of the arc to place a dot from.
@@ -942,69 +465,46 @@ impl BorderCornerClipSource {
 
                 // Now step through the dots, and distribute any extra
                 // leftover space on the arc between them evenly. Once
                 // the final arc position is determined, generate the correct
                 // arc positions and angles that get passed to the clip shader.
                 let number_of_dots = forward_dots.len() + back_dots.len();
                 let extra_space_per_dot = leftover_arc_length / (number_of_dots - 1) as f32;
 
-                self.dot_dash_data.clear();
-
-                let create_dot_data = |ellipse: &Ellipse, arc_length: f32, radius: f32| -> [f32; 8] {
+                let create_dot_data = |ellipse: &Ellipse<DevicePixel>, arc_length: f32, radius: f32| -> [f32; 8] {
                     // Represents the GPU data for drawing a single dot to a clip mask. The order
                     // these are specified must stay in sync with the way this data is read in the
                     // dot clip shader.
                     let theta = ellipse.find_angle_for_arc_length(arc_length);
                     let (center, _) = ellipse.get_point_and_tangent(theta);
-                    [center.x, center.y, radius, 0., 0., 0., 0., 0.,]
+
+                    let center = DevicePoint::new(
+                        outer.x + clip_sign.x * (self.radius.width - center.x),
+                        outer.y + clip_sign.y * (self.radius.height - center.y),
+                    );
+
+                    [center.x, center.y, radius, 0.0, 0.0, 0.0, 0.0, 0.0]
                 };
 
                 for (i, dot) in forward_dots.iter().enumerate() {
                     let extra_dist = i as f32 * extra_space_per_dot;
                     let dot_data = create_dot_data(&self.ellipse, dot.arc_pos + extra_dist, 0.5 * dot.diameter);
-                    self.dot_dash_data.push(dot_data);
+                    dot_dash_data.push(dot_data);
                 }
 
                 for (i, dot) in back_dots.iter().enumerate() {
                     let extra_dist = i as f32 * extra_space_per_dot;
                     let dot_data = create_dot_data(&self.ellipse, dot.arc_pos - extra_dist, 0.5 * dot.diameter);
-                    self.dot_dash_data.push(dot_data);
+                    dot_dash_data.push(dot_data);
                 }
             }
         }
-    }
-}
 
-/// Represents the common GPU data for writing a
-/// clip mask for a border corner.
-#[derive(Debug, Copy, Clone, PartialEq)]
-#[repr(C)]
-pub struct BorderCornerClipData {
-    /// Local space rect of the border corner.
-    corner_rect: LayoutRect,
-    /// Local space point that is the center of the
-    /// circle or ellipse that we are clipping against.
-    clip_center: LayoutPoint,
-    /// The shader needs to know which corner, to
-    /// be able to flip the dash tangents to the
-    /// right orientation.
-    corner: f32, // Of type BorderCorner enum
-    kind: f32, // Of type BorderCornerClipKind enum
-}
-
-impl BorderCornerClipData {
-    fn write(&self, request: &mut GpuDataRequest) {
-        request.push(self.corner_rect);
-        request.push([
-            self.clip_center.x,
-            self.clip_center.y,
-            self.corner,
-            self.kind,
-        ]);
+        dot_dash_data
     }
 }
 
 #[derive(Copy, Clone, Debug)]
 struct DotInfo {
     arc_pos: f32,
     diameter: f32,
 }
@@ -1024,16 +524,84 @@ pub struct BorderSegmentInfo {
 }
 
 #[derive(Debug)]
 pub struct BorderRenderTaskInfo {
     pub border_segments: Vec<BorderSegmentInfo>,
     pub size: DeviceIntSize,
 }
 
+// Information needed to place and draw a border edge.
+struct EdgeInfo {
+    // Offset in local space to place the edge from origin.
+    local_offset: f32,
+    // Size of the edge in local space.
+    local_size: f32,
+    // Size in device pixels needed in the render task.
+    device_size: f32,
+}
+
+impl EdgeInfo {
+    fn new(
+        local_offset: f32,
+        local_size: f32,
+        device_size: f32,
+    ) -> EdgeInfo {
+        EdgeInfo {
+            local_offset,
+            local_size,
+            device_size,
+        }
+    }
+}
+
+// Get the needed size in device pixels for an edge,
+// based on the border style of that edge. This is used
+// to determine how big the render task should be.
+fn get_edge_info(
+    style: BorderStyle,
+    side_width: f32,
+    avail_size: f32,
+    scale: f32,
+) -> EdgeInfo {
+    // To avoid division by zero below.
+    if side_width <= 0.0 {
+        return EdgeInfo::new(0.0, 0.0, 0.0);
+    }
+
+    match style {
+        BorderStyle::Dashed => {
+            let dash_size = 3.0 * side_width;
+            let approx_dash_count = (avail_size - dash_size) / dash_size;
+            let dash_count = 1.0 + 2.0 * (approx_dash_count / 2.0).floor();
+            let used_size = dash_count * dash_size;
+            let extra_space = avail_size - used_size;
+            let device_size = 2.0 * dash_size * scale;
+            let offset = (extra_space * 0.5).round();
+            EdgeInfo::new(offset, used_size, device_size)
+        }
+        BorderStyle::Dotted => {
+            let dot_and_space_size = 2.0 * side_width;
+            if avail_size < dot_and_space_size * 0.75 {
+                return EdgeInfo::new(0.0, 0.0, 0.0);
+            }
+            let approx_dot_count = avail_size / dot_and_space_size;
+            let dot_count = approx_dot_count.floor().max(1.0);
+            let used_size = dot_count * dot_and_space_size;
+            let extra_space = avail_size - used_size;
+            let device_size = dot_and_space_size * scale;
+            let offset = (extra_space * 0.5).round();
+            EdgeInfo::new(offset, used_size, device_size)
+        }
+        _ => {
+            EdgeInfo::new(0.0, avail_size, 8.0)
+        }
+    }
+}
+
 impl BorderRenderTaskInfo {
     pub fn new(
         rect: &LayoutRect,
         border: &NormalBorder,
         widths: &BorderWidths,
         scale: LayoutToDeviceScale,
         brush_segments: &mut Vec<BrushSegment>,
     ) -> Self {
@@ -1078,102 +646,123 @@ impl BorderRenderTaskInfo {
             border.radius.bottom_right.width.max(widths.right),
             border.radius.bottom_right.height.max(widths.bottom),
         );
         let local_size_bl = LayoutSize::new(
             border.radius.bottom_left.width.max(widths.left),
             border.radius.bottom_left.height.max(widths.bottom),
         );
 
-        // TODO(gw): The inner and outer widths don't matter for simple
-        //           border types. Once we push dashing and dotted styles
-        //           through border brushes, we need to calculate an
-        //           appropriate length here.
-        let width_inner = 16.0;
-        let height_inner = 16.0;
+        let top_edge_info = get_edge_info(
+            border.top.style,
+            widths.top,
+            rect.size.width - local_size_tl.width - local_size_tr.width,
+            scale.0,
+        );
+        let bottom_edge_info = get_edge_info(
+            border.bottom.style,
+            widths.bottom,
+            rect.size.width - local_size_bl.width - local_size_br.width,
+            scale.0,
+        );
+        let inner_width = top_edge_info.device_size.max(bottom_edge_info.device_size).ceil();
+
+        let left_edge_info = get_edge_info(
+            border.left.style,
+            widths.left,
+            rect.size.height - local_size_tl.height - local_size_bl.height,
+            scale.0,
+        );
+        let right_edge_info = get_edge_info(
+            border.right.style,
+            widths.right,
+            rect.size.height - local_size_tr.height - local_size_br.height,
+            scale.0,
+        );
+        let inner_height = left_edge_info.device_size.max(right_edge_info.device_size).ceil();
 
         let size = DeviceSize::new(
-            dp_size_tl.width.max(dp_size_bl.width) + width_inner + dp_size_tr.width.max(dp_size_br.width),
-            dp_size_tl.height.max(dp_size_tr.height) + height_inner + dp_size_bl.height.max(dp_size_br.height),
+            dp_size_tl.width.max(dp_size_bl.width) + inner_width + dp_size_tr.width.max(dp_size_br.width),
+            dp_size_tl.height.max(dp_size_tr.height) + inner_height + dp_size_bl.height.max(dp_size_br.height),
         );
 
         add_edge_segment(
             LayoutRect::from_floats(
                 rect.origin.x,
-                rect.origin.y + local_size_tl.height,
+                rect.origin.y + local_size_tl.height + left_edge_info.local_offset,
                 rect.origin.x + widths.left,
-                rect.origin.y + rect.size.height - local_size_bl.height,
+                rect.origin.y + local_size_tl.height + left_edge_info.local_offset + left_edge_info.local_size,
             ),
             DeviceRect::from_floats(
                 0.0,
                 dp_size_tl.height,
                 dp_width_left,
-                size.height - dp_size_bl.height,
+                dp_size_tl.height + left_edge_info.device_size,
             ),
             &border.left,
             BorderSegment::Left,
             EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT,
             &mut border_segments,
             BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_Y,
             brush_segments,
         );
 
         add_edge_segment(
             LayoutRect::from_floats(
-                rect.origin.x + local_size_tl.width,
+                rect.origin.x + local_size_tl.width + top_edge_info.local_offset,
                 rect.origin.y,
-                rect.origin.x + rect.size.width - local_size_tr.width,
+                rect.origin.x + local_size_tl.width + top_edge_info.local_offset + top_edge_info.local_size,
                 rect.origin.y + widths.top,
             ),
             DeviceRect::from_floats(
                 dp_size_tl.width,
                 0.0,
-                size.width - dp_size_tr.width,
+                dp_size_tl.width + top_edge_info.device_size,
                 dp_width_top,
             ),
             &border.top,
             BorderSegment::Top,
             EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM,
             &mut border_segments,
             BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_X,
             brush_segments,
         );
 
         add_edge_segment(
             LayoutRect::from_floats(
                 rect.origin.x + rect.size.width - widths.right,
-                rect.origin.y + local_size_tr.height,
+                rect.origin.y + local_size_tr.height + right_edge_info.local_offset,
                 rect.origin.x + rect.size.width,
-                rect.origin.y + rect.size.height - local_size_br.height,
+                rect.origin.y + local_size_tr.height + right_edge_info.local_offset + right_edge_info.local_size,
             ),
             DeviceRect::from_floats(
                 size.width - dp_width_right,
                 dp_size_tr.height,
                 size.width,
-                size.height - dp_size_br.height,
+                dp_size_tr.height + right_edge_info.device_size,
             ),
             &border.right,
             BorderSegment::Right,
             EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT,
             &mut border_segments,
             BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_Y,
             brush_segments,
         );
 
         add_edge_segment(
             LayoutRect::from_floats(
-                rect.origin.x + local_size_bl.width,
+                rect.origin.x + local_size_bl.width + bottom_edge_info.local_offset,
                 rect.origin.y + rect.size.height - widths.bottom,
-                rect.origin.x + rect.size.width - local_size_br.width,
+                rect.origin.x + local_size_bl.width + bottom_edge_info.local_offset + bottom_edge_info.local_size,
                 rect.origin.y + rect.size.height,
             ),
             DeviceRect::from_floats(
                 dp_size_bl.width,
                 size.height - dp_width_bottom,
-                size.width - dp_size_br.width,
+                dp_size_bl.width + bottom_edge_info.device_size,
                 size.height,
             ),
             &border.bottom,
             BorderSegment::Bottom,
             EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP,
             &mut border_segments,
             BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_X,
             brush_segments,
@@ -1272,20 +861,17 @@ impl BorderRenderTaskInfo {
         );
 
         BorderRenderTaskInfo {
             border_segments,
             size: size.to_i32(),
         }
     }
 
-    pub fn build_instances(
-        &self,
-        border: &NormalBorder,
-    ) -> Vec<BorderInstance> {
+    pub fn build_instances(&self, border: &NormalBorder) -> Vec<BorderInstance> {
         let mut instances = Vec::new();
 
         for info in &self.border_segments {
             let (side0, side1, flip0, flip1) = match info.segment {
                 BorderSegment::Left => (&border.left, &border.left, false, false),
                 BorderSegment::Top => (&border.top, &border.top, false, false),
                 BorderSegment::Right => (&border.right, &border.right, true, true),
                 BorderSegment::Bottom => (&border.bottom, &border.bottom, true, true),
@@ -1368,31 +954,139 @@ fn add_segment(
     style1: BorderStyle,
     color0: ColorF,
     color1: ColorF,
     segment: BorderSegment,
     instances: &mut Vec<BorderInstance>,
     widths: DeviceSize,
     radius: DeviceSize,
 ) {
-    let flags = (segment as i32) |
-                ((style0 as i32) << 8) |
-                ((style1 as i32) << 16);
+    let base_flags = (segment as i32) |
+                     ((style0 as i32) << 8) |
+                     ((style1 as i32) << 16);
 
     let base_instance = BorderInstance {
         task_origin: DevicePoint::zero(),
         local_rect: task_rect,
-        flags,
+        flags: base_flags,
         color0: color0.premultiplied(),
         color1: color1.premultiplied(),
         widths,
         radius,
+        clip_params: [0.0; 8],
     };
 
-    instances.push(base_instance);
+    match segment {
+        BorderSegment::TopLeft |
+        BorderSegment::TopRight |
+        BorderSegment::BottomLeft |
+        BorderSegment::BottomRight => {
+            // TODO(gw): Similarly to the old border code, we don't correctly handle a a corner
+            //           that is dashed on one edge, and dotted on another. We can handle this
+            //           in the future by submitting two instances, each one with one side
+            //           color set to have an alpha of 0.
+            if (style0 == BorderStyle::Dotted && style1 == BorderStyle::Dashed) ||
+               (style0 == BorderStyle::Dashed && style0 == BorderStyle::Dotted) {
+                warn!("TODO: Handle a corner with dotted / dashed transition.");
+            }
+
+            let clip_kind = match style0 {
+                BorderStyle::Dashed => Some(BorderCornerClipKind::Dash),
+                BorderStyle::Dotted => Some(BorderCornerClipKind::Dot),
+                _ => None,
+            };
+
+            match clip_kind {
+                Some(clip_kind) => {
+                    let clip_source = BorderCornerClipSource::new(
+                        radius,
+                        widths,
+                        clip_kind,
+                    );
+
+                    // TODO(gw): Restructure the BorderCornerClipSource code
+                    //           so that we don't allocate a Vec here.
+                    let clip_list = clip_source.write(segment);
+
+                    for params in clip_list {
+                        instances.push(BorderInstance {
+                            flags: base_flags | ((clip_kind as i32) << 24),
+                            clip_params: params,
+                            ..base_instance
+                        });
+                    }
+                }
+                None => {
+                    instances.push(base_instance);
+                }
+            }
+        }
+        BorderSegment::Top |
+        BorderSegment::Bottom |
+        BorderSegment::Right |
+        BorderSegment::Left => {
+            let is_vertical = segment == BorderSegment::Left ||
+                              segment == BorderSegment::Right;
+
+            match style0 {
+                BorderStyle::Dashed => {
+                    let rect = if is_vertical {
+                        let half_dash_size = task_rect.size.height * 0.5;
+                        let y0 = task_rect.origin.y;
+                        let y1 = y0 + half_dash_size.round();
+
+                        DeviceRect::from_floats(
+                            task_rect.origin.x,
+                            y0,
+                            task_rect.origin.x + task_rect.size.width,
+                            y1,
+                        )
+                    } else {
+                        let half_dash_size = task_rect.size.width * 0.5;
+                        let x0 = task_rect.origin.x;
+                        let x1 = x0 + half_dash_size.round();
+
+                        DeviceRect::from_floats(
+                            x0,
+                            task_rect.origin.y,
+                            x1,
+                            task_rect.origin.y + task_rect.size.height,
+                        )
+                    };
+
+                    instances.push(BorderInstance {
+                        local_rect: rect,
+                        ..base_instance
+                    });
+                }
+                BorderStyle::Dotted => {
+                    let (x, y, r) = if is_vertical {
+                        (widths.width * 0.5,
+                         widths.width,
+                         widths.width * 0.5)
+                    } else {
+                        (widths.height,
+                         widths.height * 0.5,
+                         widths.height * 0.5)
+                    };
+
+                    instances.push(BorderInstance {
+                        flags: base_flags | ((BorderCornerClipKind::Dot as i32) << 24),
+                        clip_params: [
+                            x, y, r, 0.0, 0.0, 0.0, 0.0, 0.0,
+                        ],
+                        ..base_instance
+                    });
+                }
+                _ => {
+                    instances.push(base_instance);
+                }
+            }
+        }
+    }
 }
 
 fn add_corner_segment(
     image_rect: LayoutRect,
     task_rect: DeviceRect,
     side0: &BorderSide,
     side1: &BorderSide,
     widths: DeviceSize,
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
 use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, LocalClip};
 use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle};
-use border::{BorderCornerClipSource, ensure_no_corner_overlap};
+use border::{ensure_no_corner_overlap};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
 use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId};
 use ellipse::Ellipse;
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use gpu_types::{BoxShadowStretchMode, ClipScrollNodeIndex};
 use prim_store::{ClipData, ImageMaskData};
 use render_task::to_cache_size;
@@ -82,21 +82,16 @@ impl ClipRegion {
     }
 }
 
 #[derive(Debug)]
 pub enum ClipSource {
     Rectangle(LayoutRect, ClipMode),
     RoundedRectangle(LayoutRect, BorderRadius, ClipMode),
     Image(ImageMask),
-    /// TODO(gw): This currently only handles dashed style
-    /// clips, where the border style is dashed for both
-    /// adjacent border edges. Expand to handle dotted style
-    /// and different styles per edge.
-    BorderCorner(BorderCornerClipSource),
     BoxShadow(BoxShadowClipSource),
     LineDecoration(LineDecorationClipSource),
 }
 
 impl From<ClipRegion> for ClipSources {
     fn from(region: ClipRegion) -> ClipSources {
         let mut clips = Vec::new();
 
@@ -336,17 +331,16 @@ impl ClipSources {
                     can_calculate_outer_rect = true;
                     local_outer = local_outer.and_then(|r| r.intersection(rect));
 
                     let inner_rect = extract_inner_rect_safe(rect, radius);
                     local_inner = local_inner
                         .and_then(|r| inner_rect.and_then(|ref inner| r.intersection(inner)));
                 }
                 ClipSource::BoxShadow(..) |
-                ClipSource::BorderCorner { .. } |
                 ClipSource::LineDecoration(..) => {
                     can_calculate_inner_rect = false;
                     break;
                 }
             }
         }
 
         let outer = if can_calculate_outer_rect {
@@ -395,19 +389,16 @@ impl ClipSources {
                     ClipSource::Rectangle(rect, mode) => {
                         let data = ClipData::uniform(rect, 0.0, mode);
                         data.write(&mut request);
                     }
                     ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
                         let data = ClipData::rounded_rect(rect, radius, mode);
                         data.write(&mut request);
                     }
-                    ClipSource::BorderCorner(ref mut source) => {
-                        source.write(request);
-                    }
                     ClipSource::LineDecoration(ref info) => {
                         request.push(info.rect);
                         request.push([
                             info.wavy_line_thickness,
                             pack_as_float(info.style as u32),
                             pack_as_float(info.orientation as u32),
                             0.0,
                         ]);
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -15,23 +15,24 @@ use api::{Shadow, SpecificDisplayItem, S
 use api::{TransformStyle, YuvColorSpace, YuvData};
 use app_units::Au;
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
 use clip_scroll_node::{ClipScrollNode, NodeType, StickyFrameInfo};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
 use euclid::{SideOffsets2D, vec2};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
+use gpu_cache::GpuCacheHandle;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::PictureCompositeMode;
-use prim_store::{BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient};
-use prim_store::{CachedGradientIndex, EdgeAaSegmentMask, ImageSource};
+use prim_store::{BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegmentDescriptor};
+use prim_store::{EdgeAaSegmentMask, ImageSource};
 use prim_store::{BorderSource, BrushSegment, PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitiveCpu};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::{BuiltScene, SceneRequest};
 use std::{f32, mem, usize};
 use tiling::{CompositeOps, ScrollbarPrimitive};
@@ -184,31 +185,29 @@ pub struct DisplayListFlattener<'a> {
     pub hit_testing_runs: Vec<HitTestingRun>,
 
     /// The store which holds all complex clipping information.
     pub clip_store: ClipStore,
 
     /// The configuration to use for the FrameBuilder. We consult this in
     /// order to determine the default font.
     pub config: FrameBuilderConfig,
-
-    /// The gradients collecting during display list flattening.
-    pub cached_gradients: Vec<CachedGradient>,
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn create_frame_builder(
         old_builder: FrameBuilder,
         scene: &Scene,
         clip_scroll_tree: &mut ClipScrollTree,
         font_instances: FontInstanceMap,
         view: &DocumentView,
         output_pipelines: &FastHashSet<PipelineId>,
         frame_builder_config: &FrameBuilderConfig,
         new_scene: &mut Scene,
+        scene_id: u64,
     ) -> FrameBuilder {
         // We checked that the root pipeline is available on the render backend.
         let root_pipeline_id = scene.root_pipeline_id.unwrap();
         let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap();
 
         let root_epoch = scene.pipeline_epochs[&root_pipeline_id];
 
         let background_color = root_pipeline
@@ -219,17 +218,16 @@ impl<'a> DisplayListFlattener<'a> {
             scene,
             clip_scroll_tree,
             font_instances,
             config: *frame_builder_config,
             pipeline_epochs: Vec::new(),
             output_pipelines,
             id_to_index_mapper: ClipIdToIndexMapper::default(),
             hit_testing_runs: recycle_vec(old_builder.hit_testing_runs),
-            cached_gradients: recycle_vec(old_builder.cached_gradients),
             scrollbar_prims: recycle_vec(old_builder.scrollbar_prims),
             reference_frame_stack: Vec::new(),
             picture_stack: Vec::new(),
             shadow_stack: Vec::new(),
             sc_stack: Vec::new(),
             prim_store: old_builder.prim_store.recycle(),
             clip_store: old_builder.clip_store.recycle(),
         };
@@ -249,17 +247,18 @@ impl<'a> DisplayListFlattener<'a> {
         new_scene.pipeline_epochs.insert(root_pipeline_id, root_epoch);
         new_scene.pipeline_epochs.extend(flattener.pipeline_epochs.drain(..));
         new_scene.pipelines = scene.pipelines.clone();
 
         FrameBuilder::with_display_list_flattener(
             view.inner_rect,
             background_color,
             view.window_size,
-            flattener
+            scene_id,
+            flattener,
         )
     }
 
     fn get_complex_clips(
         &self,
         pipeline_id: PipelineId,
         complex_clips: ItemRange<ComplexClipRegion>,
     ) -> Vec<ComplexClipRegion> {
@@ -631,17 +630,16 @@ impl<'a> DisplayListFlattener<'a> {
             }
             SpecificDisplayItem::Gradient(ref info) => {
                 self.add_gradient(
                     clip_and_scroll,
                     &prim_info,
                     info.gradient.start_point,
                     info.gradient.end_point,
                     item.gradient_stops(),
-                    item.display_list().get(item.gradient_stops()).count(),
                     info.gradient.extend_mode,
                     info.tile_size,
                     info.tile_spacing,
                 );
             }
             SpecificDisplayItem::RadialGradient(ref info) => {
                 self.add_radial_gradient(
                     clip_and_scroll,
@@ -674,17 +672,16 @@ impl<'a> DisplayListFlattener<'a> {
                 );
             }
             SpecificDisplayItem::Border(ref info) => {
                 self.add_border(
                     clip_and_scroll,
                     &prim_info,
                     info,
                     item.gradient_stops(),
-                    item.display_list().get(item.gradient_stops()).count(),
                 );
             }
             SpecificDisplayItem::PushStackingContext(ref info) => {
                 let mut subtraversal = item.sub_iter();
                 self.flatten_stacking_context(
                     &mut subtraversal,
                     pipeline_id,
                     &item,
@@ -1489,17 +1486,16 @@ impl<'a> DisplayListFlattener<'a> {
     }
 
     pub fn add_border(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         border_item: &BorderDisplayItem,
         gradient_stops: ItemRange<GradientStop>,
-        gradient_stops_count: usize,
     ) {
         let rect = info.rect;
         let create_segments = |outset: SideOffsets2D<f32>| {
             // Calculate the modified rect as specific by border-image-outset
             let origin = LayoutPoint::new(rect.origin.x - outset.left, rect.origin.y - outset.top);
             let size = LayoutSize::new(
                 rect.size.width + outset.left + outset.right,
                 rect.size.height + outset.top + outset.bottom,
@@ -1734,17 +1730,16 @@ impl<'a> DisplayListFlattener<'a> {
                 info.rect = segment;
 
                 self.add_gradient(
                     clip_and_scroll,
                     &info,
                     border.gradient.start_point - segment_rel,
                     border.gradient.end_point - segment_rel,
                     gradient_stops,
-                    gradient_stops_count,
                     border.gradient.extend_mode,
                     segment.size,
                     LayoutSize::zero(),
                 );
             },
             BorderDetails::RadialGradient(ref border) => {
                 for segment in create_segments(border.outset) {
                     let segment_rel = segment.origin - rect.origin;
@@ -1770,24 +1765,20 @@ impl<'a> DisplayListFlattener<'a> {
 
     pub fn add_gradient(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
         stops: ItemRange<GradientStop>,
-        stops_count: usize,
         extend_mode: ExtendMode,
         stretch_size: LayoutSize,
         mut tile_spacing: LayoutSize,
     ) {
-        let gradient_index = CachedGradientIndex(self.cached_gradients.len());
-        self.cached_gradients.push(CachedGradient::new());
-
         let mut prim_rect = info.rect;
         simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
         let info = LayoutPrimitiveInfo {
             rect: prim_rect,
             .. *info
         };
 
         // Try to ensure that if the gradient is specified in reverse, then so long as the stops
@@ -1806,22 +1797,21 @@ impl<'a> DisplayListFlattener<'a> {
             (end_point, start_point)
         } else {
             (start_point, end_point)
         };
 
         let prim = BrushPrimitive::new(
             BrushKind::LinearGradient {
                 stops_range: stops,
-                stops_count,
                 extend_mode,
                 reverse_stops,
                 start_point: sp,
                 end_point: ep,
-                gradient_index,
+                stops_handle: GpuCacheHandle::new(),
                 stretch_size,
                 tile_spacing,
                 visible_tiles: Vec::new(),
             },
             None,
         );
 
         let prim = PrimitiveContainer::Brush(prim);
@@ -1837,35 +1827,32 @@ impl<'a> DisplayListFlattener<'a> {
         start_radius: f32,
         end_radius: f32,
         ratio_xy: f32,
         stops: ItemRange<GradientStop>,
         extend_mode: ExtendMode,
         stretch_size: LayoutSize,
         mut tile_spacing: LayoutSize,
     ) {
-        let gradient_index = CachedGradientIndex(self.cached_gradients.len());
-        self.cached_gradients.push(CachedGradient::new());
-
         let mut prim_rect = info.rect;
         simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
         let info = LayoutPrimitiveInfo {
             rect: prim_rect,
             .. *info
         };
 
         let prim = BrushPrimitive::new(
             BrushKind::RadialGradient {
                 stops_range: stops,
                 extend_mode,
                 center,
                 start_radius,
                 end_radius,
                 ratio_xy,
-                gradient_index,
+                stops_handle: GpuCacheHandle::new(),
                 stretch_size,
                 tile_spacing,
                 visible_tiles: Vec::new(),
             },
             None,
         );
 
         self.add_primitive(
@@ -2057,17 +2044,18 @@ pub fn build_scene(config: &FrameBuilder
     let frame_builder = DisplayListFlattener::create_frame_builder(
         FrameBuilder::empty(), // WIP, we're not really recycling anything here, clean this up.
         &request.scene,
         &mut clip_scroll_tree,
         request.font_instances,
         &request.view,
         &request.output_pipelines,
         config,
-        &mut new_scene
+        &mut new_scene,
+        request.scene_id,
     );
 
     BuiltScene {
         scene: new_scene,
         frame_builder,
         clip_scroll_tree,
         removed_pipelines: request.removed_pipelines,
     }
--- a/gfx/webrender/src/ellipse.rs
+++ b/gfx/webrender/src/ellipse.rs
@@ -1,27 +1,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{LayoutPoint, LayoutSize, LayoutVector2D};
+use api::{LayoutPoint, LayoutVector2D};
+use euclid::TypedSize2D;
 use std::f32::consts::FRAC_PI_2;
+#[cfg(test)]
+use api::LayoutSize;
 
 /// Number of steps to integrate arc length over.
 const STEP_COUNT: usize = 20;
 
 /// Represents an ellipse centred at a local space origin.
 #[derive(Debug, Clone)]
-pub struct Ellipse {
-    pub radius: LayoutSize,
+pub struct Ellipse<U> {
+    pub radius: TypedSize2D<f32, U>,
     pub total_arc_length: f32,
 }
 
-impl Ellipse {
-    pub fn new(radius: LayoutSize) -> Ellipse {
+impl<U> Ellipse<U> {
+    pub fn new(radius: TypedSize2D<f32, U>) -> Ellipse<U> {
         // Approximate the total length of the first quadrant of this ellipse.
         let total_arc_length = get_simpson_length(FRAC_PI_2, radius.width, radius.height);
 
         Ellipse {
             radius,
             total_arc_length,
         }
     }
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -9,17 +9,17 @@ use clip::{ClipChain, ClipStore};
 use clip_scroll_node::{ClipScrollNode};
 use clip_scroll_tree::{ClipScrollNodeIndex, ClipScrollTree};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
 use gpu_types::{ClipChainRectIndex, ClipScrollNodeData, UvRectKind};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap};
 use picture::PictureSurface;
-use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveRun, PrimitiveStore};
+use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveStore};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use std::{mem, f32};
 use std::sync::Arc;
 use tiling::{Frame, RenderPass, RenderPassKind, RenderTargetContext};
@@ -36,41 +36,41 @@ pub struct FrameBuilderConfig {
     pub dual_source_blending_is_enabled: bool,
 }
 
 /// A builder structure for `tiling::Frame`
 pub struct FrameBuilder {
     screen_rect: DeviceUintRect,
     background_color: Option<ColorF>,
     window_size: DeviceUintSize,
+    scene_id: u64,
     pub prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     pub hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
-    pub cached_gradients: Vec<CachedGradient>,
     pub scrollbar_prims: Vec<ScrollbarPrimitive>,
 }
 
 pub struct FrameBuildingContext<'a> {
+    pub scene_id: u64,
     pub device_pixel_scale: DevicePixelScale,
     pub scene_properties: &'a SceneProperties,
     pub pipelines: &'a FastHashMap<PipelineId, Arc<ScenePipeline>>,
     pub screen_rect: DeviceIntRect,
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub node_data: &'a [ClipScrollNodeData],
 }
 
 pub struct FrameBuildingState<'a> {
     pub render_tasks: &'a mut RenderTaskTree,
     pub profile_counters: &'a mut FrameProfileCounters,
     pub clip_store: &'a mut ClipStore,
     pub local_clip_rects: &'a mut Vec<LayoutRect>,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
-    pub cached_gradients: &'a mut [CachedGradient],
     pub special_render_passes: &'a mut SpecialRenderPasses,
 }
 
 pub struct PictureContext<'a> {
     pub pipeline_id: PipelineId,
     pub prim_runs: Vec<PrimitiveRun>,
     pub original_reference_frame_index: Option<ClipScrollNodeIndex>,
     pub display_list: &'a BuiltDisplayList,
@@ -78,23 +78,25 @@ pub struct PictureContext<'a> {
     pub apply_local_clip_rect: bool,
     pub inflation_factor: f32,
     pub allow_subpixel_aa: bool,
 }
 
 pub struct PictureState {
     pub tasks: Vec<RenderTaskId>,
     pub has_non_root_coord_system: bool,
+    pub local_rect_changed: bool,
 }
 
 impl PictureState {
     pub fn new() -> PictureState {
         PictureState {
             tasks: Vec::new(),
             has_non_root_coord_system: false,
+            local_rect_changed: false,
         }
     }
 }
 
 pub struct PrimitiveRunContext<'a> {
     pub clip_chain: &'a ClipChain,
     pub scroll_node: &'a ClipScrollNode,
     pub clip_chain_rect_index: ClipChainRectIndex,
@@ -113,47 +115,48 @@ impl<'a> PrimitiveRunContext<'a> {
         }
     }
 }
 
 impl FrameBuilder {
     pub fn empty() -> Self {
         FrameBuilder {
             hit_testing_runs: Vec::new(),
-            cached_gradients: Vec::new(),
             scrollbar_prims: Vec::new(),
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             screen_rect: DeviceUintRect::zero(),
             window_size: DeviceUintSize::zero(),
             background_color: None,
+            scene_id: 0,
             config: FrameBuilderConfig {
                 enable_scrollbars: false,
                 default_font_render_mode: FontRenderMode::Mono,
                 dual_source_blending_is_enabled: true,
                 dual_source_blending_is_supported: false,
             },
         }
     }
 
     pub fn with_display_list_flattener(
         screen_rect: DeviceUintRect,
         background_color: Option<ColorF>,
         window_size: DeviceUintSize,
+        scene_id: u64,
         flattener: DisplayListFlattener,
     ) -> Self {
         FrameBuilder {
             hit_testing_runs: flattener.hit_testing_runs,
-            cached_gradients: flattener.cached_gradients,
             scrollbar_prims: flattener.scrollbar_prims,
             prim_store: flattener.prim_store,
             clip_store: flattener.clip_store,
             screen_rect,
             background_color,
             window_size,
+            scene_id,
             config: flattener.config,
         }
     }
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(
         &mut self,
@@ -180,33 +183,33 @@ impl FrameBuilder {
             &clip_scroll_tree.nodes[clip_scroll_tree.root_reference_frame_index().0];
 
         let display_list = &pipelines
             .get(&root_clip_scroll_node.pipeline_id)
             .expect("No display list?")
             .display_list;
 
         let frame_context = FrameBuildingContext {
+            scene_id: self.scene_id,
             device_pixel_scale,
             scene_properties,
             pipelines,
             screen_rect: self.screen_rect.to_i32(),
             clip_scroll_tree,
             node_data,
         };
 
         let mut frame_state = FrameBuildingState {
             render_tasks,
             profile_counters,
             clip_store: &mut self.clip_store,
             local_clip_rects,
             resource_cache,
             gpu_cache,
             special_render_passes,
-            cached_gradients: &mut self.cached_gradients,
         };
 
         let pic_context = PictureContext {
             pipeline_id: root_clip_scroll_node.pipeline_id,
             prim_runs: mem::replace(&mut self.prim_store.pictures[0].runs, Vec::new()),
             original_reference_frame_index: None,
             display_list,
             inv_world_transform: None,
@@ -372,17 +375,16 @@ impl FrameBuilder {
         for pass in &mut passes {
             let mut ctx = RenderTargetContext {
                 device_pixel_scale,
                 prim_store: &self.prim_store,
                 resource_cache,
                 clip_scroll_tree,
                 use_dual_source_blending,
                 node_data: &node_data,
-                cached_gradients: &self.cached_gradients,
             };
 
             pass.build(
                 &mut ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
--- a/gfx/webrender/src/gpu_cache.rs
+++ b/gfx/webrender/src/gpu_cache.rs
@@ -490,21 +490,16 @@ impl<'a> GpuDataRequest<'a> {
 
     pub fn extend_from_slice(&mut self, blocks: &[GpuBlockData]) {
         self.texture.pending_blocks.extend_from_slice(blocks);
     }
 
     pub fn current_used_block_num(&self) -> usize {
         self.texture.pending_blocks.len() - self.start_index
     }
-
-    /// Consume the request and return the number of blocks written
-    pub fn close(self) -> usize {
-        self.texture.pending_blocks.len() - self.start_index
-    }
 }
 
 impl<'a> Drop for GpuDataRequest<'a> {
     fn drop(&mut self) {
         // Push the data to the texture pending updates list.
         let block_count = self.current_used_block_num();
         debug_assert!(block_count <= self.max_block_count);
 
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -75,17 +75,17 @@ pub enum BlurDirection {
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BlurInstance {
     pub task_address: RenderTaskAddress,
     pub src_task_address: RenderTaskAddress,
     pub blur_direction: BlurDirection,
 }
 
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq)]
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BorderSegment {
     TopLeft,
     TopRight,
     BottomRight,
     BottomLeft,
@@ -102,16 +102,17 @@ pub enum BorderSegment {
 pub struct BorderInstance {
     pub task_origin: DevicePoint,
     pub local_rect: DeviceRect,
     pub color0: PremultipliedColorF,
     pub color1: PremultipliedColorF,
     pub flags: i32,
     pub widths: DeviceSize,
     pub radius: DeviceSize,
+    pub clip_params: [f32; 8],
 }
 
 /// A clipping primitive drawn into the clipping mask.
 /// Could be an image or a rectangle, which defines the
 /// way `address` is treated.
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -333,17 +333,16 @@ fn get_regions_for_clip_scroll_node(
     };
 
     clips.iter().map(|source| {
         match source.0 {
             ClipSource::Rectangle(ref rect, mode) => HitTestRegion::Rectangle(*rect, mode),
             ClipSource::RoundedRectangle(ref rect, ref radii, ref mode) =>
                 HitTestRegion::RoundedRectangle(*rect, *radii, *mode),
             ClipSource::Image(ref mask) => HitTestRegion::Rectangle(mask.rect, ClipMode::Clip),
-            ClipSource::BorderCorner(_) |
             ClipSource::LineDecoration(_) |
             ClipSource::BoxShadow(_) => {
                 unreachable!("Didn't expect to hit test against BorderCorner / BoxShadow / LineDecoration");
             }
         }
     }).collect()
 }
 
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -127,26 +127,17 @@ impl TextureUpdateList {
     pub fn push(&mut self, update: TextureUpdate) {
         self.updates.push(update);
     }
 }
 
 /// Wraps a tiling::Frame, but conceptually could hold more information
 pub struct RenderedDocument {
     pub frame: tiling::Frame,
-}
-
-impl RenderedDocument {
-    pub fn new(
-        frame: tiling::Frame,
-    ) -> Self {
-        RenderedDocument {
-            frame,
-        }
-    }
+    pub is_new_scene: bool,
 }
 
 pub enum DebugOutput {
     FetchDocuments(String),
     FetchClipScrollTree(String),
     #[cfg(feature = "capture")]
     SaveCapture(CaptureConfig, Vec<ExternalCaptureImage>),
     #[cfg(feature = "replay")]
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -75,17 +75,21 @@ pub struct PictureCacheKey {
     //       we want the cache to remain valid as it
     //       is scrolled and/or translated by animation.
     //       This is valid while we have the restriction
     //       in place that only pictures that use the
     //       root coordinate system are cached - once
     //       we relax that, we'll need to consider some
     //       extra parameters, depending on transform.
 
-    // The unique identifier for this picture.
+    // This is a globally unique id of the scene this picture 
+    // is associated with, to avoid picture id collisions.
+    scene_id: u64,
+
+    // The unique (for the scene_id) identifier for this picture.
     // TODO(gw): Currently, these will not be
     //           shared across new display lists,
     //           so will only remain valid during
     //           scrolling. Next step will be to
     //           allow deep comparisons on pictures
     //           between display lists, allowing
     //           pictures that are the same to be
     //           cached across display lists!
@@ -369,16 +373,17 @@ impl PicturePrimitive {
                     );
 
                     // Request a render task that will cache the output in the
                     // texture cache.
                     let cache_item = frame_state.resource_cache.request_render_task(
                         RenderTaskCacheKey {
                             size: device_rect.size,
                             kind: RenderTaskCacheKeyKind::Picture(PictureCacheKey {
+                                scene_id: frame_context.scene_id,
                                 picture_id: self.id,
                                 unclipped_size: prim_screen_rect.unclipped.size,
                                 pic_relative_render_rect,
                             }),
                         },
                         frame_state.gpu_cache,
                         frame_state.render_tasks,
                         None,
@@ -462,16 +467,24 @@ impl PicturePrimitive {
                 );
 
                 self.secondary_render_task_id = Some(picture_task_id);
 
                 let render_task_id = frame_state.render_tasks.add(blur_render_task);
                 pic_state.tasks.push(render_task_id);
                 self.surface = Some(PictureSurface::RenderTask(render_task_id));
 
+                // If the local rect of the contents changed, force the cache handle
+                // to be invalidated so that the primitive data below will get
+                // uploaded to the GPU this frame. This can occur during property
+                // animation.
+                if pic_state.local_rect_changed {
+                    frame_state.gpu_cache.invalidate(&mut self.extra_gpu_data_handle);
+                }
+
                 if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
                     // TODO(gw): This is very hacky code below! It stores an extra
                     //           brush primitive below for the special case of a
                     //           drop-shadow where we need a different local
                     //           rect for the shadow. To tidy this up in future,
                     //           we could consider abstracting the code in prim_store.rs
                     //           that writes a brush primitive header.
 
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -4,17 +4,17 @@
 
 use api::{AlphaType, BorderRadius, BoxShadowClipMode, BuiltDisplayList, ClipMode, ColorF, ComplexClipRegion};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode};
 use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset};
 use api::{GlyphRasterSpace, LayoutPoint, LayoutRect, LayoutSize, LayoutToWorldTransform, LayoutVector2D};
 use api::{PipelineId, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, DeviceIntSideOffsets};
 use api::{BorderWidths, LayoutToWorldScale, NormalBorder};
 use app_units::Au;
-use border::{BorderCacheKey, BorderCornerInstance, BorderRenderTaskInfo, BorderEdgeKind};
+use border::{BorderCacheKey, BorderRenderTaskInfo};
 use box_shadow::BLUR_SAMPLE_SCALE;
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
 use clip::{ClipSourcesHandle, ClipWorkItem};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveRunContext;
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey};
@@ -78,31 +78,16 @@ impl PrimitiveOpacity {
 
     pub fn from_alpha(alpha: f32) -> PrimitiveOpacity {
         PrimitiveOpacity {
             is_opaque: alpha == 1.0,
         }
     }
 }
 
-#[derive(Debug, Copy, Clone)]
-pub struct CachedGradientIndex(pub usize);
-
-pub struct CachedGradient {
-    pub handle: GpuCacheHandle,
-}
-
-impl CachedGradient {
-    pub fn new() -> CachedGradient {
-        CachedGradient {
-            handle: GpuCacheHandle::new(),
-        }
-    }
-}
-
 // Represents the local space rect of a list of
 // primitive runs. For most primitive runs, the
 // primitive runs are attached to the parent they
 // are declared in. However, when a primitive run
 // is part of a 3d rendering context, it may get
 // hoisted to a higher level in the picture tree.
 // When this happens, we need to also calculate the
 // local space rects in the original space. This
@@ -145,17 +130,16 @@ pub struct PrimitiveIndex(pub usize);
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PictureIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 pub enum PrimitiveKind {
     TextRun,
-    Border,
     Brush,
 }
 
 impl GpuCacheHandle {
     pub fn as_int(&self, gpu_cache: &GpuCache) -> i32 {
         gpu_cache.get_address(self).as_int()
     }
 }
@@ -292,31 +276,30 @@ pub enum BrushKind {
     },
     YuvImage {
         yuv_key: [ImageKey; 3],
         format: YuvFormat,
         color_space: YuvColorSpace,
         image_rendering: ImageRendering,
     },
     RadialGradient {
-        gradient_index: CachedGradientIndex,
+        stops_handle: GpuCacheHandle,
         stops_range: ItemRange<GradientStop>,
         extend_mode: ExtendMode,
         center: LayoutPoint,
         start_radius: f32,
         end_radius: f32,
         ratio_xy: f32,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         visible_tiles: Vec<VisibleGradientTile>,
     },
     LinearGradient {
-        gradient_index: CachedGradientIndex,
+        stops_handle: GpuCacheHandle,
         stops_range: ItemRange<GradientStop>,
-        stops_count: usize,
         extend_mode: ExtendMode,
         reverse_stops: bool,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         visible_tiles: Vec<VisibleGradientTile>,
     },
@@ -552,29 +535,16 @@ pub enum ImageSource {
     // An image that is pre-rendered into the texture cache
     // via a render task.
     Cache {
         size: DeviceIntSize,
         handle: Option<RenderTaskCacheEntryHandle>,
     },
 }
 
-#[derive(Debug)]
-pub struct BorderPrimitiveCpu {
-    pub corner_instances: [BorderCornerInstance; 4],
-    pub edges: [BorderEdgeKind; 4],
-    pub gpu_blocks: [GpuBlockData; 8],
-}
-
-impl ToGpuBlocks for BorderPrimitiveCpu {
-    fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
-        request.extend_from_slice(&self.gpu_blocks);
-    }
-}
-
 // The gradient entry index for the first color stop
 pub const GRADIENT_DATA_FIRST_STOP: usize = 0;
 // The gradient entry index for the last color stop
 pub const GRADIENT_DATA_LAST_STOP: usize = GRADIENT_DATA_SIZE - 1;
 
 // The start of the gradient data table
 pub const GRADIENT_DATA_TABLE_BEGIN: usize = GRADIENT_DATA_FIRST_STOP + 1;
 // The exclusive bound of the gradient data table
@@ -1080,17 +1050,16 @@ impl ClipData {
             corner.write(request);
         }
     }
 }
 
 #[derive(Debug)]
 pub enum PrimitiveContainer {
     TextRun(TextRunPrimitiveCpu),
-    Border(BorderPrimitiveCpu),
     Brush(BrushPrimitive),
 }
 
 impl PrimitiveContainer {
     // Return true if the primary primitive is visible.
     // Used to trivially reject non-visible primitives.
     // TODO(gw): Currently, primitives other than those
     //           listed here are handled before the
@@ -1113,19 +1082,16 @@ impl PrimitiveContainer {
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::Border { .. } |
                     BrushKind::LinearGradient { .. } => {
                         true
                     }
                 }
             }
-            PrimitiveContainer::Border(..) => {
-                true
-            }
         }
     }
 
     // Create a clone of this PrimitiveContainer, applying whatever
     // changes are necessary to the primitive to support rendering
     // it as part of the supplied shadow.
     pub fn create_shadow(&self, shadow: &Shadow) -> PrimitiveContainer {
         match *self {
@@ -1161,53 +1127,47 @@ impl PrimitiveContainer {
                     BrushKind::YuvImage { .. } |
                     BrushKind::Border { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::LinearGradient { .. } => {
                         panic!("bug: other brush kinds not expected here yet");
                     }
                 }
             }
-            PrimitiveContainer::Border(..) => {
-                panic!("bug: other primitive containers not expected here");
-            }
         }
     }
 }
 
 pub struct PrimitiveStore {
     /// CPU side information only.
     pub cpu_brushes: Vec<BrushPrimitive>,
     pub cpu_text_runs: Vec<TextRunPrimitiveCpu>,
     pub cpu_metadata: Vec<PrimitiveMetadata>,
-    pub cpu_borders: Vec<BorderPrimitiveCpu>,
 
     pub pictures: Vec<PicturePrimitive>,
     next_picture_id: u64,
 }
 
 impl PrimitiveStore {
     pub fn new() -> PrimitiveStore {
         PrimitiveStore {
             cpu_metadata: Vec::new(),
             cpu_brushes: Vec::new(),
             cpu_text_runs: Vec::new(),
-            cpu_borders: Vec::new(),
 
             pictures: Vec::new(),
             next_picture_id: 0,
         }
     }
 
     pub fn recycle(self) -> Self {
         PrimitiveStore {
             cpu_metadata: recycle_vec(self.cpu_metadata),
             cpu_brushes: recycle_vec(self.cpu_brushes),
             cpu_text_runs: recycle_vec(self.cpu_text_runs),
-            cpu_borders: recycle_vec(self.cpu_borders),
 
             pictures: recycle_vec(self.pictures),
             next_picture_id: self.next_picture_id,
         }
     }
 
     pub fn add_image_picture(
         &mut self,
@@ -1292,27 +1252,16 @@ impl PrimitiveStore {
                     prim_kind: PrimitiveKind::TextRun,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_runs.len()),
                     ..base_metadata
                 };
 
                 self.cpu_text_runs.push(text_cpu);
                 metadata
             }
-            PrimitiveContainer::Border(border_cpu) => {
-                let metadata = PrimitiveMetadata {
-                    opacity: PrimitiveOpacity::translucent(),
-                    prim_kind: PrimitiveKind::Border,
-                    cpu_prim_index: SpecificPrimitiveIndex(self.cpu_borders.len()),
-                    ..base_metadata
-                };
-
-                self.cpu_borders.push(border_cpu);
-                metadata
-            }
         };
 
         self.cpu_metadata.push(metadata);
 
         PrimitiveIndex(prim_index)
     }
 
     // Internal method that retrieves the primitive index of a primitive
@@ -1360,18 +1309,17 @@ impl PrimitiveStore {
                     }
                     BrushKind::Border { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::LinearGradient { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::Clear => {}
                 }
             }
-            PrimitiveKind::TextRun |
-            PrimitiveKind::Border => {}
+            PrimitiveKind::TextRun => {}
         }
 
         None
     }
 
     // Apply any optimizations to drawing this picture. Currently,
     // we just support collapsing pictures with an opacity filter
     // by pushing that opacity value into the color of a primitive
@@ -1410,18 +1358,17 @@ impl PrimitiveStore {
                         BrushKind::YuvImage { .. } |
                         BrushKind::Border { .. } |
                         BrushKind::LinearGradient { .. } |
                         BrushKind::RadialGradient { .. } => {
                             unreachable!("bug: invalid prim type for opacity collapse");
                         }
                     };
                 }
-                PrimitiveKind::TextRun |
-                PrimitiveKind::Border => {
+                PrimitiveKind::TextRun => {
                     unreachable!("bug: invalid prim type for opacity collapse");
                 }
             }
 
             // The opacity filter has been collapsed, so mark this picture
             // as a pass though. This means it will no longer allocate an
             // intermediate surface or incur an extra blend / blit. Instead,
             // the collapsed primitive will be drawn directly into the
@@ -1440,17 +1387,17 @@ impl PrimitiveStore {
 
     fn build_prim_segments_if_needed(
         &mut self,
         prim_index: PrimitiveIndex,
         pic_state: &mut PictureState,
         frame_state: &mut FrameBuildingState,
         frame_context: &FrameBuildingContext,
     ) {
-        let metadata = &self.cpu_metadata[prim_index.0];
+        let metadata = &mut self.cpu_metadata[prim_index.0];
 
         if metadata.prim_kind != PrimitiveKind::Brush {
             return;
         }
 
         let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0];
 
         if let BrushKind::Border { ref mut source, .. } = brush.kind {
@@ -1508,16 +1455,20 @@ impl PrimitiveStore {
                     }
                 ));
 
                 if needs_update {
                     brush.segment_desc = Some(BrushSegmentDescriptor {
                         segments: new_segments,
                         clip_mask_kind: BrushClipMaskKind::Unknown,
                     });
+
+                    // The segments have changed, so force the GPU cache to
+                    // re-upload the primitive information.
+                    frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
                 }
             }
         }
     }
 
     fn prepare_prim_for_render_inner(
         &mut self,
         prim_index: PrimitiveIndex,
@@ -1531,17 +1482,16 @@ impl PrimitiveStore {
         let mut is_tiled = false;
         let metadata = &mut self.cpu_metadata[prim_index.0];
         #[cfg(debug_assertions)]
         {
             metadata.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
         match metadata.prim_kind {
-            PrimitiveKind::Border => {}
             PrimitiveKind::TextRun => {
                 let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
                 // The transform only makes sense for screen space rasterization
                 let transform = Some(prim_run_context.scroll_node.world_content_transform.into());
                 text.prepare_for_render(
                     frame_context.device_pixel_scale,
                     transform,
                     pic_context.allow_subpixel_aa,
@@ -1801,30 +1751,30 @@ impl PrimitiveStore {
                             }
                             BorderSource::Border { .. } => {
                                 // Handled earlier since we need to update the segment
                                 // descriptor *before* update_clip_task() is called.
                             }
                         }
                     }
                     BrushKind::RadialGradient {
-                        gradient_index,
                         stops_range,
                         center,
                         start_radius,
                         end_radius,
                         ratio_xy,
                         extend_mode,
                         stretch_size,
                         tile_spacing,
+                        ref mut stops_handle,
                         ref mut visible_tiles,
                         ..
                     } => {
                         build_gradient_stops_request(
-                            gradient_index,
+                            stops_handle,
                             stops_range,
                             false,
                             frame_state,
                             pic_context,
                         );
 
                         if tile_spacing != LayoutSize::zero() {
                             is_tiled = true;
@@ -1853,30 +1803,30 @@ impl PrimitiveStore {
                                         stretch_size.height,
                                     ]);
                                     request.write_segment(*rect, [0.0; 4]);
                                 },
                             );
                         }
                     }
                     BrushKind::LinearGradient {
-                        gradient_index,
                         stops_range,
                         reverse_stops,
                         start_point,
                         end_point,
                         extend_mode,
                         stretch_size,
                         tile_spacing,
+                        ref mut stops_handle,
                         ref mut visible_tiles,
                         ..
                     } => {
 
                         build_gradient_stops_request(
-                            gradient_index,
+                            stops_handle,
                             stops_range,
                             reverse_stops,
                             frame_state,
                             pic_context,
                         );
 
                         if tile_spacing != LayoutSize::zero() {
                             is_tiled = true;
@@ -1943,20 +1893,16 @@ impl PrimitiveStore {
 
         // Mark this GPU resource as required for this frame.
         if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) {
             // has to match VECS_PER_BRUSH_PRIM
             request.push(metadata.local_rect);
             request.push(metadata.local_clip_rect);
 
             match metadata.prim_kind {
-                PrimitiveKind::Border => {
-                    let border = &self.cpu_borders[metadata.cpu_prim_index.0];
-                    border.write_gpu_blocks(request);
-                }
                 PrimitiveKind::TextRun => {
                     let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
                     text.write_gpu_blocks(&mut request);
                 }
                 PrimitiveKind::Brush => {
                     let brush = &self.cpu_brushes[metadata.cpu_prim_index.0];
                     brush.write_gpu_blocks(&mut request, metadata.local_rect);
 
@@ -2073,17 +2019,16 @@ impl PrimitiveStore {
                                 -0.5 * info.shadow_rect_alloc_size.width,
                                 -0.5 * info.shadow_rect_alloc_size.height,
                             ),
                             inner_clip_mode,
                         );
 
                         continue;
                     }
-                    ClipSource::BorderCorner(..) |
                     ClipSource::LineDecoration(..) |
                     ClipSource::Image(..) => {
                         rect_clips_only = false;
 
                         // TODO(gw): We can easily extend the segment builder
                         //           to support these clip sources in the
                         //           future, but they are rarely used.
                         clip_mask_kind = BrushClipMaskKind::Global;
@@ -2468,16 +2413,17 @@ impl PrimitiveStore {
 
                 let metadata = &mut self.cpu_metadata[prim_index.0];
 
                 let new_local_rect = pic.update_local_rect(result);
 
                 if new_local_rect != metadata.local_rect {
                     metadata.local_rect = new_local_rect;
                     frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
+                    pic_state.local_rect_changed = true;
                 }
             }
         }
 
         let (local_rect, unclipped_device_rect) = {
             let metadata = &mut self.cpu_metadata[prim_index.0];
             if metadata.local_rect.size.width <= 0.0 ||
                metadata.local_rect.size.height <= 0.0 {
@@ -2667,23 +2613,22 @@ impl PrimitiveStore {
             }
         }
 
         result
     }
 }
 
 fn build_gradient_stops_request(
-    gradient_index: CachedGradientIndex,
+    stops_handle: &mut GpuCacheHandle,
     stops_range: ItemRange<GradientStop>,
     reverse_stops: bool,
     frame_state: &mut FrameBuildingState,
     pic_context: &PictureContext
 ) {
-    let stops_handle = &mut frame_state.cached_gradients[gradient_index.0].handle;
     if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
         let gradient_builder = GradientGpuBlockBuilder::new(
             stops_range,
             pic_context.display_list,
         );
         gradient_builder.build(
             reverse_stops,
             &mut request,
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -1209,8 +1209,55 @@ impl Profiler {
                 renderer_timers,
                 gpu_samplers,
                 screen_fraction,
                 debug_renderer,
             );
         }
     }
 }
+
+#[cfg(feature = "debug_renderer")]
+pub struct ChangeIndicator {
+    counter: u32,
+}
+
+#[cfg(feature = "debug_renderer")]
+impl ChangeIndicator {
+    pub fn new() -> Self {
+        ChangeIndicator {
+            counter: 0
+        }
+    }
+
+    pub fn changed(&mut self) {
+        self.counter = (self.counter + 1) % 15;
+    }
+
+    pub fn draw(
+        &self,
+        x: f32, y: f32,
+        color: ColorU,
+        debug_renderer: &mut DebugRenderer
+    ) {
+        let margin = 0.0;
+        let w = 10.0;
+        let h = 5.0;
+        let tx = self.counter as f32 * w;
+        debug_renderer.add_quad(
+            x - margin,
+            y - margin,
+            x + 15.0 * w + margin,
+            y + h + margin,
+            ColorU::new(0, 0, 0, 150),
+            ColorU::new(0, 0, 0, 150),
+        );
+
+        debug_renderer.add_quad(
+            x + tx,
+            y,
+            x + tx + w,
+            y + h,
+            color,
+            ColorU::new(25, 25, 25, 255),
+        );
+    }
+}
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -106,21 +106,16 @@ struct Document {
     // made available as output textures.
     output_pipelines: FastHashSet<PipelineId>,
     // A helper switch to prevent any frames rendering triggered by scrolling
     // messages between `SetDisplayList` and `GenerateFrame`.
     // If we allow them, then a reftest that scrolls a few layers before generating
     // the first frame would produce inconsistent rendering results, because
     // scroll events are not necessarily received in deterministic order.
     render_on_scroll: Option<bool>,
-    // A helper flag to prevent any hit-tests from happening between calls
-    // to build_scene and rendering the document. In between these two calls,
-    // hit-tests produce inconsistent results because the clip_scroll_tree
-    // is out of sync with the display list.
-    render_on_hittest: bool,
 
     /// A data structure to allow hit testing against rendered frames. This is updated
     /// every time we produce a fully rendered frame.
     hit_tester: Option<HitTester>,
 
     /// Properties that are resolved during frame building and can be changed at any time
     /// without requiring the scene to be re-built.
     dynamic_properties: SceneProperties,
@@ -158,30 +153,29 @@ impl Document {
                 device_pixel_ratio: default_device_pixel_ratio,
             },
             clip_scroll_tree: ClipScrollTree::new(),
             frame_id: FrameId(0),
             frame_builder_config,
             frame_builder: None,
             output_pipelines: FastHashSet::default(),
             render_on_scroll,
-            render_on_hittest: false,
             hit_tester: None,
             dynamic_properties: SceneProperties::new(),
         }
     }
 
     fn can_render(&self) -> bool { self.frame_builder.is_some() }
 
     fn has_pixels(&self) -> bool {
         !self.view.window_size.is_empty_or_negative()
     }
 
     // TODO: We will probably get rid of this soon and always forward to the scene building thread.
-    fn build_scene(&mut self, resource_cache: &mut ResourceCache) {
+    fn build_scene(&mut self, resource_cache: &mut ResourceCache, scene_id: u64) {
         let max_texture_size = resource_cache.max_texture_size();
 
         if self.view.window_size.width > max_texture_size ||
            self.view.window_size.height > max_texture_size {
             error!("ERROR: Invalid window dimensions {}x{}. Please call api.set_window_size()",
                 self.view.window_size.width,
                 self.view.window_size.height,
             );
@@ -194,30 +188,31 @@ impl Document {
             Some(root_pipeline_id) => root_pipeline_id,
             None => return,
         };
 
         if !self.pending.scene.pipelines.contains_key(&root_pipeline_id) {
             return;
         }
 
-        // The DisplayListFlattener will re-create the up-to-date current scene's pipeline epoch
+        // The DisplayListFlattener  re-create the up-to-date current scene's pipeline epoch
         // map and clip scroll tree from the information in the pending scene.
         self.current.scene.pipeline_epochs.clear();
         let old_scrolling_states = self.clip_scroll_tree.drain();
 
         let frame_builder = DisplayListFlattener::create_frame_builder(
             old_builder,
             &self.pending.scene,
             &mut self.clip_scroll_tree,
             resource_cache.get_font_instances(),
             &self.view,
             &self.output_pipelines,
             &self.frame_builder_config,
             &mut self.current.scene,
+            scene_id,
         );
 
         self.clip_scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
 
         if !self.current.removed_pipelines.is_empty() {
             warn!("Built the scene several times without rendering it.");
         }
 
@@ -228,16 +223,17 @@ impl Document {
         self.frame_id.0 += 1;
     }
 
     fn forward_transaction_to_scene_builder(
         &mut self,
         transaction_msg: TransactionMsg,
         document_ops: &DocumentOps,
         document_id: DocumentId,
+        scene_id: u64,
         resource_cache: &ResourceCache,
         scene_tx: &Sender<SceneBuilderRequest>,
     ) {
         // Do as much of the error handling as possible here before dispatching to
         // the scene builder thread.
         let build_scene: bool = document_ops.build
             && self.pending.scene.root_pipeline_id.map(
                 |id| { self.pending.scene.pipelines.contains_key(&id) }
@@ -245,16 +241,17 @@ impl Document {
 
         let scene_request = if build_scene {
             Some(SceneRequest {
                 scene: self.pending.scene.clone(),
                 removed_pipelines: replace(&mut self.pending.removed_pipelines, Vec::new()),
                 view: self.view.clone(),
                 font_instances: resource_cache.get_font_instances(),
                 output_pipelines: self.output_pipelines.clone(),
+                scene_id,
             })
         } else {
             None
         };
 
         scene_tx.send(SceneBuilderRequest::Transaction {
             scene: scene_request,
             resource_updates: transaction_msg.resource_updates,
@@ -264,16 +261,17 @@ impl Document {
         }).unwrap();
     }
 
     fn render(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         resource_profile: &mut ResourceProfileCounters,
+        is_new_scene: bool,
     ) -> RenderedDocument {
         let accumulated_scale_factor = self.view.accumulated_scale_factor();
         let pan = self.view.pan.to_f32() / accumulated_scale_factor;
 
         let frame = {
             let frame_builder = self.frame_builder.as_mut().unwrap();
             let frame = frame_builder.build(
                 resource_cache,
@@ -287,17 +285,20 @@ impl Document {
                 &mut resource_profile.texture_cache,
                 &mut resource_profile.gpu_cache,
                 &self.dynamic_properties,
             );
             self.hit_tester = Some(frame_builder.create_hit_tester(&self.clip_scroll_tree));
             frame
         };
 
-        RenderedDocument::new(frame)
+        RenderedDocument {
+            frame,
+            is_new_scene,
+        }
     }
 
     pub fn updated_pipeline_info(&mut self) -> PipelineInfo {
         let removed_pipelines = replace(&mut self.current.removed_pipelines, Vec::new());
         PipelineInfo {
             epochs: self.current.scene.pipeline_epochs.clone(),
             removed_pipelines,
         }
@@ -392,16 +393,17 @@ static NEXT_NAMESPACE_ID: AtomicUsize = 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct PlainRenderBackend {
     default_device_pixel_ratio: f32,
     enable_render_on_scroll: bool,
     frame_config: FrameBuilderConfig,
     documents: FastHashMap<DocumentId, DocumentView>,
     resources: PlainResources,
+    last_scene_id: u64,
 }
 
 /// The render backend is responsible for transforming high level display lists into
 /// GPU-friendly work which is then submitted to the renderer in the form of a frame::Frame.
 ///
 /// The render backend operates on its own thread.
 pub struct RenderBackend {
     api_rx: MsgReceiver<ApiMsg>,
@@ -419,16 +421,17 @@ pub struct RenderBackend {
 
     frame_config: FrameBuilderConfig,
     documents: FastHashMap<DocumentId, Document>,
 
     notifier: Box<RenderNotifier>,
     recorder: Option<Box<ApiRecordingReceiver>>,
     sampler: Option<Box<AsyncPropertySampler + Send>>,
 
+    last_scene_id: u64,
     enable_render_on_scroll: bool,
 }
 
 impl RenderBackend {
     pub fn new(
         api_rx: MsgReceiver<ApiMsg>,
         payload_rx: Receiver<Payload>,
         result_tx: Sender<ResultMsg>,
@@ -455,16 +458,17 @@ impl RenderBackend {
             default_device_pixel_ratio,
             resource_cache,
             gpu_cache: GpuCache::new(),
             frame_config,
             documents: FastHashMap::default(),
             notifier,
             recorder,
             sampler,
+            last_scene_id: 0,
             enable_render_on_scroll,
         }
     }
 
     fn process_scene_msg(
         &mut self,
         document_id: DocumentId,
         message: SceneMsg,
@@ -683,16 +687,22 @@ impl RenderBackend {
             }
         }
     }
 
     fn next_namespace_id(&self) -> IdNamespace {
         IdNamespace(NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed) as u32)
     }
 
+    pub fn make_unique_scene_id(&mut self) -> u64 {
+        // 2^64 scenes ought to be enough for anybody!
+        self.last_scene_id += 1;
+        self.last_scene_id
+    }
+
     pub fn run(&mut self, mut profile_counters: BackendProfileCounters) {
         let mut frame_counter: u32 = 0;
         let mut keep_going = true;
 
         if let Some(ref sampler) = self.sampler {
             sampler.register();
         }
 
@@ -707,17 +717,16 @@ impl RenderBackend {
                         resource_updates,
                         frame_ops,
                         render,
                         result_tx,
                     } => {
                         if let Some(doc) = self.documents.get_mut(&document_id) {
                             if let Some(mut built_scene) = built_scene.take() {
                                 doc.new_async_scene_ready(built_scene);
-                                doc.render_on_hittest = true;
                             }
                             if let Some(tx) = result_tx {
                                 let (resume_tx, resume_rx) = channel();
                                 tx.send(SceneSwapResult::Complete(resume_tx)).unwrap();
                                 // Block until the post-swap hook has completed on
                                 // the scene builder thread. We need to do this before
                                 // we can sample from the sampler hook which might happen
                                 // in the update_document call below.
@@ -741,17 +750,18 @@ impl RenderBackend {
                             use_scene_builder_thread: false,
                         };
 
                         if !transaction_msg.is_empty() {
                             self.update_document(
                                 document_id,
                                 transaction_msg,
                                 &mut frame_counter,
-                                &mut profile_counters
+                                &mut profile_counters,
+                                DocumentOps::render(),
                             );
                         }
                     },
                     SceneBuilderResult::FlushComplete(tx) => {
                         tx.send(()).ok();
                     }
                     SceneBuilderResult::Stopped => {
                         panic!("We haven't sent a Stop yet, how did we get a Stopped back?");
@@ -940,78 +950,83 @@ impl RenderBackend {
             ApiMsg::ShutDown => {
                 return false;
             }
             ApiMsg::UpdateDocument(document_id, doc_msgs) => {
                 self.update_document(
                     document_id,
                     doc_msgs,
                     frame_counter,
-                    profile_counters
+                    profile_counters,
+                    DocumentOps::nop(),
                 )
             }
         }
 
         true
     }
 
     fn update_document(
         &mut self,
         document_id: DocumentId,
         mut transaction_msg: TransactionMsg,
         frame_counter: &mut u32,
         profile_counters: &mut BackendProfileCounters,
+        initial_op: DocumentOps,
     ) {
-        let mut op = DocumentOps::nop();
+        let mut op = initial_op;
 
         for scene_msg in transaction_msg.scene_ops.drain(..) {
             let _timer = profile_counters.total_time.timer();
             op.combine(
                 self.process_scene_msg(
                     document_id,
                     scene_msg,
                     *frame_counter,
                     &mut profile_counters.ipc,
                 )
             );
         }
 
         if transaction_msg.use_scene_builder_thread {
+            let scene_id = self.make_unique_scene_id();
             let doc = self.documents.get_mut(&document_id).unwrap();
+
             doc.forward_transaction_to_scene_builder(
                 transaction_msg,
                 &op,
                 document_id,
+                scene_id,
                 &self.resource_cache,
                 &self.scene_tx,
             );
 
             return;
         }
 
         self.resource_cache.update_resources(
             transaction_msg.resource_updates,
             &mut profile_counters.resources,
         );
 
         if op.build {
+            let scene_id = self.make_unique_scene_id();
             let doc = self.documents.get_mut(&document_id).unwrap();
             let _timer = profile_counters.total_time.timer();
             profile_scope!("build scene");
 
-            doc.build_scene(&mut self.resource_cache);
-            doc.render_on_hittest = true;
+            doc.build_scene(&mut self.resource_cache, scene_id);
         }
 
         // If we have a sampler, get more frame ops from it and add them
         // to the transaction. This is a hook to allow the WR user code to
         // fiddle with things after a potentially long scene build, but just
         // before rendering. This is useful for rendering with the latest
         // async transforms.
-        if transaction_msg.generate_frame {
+        if op.render || transaction_msg.generate_frame {
             if let Some(ref sampler) = self.sampler {
                 transaction_msg.frame_ops.append(&mut sampler.sample());
             }
         }
 
         for frame_msg in transaction_msg.frame_ops {
             let _timer = profile_counters.total_time.timer();
             op.combine(self.process_frame_msg(document_id, frame_msg));
@@ -1048,16 +1063,17 @@ impl RenderBackend {
             // borrow ck hack for profile_counters
             let (pending_update, rendered_document) = {
                 let _timer = profile_counters.total_time.timer();
 
                 let rendered_document = doc.render(
                     &mut self.resource_cache,
                     &mut self.gpu_cache,
                     &mut profile_counters.resources,
+                    op.build,
                 );
 
                 debug!("generated frame for document {:?} with {} passes",
                     document_id, rendered_document.frame.passes.len());
 
                 let msg = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates());
                 self.result_tx.send(msg).unwrap();
 
@@ -1072,17 +1088,16 @@ impl RenderBackend {
             let msg = ResultMsg::PublishDocument(
                 document_id,
                 rendered_document,
                 pending_update,
                 profile_counters.clone()
             );
             self.result_tx.send(msg).unwrap();
             profile_counters.reset();
-            doc.render_on_hittest = false;
         } else if op.render {
             // WR-internal optimization to avoid doing a bunch of render work if
             // there's no pixels. We still want to pretend to render and request
             // a composite to make sure that the callbacks (particularly the
             // new_frame_ready callback below) has the right flags.
             let msg = ResultMsg::PublishPipelineInfo(doc.updated_pipeline_info());
             self.result_tx.send(msg).unwrap();
         }
@@ -1242,16 +1257,17 @@ impl RenderBackend {
                 let file_name = format!("scene-{}-{}", (id.0).0, id.1);
                 config.serialize(&doc.current.scene, file_name);
             }
             if config.bits.contains(CaptureBits::FRAME) {
                 let rendered_document = doc.render(
                     &mut self.resource_cache,
                     &mut self.gpu_cache,
                     &mut profile_counters.resources,
+                    true,
                 );
                 //TODO: write down doc's pipeline info?
                 // it has `pipeline_epoch_map`,
                 // which may capture necessary details for some cases.
                 let file_name = format!("frame-{}-{}", (id.0).0, id.1);
                 config.serialize(&rendered_document.frame, file_name);
             }
         }
@@ -1264,16 +1280,17 @@ impl RenderBackend {
             default_device_pixel_ratio: self.default_device_pixel_ratio,
             enable_render_on_scroll: self.enable_render_on_scroll,
             frame_config: self.frame_config.clone(),
             documents: self.documents
                 .iter()
                 .map(|(id, doc)| (*id, doc.view.clone()))
                 .collect(),
             resources,
+            last_scene_id: self.last_scene_id,
         };
 
         config.serialize(&backend, "backend");
 
         if config.bits.contains(CaptureBits::FRAME) {
             // After we rendered the frames, there are pending updates to both
             // GPU cache and resources. Instead of serializing them, we are going to make sure
             // they are applied on the `Renderer` side.
@@ -1323,16 +1340,17 @@ impl RenderBackend {
             None => GpuCache::new(),
         };
 
         self.documents.clear();
         self.default_device_pixel_ratio = backend.default_device_pixel_ratio;
         self.frame_config = backend.frame_config;
         self.enable_render_on_scroll = backend.enable_render_on_scroll;
 
+        let mut last_scene_id = backend.last_scene_id;
         for (id, view) in backend.documents {
             debug!("\tdocument {:?}", id);
             let scene_name = format!("scene-{}-{}", (id.0).0, id.1);
             let scene = CaptureConfig::deserialize::<Scene, _>(root, &scene_name)
                 .expect(&format!("Unable to open {}.ron", scene_name));
 
             let mut doc = Document {
                 current: SceneData {
@@ -1345,33 +1363,34 @@ impl RenderBackend {
                 },
                 view,
                 clip_scroll_tree: ClipScrollTree::new(),
                 frame_id: FrameId(0),
                 frame_builder_config: self.frame_config.clone(),
                 frame_builder: Some(FrameBuilder::empty()),
                 output_pipelines: FastHashSet::default(),
                 render_on_scroll: None,
-                render_on_hittest: false,
                 dynamic_properties: SceneProperties::new(),
                 hit_tester: None,
             };
 
             let frame_name = format!("frame-{}-{}", (id.0).0, id.1);
             let render_doc = match CaptureConfig::deserialize::<Frame, _>(root, frame_name) {
                 Some(frame) => {
                     info!("\tloaded a built frame with {} passes", frame.passes.len());
-                    RenderedDocument::new(frame)
+                    RenderedDocument { frame, is_new_scene: true }
                 }
                 None => {
-                    doc.build_scene(&mut self.resource_cache);
+                    last_scene_id += 1;
+                    doc.build_scene(&mut self.resource_cache, last_scene_id);
                     doc.render(
                         &mut self.resource_cache,
                         &mut self.gpu_cache,
                         &mut profile_counters.resources,
+                        true,
                     )
                 }
             };
 
             let msg_update = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates());
             self.result_tx.send(msg_update).unwrap();
 
             let msg_publish = ResultMsg::PublishDocument(
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -448,18 +448,17 @@ impl RenderTask {
 
                                 root_task_id
                             }
                         ));
                     }
                     ClipSource::Rectangle(..) |
                     ClipSource::RoundedRectangle(..) |
                     ClipSource::Image(..) |
-                    ClipSource::LineDecoration(..) |
-                    ClipSource::BorderCorner(..) => {}
+                    ClipSource::LineDecoration(..) => {}
                 }
             }
         }
 
         RenderTask {
             children,
             location: RenderTaskLocation::Dynamic(None, Some(outer_rect.size)),
             kind: RenderTaskKind::CacheMask(CacheMaskTask {
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -77,17 +77,17 @@ cfg_if! {
         use api::channel::MsgSender;
     }
 }
 
 cfg_if! {
     if #[cfg(feature = "debug_renderer")] {
         use api::ColorU;
         use debug_render::DebugRenderer;
-        use profiler::Profiler;
+        use profiler::{Profiler, ChangeIndicator};
         use query::GpuTimer;
     }
 }
 
 pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024;
 /// Enabling this toggle would force the GPU cache scattered texture to
 /// be resized every frame, which enables GPU debuggers to see if this
 /// is performed correctly.
@@ -143,24 +143,16 @@ const GPU_TAG_SETUP_DATA: GpuProfileTag 
 const GPU_TAG_PRIM_SPLIT_COMPOSITE: GpuProfileTag = GpuProfileTag {
     label: "SplitComposite",
     color: debug_colors::DARKBLUE,
 };
 const GPU_TAG_PRIM_TEXT_RUN: GpuProfileTag = GpuProfileTag {
     label: "TextRun",
     color: debug_colors::BLUE,
 };
-const GPU_TAG_PRIM_BORDER_CORNER: GpuProfileTag = GpuProfileTag {
-    label: "BorderCorner",
-    color: debug_colors::DARKSLATEGREY,
-};
-const GPU_TAG_PRIM_BORDER_EDGE: GpuProfileTag = GpuProfileTag {
-    label: "BorderEdge",
-    color: debug_colors::LAVENDER,
-};
 const GPU_TAG_BLUR: GpuProfileTag = GpuProfileTag {
     label: "Blur",
     color: debug_colors::VIOLET,
 };
 const GPU_TAG_BLIT: GpuProfileTag = GpuProfileTag {
     label: "Blit",
     color: debug_colors::LIME,
 };
@@ -178,26 +170,22 @@ const GPU_SAMPLER_TAG_TRANSPARENT: GpuPr
     color: debug_colors::BLACK,
 };
 
 impl TransformBatchKind {
     #[cfg(feature = "debugger")]
     fn debug_name(&self) -> &'static str {
         match *self {
             TransformBatchKind::TextRun(..) => "TextRun",
-            TransformBatchKind::BorderCorner => "BorderCorner",
-            TransformBatchKind::BorderEdge => "BorderEdge",
         }
     }
 
     fn sampler_tag(&self) -> GpuProfileTag {
         match *self {
             TransformBatchKind::TextRun(..) => GPU_TAG_PRIM_TEXT_RUN,
-            TransformBatchKind::BorderCorner => GPU_TAG_PRIM_BORDER_CORNER,
-            TransformBatchKind::BorderEdge => GPU_TAG_PRIM_BORDER_EDGE,
         }
     }
 }
 
 impl BatchKind {
     #[cfg(feature = "debugger")]
     fn debug_name(&self) -> &'static str {
         match *self {
@@ -243,16 +231,18 @@ bitflags! {
         const RENDER_TARGET_DBG = 1 << 1;
         const TEXTURE_CACHE_DBG = 1 << 2;
         const GPU_TIME_QUERIES  = 1 << 3;
         const GPU_SAMPLE_QUERIES= 1 << 4;
         const DISABLE_BATCHING  = 1 << 5;
         const EPOCHS            = 1 << 6;
         const COMPACT_PROFILER  = 1 << 7;
         const ECHO_DRIVER_MESSAGES = 1 << 8;
+        const NEW_FRAME_INDICATOR = 1 << 9;
+        const NEW_SCENE_INDICATOR = 1 << 10;
     }
 }
 
 fn flag_changed(before: DebugFlags, after: DebugFlags, select: DebugFlags) -> Option<bool> {
     if before & select != after & select {
         Some(after.contains(select))
     } else {
         None
@@ -433,16 +423,26 @@ pub(crate) mod desc {
                 count: 2,
                 kind: VertexAttributeKind::F32,
             },
             VertexAttribute {
                 name: "aRadii",
                 count: 2,
                 kind: VertexAttributeKind::F32,
             },
+            VertexAttribute {
+                name: "aClipParams1",
+                count: 4,
+                kind: VertexAttributeKind::F32,
+            },
+            VertexAttribute {
+                name: "aClipParams2",
+                count: 4,
+                kind: VertexAttributeKind::F32,
+            },
         ],
     };
 
     pub const CLIP: VertexDescriptor = VertexDescriptor {
         vertex_attributes: &[
             VertexAttribute {
                 name: "aPosition",
                 count: 2,
@@ -616,17 +616,16 @@ pub(crate) mod desc {
     };
 }
 
 #[derive(Debug, Copy, Clone)]
 pub(crate) enum VertexArrayKind {
     Primitive,
     Blur,
     Clip,
-    DashAndDot,
     VectorStencil,
     VectorCover,
     Border,
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum GraphicsApi {
     OpenGL,
@@ -1370,16 +1369,21 @@ pub struct Renderer {
     enable_clear_scissor: bool,
     #[cfg(feature = "debug_renderer")]
     debug: LazyInitializedDebugRenderer,
     debug_flags: DebugFlags,
     backend_profile_counters: BackendProfileCounters,
     profile_counters: RendererProfileCounters,
     #[cfg(feature = "debug_renderer")]
     profiler: Profiler,
+    #[cfg(feature = "debug_renderer")]
+    new_frame_indicator: ChangeIndicator,
+    #[cfg(feature = "debug_renderer")]
+    new_scene_indicator: ChangeIndicator,
+
     last_time: u64,
 
     pub gpu_profile: GpuProfiler<GpuProfileTag>,
     vaos: RendererVAOs,
 
     node_data_texture: VertexDataTexture,
     local_clip_rects_texture: VertexDataTexture,
     render_task_texture: VertexDataTexture,
@@ -1772,16 +1776,20 @@ impl Renderer {
             shaders,
             #[cfg(feature = "debug_renderer")]
             debug: LazyInitializedDebugRenderer::new(),
             debug_flags,
             backend_profile_counters: BackendProfileCounters::new(),
             profile_counters: RendererProfileCounters::new(),
             #[cfg(feature = "debug_renderer")]
             profiler: Profiler::new(),
+            #[cfg(feature = "debug_renderer")]
+            new_frame_indicator: ChangeIndicator::new(),
+            #[cfg(feature = "debug_renderer")]
+            new_scene_indicator: ChangeIndicator::new(),
             max_texture_size: max_device_size,
             max_recorded_profiles: options.max_recorded_profiles,
             clear_color: options.clear_color,
             enable_clear_scissor: options.enable_clear_scissor,
             last_time: 0,
             gpu_profile,
             gpu_glyph_renderer,
             vaos: RendererVAOs {
@@ -1861,16 +1869,21 @@ impl Renderer {
                     self.pipeline_info.removed_pipelines.extend(pipeline_info.removed_pipelines.drain(..));
                 }
                 ResultMsg::PublishDocument(
                     document_id,
                     mut doc,
                     texture_update_list,
                     profile_counters,
                 ) => {
+                    if doc.is_new_scene {
+                        #[cfg(feature = "debug_renderer")]
+                        self.new_scene_indicator.changed();
+                    }
+
                     // Add a new document to the active set, expressed as a `Vec` in order
                     // to re-order based on `DocumentLayer` during rendering.
                     match self.active_documents.iter().position(|&(id, _)| id == document_id) {
                         Some(pos) => {
                             // If the document we are replacing must be drawn
                             // (in order to update the texture cache), issue
                             // a render just to off-screen targets.
                             if self.active_documents[pos].1.frame.must_be_drawn() {
@@ -1976,26 +1989,16 @@ impl Renderer {
         );
         debug_target.add(
             debug_server::BatchKind::Cache,
             "Zero Clears",
             target.zero_clears.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Clip,
-            "Clear",
-            target.clip_batcher.border_clears.len(),
-        );
-        debug_target.add(
-            debug_server::BatchKind::Clip,
-            "Borders",
-            target.clip_batcher.borders.len(),
-        );
-        debug_target.add(
-            debug_server::BatchKind::Clip,
             "BoxShadows",
             target.clip_batcher.box_shadows.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Clip,
             "LineDecorations",
             target.clip_batcher.line_decorations.len(),
         );
@@ -2146,16 +2149,22 @@ impl Renderer {
                 self.set_debug_flag(DebugFlags::RENDER_TARGET_DBG, enable);
             }
             DebugCommand::EnableGpuTimeQueries(enable) => {
                 self.set_debug_flag(DebugFlags::GPU_TIME_QUERIES, enable);
             }
             DebugCommand::EnableGpuSampleQueries(enable) => {
                 self.set_debug_flag(DebugFlags::GPU_SAMPLE_QUERIES, enable);
             }
+            DebugCommand::EnableNewFrameIndicator(enable) => {
+                self.set_debug_flag(DebugFlags::NEW_FRAME_INDICATOR, enable);
+            }
+            DebugCommand::EnableNewSceneIndicator(enable) => {
+                self.set_debug_flag(DebugFlags::NEW_SCENE_INDICATOR, enable);
+            }
             DebugCommand::EnableDualSourceBlending(_) => {
                 panic!("Should be handled by render backend");
             }
             DebugCommand::FetchDocuments |
             DebugCommand::FetchClipScrollTree => {}
             DebugCommand::FetchRenderTasks => {
                 let json = self.get_render_tasks_for_debugger();
                 self.debug_server.send(json);
@@ -2389,16 +2398,33 @@ impl Renderer {
                         &mut profile_timers,
                         &profile_samplers,
                         screen_fraction,
                         self.debug.get_mut(&mut self.device),
                         self.debug_flags.contains(DebugFlags::COMPACT_PROFILER),
                     );
                 }
             }
+
+            if self.debug_flags.contains(DebugFlags::NEW_FRAME_INDICATOR) {
+                self.new_frame_indicator.changed();
+                self.new_frame_indicator.draw(
+                    0.0, 0.0,
+                    ColorU::new(0, 110, 220, 255),
+                    self.debug.get_mut(&mut self.device)
+                );
+            }
+
+            if self.debug_flags.contains(DebugFlags::NEW_SCENE_INDICATOR) {
+                self.new_scene_indicator.draw(
+                    160.0, 0.0,
+                    ColorU::new(220, 30, 10, 255),
+                    self.debug.get_mut(&mut self.device)
+                );
+            }
         }
 
         if self.debug_flags.contains(DebugFlags::ECHO_DRIVER_MESSAGES) {
             self.device.echo_driver_messages();
         }
 
         self.backend_profile_counters.reset();
         self.profile_counters.reset();
@@ -2609,16 +2635,22 @@ impl Renderer {
     }
 
     pub(crate) fn draw_instanced_batch_with_previously_bound_textures<T>(
         &mut self,
         data: &[T],
         vertex_array_kind: VertexArrayKind,
         stats: &mut RendererStats,
     ) {
+        // If we end up with an empty draw call here, that means we have
+        // probably introduced unnecessary batch breaks during frame
+        // building - so we should be catching this earlier and removing
+        // the batch.
+        debug_assert!(!data.is_empty());
+
         let vao = get_vao(vertex_array_kind, &self.vaos, &self.gpu_glyph_renderer);
 
         self.device.bind_vao(vao);
 
         let batched = !self.debug_flags.contains(DebugFlags::DISABLE_BATCHING);
 
         if batched {
             self.device
@@ -3161,51 +3193,16 @@ impl Renderer {
         }
 
         self.handle_scaling(render_tasks, &target.scalings, SourceTexture::CacheA8);
 
         // Draw the clip items into the tiled alpha mask.
         {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_CLIP);
 
-            // If we have border corner clips, the first step is to clear out the
-            // area in the clip mask. This allows drawing multiple invididual clip
-            // in regions below.
-            if !target.clip_batcher.border_clears.is_empty() {
-                let _gm2 = self.gpu_profile.start_marker("clip borders [clear]");
-                self.device.set_blend(false);
-                self.shaders.cs_clip_border
-                    .bind(&mut self.device, projection, &mut self.renderer_errors);
-                self.draw_instanced_batch(
-                    &target.clip_batcher.border_clears,
-                    VertexArrayKind::DashAndDot,
-                    &BatchTextures::no_texture(),
-                    stats,
-                );
-            }
-
-            // Draw any dots or dashes for border corners.
-            if !target.clip_batcher.borders.is_empty() {
-                let _gm2 = self.gpu_profile.start_marker("clip borders");
-                // We are masking in parts of the corner (dots or dashes) here.
-                // Blend mode is set to max to allow drawing multiple dots.
-                // The individual dots and dashes in a border never overlap, so using
-                // a max blend mode here is fine.
-                self.device.set_blend(true);
-                self.device.set_blend_mode_max();
-                self.shaders.cs_clip_border
-                    .bind(&mut self.device, projection, &mut self.renderer_errors);
-                self.draw_instanced_batch(
-                    &target.clip_batcher.borders,
-                    VertexArrayKind::DashAndDot,
-                    &BatchTextures::no_texture(),
-                    stats,
-                );
-            }
-
             // switch to multiplicative blending
             self.device.set_blend(true);
             self.device.set_blend_mode_multiply();
 
             // draw rounded cornered rectangles
             if !target.clip_batcher.rectangles.is_empty() {
                 let _gm2 = self.gpu_profile.start_marker("clip rectangles");
                 self.shaders.cs_clip_rectangle.bind(
@@ -4548,29 +4545,27 @@ impl Renderer {
 fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
                vaos: &'a RendererVAOs,
                gpu_glyph_renderer: &'a GpuGlyphRenderer)
                -> &'a VAO {
     match vertex_array_kind {
         VertexArrayKind::Primitive => &vaos.prim_vao,
         VertexArrayKind::Clip => &vaos.clip_vao,
         VertexArrayKind::Blur => &vaos.blur_vao,
-        VertexArrayKind::DashAndDot => &vaos.dash_and_dot_vao,
         VertexArrayKind::VectorStencil => &gpu_glyph_renderer.vector_stencil_vao,
         VertexArrayKind::VectorCover => &gpu_glyph_renderer.vector_cover_vao,
         VertexArrayKind::Border => &vaos.border_vao,
     }
 }
 
 #[cfg(not(feature = "pathfinder"))]
 fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
                vaos: &'a RendererVAOs,
                _: &'a GpuGlyphRenderer)
                -> &'a VAO {
     match vertex_array_kind {
         VertexArrayKind::Primitive => &vaos.prim_vao,
         VertexArrayKind::Clip => &vaos.clip_vao,
         VertexArrayKind::Blur => &vaos.blur_vao,
-        VertexArrayKind::DashAndDot => &vaos.dash_and_dot_vao,
         VertexArrayKind::VectorStencil | VertexArrayKind::VectorCover => unreachable!(),
         VertexArrayKind::Border => &vaos.border_vao,
     }
 }
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -52,16 +52,17 @@ pub enum SceneSwapResult {
 
 /// Contains the render backend data needed to build a scene.
 pub struct SceneRequest {
     pub scene: Scene,
     pub view: DocumentView,
     pub font_instances: FontInstanceMap,
     pub output_pipelines: FastHashSet<PipelineId>,
     pub removed_pipelines: Vec<PipelineId>,
+    pub scene_id: u64,
 }
 
 pub struct BuiltScene {
     pub scene: Scene,
     pub frame_builder: FrameBuilder,
     pub clip_scroll_tree: ClipScrollTree,
     pub removed_pipelines: Vec<PipelineId>,
 }
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -45,17 +45,16 @@ impl ImageBufferKind {
 
 pub const IMAGE_BUFFER_KINDS: [ImageBufferKind; 4] = [
     ImageBufferKind::Texture2D,
     ImageBufferKind::TextureRect,
     ImageBufferKind::TextureExternal,
     ImageBufferKind::Texture2DArray,
 ];
 
-const TRANSFORM_FEATURE: &str = "TRANSFORM";
 const ALPHA_FEATURE: &str = "ALPHA_PASS";
 const DITHERING_FEATURE: &str = "DITHERING";
 const DUAL_SOURCE_FEATURE: &str = "DUAL_SOURCE_BLENDING";
 
 pub(crate) enum ShaderKind {
     Primitive,
     Cache(VertexArrayKind),
     ClipCache,
@@ -256,63 +255,16 @@ impl BrushShader {
         self.opaque.deinit(device);
         self.alpha.deinit(device);
         if let Some(dual_source) = self.dual_source {
             dual_source.deinit(device);
         }
     }
 }
 
-struct PrimitiveShader {
-    simple: LazilyCompiledShader,
-    transform: LazilyCompiledShader,
-}
-
-impl PrimitiveShader {
-    fn new(
-        name: &'static str,
-        device: &mut Device,
-        features: &[&'static str],
-        precache: bool,
-    ) -> Result<Self, ShaderError> {
-        let simple = LazilyCompiledShader::new(
-            ShaderKind::Primitive,
-            name,
-            features,
-            device,
-            precache,
-        )?;
-
-        let mut transform_features = features.to_vec();
-        transform_features.push(TRANSFORM_FEATURE);
-
-        let transform = LazilyCompiledShader::new(
-            ShaderKind::Primitive,
-            name,
-            &transform_features,
-            device,
-            precache,
-        )?;
-
-        Ok(PrimitiveShader { simple, transform })
-    }
-
-    fn get(&mut self, transform_kind: TransformedRectKind) -> &mut LazilyCompiledShader {
-        match transform_kind {
-            TransformedRectKind::AxisAligned => &mut self.simple,
-            TransformedRectKind::Complex => &mut self.transform,
-        }
-    }
-
-    fn deinit(self, device: &mut Device) {
-        self.simple.deinit(device);
-        self.transform.deinit(device);
-    }
-}
-
 pub struct TextShader {
     simple: LazilyCompiledShader,
     transform: LazilyCompiledShader,
     glyph_transform: LazilyCompiledShader,
 }
 
 impl TextShader {
     fn new(
@@ -395,17 +347,16 @@ fn create_prim_shader(
     }
 
     debug!("PrimShader {}", name);
 
     let vertex_descriptor = match vertex_format {
         VertexArrayKind::Primitive => desc::PRIM_INSTANCES,
         VertexArrayKind::Blur => desc::BLUR,
         VertexArrayKind::Clip => desc::CLIP,
-        VertexArrayKind::DashAndDot => desc::BORDER_CORNER_DASH_AND_DOT,
         VertexArrayKind::VectorStencil => desc::VECTOR_STENCIL,
         VertexArrayKind::VectorCover => desc::VECTOR_COVER,
         VertexArrayKind::Border => desc::BORDER,
     };
 
     let program = device.create_program(name, &prefix, &vertex_descriptor);
 
     if let Ok(ref program) = program {
@@ -477,30 +428,27 @@ pub struct Shaders {
     brush_linear_gradient: BrushShader,
 
     /// These are "cache clip shaders". These shaders are used to
     /// draw clip instances into the cached clip mask. The results
     /// of these shaders are also used by the primitive shaders.
     pub cs_clip_rectangle: LazilyCompiledShader,
     pub cs_clip_box_shadow: LazilyCompiledShader,
     pub cs_clip_image: LazilyCompiledShader,
-    pub cs_clip_border: LazilyCompiledShader,
     pub cs_clip_line: LazilyCompiledShader,
 
     // The are "primitive shaders". These shaders draw and blend
     // final results on screen. They are aware of tile boundaries.
     // Most draw directly to the framebuffer, but some use inputs
     // from the cache shaders to draw. Specifically, the box
     // shadow primitive shader stretches the box shadow cache
     // output, and the cache_image shader blits the results of
     // a cache shader (e.g. blur) to the screen.
     pub ps_text_run: TextShader,
     pub ps_text_run_dual_source: TextShader,
-    ps_border_corner: PrimitiveShader,
-    ps_border_edge: PrimitiveShader,
 
     ps_split_composite: LazilyCompiledShader,
 }
 
 impl Shaders {
     pub fn new(
         device: &mut Device,
         gl_type: GlType,
@@ -606,24 +554,16 @@ impl Shaders {
         let cs_clip_image = LazilyCompiledShader::new(
             ShaderKind::ClipCache,
             "cs_clip_image",
             &[],
             device,
             options.precache_shaders,
         )?;
 
-        let cs_clip_border = LazilyCompiledShader::new(
-            ShaderKind::ClipCache,
-            "cs_clip_border",
-            &[],
-            device,
-            options.precache_shaders,
-        )?;
-
         let ps_text_run = TextShader::new("ps_text_run",
             device,
             &[],
             options.precache_shaders,
         )?;
 
         let ps_text_run_dual_source = TextShader::new("ps_text_run",
             device,
@@ -694,30 +634,16 @@ impl Shaders {
                         );
                         brush_yuv_image[index] = Some(shader);
                         yuv_features.clear();
                     }
                 }
             }
         }
 
-        let ps_border_corner = PrimitiveShader::new(
-            "ps_border_corner",
-             device,
-             &[],
-             options.precache_shaders,
-        )?;
-
-        let ps_border_edge = PrimitiveShader::new(
-            "ps_border_edge",
-             device,
-             &[],
-             options.precache_shaders,
-        )?;
-
         let cs_border_segment = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::Border),
             "cs_border_segment",
              &[],
              device,
              options.precache_shaders,
         )?;
 
@@ -741,23 +667,20 @@ impl Shaders {
             brush_image,
             brush_blend,
             brush_mix_blend,
             brush_yuv_image,
             brush_radial_gradient,
             brush_linear_gradient,
             cs_clip_rectangle,
             cs_clip_box_shadow,
-            cs_clip_border,
             cs_clip_image,
             cs_clip_line,
             ps_text_run,
             ps_text_run_dual_source,
-            ps_border_corner,
-            ps_border_edge,
             ps_split_composite,
         })
     }
 
     fn get_yuv_shader_index(
         buffer_kind: ImageBufferKind,
         format: YuvFormat,
         color_space: YuvColorSpace,
@@ -799,63 +722,53 @@ impl Shaders {
                         self.brush_yuv_image[shader_index]
                             .as_mut()
                             .expect("Unsupported YUV shader kind")
                     }
                 };
                 brush_shader.get(key.blend_mode)
             }
             BatchKind::Transformable(transform_kind, batch_kind) => {
-                let prim_shader = match batch_kind {
+                match batch_kind {
                     TransformBatchKind::TextRun(glyph_format) => {
                         let text_shader = match key.blend_mode {
                             BlendMode::SubpixelDualSource => {
                                 &mut self.ps_text_run_dual_source
                             }
                             _ => {
                                 &mut self.ps_text_run
                             }
                         };
                         return text_shader.get(glyph_format, transform_kind);
                     }
-                    TransformBatchKind::BorderCorner => {
-                        &mut self.ps_border_corner
-                    }
-                    TransformBatchKind::BorderEdge => {
-                        &mut self.ps_border_edge
-                    }
-                };
-                prim_shader.get(transform_kind)
+                }
             }
         }
     }
 
     pub fn deinit(self, device: &mut Device) {
         self.cs_blur_a8.deinit(device);
         self.cs_blur_rgba8.deinit(device);
         self.brush_solid.deinit(device);
         self.brush_blend.deinit(device);
         self.brush_mix_blend.deinit(device);
         self.brush_radial_gradient.deinit(device);
         self.brush_linear_gradient.deinit(device);
         self.cs_clip_rectangle.deinit(device);
         self.cs_clip_box_shadow.deinit(device);
         self.cs_clip_image.deinit(device);
-        self.cs_clip_border.deinit(device);
         self.cs_clip_line.deinit(device);
         self.ps_text_run.deinit(device);
         self.ps_text_run_dual_source.deinit(device);
         for shader in self.brush_image {
             if let Some(shader) = shader {
                 shader.deinit(device);
             }
         }
         for shader in self.brush_yuv_image {
             if let Some(shader) = shader {
                 shader.deinit(device);
             }
         }
-        self.ps_border_corner.deinit(device);
-        self.ps_border_edge.deinit(device);
         self.cs_border_segment.deinit(device);
         self.ps_split_composite.deinit(device);
     }
 }
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -12,17 +12,17 @@ use device::{FrameId, Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use gpu_cache::{GpuCache};
 use gpu_types::{BorderInstance, BlurDirection, BlurInstance};
 use gpu_types::{ClipScrollNodeData, ZBufferIdGenerator};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
-use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveKind, PrimitiveStore};
+use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveStore};
 use prim_store::{BrushKind, DeferredResolve};
 use profiler::FrameProfileCounters;
 use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree};
 use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32, mem};
 use texture_allocator::GuillotineAllocator;
 #[cfg(feature = "pathfinder")]
@@ -44,17 +44,16 @@ pub struct RenderTargetIndex(pub usize);
 
 pub struct RenderTargetContext<'a, 'rc> {
     pub device_pixel_scale: DevicePixelScale,
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'rc mut ResourceCache,
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub use_dual_source_blending: bool,
     pub node_data: &'a [ClipScrollNodeData],
-    pub cached_gradients: &'a [CachedGradient],
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct TextureAllocator {
     // TODO(gw): Replace this with a simpler allocator for
     // render target allocation - this use case doesn't need
     // to deal with coalescing etc that the general texture
--- a/gfx/webrender/tests/angle_shader_validation.rs
+++ b/gfx/webrender/tests/angle_shader_validation.rs
@@ -32,37 +32,29 @@ const SHADERS: &[Shader] = &[
         name: "cs_clip_image",
         features: CLIP_FEATURES,
     },
     Shader {
         name: "cs_clip_box_shadow",
         features: CLIP_FEATURES,
     },
     Shader {
-        name: "cs_clip_border",
-        features: CLIP_FEATURES,
-    },
-    Shader {
         name: "cs_clip_line",
         features: CLIP_FEATURES,
     },
     // Cache shaders
     Shader {
         name: "cs_blur",
         features: CACHE_FEATURES,
     },
-    // Prim shaders
     Shader {
-        name: "ps_border_corner",
-        features: PRIM_FEATURES,
+        name: "cs_border_segment",
+        features: CACHE_FEATURES,
     },
-    Shader {
-        name: "ps_border_edge",
-        features: PRIM_FEATURES,
-    },
+    // Prim shaders
     Shader {
         name: "ps_split_composite",
         features: PRIM_FEATURES,
     },
     Shader {
         name: "ps_text_run",
         features: PRIM_FEATURES,
     },
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -556,16 +556,20 @@ pub enum DebugCommand {
     /// Display intermediate render targets on screen.
     EnableRenderTargetDebug(bool),
     /// Display GPU timing results.
     EnableGpuTimeQueries(bool),
     /// Display GPU overdraw results
     EnableGpuSampleQueries(bool),
     /// Configure if dual-source blending is used, if available.
     EnableDualSourceBlending(bool),
+    /// Show an indicator that moves every time a frame is rendered.
+    EnableNewFrameIndicator(bool),
+    /// Show an indicator that moves every time a scene is built.
+    EnableNewSceneIndicator(bool),
     /// Fetch current documents and display lists.
     FetchDocuments,
     /// Fetch current passes and batches.
     FetchPasses,
     /// Fetch clip-scroll tree.
     FetchClipScrollTree,
     /// Fetch render tasks.
     FetchRenderTasks,
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-8e697f8cb1f1aab2e5f6b9b903eb7191340b10c5
+aff9f409f3d6a3518c38c1f7755657f564c1083a
--- a/gfx/wrench/src/rawtest.rs
+++ b/gfx/wrench/src/rawtest.rs
@@ -44,16 +44,17 @@ impl<'a> RawtestHarness<'a> {
         self.test_hit_testing();
         self.test_retained_blob_images_test();
         self.test_blob_update_test();
         self.test_blob_update_epoch_test();
         self.test_tile_decomposition();
         self.test_very_large_blob();
         self.test_offscreen_blob();
         self.test_save_restore();
+        self.test_blur_cache();
         self.test_capture();
         self.test_zero_height_window();
     }
 
     fn render_and_get_pixels(&mut self, window_rect: DeviceUintRect) -> Vec<u8> {
         self.rx.recv().unwrap();
         self.wrench.render();
         self.wrench.renderer.read_pixels_rgba8(window_rect)
@@ -715,16 +716,61 @@ impl<'a> RawtestHarness<'a> {
         };
 
         let first = do_test(false);
         let second = do_test(true);
 
         assert_eq!(first, second);
     }
 
+    // regression test for #2769
+    // "async scene building: cache collisions from reused picture ids"
+    fn test_blur_cache(&mut self) {
+        println!("\tblur cache...");
+        let window_size = self.window.get_inner_size();
+
+        let test_size = DeviceUintSize::new(400, 400);
+
+        let window_rect = DeviceUintRect::new(
+            DeviceUintPoint::new(0, window_size.height - test_size.height),
+            test_size,
+        );
+        let layout_size = LayoutSize::new(400., 400.);
+
+        let mut do_test = |shadow_is_red| {
+            let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
+            let shadow_color = if shadow_is_red {
+                ColorF::new(1.0, 0.0, 0.0, 1.0)
+            } else {
+                ColorF::new(0.0, 1.0, 0.0, 1.0)
+            };
+
+            builder.push_shadow(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
+                Shadow {
+                    offset: LayoutVector2D::new(1.0, 1.0),
+                    blur_radius: 1.0,
+                    color: shadow_color,
+                });
+            builder.push_line(&PrimitiveInfo::new(rect(110., 110., 50., 2.)),
+                              0.0, LineOrientation::Horizontal,
+                              &ColorF::new(0.0, 0.0, 0.0, 1.0), LineStyle::Solid);
+            builder.pop_all_shadows();
+
+            let txn = Transaction::new();
+            self.submit_dl(&mut Epoch(0), layout_size, builder, &txn.resource_updates);
+
+            self.render_and_get_pixels(window_rect)
+        };
+
+        let first = do_test(false);
+        let second = do_test(true);
+
+        assert_ne!(first, second);
+    }
+
     fn test_capture(&mut self) {
         println!("\tcapture...");
         let path = "../captures/test";
         let layout_size = LayoutSize::new(400., 400.);
         let dim = self.window.get_inner_size();
         let window_rect = DeviceUintRect::new(
             point(0, dim.height - layout_size.height as u32),
             size(layout_size.width as u32, layout_size.height as u32),
--- a/intl/l10n/L10nRegistry.jsm
+++ b/intl/l10n/L10nRegistry.jsm
@@ -240,16 +240,103 @@ const MSG_CONTEXT_OPTIONS = {
         default:
           return "other";
       }
     }
   }
 };
 
 /**
+ * Pseudolocalizations
+ *
+ * PSEUDO_STRATEGIES is a dict of strategies to be used to modify a
+ * context in order to create pseudolocalizations.  These can be used by
+ * developers to test the localizability of their code without having to
+ * actually speak a foreign language.
+ *
+ * Currently, the following pseudolocales are supported:
+ *
+ *   accented - Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ
+ *
+ *     In Accented English all Latin letters are replaced by accented
+ *     Unicode counterparts which don't impair the readability of the content.
+ *     This allows developers to quickly test if any given string is being
+ *     correctly displayed in its 'translated' form.  Additionally, simple
+ *     heuristics are used to make certain words longer to better simulate the
+ *     experience of international users.
+ *
+ *   bidi - ɥsıʅƃuƎ ıpıԐ
+ *
+ *     Bidi English is a fake RTL locale.  All words are surrounded by
+ *     Unicode formatting marks forcing the RTL directionality of characters.
+ *     In addition, to make the reversed text easier to read, individual
+ *     letters are flipped.
+ *
+ *     Note: The name above is hardcoded to be RTL in case code editors have
+ *     trouble with the RLO and PDF Unicode marks.  In reality, it should be
+ *     surrounded by those marks as well.
+ *
+ * See https://bugzil.la/1450781 for more information.
+ *
+ * In this implementation we use code points instead of inline unicode characters
+ * because the encoding of JSM files mangles them otherwise.
+ */
+
+const ACCENTED_MAP = {
+      // ȦƁƇḒḖƑƓĦĪĴĶĿḾȠǾƤɊŘŞŦŬṼẆẊẎẐ
+      "caps": [550, 385, 391, 7698, 7702, 401, 403, 294, 298, 308, 310, 319, 7742, 544, 510, 420, 586, 344, 350, 358, 364, 7804, 7814, 7818, 7822, 7824],
+      // ȧƀƈḓḗƒɠħīĵķŀḿƞǿƥɋřşŧŭṽẇẋẏẑ
+      "small": [551, 384, 392, 7699, 7703, 402, 608, 295, 299, 309, 311, 320, 7743, 414, 511, 421, 587, 345, 351, 359, 365, 7805, 7815, 7819, 7823, 7825],
+};
+
+const FLIPPED_MAP = {
+      // ∀ԐↃᗡƎℲ⅁HIſӼ⅂WNOԀÒᴚS⊥∩ɅMX⅄Z
+      "caps": [8704, 1296, 8579, 5601, 398, 8498, 8513, 72, 73, 383, 1276, 8514, 87, 78, 79, 1280, 210, 7450, 83, 8869, 8745, 581, 77, 88, 8516, 90],
+      // ɐqɔpǝɟƃɥıɾʞʅɯuodbɹsʇnʌʍxʎz
+      "small": [592, 113, 596, 112, 477, 607, 387, 613, 305, 638, 670, 645, 623, 117, 111, 100, 98, 633, 115, 647, 110, 652, 653, 120, 654, 122],
+};
+
+function transformString(map, elongate = false, prefix = "", postfix = "", msg) {
+  // Exclude access-keys and other single-char messages
+  if (msg.length === 1) {
+    return msg;
+  }
+  // XML entities (&#x202a;) and XML tags.
+  const reExcluded = /(&[#\w]+;|<\s*.+?\s*>)/;
+
+  const parts = msg.split(reExcluded);
+  const modified = parts.map((part) => {
+    if (reExcluded.test(part)) {
+      return part;
+    }
+    return prefix + part.replace(/[a-z]/ig, (ch) => {
+      let cc = ch.charCodeAt(0);
+      if (cc >= 97 && cc <= 122) {
+        const newChar = String.fromCodePoint(map.small[cc - 97]);
+        // duplicate "a", "e", "o" and "u" to emulate ~30% longer text
+        if (elongate && (cc === 97 || cc === 101 || cc === 111 || cc === 117)) {
+          return newChar + newChar;
+        }
+        return newChar;
+      }
+      if (cc >= 65 && cc <= 90) {
+        return String.fromCodePoint(map.caps[cc - 65]);
+      }
+      return ch;
+    }) + postfix;
+  });
+  return modified.join("");
+}
+
+const PSEUDO_STRATEGIES = {
+  "accented": transformString.bind(null, ACCENTED_MAP, true, "", ""),
+  "bidi": transformString.bind(null, FLIPPED_MAP, false, "\u202e", "\u202c"),
+};
+
+/**
  * Generates a single MessageContext by loading all resources
  * from the listed sources for a given locale.
  *
  * The function casts all error cases into a Promise that resolves with
  * value `null`.
  * This allows the caller to be an async generator without using
  * try/catch clauses.
  *
@@ -265,17 +352,21 @@ function generateContext(locale, sources
   }
 
   const fetchPromises = resourceIds.map((resourceId, i) => {
     return L10nRegistry.sources.get(sourcesOrder[i]).fetchFile(locale, resourceId);
   });
 
   const ctxPromise = Promise.all(fetchPromises).then(
     dataSets => {
-      const ctx = new MessageContext(locale, MSG_CONTEXT_OPTIONS);
+      const pseudoNameFromPref = Services.prefs.getStringPref("intl.l10n.pseudo", "");
+      const ctx = new MessageContext(locale, {
+        ...MSG_CONTEXT_OPTIONS,
+        transform: PSEUDO_STRATEGIES[pseudoNameFromPref],
+      });
       for (const data of dataSets) {
         if (data === null) {
           return null;
         }
         ctx.addMessages(data);
       }
       return ctx;
     },
--- a/intl/l10n/Localization.jsm
+++ b/intl/l10n/Localization.jsm
@@ -238,30 +238,38 @@ class Localization {
     return val;
   }
 
   /**
    * Register weak observers on events that will trigger cache invalidation
    */
   registerObservers() {
     Services.obs.addObserver(this, "intl:app-locales-changed", true);
+    Services.prefs.addObserver("intl.l10n.pseudo", this, true);
   }
 
   /**
    * Default observer handler method.
    *
    * @param {String} subject
    * @param {String} topic
    * @param {Object} data
    */
   observe(subject, topic, data) {
     switch (topic) {
       case "intl:app-locales-changed":
         this.onChange();
         break;
+      case "nsPref:changed":
+        switch (data) {
+          case "intl.l10n.pseudo":
+            L10nRegistry.ctxCache.clear();
+            this.onChange();
+        }
+        break;
       default:
         break;
     }
   }
 
   /**
    * This method should be called when there's a reason to believe
    * that language negotiation or available resources changed.
--- a/intl/l10n/MessageContext.jsm
+++ b/intl/l10n/MessageContext.jsm
@@ -1412,17 +1412,20 @@ function SelectExpression(env, {exp, var
  * @param   {Object} expr
  *    An expression object to be resolved into a Fluent type.
  * @returns {FluentType}
  * @private
  */
 function Type(env, expr) {
   // A fast-path for strings which are the most common case, and for
   // `FluentNone` which doesn't require any additional logic.
-  if (typeof expr === "string" || expr instanceof FluentNone) {
+  if (typeof expr === "string") {
+    return env.ctx._transform(expr);
+  }
+  if (expr instanceof FluentNone) {
     return expr;
   }
 
   // The Runtime AST (Entries) encodes patterns (complex strings with
   // placeables) as Arrays.
   if (Array.isArray(expr)) {
     return Pattern(env, expr);
   }
@@ -1609,17 +1612,17 @@ function Pattern(env, ptn) {
   const result = [];
 
   // Wrap interpolations with Directional Isolate Formatting characters
   // only when the pattern has more than one element.
   const useIsolating = ctx._useIsolating && ptn.length > 1;
 
   for (const elem of ptn) {
     if (typeof elem === "string") {
-      result.push(elem);
+      result.push(ctx._transform(elem));
       continue;
     }
 
     const part = Type(env, elem).toString(ctx);
 
     if (useIsolating) {
       result.push(FSI);
     }
@@ -1708,23 +1711,24 @@ class MessageContext {
    *
    *   - `useIsolating` - boolean specifying whether to use Unicode isolation
    *                    marks (FSI, PDI) for bidi interpolations.
    *
    * @param   {string|Array<string>} locales - Locale or locales of the context
    * @param   {Object} [options]
    * @returns {MessageContext}
    */
-  constructor(locales, { functions = {}, useIsolating = true } = {}) {
+  constructor(locales, { functions = {}, useIsolating = true, transform = v => v } = {}) {
     this.locales = Array.isArray(locales) ? locales : [locales];
 
     this._terms = new Map();
     this._messages = new Map();
     this._functions = functions;
     this._useIsolating = useIsolating;
+    this._transform = transform;
     this._intls = new WeakMap();
   }
 
   /*
    * Return an iterator over public `[id, message]` pairs.
    *
    * @returns {Iterator}
    */
@@ -1824,22 +1828,22 @@ class MessageContext {
    * @param   {Object | string}    message
    * @param   {Object | undefined} args
    * @param   {Array}              errors
    * @returns {?string}
    */
   format(message, args, errors) {
     // optimize entities which are simple strings with no attributes
     if (typeof message === "string") {
-      return message;
+      return this._transform(message);
     }
 
     // optimize simple-string entities with attributes
     if (typeof message.val === "string") {
-      return message.val;
+      return this._transform(message.val);
     }
 
     // optimize entities with null values
     if (message.val === undefined) {
       return null;
     }
 
     return resolve(this, args, message, errors);
--- a/intl/l10n/docs/fluent_tutorial.rst
+++ b/intl/l10n/docs/fluent_tutorial.rst
@@ -75,16 +75,17 @@ Developers
  - 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
+ - Support for pseudolocalization
 
 __ 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
 ------------------
@@ -608,16 +609,63 @@ always better to scan for a variable:
   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.
 
 
+Pseudolocalization
+==================
+
+When working with a Fluent-backed UI, the developer gets a new tool to test their UI
+against several classes of problems.
+
+Pseudolocalization is a mechanism which transforms messages on-fly, using specific
+logic to help emulate how the UI will look once it gets localized.
+
+The three classes of potential problems that this can help with are:
+
+ - Hardcoded strings.
+
+   Turning on pseudolocalization should expose any string that were left
+   hardcoded in the source, since they won't get transfomed.
+
+
+ - UI space not adapting to longer text.
+
+   Many languages use longer strings than English. For example, German string
+   may be 30% longer. Turning on pseudolocalization is a quick way to test how
+   the layout handles such locales.
+
+
+ - Bidi adaptation.
+
+   For many developers, testing the UI in right-to-left mode is hard. Mozilla
+   offers a pref :js:`intl.uidirection` which switches the direction of the layout,
+   but that doesn't expose problems related to right-to-left text.
+   Pseudolocalization shows how a right-to-left locale will look like.
+
+To turn on pseudolocalization, add a new string pref :js:`intl.l10n.pseudo` and
+select the strategy to be used:
+
+ - :js:`accented` - Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ
+
+   This strategy replaces all Latin characters with their accented equivalents,
+   and duplicates some vovels to create roughly 30% longer strings.
+
+
+ - :js:`bidi` - ɥsıʅƃuƎ ıpıԐ
+
+   This strategy replaces all Latin characters with their 180 degree rotated versions
+   and enforces right to left text flow using Unicode UAX#9 `Explicit Directional Embeddings`__.
+
+__ https://www.unicode.org/reports/tr9/#Explicit_Directional_Embeddings
+
 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.
 
new file mode 100644
--- /dev/null
+++ b/intl/l10n/test/test_pseudo.js
@@ -0,0 +1,132 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { Localization } = ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
+const { L10nRegistry, FileSource } =
+  ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
+
+const originalValues = {};
+
+function addMockFileSource() {
+
+  const fs = {
+    "/localization/de/browser/menu.ftl": `
+key = This is a single message
+    .tooltip = This is a tooltip
+    .accesskey = f`,
+  };
+  originalValues.load = L10nRegistry.load;
+  originalValues.requested = Services.locale.getRequestedLocales();
+
+  L10nRegistry.load = async function(url) {
+    return fs[url];
+  };
+
+  const source = new FileSource("test", ["de"], "/localization/{locale}");
+  L10nRegistry.registerSource(source);
+
+  return async function* generateMessages(resIds) {
+    yield * await L10nRegistry.generateContexts(["de"], resIds);
+  };
+}
+
+/**
+ * This test verifies that as we switching between
+ * different pseudo strategies the Localization object
+ * follows and formats using the given strategy.
+ *
+ * We test values and attributes and make sure that
+ * a single-character attributes, commonly used for access keys
+ * don't get transformed.
+ */
+add_task(async function test_accented_works() {
+  Services.prefs.setStringPref("intl.l10n.pseudo", "");
+
+  let generateMessages = addMockFileSource();
+
+  const l10n = new Localization([
+    "/browser/menu.ftl"
+  ], generateMessages);
+  l10n.registerObservers();
+
+  {
+    // 1. Start with no pseudo
+
+    let message = (await l10n.formatMessages([{id: "key"}]))[0];
+
+    ok(message.value.includes("This is a single message"));
+    ok(message.attributes[0].value.includes("This is a tooltip"));
+    equal(message.attributes[1].value, "f");
+  }
+
+  {
+    // 2. Set Accented Pseudo
+
+    Services.prefs.setStringPref("intl.l10n.pseudo", "accented");
+    let message = (await l10n.formatMessages([{id: "key"}]))[0];
+
+    ok(message.value.includes("Ŧħīş īş ȧȧ şīƞɠŀḗḗ ḿḗḗşşȧȧɠḗḗ"));
+    ok(message.attributes[0].value.includes("Ŧħīş īş ȧȧ ŧǿǿǿǿŀŧīƥ"));
+    equal(message.attributes[1].value, "f");
+  }
+
+  {
+    // 3. Set Bidi Pseudo
+
+    Services.prefs.setStringPref("intl.l10n.pseudo", "bidi");
+    let message = (await l10n.formatMessages([{id: "key"}]))[0];
+
+    ok(message.value.includes("ıs ɐ sıuƃʅǝ ɯǝssɐƃǝ"));
+    ok(message.attributes[0].value.includes("⊥ɥıs ıs ɐ ʇooʅʇıd"));
+    equal(message.attributes[1].value, "f");
+  }
+
+  {
+    // 4. Remove pseudo
+
+    Services.prefs.setStringPref("intl.l10n.pseudo", "");
+    let message = (await l10n.formatMessages([{id: "key"}]))[0];
+
+    ok(message.value.includes("This is a single message"));
+    ok(message.attributes[0].value.includes("This is a tooltip"));
+    equal(message.attributes[1].value, "f");
+  }
+
+  L10nRegistry.sources.clear();
+  L10nRegistry.ctxCache.clear();
+  L10nRegistry.load = originalValues.load;
+  Services.locale.setRequestedLocales(originalValues.requested);
+});
+
+/**
+ * This test verifies that setting a bogus pseudo locale
+ * strategy doesn't break anything.
+ */
+add_task(async function test_unavailable_strategy_works() {
+  Services.prefs.setStringPref("intl.l10n.pseudo", "");
+
+  let generateMessages = addMockFileSource();
+
+  const l10n = new Localization([
+    "/browser/menu.ftl"
+  ], generateMessages);
+  l10n.registerObservers();
+
+  {
+    // 1. Set unavailable pseudo strategy
+    Services.prefs.setStringPref("intl.l10n.pseudo", "unknown-strategy");
+
+    let message = (await l10n.formatMessages([{id: "key"}]))[0];
+
+    ok(message.value.includes("This is a single message"));
+    ok(message.attributes[0].value.includes("This is a tooltip"));
+    equal(message.attributes[1].value, "f");
+  }
+
+  Services.prefs.setStringPref("intl.l10n.pseudo", "");
+  L10nRegistry.sources.clear();
+  L10nRegistry.ctxCache.clear();
+  L10nRegistry.load = originalValues.load;
+  Services.locale.setRequestedLocales(originalValues.requested);
+});
--- a/intl/l10n/test/xpcshell.ini
+++ b/intl/l10n/test/xpcshell.ini
@@ -1,7 +1,8 @@
 [DEFAULT]
 head =
 
 [test_domlocalization.js]
 [test_l10nregistry.js]
 [test_localization.js]
 [test_messagecontext.js]
+[test_pseudo.js]
--- a/layout/reftests/css-break/reftest.list
+++ b/layout/reftests/css-break/reftest.list
@@ -1,12 +1,12 @@
 default-preferences pref(layout.css.box-decoration-break.enabled,true)
 
 == box-decoration-break-1.html box-decoration-break-1-ref.html
-fuzzy(1,20) fuzzy-if(skiaContent,1,700) fuzzy-if(webrender,21-26,8950-12245) == box-decoration-break-with-inset-box-shadow-1.html box-decoration-break-with-inset-box-shadow-1-ref.html
+fuzzy(1,20) fuzzy-if(skiaContent,1,700) fuzzy-if(webrender,21-26,8910-12357) == box-decoration-break-with-inset-box-shadow-1.html box-decoration-break-with-inset-box-shadow-1-ref.html
 fuzzy(45,460) fuzzy-if(skiaContent,57,439) fuzzy-if(Android,57,1330) == box-decoration-break-with-outset-box-shadow-1.html box-decoration-break-with-outset-box-shadow-1-ref.html # Bug 1386543
 random-if(!gtkWidget) == box-decoration-break-border-image.html box-decoration-break-border-image-ref.html
 == box-decoration-break-block-border-padding.html box-decoration-break-block-border-padding-ref.html
 == box-decoration-break-block-margin.html box-decoration-break-block-margin-ref.html
 fuzzy-if(!Android,1,62) fuzzy-if(Android,8,6627) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == box-decoration-break-first-letter.html box-decoration-break-first-letter-ref.html #Bug 1313773 # Bug 1392106
 == box-decoration-break-with-bidi.html box-decoration-break-with-bidi-ref.html
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == box-decoration-break-bug-1235152.html box-decoration-break-bug-1235152-ref.html # Bug 1392106
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == box-decoration-break-bug-1249913.html box-decoration-break-bug-1249913-ref.html # Bug 1392106
--- a/media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
@@ -358,16 +358,22 @@ TransceiverImpl::HasSendTrack(const dom:
 
 void
 TransceiverImpl::SyncWithJS(dom::RTCRtpTransceiver& aJsTransceiver,
                             ErrorResult& aRv)
 {
   MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: " << __FUNCTION__
                       << " Syncing with JS transceiver");
 
+  if (!mTransmitPipeline) {
+    // Shutdown_m has already been called, probably due to pc.close(). Just
+    // nod and smile.
+    return;
+  }
+
   // Update stopped, both ways, since either JSEP or JS can stop these
   if (mJsepTransceiver->IsStopped()) {
     // We don't call RTCRtpTransceiver::Stop(), because that causes another sync
     aJsTransceiver.SetStopped(aRv);
     Stop();
   } else if (aJsTransceiver.GetStopped(aRv)) {
     mJsepTransceiver->Stop();
     Stop();
deleted file mode 100644
index 03268c57926eba0601e3d6f217978b5ef234fb5d..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index e9b5ca3dea41889e88f5406fa898169189852471..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/mobile/android/base/AppConstants.java.in
+++ b/mobile/android/base/AppConstants.java.in
@@ -63,16 +63,17 @@ public class AppConstants {
          */
         public static final boolean preMarshmallow = MAX_SDK_VERSION < 23 || (MIN_SDK_VERSION < 23 && Build.VERSION.SDK_INT < 23);
         public static final boolean preLollipopMR1 = MAX_SDK_VERSION < 22 || (MIN_SDK_VERSION < 22 && Build.VERSION.SDK_INT < 22);
         public static final boolean preLollipop = MAX_SDK_VERSION < 21 || (MIN_SDK_VERSION < 21 && Build.VERSION.SDK_INT < 21);
         public static final boolean preJBMR2 = MAX_SDK_VERSION < 18 || (MIN_SDK_VERSION < 18 && Build.VERSION.SDK_INT < 18);
         public static final boolean preJBMR1 = MAX_SDK_VERSION < 17 || (MIN_SDK_VERSION < 17 && Build.VERSION.SDK_INT < 17);
         public static final boolean preJB = MAX_SDK_VERSION < 16 || (MIN_SDK_VERSION < 16 && Build.VERSION.SDK_INT < 16);
         public static final boolean preN = MAX_SDK_VERSION < 24 || (MIN_SDK_VERSION < 24 && Build.VERSION.SDK_INT < 24);
+        public static final boolean preO = MAX_SDK_VERSION < 26 || (MIN_SDK_VERSION < 26 && Build.VERSION.SDK_INT < 26);
     }
 
     /**
      * The name of the Java class that represents the android application.
      */
     public static final String MOZ_ANDROID_APPLICATION_CLASS = "@MOZ_ANDROID_APPLICATION_CLASS@";
     /**
      * The name of the Java class that launches the browser activity.
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/TabQueuePanel.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.firstrun;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.graphics.Typeface;
-import android.os.Bundle;
-import android.support.v4.content.ContextCompat;
-import android.support.v7.widget.SwitchCompat;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CompoundButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.tabqueue.TabQueueHelper;
-import org.mozilla.gecko.tabqueue.TabQueuePrompt;
-
-public class TabQueuePanel extends FirstrunPanel {
-    private static final int REQUEST_CODE_TAB_QUEUE = 1;
-    private SwitchCompat toggleSwitch;
-    private ImageView imageView;
-    private TextView messageTextView;
-    private TextView subtextTextView;
-    private Context context;
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstance) {
-        context = getContext();
-        final View root = super.onCreateView(inflater, container, savedInstance);
-
-        imageView = (ImageView) root.findViewById(R.id.firstrun_image);
-        messageTextView = (TextView) root.findViewById(R.id.firstrun_text);
-        subtextTextView = (TextView) root.findViewById(R.id.firstrun_subtext);
-
-        toggleSwitch = (SwitchCompat) root.findViewById(R.id.firstrun_switch);
-        toggleSwitch.setVisibility(View.VISIBLE);
-        toggleSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
-            @Override
-            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
-                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.DIALOG, "firstrun_tabqueue-permissions");
-                if (b && !TabQueueHelper.canDrawOverlays(context)) {
-                    Intent promptIntent = new Intent(context, TabQueuePrompt.class);
-                    startActivityForResult(promptIntent, REQUEST_CODE_TAB_QUEUE);
-                    return;
-                }
-
-                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-tabqueue-" + b);
-
-                final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
-                final SharedPreferences.Editor editor = prefs.edit();
-                editor.putBoolean(GeckoPreferences.PREFS_TAB_QUEUE, b).apply();
-
-                // Set image, text, and typeface changes.
-                imageView.setImageResource(b ? R.drawable.firstrun_tabqueue_on : R.drawable.firstrun_tabqueue_off);
-                messageTextView.setText(b ? R.string.firstrun_tabqueue_message_on : R.string.firstrun_tabqueue_message_off);
-                messageTextView.setTypeface(b ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT);
-                subtextTextView.setText(b ? R.string.firstrun_tabqueue_subtext_on : R.string.firstrun_tabqueue_subtext_off);
-                subtextTextView.setTypeface(b ? Typeface.defaultFromStyle(Typeface.ITALIC) : Typeface.DEFAULT);
-                subtextTextView.setTextColor(b ? ContextCompat.getColor(context, R.color.fennec_ui_accent) : ContextCompat.getColor(context, R.color.placeholder_grey));
-            }
-        });
-
-        return root;
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        switch (requestCode) {
-            case REQUEST_CODE_TAB_QUEUE:
-                final boolean accepted = TabQueueHelper.processTabQueuePromptResponse(resultCode, context);
-                if (accepted) {
-                    Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.DIALOG, "firstrun_tabqueue-permissions-yes");
-                    toggleSwitch.setChecked(true);
-                    Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-tabqueue-true");
-                }
-                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.DIALOG, "firstrun_tabqueue-permissions-" + (accepted ? "accepted" : "rejected"));
-                break;
-        }
-    }
-
-}
--- a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueHelper.java
@@ -16,16 +16,17 @@ import org.mozilla.gecko.util.ThreadUtil
 
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
+import android.os.Build;
 import android.support.v4.app.NotificationCompat;
 import android.support.v4.content.ContextCompat;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.WindowManager;
 
 import org.json.JSONArray;
@@ -73,17 +74,19 @@ public class TabQueueHelper {
         // Instead we'll add and remove an invisible view. If this is successful then we seem to
         // have permission to draw overlays.
 
         View view = new View(context);
         view.setVisibility(View.INVISIBLE);
 
         WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                 1, 1,
-                WindowManager.LayoutParams.TYPE_PHONE,
+                AppConstants.Versions.preO ?
+                        WindowManager.LayoutParams.TYPE_PHONE :
+                        WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
                 PixelFormat.TRANSLUCENT);
 
         WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
 
         try {
             windowManager.addView(view, layoutParams);
             windowManager.removeView(view);
--- a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java
@@ -107,17 +107,19 @@ public class TabQueueService extends Ser
         messageView.setText(resources.getText(R.string.tab_queue_toast_message));
 
         openNowButton = (Button) toastLayout.findViewById(R.id.toast_button);
         openNowButton.setText(resources.getText(R.string.tab_queue_toast_action));
 
         toastLayoutParams = new WindowManager.LayoutParams(
                 WindowManager.LayoutParams.MATCH_PARENT,
                 WindowManager.LayoutParams.WRAP_CONTENT,
-                WindowManager.LayoutParams.TYPE_PHONE,
+                AppConstants.Versions.preO ?
+                        WindowManager.LayoutParams.TYPE_PHONE :
+                        WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                 WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
                         WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                 PixelFormat.TRANSLUCENT);
 
         toastLayoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
     }
 
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -22,22 +22,16 @@
 <!ENTITY firstrun_sync_subtext2 "Use Sync to find the bookmarks, passwords, and other things you save to &brandShortName; on all your devices.">
 <!ENTITY firstrun_signin_message "Get connected, get started">
 <!ENTITY firstrun_signin_button "Sign in to Sync">
 <!ENTITY  onboard_start_button_browser "Start Browsing">
 <!ENTITY firstrun_button_notnow "Not right now">
 <!ENTITY firstrun_button_next "Next">
 
 <!ENTITY firstrun_tabqueue_title "Links">
-<!-- Localization note (firstrun_tabqueue_message): 'Tab queue' is a feature that allows users to queue up or save links from outside of Firefox (without switching apps) - these links will be loaded in Firefox the next time Firefox is opened. -->
-<!ENTITY firstrun_tabqueue_message_off "Turn on Tab queue">
-<!ENTITY firstrun_tabqueue_subtext_off "Save links for later in &brandShortName; when tapping them in other apps.">
-
-<!ENTITY firstrun_tabqueue_message_on "Success!">
-<!ENTITY firstrun_tabqueue_subtext_on "You can always turn this off in &settings; under &pref_category_general;.">
 
 <!ENTITY firstrun_readerview_title "Articles">
 <!-- Localization note (firstrun_readerview_message): This is a casual way of describing getting rid of unnecessary things, and is referring to simplifying websites so only the article text and images are visible, removing unnecessary headers or ads. -->
 <!ENTITY firstrun_readerview_message "Lose the clutter">
 <!ENTITY firstrun_readerview_subtext "Use Reader View to make articles nicer to read \u2014 even offline.">
 
 <!-- Localization note (firstrun_devices_title): This is a casual way of addressing the user, somewhat referring to their online identity (which would include other devices, Firefox usage, accounts, etc). -->
 <!ENTITY firstrun_account_title "You">
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -38,21 +38,16 @@
   <string name="firstrun_customize_subtext">&firstrun_customize_subtext;</string>
   <string name="firstrun_sync_title">&firstrun_sync_title;</string>
   <string name="firstrun_sync_message">&firstrun_sync_message2;</string>
   <string name="firstrun_sync_subtext">&firstrun_sync_subtext2;</string>
   <string name="firstrun_signin_button">&firstrun_signin_button;</string>
   <string name="firstrun_welcome_button_browser">&onboard_start_button_browser;</string>
   <string name="firstrun_button_next">&firstrun_button_next;</string>
 
-  <string name="firstrun_tabqueue_message_off">&firstrun_tabqueue_message_off;</string>
-  <string name="firstrun_tabqueue_subtext_off">&firstrun_tabqueue_subtext_off;</string>
-  <string name="firstrun_tabqueue_message_on">&firstrun_tabqueue_message_on;</string>
-  <string name="firstrun_tabqueue_subtext_on">&firstrun_tabqueue_subtext_on;</string>
-
   <string name="firstrun_welcome_restricted">&onboard_start_restricted1;</string>
 
   <string name="bookmarks_title">&bookmarks_title;</string>
   <string name="history_title">&history_title;</string>
 
   <string name="switch_to_tab">&switch_to_tab;</string>
 
   <string name="tab_offline_version">&tab_offline_version;</string>
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4936,16 +4936,19 @@ pref("extensions.webextensions.remote", 
 // unless other process sandboxing and extension remoting prefs are changed.
 pref("extensions.webextensions.protocol.remote", true);
 
 // Enable tab hiding API by default.
 pref("extensions.webextensions.tabhide.enabled", true);
 
 pref("extensions.webextensions.background-delayed-startup", false);
 
+// Whether or not the installed extensions should be migrated to the storage.local IndexedDB backend.
+pref("extensions.webextensions.ExtensionStorageIDB.enabled", false);
+
 // Report Site Issue button
 pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
 #if defined(MOZ_DEV_EDITION) || defined(NIGHTLY_BUILD)
 pref("extensions.webcompat-reporter.enabled", true);
 #else
 pref("extensions.webcompat-reporter.enabled", false);
 #endif
 
--- a/security/manager/locales/en-US/chrome/pipnss/pipnss.properties
+++ b/security/manager/locales/en-US/chrome/pipnss/pipnss.properties
@@ -51,20 +51,16 @@ PrivateSlotDescription=PSM Private Keys
 # LOCALIZATION NOTE (Fips140TokenDescription): string limit is 32 bytes after
 # conversion to UTF-8.
 # length_limit = 32 bytes
 Fips140TokenDescription=Software Security Device (FIPS)
 # LOCALIZATION NOTE (Fips140SlotDescription): string limit is 64 bytes after
 # conversion to UTF-8.
 # length_limit = 64 bytes
 Fips140SlotDescription=FIPS 140 Cryptographic, Key and Certificate Services
-# LOCALIZATION NOTE (InternalToken): string limit is 32 bytes after conversion
-# to UTF-8.
-# length_limit = 32 bytes
-InternalToken=Software Security Device
 
 VerifySSLClient=SSL Client Certificate
 VerifySSLServer=SSL Server Certificate
 VerifySSLCA=SSL Certificate Authority
 VerifyEmailSigner=Email Signer Certificate
 VerifyEmailRecip=Email Recipient Certificate
 HighGrade=High Grade
 MediumGrade=Medium Grade
--- a/security/manager/pki/resources/content/device_manager.js
+++ b/security/manager/pki/resources/content/device_manager.js
@@ -358,17 +358,17 @@ function changePassword() {
   enableButtons();
 }
 
 // -------------------------------------   Old code
 
 function showTokenInfo() {
   var selected_token = selected_slot.getToken();
   AddInfoRow(bundle.getString("devinfo_label"),
-             selected_token.tokenLabel, "tok_label");
+             selected_token.tokenName, "tok_label");
   AddInfoRow(bundle.getString("devinfo_manID"),
              selected_token.tokenManID, "tok_manID");
   AddInfoRow(bundle.getString("devinfo_serialnum"),
              selected_token.tokenSerialNumber, "tok_sNum");
   AddInfoRow(bundle.getString("devinfo_hwversion"),
              selected_token.tokenHWVersion, "tok_hwv");
   AddInfoRow(bundle.getString("devinfo_fwversion"),
              selected_token.tokenFWVersion, "tok_fwv");
--- a/security/manager/ssl/nsIPK11Token.idl
+++ b/security/manager/ssl/nsIPK11Token.idl
@@ -10,18 +10,16 @@
 interface nsIPK11Token : nsISupports
 {
   /*
    * The name of the token
    */
   [must_use]
   readonly attribute AUTF8String tokenName;
   [must_use]
-  readonly attribute AUTF8String tokenLabel;
-  [must_use]
   readonly attribute boolean isInternalKeyToken;
   /**
    * Manufacturer ID of the token.
    */
   [must_use]
   readonly attribute AUTF8String tokenManID;
   /**
    * Hardware version of the token.
--- a/security/manager/ssl/nsNSSCertHelper.cpp
+++ b/security/manager/ssl/nsNSSCertHelper.cpp
@@ -61,16 +61,28 @@ GetPIPNSSBundleString(const char* string
   if (NS_FAILED(rv)) {
     return rv;
   }
   result.Truncate();
   return pipnssBundle->GetStringFromName(stringName, result);
 }
 
 nsresult
+GetPIPNSSBundleString(const char* stringName, nsACString& result)
+{
+  nsAutoString tmp;
+  nsresult rv = GetPIPNSSBundleString(stringName, tmp);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  result.Assign(NS_ConvertUTF16toUTF8(tmp));
+  return NS_OK;
+}
+
+nsresult
 PIPBundleFormatStringFromName(const char* stringName, const char16_t** params,
                               uint32_t numParams, nsAString& result)
 {
   MOZ_ASSERT(stringName);
   MOZ_ASSERT(params);
   if (!stringName || !params) {
     return NS_ERROR_INVALID_ARG;
   }
--- a/security/manager/ssl/nsNSSCertHelper.h
+++ b/security/manager/ssl/nsNSSCertHelper.h
@@ -24,12 +24,14 @@ GetCertFingerprintByOidTag(CERTCertifica
 
 void
 LossyUTF8ToUTF16(const char* str, uint32_t len, /*out*/ nsAString& result);
 
 // Must be used on the main thread only.
 nsresult
 GetPIPNSSBundleString(const char* stringName, nsAString& result);
 nsresult
+GetPIPNSSBundleString(const char* stringName, nsACString& result);
+nsresult
 PIPBundleFormatStringFromName(const char* stringName, const char16_t** params,
                               uint32_t numParams, nsAString& result);
 
 #endif // nsNSSCertHelper_h
--- a/security/manager/ssl/nsNSSCertificate.cpp
+++ b/security/manager/ssl/nsNSSCertificate.cpp
@@ -666,38 +666,32 @@ NS_IMETHODIMP
 nsNSSCertificate::GetSha1Fingerprint(nsAString& _sha1Fingerprint)
 {
   return GetCertificateHash(_sha1Fingerprint, SEC_OID_SHA1);
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetTokenName(nsAString& aTokenName)
 {
-  aTokenName.Truncate();
-  if (mCert) {
-    // HACK alert
-    // When the trust of a builtin cert is modified, NSS copies it into the
-    // cert db.  At this point, it is now "managed" by the user, and should
-    // not be listed with the builtins.  However, in the collection code
-    // used by PK11_ListCerts, the cert is found in the temp db, where it
-    // has been loaded from the token.  Though the trust is correct (grabbed
-    // from the cert db), the source is wrong.  I believe this is a safe
-    // way to work around this.
-    if (mCert->slot) {
-      char* token = PK11_GetTokenName(mCert->slot);
-      if (token) {
-        aTokenName = NS_ConvertUTF8toUTF16(token);
-      }
-    } else {
-      nsAutoString tok;
-      if (NS_SUCCEEDED(GetPIPNSSBundleString("InternalToken", tok))) {
-        aTokenName = tok;
-      }
-    }
+  MOZ_ASSERT(mCert);
+  if (!mCert) {
+    return NS_ERROR_FAILURE;
+  }
+  UniquePK11SlotInfo internalSlot(PK11_GetInternalSlot());
+  if (!internalSlot) {
+    return NS_ERROR_FAILURE;
   }
+  nsCOMPtr<nsIPK11Token> token(
+    new nsPK11Token(mCert->slot ? mCert->slot : internalSlot.get()));
+  nsAutoCString tmp;
+  nsresult rv = token->GetTokenName(tmp);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  aTokenName.Assign(NS_ConvertUTF8toUTF16(tmp));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetSha256SubjectPublicKeyInfoDigest(nsACString& aSha256SPKIDigest)
 {
   aSha256SPKIDigest.Truncate();
   Digest digest;
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -1224,65 +1224,16 @@ LoadLoadableRootsTask::LoadLoadableRoots
                                             possibleCKBILocation.get()));
       return NS_OK;
     }
   }
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("could not load loadable roots"));
   return NS_ERROR_FAILURE;
 }
 
-nsresult
-nsNSSComponent::ConfigureInternalPKCS11Token()
-{
-  nsAutoString manufacturerID;
-  nsAutoString libraryDescription;
-  nsAutoString tokenDescription;
-  nsAutoString privateTokenDescription;
-  nsAutoString slotDescription;
-  nsAutoString privateSlotDescription;
-  nsAutoString fips140SlotDescription;
-  nsAutoString fips140TokenDescription;
-
-  nsresult rv;
-  rv = GetPIPNSSBundleString("ManufacturerID", manufacturerID);
-  if (NS_FAILED(rv)) return rv;
-
-  rv = GetPIPNSSBundleString("LibraryDescription", libraryDescription);
-  if (NS_FAILED(rv)) return rv;
-
-  rv = GetPIPNSSBundleString("TokenDescription", tokenDescription);
-  if (NS_FAILED(rv)) return rv;
-
-  rv = GetPIPNSSBundleString("PrivateTokenDescription", privateTokenDescription);
-  if (NS_FAILED(rv)) return rv;
-
-  rv = GetPIPNSSBundleString("SlotDescription", slotDescription);
-  if (NS_FAILED(rv)) return rv;
-
-  rv = GetPIPNSSBundleString("PrivateSlotDescription", privateSlotDescription);
-  if (NS_FAILED(rv)) return rv;
-
-  rv = GetPIPNSSBundleString("Fips140SlotDescription", fips140SlotDescription);
-  if (NS_FAILED(rv)) return rv;
-
-  rv = GetPIPNSSBundleString("Fips140TokenDescription", fips140TokenDescription);
-  if (NS_FAILED(rv)) return rv;
-
-  PK11_ConfigurePKCS11(NS_ConvertUTF16toUTF8(manufacturerID).get(),
-                       NS_ConvertUTF16toUTF8(libraryDescription).get(),
-                       NS_ConvertUTF16toUTF8(tokenDescription).get(),
-                       NS_ConvertUTF16toUTF8(privateTokenDescription).get(),
-                       NS_ConvertUTF16toUTF8(slotDescription).get(),
-                       NS_ConvertUTF16toUTF8(privateSlotDescription).get(),
-                       NS_ConvertUTF16toUTF8(fips140SlotDescription).get(),
-                       NS_ConvertUTF16toUTF8(fips140TokenDescription).get(),
-                       0, 0);
-  return NS_OK;
-}
-
 // Table of pref names and SSL cipher ID
 typedef struct {
   const char* pref;
   long id;
   bool enabledByDefault;
 } CipherPref;
 
 // Update the switch statement in AccumulateCipherSuite in nsNSSCallbacks.cpp
@@ -1929,24 +1880,16 @@ nsNSSComponent::InitializeNSS()
   static_assert(nsINSSErrorsService::NSS_SEC_ERROR_BASE == SEC_ERROR_BASE &&
                 nsINSSErrorsService::NSS_SEC_ERROR_LIMIT == SEC_ERROR_LIMIT &&
                 nsINSSErrorsService::NSS_SSL_ERROR_BASE == SSL_ERROR_BASE &&
                 nsINSSErrorsService::NSS_SSL_ERROR_LIMIT == SSL_ERROR_LIMIT,
                 "You must update the values in nsINSSErrorsService.idl");
 
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS Initialization beginning\n"));
 
-  // The call to ConfigureInternalPKCS11Token needs to be done before NSS is initialized,
-  // but affects only static data.
-  // If we could assume i18n will not change between profiles, one call per application
-  // run were sufficient. As I can't predict what happens in the future, let's repeat
-  // this call for every re-init of NSS.
-
-  ConfigureInternalPKCS11Token();
-
   nsAutoCString profileStr;
   nsresult rv = GetNSSProfilePath(profileStr);
   if (NS_FAILED(rv)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
 #if defined(XP_WIN) || (defined(XP_LINUX) && !defined(ANDROID))
   SetNSSDatabaseCacheModeAsAppropriate();
--- a/security/manager/ssl/nsNSSComponent.h
+++ b/security/manager/ssl/nsNSSComponent.h
@@ -142,17 +142,16 @@ protected:
 
 private:
   nsresult InitializeNSS();
   void ShutdownNSS();
 
   void setValidationOptions(bool isInitialSetting,
                             const mozilla::MutexAutoLock& proofOfLock);
   nsresult setEnabledTLSVersions();
-  nsresult ConfigureInternalPKCS11Token();
   nsresult RegisterObservers();
 
   void MaybeEnableFamilySafetyCompatibility();
   void MaybeImportEnterpriseRoots();
 #ifdef XP_WIN
   void ImportEnterpriseRootsForLocation(
     DWORD locationFlag, const mozilla::MutexAutoLock& proofOfLock);
   nsresult MaybeImportFamilySafetyRoot(PCCERT_CONTEXT certificate,
--- a/security/manager/ssl/nsPK11TokenDB.cpp
+++ b/security/manager/ssl/nsPK11TokenDB.cpp
@@ -22,44 +22,65 @@ extern mozilla::LazyLogModule gPIPNSSLog
 
 NS_IMPL_ISUPPORTS(nsPK11Token, nsIPK11Token)
 
 nsPK11Token::nsPK11Token(PK11SlotInfo* slot)
   : mUIContext(new PipUIContext())
 {
   MOZ_ASSERT(slot);
   mSlot.reset(PK11_ReferenceSlot(slot));
+  mIsInternalCryptoToken = PK11_IsInternal(mSlot.get()) &&
+                           !PK11_IsInternalKeySlot(mSlot.get());
+  mIsInternalKeyToken = PK11_IsInternalKeySlot(mSlot.get());
   mSeries = PK11_GetSlotSeries(slot);
-
   Unused << refreshTokenInfo();
 }
 
 nsresult
 nsPK11Token::refreshTokenInfo()
 {
-  mTokenName = PK11_GetTokenName(mSlot.get());
+  if (mIsInternalCryptoToken) {
+    nsresult rv;
+    if (PK11_IsFIPS()) {
+      rv = GetPIPNSSBundleString("Fips140TokenDescription", mTokenName);
+    } else {
+      rv = GetPIPNSSBundleString("TokenDescription", mTokenName);
+    }
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  } else if (mIsInternalKeyToken) {
+    nsresult rv = GetPIPNSSBundleString("PrivateTokenDescription", mTokenName);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  } else {
+    mTokenName.Assign(PK11_GetTokenName(mSlot.get()));
+  }
 
   CK_TOKEN_INFO tokInfo;
   nsresult rv = MapSECStatus(PK11_GetTokenInfo(mSlot.get(), &tokInfo));
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  // Set the Label field
-  const char* ccLabel = mozilla::BitwiseCast<char*, CK_UTF8CHAR*>(tokInfo.label);
-  mTokenLabel.Assign(ccLabel, strnlen(ccLabel, sizeof(tokInfo.label)));
-  mTokenLabel.Trim(" ", false, true);
-
   // Set the Manufacturer field
-  const char* ccManID =
-    mozilla::BitwiseCast<char*, CK_UTF8CHAR*>(tokInfo.manufacturerID);
-  mTokenManufacturerID.Assign(
-    ccManID,
-    strnlen(ccManID, sizeof(tokInfo.manufacturerID)));
-  mTokenManufacturerID.Trim(" ", false, true);
+  if (mIsInternalCryptoToken || mIsInternalKeyToken) {
+    rv = GetPIPNSSBundleString("ManufacturerID", mTokenManufacturerID);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  } else {
+    const char* ccManID =
+      mozilla::BitwiseCast<char*, CK_UTF8CHAR*>(tokInfo.manufacturerID);
+    mTokenManufacturerID.Assign(
+      ccManID,
+      strnlen(ccManID, sizeof(tokInfo.manufacturerID)));
+    mTokenManufacturerID.Trim(" ", false, true);
+  }
 
   // Set the Hardware Version field
   mTokenHWVersion.Truncate();
   mTokenHWVersion.AppendInt(tokInfo.hardwareVersion.major);
   mTokenHWVersion.Append('.');
   mTokenHWVersion.AppendInt(tokInfo.hardwareVersion.minor);
 
   // Set the Firmware Version field
@@ -96,26 +117,20 @@ nsPK11Token::GetAttributeHelper(const ns
 
 NS_IMETHODIMP
 nsPK11Token::GetTokenName(/*out*/ nsACString& tokenName)
 {
   return GetAttributeHelper(mTokenName, tokenName);
 }
 
 NS_IMETHODIMP
-nsPK11Token::GetTokenLabel(/*out*/ nsACString& tokenLabel)
-{
-  return GetAttributeHelper(mTokenLabel, tokenLabel);
-}
-
-NS_IMETHODIMP
 nsPK11Token::GetIsInternalKeyToken(/*out*/ bool* _retval)
 {
   NS_ENSURE_ARG_POINTER(_retval);
-  *_retval = PK11_IsInternalKeySlot(mSlot.get());
+  *_retval = mIsInternalKeyToken;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPK11Token::GetTokenManID(/*out*/ nsACString& tokenManufacturerID)
 {
   return GetAttributeHelper(mTokenManufacturerID, tokenManufacturerID);
 }
--- a/security/manager/ssl/nsPK11TokenDB.h
+++ b/security/manager/ssl/nsPK11TokenDB.h
@@ -27,22 +27,25 @@ public:
 protected:
   virtual ~nsPK11Token() {}
 
 private:
   friend class nsPK11TokenDB;
   nsresult refreshTokenInfo();
 
   nsCString mTokenName;
-  nsCString mTokenLabel;
   nsCString mTokenManufacturerID;
   nsCString mTokenHWVersion;
   nsCString mTokenFWVersion;
   nsCString mTokenSerialNum;
   mozilla::UniquePK11SlotInfo mSlot;
+  // True if this is the "PKCS#11 token" that provides cryptographic functions.
+  bool mIsInternalCryptoToken;
+  // True if this is the "PKCS#11 token" where private keys are stored.
+  bool mIsInternalKeyToken;
   int mSeries;
   nsCOMPtr<nsIInterfaceRequestor> mUIContext;
   nsresult GetAttributeHelper(const nsACString& attribute,
                       /*out*/ nsACString& xpcomOutParam);
 };
 
 class nsPK11TokenDB : public nsIPK11TokenDB
 {
--- a/security/manager/ssl/nsPKCS11Slot.cpp
+++ b/security/manager/ssl/nsPKCS11Slot.cpp
@@ -22,42 +22,69 @@ using mozilla::LogLevel;
 extern mozilla::LazyLogModule gPIPNSSLog;
 
 NS_IMPL_ISUPPORTS(nsPKCS11Slot, nsIPKCS11Slot)
 
 nsPKCS11Slot::nsPKCS11Slot(PK11SlotInfo* slot)
 {
   MOZ_ASSERT(slot);
   mSlot.reset(PK11_ReferenceSlot(slot));
+  mIsInternalCryptoSlot = PK11_IsInternal(mSlot.get()) &&
+                          !PK11_IsInternalKeySlot(mSlot.get());
+  mIsInternalKeySlot = PK11_IsInternalKeySlot(mSlot.get());
   mSeries = PK11_GetSlotSeries(slot);
   Unused << refreshSlotInfo();
 }
 
 nsresult
 nsPKCS11Slot::refreshSlotInfo()
 {
   CK_SLOT_INFO slotInfo;
   nsresult rv = MapSECStatus(PK11_GetSlotInfo(mSlot.get(), &slotInfo));
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // Set the Description field
-  const char* ccDesc =
-    mozilla::BitwiseCast<char*, CK_UTF8CHAR*>(slotInfo.slotDescription);
-  mSlotDesc.Assign(ccDesc, strnlen(ccDesc, sizeof(slotInfo.slotDescription)));
-  mSlotDesc.Trim(" ", false, true);
+  if (mIsInternalCryptoSlot) {
+    nsresult rv;
+    if (PK11_IsFIPS()) {
+      rv = GetPIPNSSBundleString("Fips140SlotDescription", mSlotDesc);
+    } else {
+      rv = GetPIPNSSBundleString("SlotDescription", mSlotDesc);
+    }
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  } else if (mIsInternalKeySlot) {
+    rv = GetPIPNSSBundleString("PrivateSlotDescription", mSlotDesc);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  } else {
+    const char* ccDesc =
+      mozilla::BitwiseCast<char*, CK_UTF8CHAR*>(slotInfo.slotDescription);
+    mSlotDesc.Assign(ccDesc, strnlen(ccDesc, sizeof(slotInfo.slotDescription)));
+    mSlotDesc.Trim(" ", false, true);
+  }
 
   // Set the Manufacturer field
-  const char* ccManID =
-    mozilla::BitwiseCast<char*, CK_UTF8CHAR*>(slotInfo.manufacturerID);
-  mSlotManufacturerID.Assign(
-    ccManID,
-    strnlen(ccManID, sizeof(slotInfo.manufacturerID)));
-  mSlotManufacturerID.Trim(" ", false, true);
+  if (mIsInternalCryptoSlot || mIsInternalKeySlot) {
+    rv = GetPIPNSSBundleString("ManufacturerID", mSlotManufacturerID);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  } else {
+    const char* ccManID =
+      mozilla::BitwiseCast<char*, CK_UTF8CHAR*>(slotInfo.manufacturerID);
+    mSlotManufacturerID.Assign(
+      ccManID,
+      strnlen(ccManID, sizeof(slotInfo.manufacturerID)));
+    mSlotManufacturerID.Trim(" ", false, true);
+  }
 
   // Set the Hardware Version field
   mSlotHWVersion.Truncate();
   mSlotHWVersion.AppendInt(slotInfo.hardwareVersion.major);
   mSlotHWVersion.Append('.');
   mSlotHWVersion.AppendInt(slotInfo.hardwareVersion.minor);
 
   // Set the Firmware Version field
@@ -82,29 +109,26 @@ nsPKCS11Slot::GetAttributeHelper(const n
 
   xpcomOutParam = attribute;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPKCS11Slot::GetName(/*out*/ nsACString& name)
 {
-  // |csn| is non-owning.
-  char* csn = PK11_GetSlotName(mSlot.get());
-  if (csn && *csn) {
-    name = csn;
-  } else if (PK11_HasRootCerts(mSlot.get())) {
-    // This is a workaround to an Root Module bug - the root certs module has
-    // no slot name.  Not bothering to localize, because this is a workaround
-    // and for now all the slot names returned by NSS are char * anyway.
-    name = NS_LITERAL_CSTRING("Root Certificates");
-  } else {
-    // same as above, this is a catch-all
-    name = NS_LITERAL_CSTRING("Unnamed Slot");
+  if (mIsInternalCryptoSlot) {
+    if (PK11_IsFIPS()) {
+      return GetPIPNSSBundleString("Fips140TokenDescription", name);
+    }
+    return GetPIPNSSBundleString("TokenDescription", name);
   }
+  if (mIsInternalKeySlot) {
+    return GetPIPNSSBundleString("PrivateTokenDescription", name);
+  }
+  name.Assign(PK11_GetSlotName(mSlot.get()));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPKCS11Slot::GetDesc(/*out*/ nsACString& desc)
 {
   return GetAttributeHelper(mSlotDesc, desc);
@@ -147,17 +171,27 @@ nsPKCS11Slot::GetTokenName(/*out*/ nsACS
 
   if (PK11_GetSlotSeries(mSlot.get()) != mSeries) {
     nsresult rv = refreshSlotInfo();
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
-  tokenName = PK11_GetTokenName(mSlot.get());
+  if (mIsInternalCryptoSlot) {
+    if (PK11_IsFIPS()) {
+      return GetPIPNSSBundleString("Fips140TokenDescription", tokenName);
+    }
+    return GetPIPNSSBundleString("TokenDescription", tokenName);
+  }
+  if (mIsInternalKeySlot) {
+    return GetPIPNSSBundleString("PrivateTokenDescription", tokenName);
+  }
+
+  tokenName.Assign(PK11_GetTokenName(mSlot.get()));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPKCS11Slot::GetStatus(uint32_t* _retval)
 {
   NS_ENSURE_ARG_POINTER(_retval);
   if (PK11_IsDisabled(mSlot.get())) {
--- a/security/manager/ssl/nsPKCS11Slot.h
+++ b/security/manager/ssl/nsPKCS11Slot.h
@@ -22,16 +22,20 @@ public:
 
   explicit nsPKCS11Slot(PK11SlotInfo* slot);
 
 protected:
   virtual ~nsPKCS11Slot() {}
 
 private:
   mozilla::UniquePK11SlotInfo mSlot;
+  // True if this is the "PKCS#11 slot" that provides cryptographic functions.
+  bool mIsInternalCryptoSlot;
+  // True if this is the "PKCS#11 slot" where private keys are stored.
+  bool mIsInternalKeySlot;
   nsCString mSlotDesc;
   nsCString mSlotManufacturerID;
   nsCString mSlotHWVersion;
   nsCString mSlotFWVersion;
   int mSeries;
 
   nsresult refreshSlotInfo();
   nsresult GetAttributeHelper(const nsACString& attribute,
--- a/security/manager/ssl/tests/unit/test_certDB_import.js
+++ b/security/manager/ssl/tests/unit/test_certDB_import.js
@@ -120,11 +120,16 @@ function run_test() {
 
   // Import the CA cert so that the e-mail import succeeds.
   testImportCACert();
 
   // Import the e-mail cert and check for success.
   let emailArray = getCertAsByteArray("test_certDB_import/emailEE.pem");
   gCertDB.importEmailCertificate(emailArray, emailArray.length,
                                  gInterfaceRequestor);
-  notEqual(findCertByEmailAddress(TEST_EMAIL_ADDRESS), null,
-           "E-mail cert should now be found in the database");
+  let emailCert = findCertByEmailAddress(TEST_EMAIL_ADDRESS);
+  notEqual(emailCert, null, "E-mail cert should now be found in the database");
+  let bundle =
+    Services.strings.createBundle("chrome://pipnss/locale/pipnss.properties");
+  equal(emailCert.tokenName,
+        bundle.GetStringFromName("PrivateTokenDescription"),
+        "cert's tokenName should be the expected localized value");
 }
--- a/security/manager/ssl/tests/unit/test_pkcs11_slot.js
+++ b/security/manager/ssl/tests/unit/test_pkcs11_slot.js
@@ -13,29 +13,33 @@ function find_slot_by_name(module, name)
                                                    Ci.nsIPKCS11Slot)) {
     if (slot.name == name) {
       return slot;
     }
   }
   return null;
 }
 
+function find_module_by_name(moduleDB, name) {
+  for (let slot of XPCOMUtils.IterSimpleEnumerator(moduleDB.listModules(),
+                                                   Ci.nsIPKCS11Module)) {
+    if (slot.name == name) {
+      return slot;
+    }
+  }
+  return null;
+}
+
 function run_test() {
   loadPKCS11TestModule(false);
 
   let moduleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"]
                    .getService(Ci.nsIPKCS11ModuleDB);
-  let testModule;
-  for (let module of XPCOMUtils.IterSimpleEnumerator(moduleDB.listModules(),
-                                                     Ci.nsIPKCS11Module)) {
-    if (module.name == "PKCS11 Test Module") {
-      testModule = module;
-      break;
-    }
-  }
+  let testModule = find_module_by_name(moduleDB, "PKCS11 Test Module");
+  notEqual(testModule, null, "should be able to find test module");
   let testSlot = find_slot_by_name(testModule, "Test PKCS11 Slot 二");
   notEqual(testSlot, null, "should be able to find 'Test PKCS11 Slot 二'");
 
   equal(testSlot.name, "Test PKCS11 Slot 二",
         "Actual and expected name should match");
   equal(testSlot.desc, "Test PKCS11 Slot 二",
         "Actual and expected description should match");
   equal(testSlot.manID, "Test PKCS11 Manufacturer ID",
@@ -46,18 +50,38 @@ function run_test() {
         "Actual and expected firmware version should match");
   equal(testSlot.status, Ci.nsIPKCS11Slot.SLOT_READY,
         "Actual and expected status should match");
   equal(testSlot.tokenName, "Test PKCS11 Tokeñ 2 Label",
         "Actual and expected token name should match");
 
   let testToken = testSlot.getToken();
   notEqual(testToken, null, "getToken() should succeed");
-  equal(testToken.tokenLabel, "Test PKCS11 Tokeñ 2 Label",
-        "Spot check: the actual and expected test token labels should be equal");
+  equal(testToken.tokenName, "Test PKCS11 Tokeñ 2 Label",
+        "Spot check: the actual and expected test token names should be equal");
   ok(!testToken.isInternalKeyToken, "This token is not the internal key token");
 
   testSlot = find_slot_by_name(testModule, "Empty PKCS11 Slot");
   notEqual(testSlot, null, "should be able to find 'Empty PKCS11 Slot'");
   equal(testSlot.tokenName, null, "Empty slot is empty");
   equal(testSlot.status, Ci.nsIPKCS11Slot.SLOT_NOT_PRESENT,
         "Actual and expected status should match");
+
+  let bundle =
+    Services.strings.createBundle("chrome://pipnss/locale/pipnss.properties");
+  let internalModule = find_module_by_name(moduleDB,
+                                           "NSS Internal PKCS #11 Module");
+  notEqual(internalModule, null, "should be able to find internal module");
+  let cryptoSlot = find_slot_by_name(
+    internalModule, bundle.GetStringFromName("TokenDescription"));
+  notEqual(cryptoSlot, "should be able to find internal crypto slot");
+  equal(cryptoSlot.desc, bundle.GetStringFromName("SlotDescription"),
+        "crypto slot should have expected 'desc'");
+  equal(cryptoSlot.manID, bundle.GetStringFromName("ManufacturerID"),
+        "crypto slot should have expected 'manID'");
+  let keySlot = find_slot_by_name(
+    internalModule, bundle.GetStringFromName("PrivateTokenDescription"));
+  notEqual(keySlot, "should be able to find internal key slot");
+  equal(keySlot.desc, bundle.GetStringFromName("PrivateSlotDescription"),
+        "key slot should have expected 'desc'");
+  equal(keySlot.manID, bundle.GetStringFromName("ManufacturerID"),
+        "key slot should have expected 'manID'");
 }
--- a/security/manager/ssl/tests/unit/test_pkcs11_token.js
+++ b/security/manager/ssl/tests/unit/test_pkcs11_token.js
@@ -19,18 +19,16 @@ do_get_profile();
 
 function checkBasicAttributes(token) {
   let bundle =
     Services.strings.createBundle("chrome://pipnss/locale/pipnss.properties");
 
   let expectedTokenName = bundle.GetStringFromName("PrivateTokenDescription");
   equal(token.tokenName, expectedTokenName,
         "Actual and expected name should match");
-  equal(token.tokenLabel, expectedTokenName,
-        "Actual and expected label should match");
   equal(token.tokenManID, bundle.GetStringFromName("ManufacturerID"),
         "Actual and expected manufacturer ID should match");
   equal(token.tokenHWVersion, "0.0",
         "Actual and expected hardware version should match");
   equal(token.tokenFWVersion, "0.0",
         "Actual and expected firmware version should match");
   equal(token.tokenSerialNumber, "0000000000000000",
         "Actual and expected serial number should match");
--- a/taskcluster/mach_commands.py
+++ b/taskcluster/mach_commands.py
@@ -267,53 +267,45 @@ class MachCommands(MachCommandBase):
                      help='Action input (.yml or .json)')
     @CommandArgument('--task', default=None,
                      help='Task definition (.yml or .json; if omitted, the task will be'
                           'fetched from the queue)')
     @CommandArgument('callback', default=None,
                      help='Action callback name (Python function name)')
     def test_action_callback(self, **options):
         import taskgraph.parameters
-        from taskgraph.util.taskcluster import get_task_definition
         import taskgraph.actions
         import yaml
 
         def load_data(filename):
             with open(filename) as f:
                 if filename.endswith('.yml'):
                     return yaml.safe_load(f)
                 elif filename.endswith('.json'):
                     return json.load(f)
                 else:
                     raise Exception("unknown filename {}".format(filename))
 
         try:
             self.setup_logging()
             task_id = options['task_id']
-            if options['task']:
-                task = load_data(options['task'])
-            elif task_id:
-                task = get_task_definition(task_id)
-            else:
-                task = None
 
             if options['input']:
                 input = load_data(options['input'])
             else:
                 input = None
 
             parameters = taskgraph.parameters.load_parameters_file(options['parameters'])
             parameters.check()
 
             root = options['root']
 
             return taskgraph.actions.trigger_action_callback(
                     task_group_id=options['task_group_id'],
                     task_id=task_id,
-                    task=task,
                     input=input,
                     callback=options['callback'],
                     parameters=parameters,
                     root=root,
                     test=True)
         except Exception:
             traceback.print_exc()
             sys.exit(1)
--- a/taskcluster/taskgraph/actions/backfill.py
+++ b/taskcluster/taskgraph/actions/backfill.py
@@ -7,17 +7,18 @@
 from __future__ import absolute_import, print_function, unicode_literals
 
 import logging
 
 import requests
 from requests.exceptions import HTTPError
 
 from .registry import register_callback_action
-from .util import find_decision_task, create_tasks
+from .util import find_decision_task, create_task_from_def, fix_task_dependencies
+from slugid import nice as slugid
 from taskgraph.util.taskcluster import get_artifact_from_index
 from taskgraph.taskgraph import TaskGraph
 
 PUSHLOG_TMPL = '{}/json-pushes?version=2&startID={}&endID={}'
 INDEX_TMPL = 'gecko.v2.{}.pushlog-id.{}.decision'
 
 logger = logging.getLogger(__name__)
 
@@ -39,27 +40,46 @@ logger = logging.getLogger(__name__)
             'depth': {
                 'type': 'integer',
                 'default': 5,
                 'minimum': 1,
                 'maximum': 10,
                 'title': 'Depth',
                 'description': ('The number of previous pushes before the current '
                                 'push to attempt to trigger this task on.')
+            },
+            'inclusive': {
+                'type': 'boolean',
+                'default': False,
+                'title': 'Inclusive Range',
+                'description': ('If true, the backfill will also retrigger the task '
+                                'on the selected push.')
+            },
+            'addGeckoProfile': {
+                'type': 'boolean',
+                'default': False,
+                'title': 'Add Gecko Profile',
+                'description': 'If true, appends --geckoProfile to mozharness options.'
+            },
+            'testPath': {
+                'type': 'string',
+                'title': 'Test Path',
+                'description': 'If specified, set MOZHARNESS_TEST_PATHS to this value.'
             }
         },
         'additionalProperties': False
     },
     available=lambda parameters: parameters.get('project', None) != 'try'
 )
 def backfill_action(parameters, graph_config, input, task_group_id, task_id, task):
     label = task['metadata']['name']
     pushes = []
-    depth = input.get('depth', 5)
-    end_id = int(parameters['pushlog_id']) - 1
+    inclusive_tweak = 1 if input.get('inclusive') else 0
+    depth = input.get('depth', 5) + inclusive_tweak
+    end_id = int(parameters['pushlog_id']) - (1 - inclusive_tweak)
 
     while True:
         start_id = max(end_id - depth, 0)
         pushlog_url = PUSHLOG_TMPL.format(parameters['head_repository'], start_id, end_id)
         r = requests.get(pushlog_url)
         r.raise_for_status()
         pushes = pushes + r.json()['pushes'].keys()
         if len(pushes) >= depth:
@@ -85,13 +105,26 @@ def backfill_action(parameters, graph_co
                     INDEX_TMPL.format(parameters['project'], push),
                     'public/parameters.yml')
             push_decision_task_id = find_decision_task(push_params, graph_config)
         except HTTPError as e:
             logger.info('Skipping {} due to missing index artifacts! Error: {}'.format(push, e))
             continue
 
         if label in full_task_graph.tasks.keys():
-            create_tasks(
-                    [label], full_task_graph, label_to_taskid,
-                    push_params, push_decision_task_id, push)
+            task_def = fix_task_dependencies(full_task_graph.tasks[label], label_to_taskid)
+            task_def['taskGroupId'] = push_decision_task_id
+
+            if input.get('addGeckoProfile'):
+                mh_options = task_def['payload'].setdefault('env', {}) \
+                                                .get('MOZHARNESS_OPTIONS', '')
+                task_def['payload']['env']['MOZHARNESS_OPTIONS'] = mh_options + ' --geckoProfile'
+                task_def['extra']['treeherder']['symbol'] += '-p'
+
+            if input.get('testPath'):
+                env = task_def['payload'].setdefault('env', {})
+                env['MOZHARNESS_TEST_PATHS'] = input.get('testPath')
+                task_def['extra']['treeherder']['symbol'] += '-b'
+
+            new_task_id = slugid()
+            create_task_from_def(new_task_id, task_def, parameters['level'])
         else:
             logging.info('Could not find {} on {}. Skipping.'.format(label, push))
--- a/taskcluster/taskgraph/actions/mochitest_retrigger.py
+++ b/taskcluster/taskgraph/actions/mochitest_retrigger.py
@@ -6,19 +6,18 @@
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import json
 import logging
 
 from slugid import nice as slugid
 
-from .util import (create_task_from_def, fetch_graph_and_labels)
+from .util import (create_task_from_def, fetch_graph_and_labels, fix_task_dependencies)
 from .registry import register_callback_action
-from taskgraph.util.parameterization import resolve_task_references
 
 TASKCLUSTER_QUEUE_URL = "https://queue.taskcluster.net/v1/task"
 
 logger = logging.getLogger(__name__)
 
 
 @register_callback_action(
     name='retrigger-mochitest-reftest-with-options',
@@ -79,23 +78,17 @@ logger = logging.getLogger(__name__)
         'required': ['path']
     }
 )
 def mochitest_retrigger_action(parameters, graph_config, input, task_group_id, task_id, task):
     decision_task_id, full_task_graph, label_to_taskid = fetch_graph_and_labels(
         parameters, graph_config)
 
     pre_task = full_task_graph.tasks[task['metadata']['name']]
-
-    # fix up the task's dependencies, similar to how optimization would
-    # have done in the decision
-    dependencies = {name: label_to_taskid[label]
-                    for name, label in pre_task.dependencies.iteritems()}
-    new_task_definition = resolve_task_references(pre_task.label, pre_task.task, dependencies)
-    new_task_definition.setdefault('dependencies', []).extend(dependencies.itervalues())
+    new_task_definition = fix_task_dependencies(pre_task, label_to_taskid)
 
     # don't want to run mozharness tests, want a custom mach command instead
     new_task_definition['payload']['command'] += ['--no-run-tests']
 
     custom_mach_command = [task['tags']['test-type']]
 
     # mochitests may specify a flavor
     if new_task_definition['payload']['env'].get('MOCHITEST_FLAVOR'):
--- a/taskcluster/taskgraph/actions/retrigger.py
+++ b/taskcluster/taskgraph/actions/retrigger.py
@@ -16,16 +16,18 @@ from .registry import register_callback_
 
 logger = logging.getLogger(__name__)
 
 
 @register_callback_action(
     title='Retrigger',
     name='retrigger',
     symbol='rt',
+    kind='hook',
+    generic=True,
     description=(
         'Create a clone of the task.\n\n'
     ),
     order=1,
     context=[{}],
     schema={
         'type': 'object',
         'properties': {
--- a/taskcluster/taskgraph/actions/util.py
+++ b/taskcluster/taskgraph/actions/util.py
@@ -13,16 +13,17 @@ import os
 
 from requests.exceptions import HTTPError
 
 from taskgraph import create
 from taskgraph.decision import write_artifact
 from taskgraph.taskgraph import TaskGraph
 from taskgraph.optimize import optimize_task_graph
 from taskgraph.util.taskcluster import get_session, find_task_id, get_artifact, list_tasks
+from taskgraph.util.parameterization import resolve_task_references
 
 logger = logging.getLogger(__name__)
 
 PUSHLOG_TMPL = '{}/json-pushes?version=2&changeset={}&tipsonly=1&full=1'
 
 
 def find_decision_task(parameters, graph_config):
     """Given the parameters for this action, find the taskId of the decision
@@ -100,16 +101,26 @@ def create_task_from_def(task_id, task_d
     it to this function. No dependencies will be scheduled. You must handle
     this yourself. Seeing how create_tasks handles it might prove helpful."""
     task_def['schedulerId'] = 'gecko-level-{}'.format(level)
     label = task_def['metadata']['name']
     session = get_session()
     create.create_task(session, task_id, label, task_def)
 
 
+def fix_task_dependencies(task_def, label_to_taskid):
+    """fix up the task's dependencies, similar to how optimization would
+    have done in the decision"""
+    dependencies = {name: label_to_taskid[label]
+                    for name, label in task_def.dependencies.iteritems()}
+    new_task_definition = resolve_task_references(task_def.label, task_def.task, dependencies)
+    new_task_definition.setdefault('dependencies', []).extend(dependencies.itervalues())
+    return new_task_definition
+
+
 def update_parent(task, graph):
     task.task.setdefault('extra', {})['parent'] = os.environ.get('TASK_ID', '')
     return task
 
 
 def create_tasks(to_run, full_task_graph, label_to_taskid,
                  params, decision_task_id=None, suffix=''):
     """Create new tasks.  The task definition will have {relative-datestamp':
--- a/testing/marionette/.eslintrc.js
+++ b/testing/marionette/.eslintrc.js
@@ -1,26 +1,25 @@
 "use strict";
 
+// inherits from ../../tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js
+
 module.exports = {
   "rules": {
     "camelcase": "error",
     "comma-dangle": ["error", "always-multiline"],
     "indent-legacy": ["error", 2, {
       "CallExpression": {"arguments": 2},
       "FunctionExpression": {"body": 1, "parameters": 2},
       "MemberExpression": 2,
       "SwitchCase": 1,
     }],
     "max-len": ["error", 78, {
       "ignoreStrings": true,
       "ignoreTemplateLiterals": true,
       "ignoreUrls": true,
     }],
     "no-fallthrough": "error",
-    "no-new-object": "error",
     "no-undef-init": "error",
-    "no-unused-vars": ["error", {}],
     "no-var": "error",
     "object-curly-spacing": ["error", "never"],
-    "semi": "error",
   }
 };
--- a/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
+++ b/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
@@ -33,17 +33,17 @@ skip-if = appname == 'fennec'
 [test_execute_async_script.py]
 [test_execute_script.py]
 [test_element_retrieval.py]
 [test_findelement_chrome.py]
 skip-if = appname == 'fennec'
 
 [test_get_current_url_chrome.py]
 [test_navigation.py]
-
+skip-if = appname == 'fennec' && debug # Bug 1454680
 [test_timeouts.py]
 
 [test_single_finger_desktop.py]
 skip-if = appname == 'fennec' || os == "win" # Bug 1025040
 
 [test_anonymous_content.py]
 skip-if = appname == 'fennec'
 [test_switch_frame.py]
--- a/testing/web-platform/mach_commands.py
+++ b/testing/web-platform/mach_commands.py
@@ -286,16 +286,25 @@ class WPTManifestUpdater(MozbuildObject)
     def run_update(self, check_clean=False, rebuild=False, **kwargs):
         import manifestupdate
         from wptrunner import wptlogging
         logger = wptlogging.setup(kwargs, {"mach": sys.stdout})
         wpt_dir = os.path.abspath(os.path.join(self.topsrcdir, 'testing', 'web-platform'))
         manifestupdate.update(logger, wpt_dir, check_clean, rebuild)
 
 
+class WPTManifestDownloader(MozbuildObject):
+    def run_download(self, path=None, tests_root=None, force=False, **kwargs):
+        import manifestdownload
+        from wptrunner import wptlogging
+        logger = wptlogging.setup(kwargs, {"mach": sys.stdout})
+        wpt_dir = os.path.abspath(os.path.join(self.topsrcdir, 'testing', 'web-platform'))
+        manifestdownload.run(logger, wpt_dir, self.topsrcdir, force)
+
+
 def create_parser_update():
     from update import updatecommandline
     return updatecommandline.create_parser()
 
 def create_parser_reduce():
     from wptrunner import wptcommandline
     return wptcommandline.create_parser_reduce()
 
@@ -319,16 +328,20 @@ def create_parser_create():
     p.add_argument("path", action="store", help="Path to the test file")
     return p
 
 
 def create_parser_manifest_update():
     import manifestupdate
     return manifestupdate.create_parser()
 
+def create_parser_manifest_download():
+    import manifestdownload
+    return manifestdownload.create_parser()
+
 
 @CommandProvider
 class MachCommands(MachCommandBase):
     def setup(self):
         self._activate_virtualenv()
 
     @Command("web-platform-tests",
              category="testing",
@@ -402,8 +415,17 @@ class MachCommands(MachCommandBase):
 
     @Command("wpt-manifest-update",
              category="testing",
              parser=create_parser_manifest_update)
     def wpt_manifest_update(self, **params):
         self.setup()
         wpt_manifest_updater = self._spawn(WPTManifestUpdater)
         return wpt_manifest_updater.run_update(**params)
+
+
+    @Command("wpt-manifest-download",
+             category="testing",
+             parser=create_parser_manifest_download)
+    def wpt_manifest_download(self, **params):
+        self.setup()
+        wpt_manifest_downloader = self._spawn(WPTManifestDownloader)
+        return wpt_manifest_downloader.run_download(**params)
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/manifestdownload.py
@@ -0,0 +1,130 @@
+from __future__ import absolute_import
+
+import argparse
+import json
+import io
+import os
+from datetime import datetime, timedelta
+import gzip
+from vcs import Mercurial
+import requests
+
+from six.moves.urllib.request import urlopen
+
+def abs_path(path):
+    return os.path.abspath(os.path.expanduser(path))
+
+
+def hg_commits(repo_root):
+    hg = Mercurial.get_func(repo_root)
+    return [item for item in hg("log", "-fl50", "--template={node}\n",
+            "testing/web-platform/tests/", "testing/web-platform/mozilla/tests").split("\n")
+            if item]
+
+
+def should_download(logger, manifest_path, rebuild_time=timedelta(days=5)):
+    # TODO: Improve logic for when to download. Maybe if x revisions behind?
+    if not os.path.exists(manifest_path):
+        return True
+    mtime = datetime.fromtimestamp(os.path.getmtime(manifest_path))
+    if mtime < datetime.now() - rebuild_time:
+        return True
+    logger.info("Skipping manifest download because existing file is recent")
+    return False
+
+
+def taskcluster_url(logger, commits):
+    cset_url = ('https://hg.mozilla.org/mozilla-central/json-pushes?'
+                'changeset={changeset}&version=2&tipsonly=1')
+
+    tc_url = ('https://index.taskcluster.net/v1/task/gecko.v2.mozilla-central.'
+              'revision.{changeset}.source.manifest-upload')
+
+    for revision in commits:
+        req = requests.get(cset_url.format(changeset=revision),
+                           headers={'Accept': 'application/json'})
+
+        req.raise_for_status()
+
+        result = req.json()
+        [cset] = result['pushes'].values()[0]['changesets']
+        resp = requests.get(tc_url.format(changeset=cset))
+
+        if req.status_code == 200:
+            return tc_url.format(changeset=cset)
+
+    logger.info("Can't find a commit-specific manifest so just using the most"
+                "recent one")
+
+    return ("https://index.taskcluster.net/v1/task/gecko.v2.mozilla-central."
+            "latest.source.manifest-upload")
+
+
+def download_manifest(logger, wpt_dir, commits_func, url_func, force=False):
+    if not force and not should_download(logger, wpt_dir):
+        return False
+
+    commits = commits_func()
+    url = url_func(logger, commits) + "/artifacts/public/"
+
+    man_url= url + "manifest.json.gz"
+    moz_man_url= url + "moz_manifest.json.gz"
+
+    return ( _download(logger, os.path.join(wpt_dir, "meta", "MANIFEST.json"), man_url) and
+             _download(logger, os.path.join(wpt_dir,"mozilla", "meta", "MANIFEST.json"), moz_man_url))
+
+
+def _download(logger, manifest_path, url):
+    if not url:
+        logger.warning("No generated manifest found")
+        return False
+
+    logger.info("Downloading manifest from %s" % url)
+    try:
+        resp = urlopen(url)
+    except Exception:
+        logger.warning("Downloading pregenerated manifest failed")
+        return False
+
+    if resp.code != 200:
+        logger.warning("Downloading pregenerated manifest failed; got"
+                        "HTTP status %d" % resp.code)
+        return False
+
+    gzf = gzip.GzipFile(fileobj=io.BytesIO(resp.read()))
+
+    try:
+        decompressed = gzf.read()
+    except IOError:
+        logger.warning("Failed to decompress downloaded file")
+        return False
+
+    try:
+        with open(manifest_path, 'wb') as f:
+            f.write(decompressed)
+    except Exception as e:
+        logger.warning("Failed to write manifest at %s" % manifest_path)
+        return False
+
+    logger.info("Manifest at %s downloaded" % manifest_path)
+    return True
+
+
+def create_parser():
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "-p", "--path", type=abs_path, help="Path to manifest file.")
+    parser.add_argument(
+        "--force", action="store_true",
+        help="Always download, even if the existing manifest is recent")
+    return parser
+
+
+def download_from_taskcluster(logger, wpt_dir, repo_root, force=False):
+    return download_manifest(logger, wpt_dir, lambda: hg_commits(repo_root),
+                             taskcluster_url, force)
+
+
+def run(logger, wpt_dir, repo_root, force=False):
+    success = download_from_taskcluster(logger, wpt_dir, repo_root, force)
+    return 0 if success else 1
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/vcs.py
@@ -0,0 +1,20 @@
+import os
+import subprocess
+
+class Mercurial(object):
+
+    def __init__(self, repo_root):
+        self.root = os.path.abspath(repo_root)
+        self.hg = Mercurial.get_func(repo_root)
+
+
+    @staticmethod
+    def get_func(repo_path):
+        def hg(cmd, *args):
+            full_cmd = ["hg", cmd] + list(args)
+            try:
+                return subprocess.check_output(full_cmd, cwd=repo_path, stderr=subprocess.STDOUT)
+            except Exception as e:
+                raise(e)
+            # TODO: Test on Windows.
+        return hg
--- a/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.js
+++ b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.js
@@ -8,20 +8,21 @@ var trace;
 var service;
 var reports;
 
 function onLoad() {
   trace = document.getElementById("trace");
   service = new CheckerboardReportService();
   updateEnabled();
   reports = service.getReports();
-  for (var i = 0; i < reports.length; i++) {
+  for (let i = 0; i < reports.length; i++) {
     let text = "Severity " + reports[i].severity + " at " + new Date(reports[i].timestamp).toString();
     let link = document.createElement("a");
-    link.href = "javascript:showReport(" + i + ")";
+    link.href = "#";
+    link.addEventListener("click", function() { showReport(i); return false; });
     link.textContent = text;
     let bullet = document.createElement("li");
     bullet.appendChild(link);
     document.getElementById(reports[i].reason).appendChild(bullet);
   }
 }
 
 function updateEnabled() {
--- a/toolkit/components/contextualidentity/ContextualIdentityService.jsm
+++ b/toolkit/components/contextualidentity/ContextualIdentityService.jsm
@@ -2,16 +2,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var EXPORTED_SYMBOLS = ["ContextualIdentityService"];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
+// The maximum valid numeric value for the userContextId.
+const MAX_USER_CONTEXT_ID = -1 >>> 0;
+const LAST_CONTAINERS_JSON_VERSION = 4;
 const SAVE_DELAY_MS = 1500;
 const CONTEXTUAL_IDENTITY_ENABLED_PREF = "privacy.userContext.enabled";
 
 XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
   return Services.strings.createBundle("chrome://browser/locale/browser.properties");
 });
 
 XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function() {
@@ -57,16 +60,18 @@ function _TabRemovalObserver(resolver, t
   }
 };
 
 function _ContextualIdentityService(path) {
   this.init(path);
 }
 
 _ContextualIdentityService.prototype = {
+  LAST_CONTAINERS_JSON_VERSION,
+
   _defaultIdentities: [
     { userContextId: 1,
       public: true,
       icon: "fingerprint",
       color: "blue",
       l10nID: "userContextPersonal.label",
       accessKey: "userContextPersonal.accesskey",
       telemetryId: 1,
@@ -95,17 +100,29 @@ function _ContextualIdentityService(path
       accessKey: "userContextShopping.accesskey",
       telemetryId: 4,
     },
     { userContextId: 5,
       public: false,
       icon: "",
       color: "",
       name: "userContextIdInternal.thumbnail",
-      accessKey: "" },
+      accessKey: "",
+    },
+    // This userContextId is used by ExtensionStorageIDB.jsm to create an IndexedDB database
+    // opened with the extension principal but not directly accessible to the extension code
+    // (do not change the userContextId assigned here, otherwise the installed extensions will
+    // not be able to access the data previously stored with the browser.storage.local API).
+    { userContextId: MAX_USER_CONTEXT_ID,
+      public: false,
+      icon: "",
+      color: "",
+      name: "userContextIdInternal.webextStorageLocal",
+      accessKey: "",
+    },
   ],
 
   _identities: null,
   _openedIdentities: new Set(),
   _lastUserContextId: 0,
 
   _path: null,
   _dataReady: false,
@@ -143,20 +160,29 @@ function _ContextualIdentityService(path
       }
     }, (error) => {
       this.loadError(error);
     });
   },
 
   resetDefault() {
     this._identities = [];
+
+    // Compute the last used context id (excluding the reserved userContextIds
+    // "userContextIdInternal.webextStorageLocal" which has UINT32_MAX as its
+    // userContextId).
+    this._lastUserContextId = this._defaultIdentities
+      .filter(identity => identity.userContextId < MAX_USER_CONTEXT_ID)
+      .map(identity => identity.userContextId)
+      .sort((a, b) => a >= b)
+      .pop();
+
     // Clone the array
-    this._lastUserContextId = this._defaultIdentities.length;
-    for (let i = 0; i < this._lastUserContextId; i++) {
-      this._identities.push(Object.assign({}, this._defaultIdentities[i]));
+    for (let identity of this._defaultIdentities) {
+      this._identities.push(Object.assign({}, identity));
     }
     this._openedIdentities = new Set();
 
     this._dataReady = true;
 
     // Let's delete all the data of any userContextId. 1 is the first valid
     // userContextId value.
     this.deleteContainerData();
@@ -197,31 +223,41 @@ function _ContextualIdentityService(path
 
   save() {
     AsyncShutdown.profileBeforeChange.removeBlocker(this._saverCallback);
 
     this._saver = null;
     this._saverCallback = null;
 
     let object = {
-      version: 3,
+      version: LAST_CONTAINERS_JSON_VERSION,
       lastUserContextId: this._lastUserContextId,
       identities: this._identities
     };
 
     let bytes = gTextEncoder.encode(JSON.stringify(object));
     return OS.File.writeAtomic(this._path, bytes,
                                { tmpPath: this._path + ".tmp" });
   },
 
   create(name, icon, color) {
     this.ensureDataReady();
 
+    // Retrieve the next userContextId available.
+    let userContextId = ++this._lastUserContextId;
+
+    // Throw an error if the next userContextId available is invalid (the one associated to
+    // MAX_USER_CONTEXT_ID is already reserved to "userContextIdInternal.webextStorageLocal", which
+    // is also the last valid userContextId available, because its userContextId is equal to UINT32_MAX).
+    if (userContextId >= MAX_USER_CONTEXT_ID) {
+      throw new Error(`Unable to create a new userContext with id '${userContextId}'`);
+    }
+
     let identity = {
-      userContextId: ++this._lastUserContextId,
+      userContextId,
       public: true,
       icon,
       color,
       name
     };
 
     this._identities.push(identity);
     this.saveSoon();
@@ -291,17 +327,22 @@ function _ContextualIdentityService(path
 
     let saveNeeded = false;
 
     if (data.version == 2) {
       data = this.migrate2to3(data);
       saveNeeded = true;
     }
 
-    if (data.version != 3) {
+    if (data.version == 3) {
+      data = this.migrate3to4(data);
+      saveNeeded = true;
+    }
+
+    if (data.version != LAST_CONTAINERS_JSON_VERSION) {
       dump("ERROR - ContextualIdentityService - Unknown version found in " + this._path + "\n");
       this.loadError(null);
       return;
     }
 
     this._identities = data.identities;
     this._lastUserContextId = data.lastUserContextId;
 
@@ -330,26 +371,39 @@ function _ContextualIdentityService(path
       } finally {
         inputStream.close();
       }
     } catch (error) {
       this.loadError(error);
     }
   },
 
+  getPrivateUserContextIds() {
+    return this._identities
+      .filter(identity => !identity.public)
+      .map(identity => identity.userContextId);
+  },
+
   getPublicIdentities() {
     this.ensureDataReady();
     return Cu.cloneInto(this._identities.filter(info => info.public), {});
   },
 
   getPrivateIdentity(name) {
     this.ensureDataReady();
     return Cu.cloneInto(this._identities.find(info => !info.public && info.name == name), {});
   },
 
+  // getDefaultPrivateIdentity is similar to getPrivateIdentity but it only looks in the
+  // default identities (e.g. it is used in the data migration methods to retrieve a new default
+  // private identity and add it to the containers data stored on file).
+  getDefaultPrivateIdentity(name) {
+    return Cu.cloneInto(this._defaultIdentities.find(info => !info.public && info.name == name), {});
+  },
+
   getPublicIdentityFromId(userContextId) {
     this.ensureDataReady();
     return Cu.cloneInto(this._identities.find(info => info.userContextId == userContextId &&
                                               info.public), {});
   },
 
   getUserContextLabel(userContextId) {
     let identity = this.getPublicIdentityFromId(userContextId);
@@ -403,16 +457,21 @@ function _ContextualIdentityService(path
       }
 
       new _TabRemovalObserver(resolve, tabParentIds);
     });
   },
 
   notifyAllContainersCleared() {
     for (let identity of this._identities) {
+      // Don't clear the data related to private identities (e.g. the one used internally
+      // for the thumbnails and the one used for the storage.local IndexedDB backend).
+      if (!identity.public) {
+        continue;
+      }
       Services.obs.notifyObservers(null, "clear-origin-attributes-data",
                                    JSON.stringify({ userContextId: identity.userContextId }));
     }
   },
 
   _forEachContainerTab(callback, userContextId = 0) {
     let windowList = Services.wm.getEnumerator("navigator:browser");
     while (windowList.hasMoreElements()) {
@@ -455,35 +514,64 @@ function _ContextualIdentityService(path
     }
   },
 
   createNewInstanceForTesting(path) {
     return new _ContextualIdentityService(path);
   },
 
   deleteContainerData() {
+    // The userContextId 0 is reserved to the default firefox identity,
+    // and it should not be clear when we delete the public containers data.
     let minUserContextId = 1;
-    let maxUserContextId = minUserContextId;
+
+    // Collect the userContextId related to the identities that should not be cleared
+    // (the ones marked as `public = false`).
+    const keepDataContextIds = this.getPrivateUserContextIds();
+
+    // Collect the userContextIds currently used by any stored cookie.
+    let cookiesUserContextIds = new Set();
+
     const enumerator = Services.cookies.enumerator;
     while (enumerator.hasMoreElements()) {
       const cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
-      if (cookie.originAttributes.userContextId > maxUserContextId) {
-        maxUserContextId = cookie.originAttributes.userContextId;
+
+      // Skip any userContextIds that should not be cleared.
+      if (cookie.originAttributes.userContextId >= minUserContextId &&
+          !keepDataContextIds.includes(cookie.originAttributes.userContextId)) {
+        cookiesUserContextIds.add(cookie.originAttributes.userContextId);
       }
     }
 
-    for (let i = minUserContextId; i <= maxUserContextId; ++i) {
+    for (let userContextId of cookiesUserContextIds) {
       Services.obs.notifyObservers(null, "clear-origin-attributes-data",
-                                   JSON.stringify({ userContextId: i }));
+                                   JSON.stringify({ userContextId }));
     }
   },
 
   migrate2to3(data) {
     // migrating from 2 to 3 is basically just increasing the version id.
     // This migration was needed for bug 1419591. See bug 1419591 to know more.
     data.version = 3;
 
     return data;
   },
+
+  migrate3to4(data) {
+    // Migrating from 3 to 4 is:
+    // - adding the reserver userContextId used by the webextension storage.local API
+    // - add the keepData property to all the existent identities
+    // - increasing the version id.
+    //
+    // This migration was needed for Bug 1406181. See bug 1406181 for rationale.
+    const webextStorageLocalIdentity = this.getDefaultPrivateIdentity(
+      "userContextIdInternal.webextStorageLocal");
+
+    data.identities.push(webextStorageLocalIdentity);
+
+    data.version = 4;
+
+    return data;
+  },
 };
 
 let path = OS.Path.join(OS.Constants.Path.profileDir, "containers.json");
 var ContextualIdentityService = new _ContextualIdentityService(path);
--- a/toolkit/components/contextualidentity/tests/unit/test_corruptedFile.js
+++ b/toolkit/components/contextualidentity/tests/unit/test_corruptedFile.js
@@ -14,53 +14,102 @@ const COOKIE = {
   host: BASE_URL,
   path: "/",
   name: "test",
   value: "yes",
   isSecure: false,
   isHttpOnly: false,
   isSession: true,
   expiry: 2145934800,
-  originAttributes: { userContextId: 1 },
 };
 
-function createCookie() {
+function createCookie(userContextId) {
   Services.cookies.add(COOKIE.host,
                        COOKIE.path,
                        COOKIE.name,
                        COOKIE.value,
                        COOKIE.isSecure,
                        COOKIE.isHttpOnly,
                        COOKIE.isSession,
                        COOKIE.expiry,
-                       COOKIE.originAttributes);
+                       {userContextId});
 }
 
-function hasCookie() {
+function hasCookie(userContextId) {
   let found = false;
-  let enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE.originAttributes);
+  let enumerator = Services.cookies.getCookiesFromHost(BASE_URL, {userContextId});
   while (enumerator.hasMoreElements()) {
     let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
-    if (cookie.originAttributes.userContextId == COOKIE.originAttributes.userContextId) {
+    if (cookie.originAttributes.userContextId == userContextId) {
       found = true;
       break;
     }
   }
   return found;
 }
 
 // Correpted file should delete all.
 add_task(async function corruptedFile() {
-  createCookie();
-  ok(hasCookie(), "We have the new cookie!");
+  const thumbnailPrivateId = ContextualIdentityService._defaultIdentities.filter(
+    identity => identity.name === "userContextIdInternal.thumbnail").pop().userContextId;
+
+  const webextStoragePrivateId = ContextualIdentityService._defaultIdentities.filter(
+    identity => identity.name === "userContextIdInternal.webextStorageLocal").pop().userContextId;
+
+  // Create a cookie in the default Firefox identity (userContextId 0).
+  createCookie(0);
+
+  // Create a cookie in the userContextId 1.
+  createCookie(1);
+
+  // Create a cookie in the thumbnail private userContextId.
+  createCookie(thumbnailPrivateId);
+
+  // Create a cookie in the extension storage private userContextId.
+  createCookie(webextStoragePrivateId);
+
+  ok(hasCookie(0), "We have the new cookie the default firefox identity!");
+  ok(hasCookie(1), "We have the new cookie in a public identity!");
+  ok(hasCookie(thumbnailPrivateId), "We have the new cookie in the thumbnail private identity!");
+  ok(hasCookie(webextStoragePrivateId), "We have the new cookie in the extension storage private identity!");
 
   // Let's create a corrupted file.
   await OS.File.writeAtomic(TEST_STORE_FILE_PATH, "{ vers",
                             { tmpPath: TEST_STORE_FILE_PATH + ".tmp" });
 
   let cis = ContextualIdentityService.createNewInstanceForTesting(TEST_STORE_FILE_PATH);
   ok(!!cis, "We have our instance of ContextualIdentityService");
 
-  equal(cis.getPublicIdentities().length, 4, "We should have the default identities");
+  equal(cis.getPublicIdentities().length, 4, "We should have the default public identities");
+
+  // Verify that when the containers.json file is being rebuilt, the computed lastUserContextId
+  // is the expected one.
+  equal(cis._lastUserContextId, thumbnailPrivateId,
+         "Expect cis._lastUserContextId to be equal to the thumbnails userContextId");
+
+  const privThumbnailIdentity = cis.getPrivateIdentity("userContextIdInternal.thumbnail");
+  equal(privThumbnailIdentity && privThumbnailIdentity.userContextId, thumbnailPrivateId,
+        "We should have the default thumbnail private identity");
+
+  const privWebextStorageIdentity = cis.getPrivateIdentity("userContextIdInternal.webextStorageLocal");
+  equal(privWebextStorageIdentity && privWebextStorageIdentity.userContextId,
+        webextStoragePrivateId,
+        "We should have the default extensions storage.local private identity");
 
   // Cookie is gone!
-  ok(!hasCookie(), "We should not have the new cookie!");
+  ok(!hasCookie(1), "We should not have the new cookie in the userContextId 1!");
+
+  // The data stored in the Firefox default userContextId (0), should have not be cleared.
+  ok(hasCookie(0), "We should not have the new cookie in the default Firefox identity!");
+
+  // The data stored in the non-public userContextId (e.g. thumbnails private identity)
+  // should have not be cleared.
+  ok(hasCookie(thumbnailPrivateId),
+     "We should have the new cookie in the thumbnail private userContextId!");
+  ok(hasCookie(webextStoragePrivateId),
+     "We should have the new cookie in the extension storage private userContextId!");
+
+  // Verify the version of the newly created containers.json file.
+  cis.save();
+  const stateFileText = await OS.File.read(TEST_STORE_FILE_PATH, {encoding: "utf-8"});
+  equal(JSON.parse(stateFileText).version, cis.LAST_CONTAINERS_JSON_VERSION,
+        "Expect the new containers.json file to have the expected version");
 });
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contextualidentity/tests/unit/test_migratedFile.js
@@ -0,0 +1,93 @@
+"use strict";
+
+const profileDir = do_get_profile();
+
+ChromeUtils.import(&qu