Merge autoland to mozilla-central. a=merge
authorBogdan Tara <btara@mozilla.com>
Wed, 19 Dec 2018 06:46:17 +0200
changeset 451262 be5e54edad6088b4179c8c1b617664db62150388
parent 451167 e3cb278bc8a4dc19b3f3029ed40cb675c349c2ed (current diff)
parent 451261 8d24aeda8861e73c1491f5c45f4e57c2a77422d5 (diff)
child 451263 204ab379fb829827a82efd9ef72bb18acdcf42f6
push id110633
push userbtara@mozilla.com
push dateWed, 19 Dec 2018 05:00:39 +0000
treeherdermozilla-inbound@cde631af8c88 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
testing/web-platform/meta/css/css-grid/grid-model/grid-container-sizing-constraints-001.html.ini
--- a/.eslintignore
+++ b/.eslintignore
@@ -186,17 +186,16 @@ dom/encoding/**
 dom/events/**
 dom/fetch/**
 dom/file/**
 dom/flex/**
 dom/grid/**
 dom/html/**
 dom/jsurl/**
 dom/localstorage/**
-dom/manifest/**
 dom/media/test/**
 dom/media/tests/**
 dom/media/webaudio/**
 dom/media/webspeech/**
 dom/messagechannel/**
 dom/midi/**
 dom/network/**
 dom/notification/Notification*.*
--- a/.taskcluster.yml
+++ b/.taskcluster.yml
@@ -141,16 +141,19 @@ tasks:
             $merge:
               - GECKO_BASE_REPOSITORY: 'https://hg.mozilla.org/mozilla-unified'
                 GECKO_HEAD_REPOSITORY: '${repoUrl}'
                 GECKO_HEAD_REF: '${push.revision}'
                 GECKO_HEAD_REV: '${push.revision}'
                 GECKO_COMMIT_MSG: {$if: 'tasks_for != "action"', then: '${push.comment}'}
                 HG_STORE_PATH: /builds/worker/checkouts/hg-store
                 TASKCLUSTER_CACHES: /builds/worker/checkouts
+                # someday, these will be provided by the worker - Bug 1492664
+                TASKCLUSTER_ROOT_URL: https://taskcluster.net
+                TASKCLUSTER_PROXY_URL: http://taskcluster
               - $if: 'tasks_for == "action"'
                 then:
                   ACTION_TASK_GROUP_ID: '${action.taskGroupId}'     # taskGroupId of the target task
                   ACTION_TASK_ID: {$json: {$eval: 'taskId'}} # taskId of the target task (JSON-encoded)
                   ACTION_INPUT: {$json: {$eval: 'input'}}
                   ACTION_CALLBACK: '${action.cb_name}'
                   ACTION_PARAMETERS: {$json: {$eval: 'parameters'}}
 
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -8,11 +8,13 @@
         "dzannotti.vscode-babel-coloring",
         // ESLint support.
         "dbaeumer.vscode-eslint",
         // C/C++ language support.
         "ms-vscode.cpptools",
         // Rust language support.
         "rust-lang.rust",
         // CSS support for HTML documents.
-        "ecmel.vscode-html-css"
+        "ecmel.vscode-html-css",
+        // IDL language support
+        "mythmon.idl"
     ]
 }
--- a/browser/actors/ContextMenuChild.jsm
+++ b/browser/actors/ContextMenuChild.jsm
@@ -120,51 +120,28 @@ const messageListeners = {
     let formBaseURI = Services.io.newURI(node.form.baseURI, charset);
     let formURI = Services.io.newURI(node.form.getAttribute("action"),
                                      charset, formBaseURI);
     let spec = formURI.spec;
     let isURLEncoded =  (node.form.method.toUpperCase() == "POST" &&
                          (node.form.enctype == "application/x-www-form-urlencoded" ||
                           node.form.enctype == ""));
     let title = node.ownerDocument.title;
-    let formData = [];
 
-    function escapeNameValuePair(aName, aValue, aIsFormUrlEncoded) {
-      if (aIsFormUrlEncoded) {
+    function escapeNameValuePair([aName, aValue]) {
+      if (isURLEncoded) {
         return escape(aName + "=" + aValue);
       }
 
       return escape(aName) + "=" + escape(aValue);
     }
-
-    for (let el of node.form.elements) {
-      if (!el.type) // happens with fieldsets
-        continue;
-
-      if (el == node) {
-        formData.push((isURLEncoded) ? escapeNameValuePair(el.name, "%s", true) :
-                                       // Don't escape "%s", just append
-                                       escapeNameValuePair(el.name, "", false) + "%s");
-        continue;
-      }
-
-      let type = el.type.toLowerCase();
-
-      if (((el instanceof this.content.HTMLInputElement && el.mozIsTextField(true)) ||
-          type == "hidden" || type == "textarea") ||
-          ((type == "checkbox" || type == "radio") && el.checked)) {
-        formData.push(escapeNameValuePair(el.name, el.value, isURLEncoded));
-      } else if (el instanceof this.content.HTMLSelectElement && el.selectedIndex >= 0) {
-        for (let j = 0; j < el.options.length; j++) {
-          if (el.options[j].selected)
-            formData.push(escapeNameValuePair(el.name, el.options[j].value,
-                                              isURLEncoded));
-        }
-      }
-    }
+    let formData = new this.content.FormData(node.form);
+    formData.delete(node.name);
+    formData = Array.from(formData).map(escapeNameValuePair);
+    formData.push(escape(node.name) + (isURLEncoded ? escape("=%s") : "=%s"));
 
     let postData;
 
     if (isURLEncoded) {
       postData = formData.join("&");
     } else {
       let separator = spec.includes("?") ? "&" : "?";
       spec += separator + formData.join("&");
--- a/browser/base/content/browser-contentblocking.js
+++ b/browser/base/content/browser-contentblocking.js
@@ -424,18 +424,18 @@ var ThirdPartyCookies = {
         if (this.isDetected(state)) {
           hasCookie = true;
         }
         if (TrackingProtection.isAllowing(state)) {
           isTracker = true;
         }
         // blocked tells us whether the resource was actually blocked
         // (which it may not be in case of an exception).
-        if (this.isBlocking(state) && blocked) {
-          info.isAllowed = false;
+        if (this.isBlocking(state)) {
+          info.isAllowed = !blocked;
         }
       }
 
       if (!hasCookie) {
         continue;
       }
 
       let isFirstParty = false;
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -923,17 +923,17 @@ html|*.pointerlockfswarning {
   z-index: 2147483647 !important;
   visibility: visible;
   transition: transform 300ms ease-in;
   /* To center the warning box horizontally,
      we use left: 50% with translateX(-50%). */
   top: 0; left: 50%;
   transform: translate(-50%, -100%);
   box-sizing: border-box;
-  width: -moz-max-content;
+  width: max-content;
   max-width: 95%;
   pointer-events: none;
 }
 html|*.pointerlockfswarning:not([hidden]) {
   display: flex;
   will-change: transform;
 }
 html|*.pointerlockfswarning[onscreen] {
--- a/browser/base/content/test/general/browser_alltabslistener.js
+++ b/browser/base/content/test/general/browser_alltabslistener.js
@@ -146,56 +146,52 @@ function runTest(browser, url, next) {
 
 function startTest1() {
   info("\nTest 1");
   gBrowser.addProgressListener(gFrontProgressListener);
   gBrowser.addTabsProgressListener(gAllProgressListener);
 
   gAllNotifications = [
     "onStateChange",
-    "onSecurityChange",
     "onLocationChange",
     "onSecurityChange",
     "onStateChange",
   ];
   gFrontNotifications = gAllNotifications;
   runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest2);
 }
 
 function startTest2() {
   info("\nTest 2");
   gAllNotifications = [
     "onStateChange",
-    "onSecurityChange",
     "onLocationChange",
     "onSecurityChange",
     "onStateChange",
   ];
   gFrontNotifications = gAllNotifications;
   runTest(gForegroundBrowser, "https://example.com" + gTestPage, startTest3);
 }
 
 function startTest3() {
   info("\nTest 3");
   gAllNotifications = [
     "onStateChange",
-    "onSecurityChange",
     "onLocationChange",
     "onSecurityChange",
     "onStateChange",
   ];
   gFrontNotifications = [];
   runTest(gBackgroundBrowser, "http://example.org" + gTestPage, startTest4);
 }
 
 function startTest4() {
   info("\nTest 4");
   gAllNotifications = [
     "onStateChange",
-    "onSecurityChange",
     "onLocationChange",
     "onSecurityChange",
     "onStateChange",
   ];
   gFrontNotifications = [];
   runTest(gBackgroundBrowser, "https://example.com" + gTestPage, startTest5);
 }
 
@@ -206,30 +202,28 @@ function startTest5() {
   [gForegroundTab, gBackgroundTab] = [gBackgroundTab, gForegroundTab];
   // Avoid the onLocationChange this will fire
   gBrowser.removeProgressListener(gFrontProgressListener);
   gBrowser.selectedTab = gForegroundTab;
   gBrowser.addProgressListener(gFrontProgressListener);
 
   gAllNotifications = [
     "onStateChange",
-    "onSecurityChange",
     "onLocationChange",
     "onSecurityChange",
     "onStateChange",
   ];
   gFrontNotifications = gAllNotifications;
   runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest6);
 }
 
 function startTest6() {
   info("\nTest 6");
   gAllNotifications = [
     "onStateChange",
-    "onSecurityChange",
     "onLocationChange",
     "onSecurityChange",
     "onStateChange",
   ];
   gFrontNotifications = [];
   runTest(gBackgroundBrowser, "http://example.org" + gTestPage, finishTest);
 }
 
--- a/browser/base/content/test/siteIdentity/browser_ignore_same_page_navigation.js
+++ b/browser/base/content/test/siteIdentity/browser_ignore_same_page_navigation.js
@@ -26,16 +26,16 @@ add_task(async function() {
     };
     browser.addProgressListener(progressListener, Ci.nsIWebProgress.NOTIFY_ALL);
 
     let uri = getRootDirectory(gTestPath).replace("chrome://mochitests/content",
                                                   "https://example.com") + "dummy_page.html";
     BrowserTestUtils.loadURI(browser, uri);
     await BrowserTestUtils.browserLoaded(browser, false, uri);
     is(onLocationChangeCount, 1, "should have 1 onLocationChange event");
-    is(onSecurityChangeCount, 2, "should have 2 onSecurityChange event");
+    is(onSecurityChangeCount, 1, "should have 1 onSecurityChange event");
     await ContentTask.spawn(browser, null, async () => {
       content.history.pushState({}, "", "https://example.com");
     });
     is(onLocationChangeCount, 2, "should have 2 onLocationChange events");
-    is(onSecurityChangeCount, 2, "should still have only 2 onSecurityChange event");
+    is(onSecurityChangeCount, 1, "should still have only 1 onSecurityChange event");
   });
 });
--- a/browser/base/content/test/trackingUI/browser_trackingUI_animation_2.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_animation_2.js
@@ -64,26 +64,26 @@ async function testTrackingProtectionAni
   securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
   tabbrowser.selectedTab = trackingCookiesTab;
   await securityChanged;
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
   info("Reload tracking cookies tab");
-  securityChanged = waitForSecurityChange(3, tabbrowser.ownerGlobal);
+  securityChanged = waitForSecurityChange(2, tabbrowser.ownerGlobal);
   tabbrowser.reload();
   await securityChanged;
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(ContentBlocking.iconBox.hasAttribute("animate"), "iconBox animating");
   await BrowserTestUtils.waitForEvent(ContentBlocking.animatedIcon, "animationend");
 
   info("Reload tracking tab");
-  securityChanged = waitForSecurityChange(4, tabbrowser.ownerGlobal);
+  securityChanged = waitForSecurityChange(3, tabbrowser.ownerGlobal);
   tabbrowser.selectedTab = trackingTab;
   tabbrowser.reload();
   await securityChanged;
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(ContentBlocking.iconBox.hasAttribute("animate"), "iconBox animating");
   await BrowserTestUtils.waitForEvent(ContentBlocking.animatedIcon, "animationend");
 
--- a/browser/base/content/test/trackingUI/browser_trackingUI_cookies_subview.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_cookies_subview.js
@@ -171,8 +171,74 @@ add_task(async function testCookiesSubVi
     ok(BrowserTestUtils.is_visible(button), "Permission remove button is visible");
     button.click();
     is(Services.perms.testExactPermissionFromPrincipal(principal, "cookie"), Services.perms.UNKNOWN_ACTION, "Button click should remove cookie pref.");
     ok(!listItem.classList.contains("allowed"), "Has removed the allowed class");
   });
 
   Services.prefs.clearUserPref(TPC_PREF);
 });
+
+add_task(async function testCookiesSubViewAllowedHeuristic() {
+  Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER);
+  let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("http://not-tracking.example.com/");
+
+  // Pretend that the tracker has already been interacted with
+  let trackerPrincipal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("http://trackertest.org/");
+  Services.perms.addFromPrincipal(trackerPrincipal, "storageAccessAPI", Services.perms.ALLOW_ACTION);
+
+  await BrowserTestUtils.withNewTab(COOKIE_PAGE, async function(browser) {
+    let popup;
+    let windowCreated = TestUtils.topicObserved("chrome-document-global-created", (subject, data) => {
+      popup = subject;
+      return true;
+    });
+    let permChanged = TestUtils.topicObserved("perm-changed",
+      (subject, data) => {
+        return subject &&
+               subject.QueryInterface(Ci.nsIPermission)
+                      .type == "3rdPartyStorage^http://trackertest.org" &&
+               subject.principal.origin == principal.origin &&
+               data == "added";
+      });
+
+    await ContentTask.spawn(browser, {}, function() {
+      content.postMessage("window-open", "*");
+    });
+    await Promise.all([windowCreated, permChanged]);
+
+    await new Promise(resolve => waitForFocus(resolve, popup));
+    await new Promise(resolve => waitForFocus(resolve, window));
+
+    await openIdentityPopup();
+
+    let categoryItem =
+      document.getElementById("identity-popup-content-blocking-category-cookies");
+    ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible");
+    let cookiesView = document.getElementById("identity-popup-cookiesView");
+    let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
+    categoryItem.click();
+    await viewShown;
+
+    ok(true, "Cookies view was shown");
+
+    let listItems = cookiesView.querySelectorAll(".identity-popup-content-blocking-list-item");
+    is(listItems.length, 1, "We have 1 cookie in the list");
+
+    let listItem = listItems[0];
+    let label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
+    is(label.value, "http://trackertest.org", "Has an item for trackertest.org");
+    ok(BrowserTestUtils.is_visible(listItem), "List item is visible");
+    ok(listItem.classList.contains("allowed"), "Indicates whether the cookie was blocked or allowed");
+
+    let button = listItem.querySelector(".identity-popup-permission-remove-button");
+    ok(BrowserTestUtils.is_visible(button), "Permission remove button is visible");
+    button.click();
+    is(Services.perms.testExactPermissionFromPrincipal(principal, "3rdPartyStorage^http://trackertest.org"), Services.perms.UNKNOWN_ACTION, "Button click should remove the storage pref.");
+    ok(!listItem.classList.contains("allowed"), "Has removed the allowed class");
+
+    await ContentTask.spawn(browser, {}, function() {
+      content.postMessage("window-close", "*");
+    });
+  });
+
+  Services.prefs.clearUserPref(TPC_PREF);
+});
--- a/browser/base/content/test/trackingUI/browser_trackingUI_state.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_state.js
@@ -130,18 +130,23 @@ function testTrackingPage(window) {
        "unblockButton is" + (blockedByTP ? "" : " not") + " visible");
   }
 
   ok(hidden("#identity-popup-content-blocking-not-detected"), "blocking not detected label is hidden");
   ok(!hidden("#identity-popup-content-blocking-detected"), "blocking detected label is visible");
 
   ok(!hidden("#identity-popup-content-blocking-category-tracking-protection"),
     "Showing trackers category");
-  ok(!hidden("#identity-popup-content-blocking-category-cookies"),
-    "Showing cookie restrictions category");
+  if (gTrackingPageURL == COOKIE_PAGE) {
+    ok(!hidden("#identity-popup-content-blocking-category-cookies"),
+      "Showing cookie restrictions category");
+  } else {
+    ok(hidden("#identity-popup-content-blocking-category-cookies"),
+      "Not showing cookie restrictions category");
+  }
 }
 
 function testTrackingPageUnblocked(blockedByTP, window) {
   info("Tracking content must be white-listed and not blocked");
   ok(ContentBlocking.content.hasAttribute("detected"), "trackers are detected");
   ok(ContentBlocking.content.hasAttribute("hasException"), "content shows exception");
 
   ok(!ContentBlocking.iconBox.hasAttribute("active"), "shield is not active");
@@ -153,18 +158,23 @@ function testTrackingPageUnblocked(block
   ok(!hidden("#tracking-action-block"), "blockButton is visible");
   ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
 
   ok(hidden("#identity-popup-content-blocking-not-detected"), "blocking not detected label is hidden");
   ok(!hidden("#identity-popup-content-blocking-detected"), "blocking detected label is visible");
 
   ok(!hidden("#identity-popup-content-blocking-category-tracking-protection"),
     "Showing trackers category");
-  ok(!hidden("#identity-popup-content-blocking-category-cookies"),
-    "Showing cookie restrictions category");
+  if (gTrackingPageURL == COOKIE_PAGE) {
+    ok(!hidden("#identity-popup-content-blocking-category-cookies"),
+      "Showing cookie restrictions category");
+  } else {
+    ok(hidden("#identity-popup-content-blocking-category-cookies"),
+      "Not showing cookie restrictions category");
+  }
 }
 
 async function testContentBlocking(tab) {
   info("Testing with Tracking Protection ENABLED.");
 
   info("Load a test page not containing tracking elements");
   await promiseTabLoadEvent(tab, BENIGN_PAGE);
   testBenignPage();
--- a/browser/base/content/test/trackingUI/trackingAPI.js
+++ b/browser/base/content/test/trackingUI/trackingAPI.js
@@ -28,10 +28,17 @@ onmessage = event => {
     }
     break;
   case "third-party-cookie": {
       let ifr = document.createElement("iframe");
       ifr.src = "https://test1.example.org/browser/browser/base/content/test/trackingUI/cookieServer.sjs";
       document.body.appendChild(ifr);
     }
     break;
+  case "window-open":
+      window.win = window.open("http://trackertest.org/browser/browser/base/content/test/trackingUI/cookieServer.sjs", "_blank", "width=100,height=100");
+    break;
+  case "window-close":
+      window.win.close();
+      window.win = null;
+    break;
   }
 };
--- a/browser/installer/windows/nsis/defines.nsi.in
+++ b/browser/installer/windows/nsis/defines.nsi.in
@@ -86,18 +86,23 @@
 !endif
 
 !define BaseURLStubPing "http://download-stats.mozilla.org/stub"
 
 # ARCH is used when it is necessary to differentiate the x64 registry keys from
 # the x86 registry keys (e.g. the uninstall registry key).
 #ifdef HAVE_64BIT_BUILD
 !define HAVE_64BIT_BUILD
+#ifdef _ARM64_
+!define ARCH "AArch64"
+!define MinSupportedVer "Microsoft Windows 10 for ARM"
+#else
 !define ARCH "x64"
 !define MinSupportedVer "Microsoft Windows 7 x64"
+#endif
 #else
 !define MinSupportedVer "Microsoft Windows 7"
 !define ARCH "x86"
 #endif
 
 !define MinSupportedCPU "SSE2"
 
 #ifdef MOZ_MAINTENANCE_SERVICE
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -1546,22 +1546,28 @@ Function .onInit
   ; SSE2 CPU support
   ${If} "$R7" == "0"
     MessageBox MB_OKCANCEL|MB_ICONSTOP "$(WARN_MIN_SUPPORTED_CPU_MSG)" IDCANCEL +2
     ExecShell "open" "${URLSystemRequirements}"
     Quit
   ${EndIf}
 
 !ifdef HAVE_64BIT_BUILD
-  ${Unless} ${RunningX64}
-  ${AndUnless} ${IsNativeARM64}
+  ${If} "${ARCH}" == "AArch64"
+    ${IfNot} ${IsNativeARM64}
+    ${OrIfNot} ${AtLeastWin10}
+      MessageBox MB_OKCANCEL|MB_ICONSTOP "$(WARN_MIN_SUPPORTED_OSVER_MSG)" IDCANCEL +2
+      ExecShell "open" "${URLSystemRequirements}"
+      Quit
+    ${EndIf}
+  ${ElseIfNot} ${RunningX64}
     MessageBox MB_OKCANCEL|MB_ICONSTOP "$(WARN_MIN_SUPPORTED_OSVER_MSG)" IDCANCEL +2
     ExecShell "open" "${URLSystemRequirements}"
     Quit
-  ${EndUnless}
+  ${EndIf}
   SetRegView 64
 !endif
 
   SetShellVarContext all
   ${GetFirstInstallPath} "Software\Mozilla\${BrandFullNameInternal}" $0
   ${If} "$0" == "false"
     SetShellVarContext current
     ${GetFirstInstallPath} "Software\Mozilla\${BrandFullNameInternal}" $0
--- a/browser/themes/shared/syncedtabs/sidebar.inc.css
+++ b/browser/themes/shared/syncedtabs/sidebar.inc.css
@@ -181,17 +181,17 @@ body {
 
 .textbox-input-box {
   display: flex;
   flex-direction: row;
 }
 
 .tabsFilter {
   flex: 1;
-  /* min-width of anything to override the implicit "-moz-min-content" value.
+  /* min-width of anything to override the implicit "min-content" value.
      0px is safe as the sidebar itself has a constrained size meaning we will
      never actually hit this minimum
   */
   min-width: 0px;
 }
 
 .sync-state > p {
   padding-inline-end: 10px;
--- a/build.gradle
+++ b/build.gradle
@@ -85,54 +85,160 @@ buildscript {
     if (gradle.mozconfig.substs.MOZ_ANDROID_GOOGLE_PLAY_SERVICES) {
         ext.google_play_services_version = '15.0.1'
     }
 
     dependencies {
         classpath 'org.mozilla.apilint:apilint:0.1.5'
         classpath 'com.android.tools.build:gradle:3.1.4'
         classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.2'
+        classpath 'org.apache.commons:commons-exec:1.3'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
     }
 }
 
-if ('multi' == System.env.AB_CD) {
+// A stream that processes bytes line by line, prepending a tag before sending
+// each line to Gradle's logging.
+class TaggedLogOutputStream extends org.apache.commons.exec.LogOutputStream {
+    String tag
+    Logger logger
+
+    TaggedLogOutputStream(tag, logger) {
+        this.tag = tag
+        this.logger = logger
+    }
+
+    void processLine(String line, int level) {
+        logger.lifecycle("${this.tag} ${line}")
+    }
+}
+
+ext.geckoBinariesOnlyIf = { task ->
+    // Never for official builds.
+    if (mozconfig.substs.MOZILLA_OFFICIAL) {
+        rootProject.logger.lifecycle("Skipping task ${task.path} because: MOZILLA_OFFICIAL")
+        return false
+    }
+
     // Multi-l10n builds set `AB_CD=multi`, which isn't a valid locale.  This
     // causes the
     //
-    // |mach build| > |mach gradle| > |make gradle-targets| > AndroidManifest.xml > strings.xml > multi/brand.dtd
+    // |mach build| > |mach gradle| >
+    // |mach build mobile/android/base/generated_android_code_and_resources| >
+    // AndroidManifest.xml > strings.xml > multi/brand.dtd
     //
     // dependency chain to fail, since multi isn't a real locale.  To avoid
     // this, if Gradle is invoked with AB_CD=multi, we don't invoke Make at all.
-    task generateCodeAndResources()
-} else if (System.env.IS_LANGUAGE_REPACK == '1') {
+    if ('multi' == System.env.AB_CD) {
+        rootProject.logger.lifecycle("Skipping task ${task.path} because: AB_CD=multi")
+        return false
+    }
+
     // Single-locale l10n repacks set `IS_LANGUAGE_REPACK=1` and handle resource
     // and code generation themselves.
-    task generateCodeAndResources()
-} else {
-    task generateCodeAndResources(type:Exec) {
+    if ('1' == System.env.IS_LANGUAGE_REPACK) {
+        rootProject.logger.lifecycle("Skipping task ${task.path} because: IS_LANGUAGE_REPACK")
+        return false
+    }
+
+    rootProject.logger.lifecycle("Executing task ${task.path}")
+    return true
+}
+
+task machBuildGeneratedAndroidCodeAndResources(type: Exec) {
+    onlyIf rootProject.ext.geckoBinariesOnlyIf
+
+    workingDir "${topsrcdir}"
+
+    commandLine mozconfig.substs.PYTHON
+    args "${topsrcdir}/mach"
+    args 'build'
+    args 'mobile/android/base/generated_android_code_and_resources'
+
+    // Add `-v` if we're running under `--info` (or `--debug`).
+    if (project.logger.isEnabled(LogLevel.INFO)) {
+        args '-v'
+    }
+
+    // `path` is like `:machBuildGeneratedAndroidCodeAndResources`.
+    standardOutput = new TaggedLogOutputStream("${path}>", logger)
+    errorOutput = standardOutput
+}
+
+// Why |mach build mobile/android/base/...| and |mach build faster|?  |mach
+// build faster| generates dependentlibs.list, which in turn depends on compiled
+// code.  That causes a circular dependency between Java compilation/JNI wrapper
+// generation/native code compilation.  So we have the special target for
+// Android-specific generated code, and the |mach build faster| target for all
+// the stuff that goes into the omnijar.
+task machBuildFaster(type: Exec) {
+    onlyIf rootProject.ext.geckoBinariesOnlyIf
+
+    workingDir "${topsrcdir}"
+
+    commandLine mozconfig.substs.PYTHON
+    args "${topsrcdir}/mach"
+    args 'build'
+    args 'faster'
+
+    // Add `-v` if we're running under `--info` (or `--debug`).
+    if (project.logger.isEnabled(LogLevel.INFO)) {
+        args '-v'
+    }
+
+    // `path` is like `:machBuildFaster`.
+    standardOutput = new TaggedLogOutputStream("${path}>", logger)
+    errorOutput = standardOutput
+}
+
+def createMachStagePackageTask(name) {
+    return task(name, type: Exec) {
+        onlyIf rootProject.ext.geckoBinariesOnlyIf
+
+        dependsOn rootProject.machBuildFaster
+
+        // We'd prefer to take these from the :omnijar project directly, but
+        // it's awkward to reach across projects at evaluation time, so we
+        // duplicate the list here.
+        inputs.dir "${topsrcdir}/mobile/android/chrome"
+        inputs.dir "${topsrcdir}/mobile/android/components"
+        inputs.dir "${topsrcdir}/mobile/android/locales"
+        inputs.dir "${topsrcdir}/mobile/android/modules"
+        inputs.dir "${topsrcdir}/mobile/android/themes"
+        inputs.dir "${topsrcdir}/toolkit"
+
         workingDir "${topobjdir}"
 
+        // We'd prefer this to be a `mach` invocation, but `mach build
+        // mobile/android/installer/stage-package` doesn't work as expected.
         commandLine mozconfig.substs.GMAKE
         args '-C'
-        args "${topobjdir}/mobile/android/base"
-        args 'gradle-targets'
+        args "${topobjdir}/mobile/android/installer"
+        args 'stage-package'
 
-        // Only show the output if something went wrong.
-        ignoreExitValue = true
-        standardOutput = new ByteArrayOutputStream()
+        outputs.file "${topobjdir}/dist/fennec/assets/${mozconfig.substs.ANDROID_CPU_ARCH}/libxul.so"
+        outputs.file "${topobjdir}/dist/fennec/lib/${mozconfig.substs.ANDROID_CPU_ARCH}/libmozglue.so"
+
+        // `path` is like `:machStagePackage`.
+        standardOutput = new TaggedLogOutputStream("${path}>", logger)
         errorOutput = standardOutput
-        doLast {
-            if (execResult.exitValue != 0) {
-                throw new GradleException("Process '${commandLine}' finished with non-zero exit value ${execResult.exitValue}:\n\n${standardOutput.toString()}")
-            }
-        }
     }
 }
 
+createMachStagePackageTask("machStagePackageForFennec").with {
+    outputs.file "${topobjdir}/dist/fennec/assets/omni.ja"
+}
+
+createMachStagePackageTask("machStagePackageForGeckoview").with {
+    args 'MOZ_GECKOVIEW_JAR=1'
+    outputs.file "${topobjdir}/dist/geckoview/assets/omni.ja"
+    // Avoid races between stage-package invocations.
+    mustRunAfter tasks["machStagePackageForFennec"]
+}
+
 afterEvaluate {
     subprojects { project ->
         if (project.name != 'thirdparty') {
             tasks.withType(JavaCompile) {
                 // Add compiler args for all code except third-party code.
                 options.compilerArgs += [
                     // Turn on all warnings, except...
                     "-Xlint:all",
@@ -158,20 +264,20 @@ afterEvaluate {
                 }
             }
         }
 
         if (!hasProperty('android')) {
             return
         }
         android.applicationVariants.all {
-            preBuild.dependsOn rootProject.generateCodeAndResources
+            preBuild.dependsOn rootProject.machBuildGeneratedAndroidCodeAndResources
         }
         android.libraryVariants.all {
-            preBuild.dependsOn rootProject.generateCodeAndResources
+            preBuild.dependsOn rootProject.machBuildGeneratedAndroidCodeAndResources
         }
     }
 }
 
 apply plugin: 'idea'
 
 idea {
     project {
--- a/build/virtualenv_packages.txt
+++ b/build/virtualenv_packages.txt
@@ -40,16 +40,17 @@ mozilla.pth:third_party/python/scandir
 mozilla.pth:third_party/python/slugid
 mozilla.pth:third_party/python/taskcluster
 mozilla.pth:third_party/python/taskcluster-urls
 mozilla.pth:third_party/python/py
 mozilla.pth:third_party/python/pytest/src
 mozilla.pth:third_party/python/pytoml
 mozilla.pth:third_party/python/redo
 mozilla.pth:third_party/python/six
+mozilla.pth:third_party/python/taskcluster-urls
 mozilla.pth:third_party/python/voluptuous
 mozilla.pth:third_party/python/json-e
 mozilla.pth:build
 objdir:build
 mozilla.pth:build/pymake
 mozilla.pth:config
 mozilla.pth:config/mozunit
 mozilla.pth:dom/bindings
--- a/config/faster/rules.mk
+++ b/config/faster/rules.mk
@@ -61,16 +61,23 @@ endif
 # Extra define to trigger some workarounds. We should strive to limit the
 # use of those. As of writing the only one is in browser/locales/jar.mn.
 ACDEFINES += -DBUILD_FASTER
 
 # Files under the faster/ sub-directory, however, are not meant to use the
 # fallback
 $(TOPOBJDIR)/faster/%: ;
 
+ifeq ($(MOZ_BUILD_APP),mobile/android)
+# The generic rule doesn't handle relative directories, which are used
+# extensively in mobile/android/base.
+$(TOPOBJDIR)/mobile/android/base/% : $(TOPOBJDIR)/buildid.h FORCE
+	$(MAKE) -C $(TOPOBJDIR)/mobile/android/base $*
+endif
+
 # Generic rule to fall back to the recursive make backend.
 # This needs to stay after other $(TOPOBJDIR)/* rules because GNU Make
 # <3.82 apply pattern rules in definition order, not stem length like
 # modern GNU Make.
 $(TOPOBJDIR)/%: FORCE
 	$(MAKE) -C $(dir $@) $(notdir $@)
 
 # Install files using install manifests
--- a/devtools/client/aboutdebugging-new/tmp-locale/en-US/aboutdebugging.notftl
+++ b/devtools/client/aboutdebugging-new/tmp-locale/en-US/aboutdebugging.notftl
@@ -39,17 +39,17 @@ about-debugging-sidebar-item-connected-l
 # Temporary text displayed in a sidebar button to refresh USB devices. Temporary
 # UI, do not localize.
 about-debugging-refresh-usb-devices-button = Refresh devices
 
 # Title of the Connect page.
 about-debugging-connect-title = Connect a Device
 
 # WiFi section of the Connect page
-about-debugging-connect-wifi
+about-debugging-connect-wifi =
   .title = Via WiFi
 
 # Temporary text displayed when wifi support is turned off via preferences.
 # { $pref } is the name of the preference that enables wifi
 # Do not localize
 about-debugging-connect-wifi-disabled = WiFi debugging currently under development. You can enable it with the preference "{ $pref }".
 
 # WiFi section step by step guide
@@ -60,17 +60,17 @@ about-debugging-connect-wifi-step-open-f
 
 # WiFi section step by step guide
 about-debugging-connect-wifi-step-open-options = Go to Options -> Settings -> Advanced
 
 # WiFi section step by step guide
 about-debugging-connect-wifi-step-enable-debug = Enable Remote Debugging via WiFi in the Developer Tools section
 
 # USB section of the Connect page
-about-debugging-connect-usb
+about-debugging-connect-usb =
   .title = Via USB
 
 about-debugging-connect-usb-disabled = Enabling this will download and add the required Android USB debugging components to Firefox.
 about-debugging-connect-usb-enable-button = Enable USB Devices
 about-debugging-connect-usb-disable-button = Disable USB Devices
 about-debugging-connect-usb-updating-button = Updating…
 
 # USB section step by step guide
@@ -78,17 +78,17 @@ about-debugging-connect-usb-step-enable-
 
 # USB section step by step guide
 about-debugging-connect-usb-step-enable-debug = Enable USB Debugging on the Android Developer Menu
 
 # USB section step by step guide
 about-debugging-connect-usb-step-plug-device = Connect the USB Device to your computer
 
 # Network section of the Connect page
-about-debugging-connect-network
+about-debugging-connect-network =
   .title = Via Network Location
 
 # Temporary text displayed when network location support is turned off via preferences.
 # { $pref } is the name of the preference that enables network locations
 # Do not localize
 about-debugging-connect-network-disabled = Network location support currently under development. You can enable it with the preference "{ $pref }".
 
 # Below are the titles for the various categories of debug targets that can be found
@@ -163,17 +163,17 @@ about-debugging-network-locations-empty-
 
 # Text of the label for the text input that allows users to add new network locations in
 # the Connect page. A host is a hostname and a port separated by a colon, as suggested by
 # the input's placeholder "localhost:6080".
 about-debugging-network-locations-host-input-label = Host
 
 # Text of a button displayed next to existing network locations in the Connect page.
 # Clicking on it removes the network location from the list.
-about-debugging-network-locations-remove-button
+about-debugging-network-locations-remove-button = Remove
 
 # This string is displayed as a label of the button that pushes a test payload
 # to a service worker.
 # Notes, this relates to the "Push" API, which is normally not localized so it is
 # probably better to not localize it.
 about-debugging-worker-action-push = Push
 
 # This string is displayed as a label of the button that starts a service worker.
@@ -183,26 +183,26 @@ about-debugging-worker-action-start = St
 # "Fetch" is an event type and should not be localized.
 # The status is used to indicate whether the service worker is currently listening
 # or not to "fetch" events.
 about-debugging-worker-fetch =
   .label = Fetch
   .value =
     { $status ->
       [listening] Listening for fetch events
-      [not-listening] Not listening for fetch events
+     *[not-listening] Not listening for fetch events
     }
 
 # Displayed for service workers in runtime pages, to indicate the status of a worker.
 # For workers for which no registration could be found yet, they are considered as
 # 'registering' (only active registrations are visible from about:debugging).
 about-debugging-worker-status =
   { $status ->
     [running] Running
-    [stopped] Stopped
+   *[stopped] Stopped
     [registering] Registering
   }
 
 # Displayed for service workers in runtime pages, to label the scope of a worker
 about-debugging-worker-scope =
   .label = Scope
 
 # Displayed for runtime info in runtime pages.
--- a/devtools/client/themes/tooltips.css
+++ b/devtools/client/themes/tooltips.css
@@ -420,17 +420,19 @@
 }
 
 .tooltip-container[type="doorhanger"] .menuitem > .command.iconic > .label::before {
   content: " ";
   display: inline-block;
   margin-inline-end: 8px;
   width: 16px;
   height: 16px;
-  vertical-align: top;
+  /* Better optical alignment than with 'vertical-align: middle'.
+     Works well with font sizes between 12px and 16px. */
+  vertical-align: -3px;
   -moz-context-properties: fill;
   fill: currentColor;
   background-image: var(--menuitem-icon-image);
   background-size: contain;
   /*
    * The icons in the sidebar menu have opacity: 0.8 here, but those in the
    * hamburger menu don't. For now we match the hamburger menu styling,
    * especially because the 80% opacity makes the icons look dull in dark mode.
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -2088,16 +2088,18 @@ exports.CSS_PROPERTIES = {
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "auto",
       "content",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "unset"
     ]
   },
   "-webkit-flex-basis": {
     "isInherited": false,
     "subproperties": [
       "flex-basis"
     ],
@@ -2106,16 +2108,18 @@ exports.CSS_PROPERTIES = {
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "auto",
       "content",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "unset"
     ]
   },
   "-webkit-flex-direction": {
     "isInherited": false,
     "subproperties": [
       "flex-direction"
     ],
@@ -3619,16 +3623,18 @@ exports.CSS_PROPERTIES = {
     "values": [
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "auto",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "unset"
     ]
   },
   "border": {
     "isInherited": false,
     "subproperties": [
       "border-top-color",
       "border-top-style",
@@ -5459,16 +5465,18 @@ exports.CSS_PROPERTIES = {
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "auto",
       "content",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "unset"
     ]
   },
   "flex-basis": {
     "isInherited": false,
     "subproperties": [
       "flex-basis"
     ],
@@ -5477,16 +5485,18 @@ exports.CSS_PROPERTIES = {
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "auto",
       "content",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "unset"
     ]
   },
   "flex-direction": {
     "isInherited": false,
     "subproperties": [
       "flex-direction"
     ],
@@ -6342,16 +6352,18 @@ exports.CSS_PROPERTIES = {
     "values": [
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "auto",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "unset"
     ]
   },
   "hyphens": {
     "isInherited": true,
     "subproperties": [
       "hyphens"
     ],
@@ -6422,16 +6434,18 @@ exports.CSS_PROPERTIES = {
     "values": [
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "auto",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "unset"
     ]
   },
   "inset-block-end": {
     "isInherited": false,
     "subproperties": [
       "inset-block-end"
     ],
@@ -7259,16 +7273,18 @@ exports.CSS_PROPERTIES = {
     "supports": [],
     "values": [
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "none",
       "unset"
     ]
   },
   "max-height": {
     "isInherited": false,
     "subproperties": [
       "max-height"
@@ -7276,16 +7292,18 @@ exports.CSS_PROPERTIES = {
     "supports": [],
     "values": [
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "none",
       "unset"
     ]
   },
   "max-inline-size": {
     "isInherited": false,
     "subproperties": [
       "max-inline-size"
@@ -7293,16 +7311,18 @@ exports.CSS_PROPERTIES = {
     "supports": [],
     "values": [
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "none",
       "unset"
     ]
   },
   "max-width": {
     "isInherited": false,
     "subproperties": [
       "max-width"
@@ -7310,16 +7330,18 @@ exports.CSS_PROPERTIES = {
     "supports": [],
     "values": [
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "none",
       "unset"
     ]
   },
   "min-block-size": {
     "isInherited": false,
     "subproperties": [
       "min-block-size"
@@ -7328,16 +7350,18 @@ exports.CSS_PROPERTIES = {
     "values": [
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "auto",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "unset"
     ]
   },
   "min-height": {
     "isInherited": false,
     "subproperties": [
       "min-height"
     ],
@@ -7345,16 +7369,18 @@ exports.CSS_PROPERTIES = {
     "values": [
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "auto",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "unset"
     ]
   },
   "min-inline-size": {
     "isInherited": false,
     "subproperties": [
       "min-inline-size"
     ],
@@ -7362,16 +7388,18 @@ exports.CSS_PROPERTIES = {
     "values": [
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "auto",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "unset"
     ]
   },
   "min-width": {
     "isInherited": false,
     "subproperties": [
       "min-width"
     ],
@@ -7379,16 +7407,18 @@ exports.CSS_PROPERTIES = {
     "values": [
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "auto",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "unset"
     ]
   },
   "mix-blend-mode": {
     "isInherited": false,
     "subproperties": [
       "mix-blend-mode"
     ],
@@ -9193,16 +9223,18 @@ exports.CSS_PROPERTIES = {
     "values": [
       "-moz-available",
       "-moz-fit-content",
       "-moz-max-content",
       "-moz-min-content",
       "auto",
       "inherit",
       "initial",
+      "max-content",
+      "min-content",
       "unset"
     ]
   },
   "will-change": {
     "isInherited": false,
     "subproperties": [
       "will-change"
     ],
--- a/dom/clients/manager/ClientManagerService.cpp
+++ b/dom/clients/manager/ClientManagerService.cpp
@@ -574,16 +574,26 @@ class OpenWindowRunnable final : public 
       mPromise->Reject(NS_ERROR_ABORT, __func__);
       mPromise = nullptr;
       return NS_OK;
     }
 
     ClientOpenWindowOpParent* actor =
         new ClientOpenWindowOpParent(mArgs, mPromise);
 
+    // Normally, we call TransmitPermissionsForPrincipal for the first http
+    // load, but in this case, ClientOpenWindowOpChild will cause the initial
+    // about:blank load in the child to have this principal. That causes us to
+    // assert because the child process doesn't know that it's loading this
+    // principal.
+    nsCOMPtr<nsIPrincipal> principal =
+        PrincipalInfoToPrincipal(mArgs.principalInfo());
+    nsresult rv = targetProcess->TransmitPermissionsForPrincipal(principal);
+    Unused << NS_WARN_IF(NS_FAILED(rv));
+
     // If this fails the actor will be automatically destroyed which will
     // reject the promise.
     Unused << targetProcess->SendPClientOpenWindowOpConstructor(actor, mArgs);
 
     return NS_OK;
   }
 };
 
--- a/dom/encoding/TextEncoder.cpp
+++ b/dom/encoding/TextEncoder.cpp
@@ -1,39 +1,53 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/TextEncoder.h"
-#include "mozilla/Encoding.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsReadableUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 void TextEncoder::Init() {}
 
 void TextEncoder::Encode(JSContext* aCx, JS::Handle<JSObject*> aObj,
                          const nsAString& aString,
                          JS::MutableHandle<JSObject*> aRetval,
                          ErrorResult& aRv) {
-  nsAutoCString utf8;
-  nsresult rv;
-  const Encoding* ignored;
-  Tie(rv, ignored) = UTF_8_ENCODING->Encode(aString, utf8);
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
+  // Given nsTSubstring<char16_t>::kMaxCapacity, it should be
+  // impossible for the length computation to overflow, but
+  // let's use checked math in case someone changes something
+  // in the future.
+  // Uint8Array::Create takes uint32_t as the length.
+  CheckedInt<uint32_t> bufLen(aString.Length());
+  bufLen *= 3;
+  bufLen += 1;  // plus one is part of the contract for ConvertUTF16toUTF8
+  if (!bufLen.isValid()) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
+  auto data = mozilla::MakeUniqueFallible<uint8_t[]>(bufLen.value());
+  if (!data) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+
+  size_t utf8Len = ConvertUTF16toUTF8(
+      aString, MakeSpan(reinterpret_cast<char*>(data.get()), bufLen.value()));
+  MOZ_ASSERT(utf8Len <= bufLen.value());
+
   JSAutoRealm ar(aCx, aObj);
-  JSObject* outView =
-      Uint8Array::Create(aCx, utf8.Length(),
-                         reinterpret_cast<const uint8_t*>(utf8.BeginReading()));
+  JSObject* outView = Uint8Array::Create(aCx, utf8Len, data.get());
   if (!outView) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
   aRetval.set(outView);
 }
 
--- a/dom/manifest/ImageObjectProcessor.jsm
+++ b/dom/manifest/ImageObjectProcessor.jsm
@@ -12,143 +12,137 @@
  * W3C specification. This is used to process things like the
  * icon member and the splash_screen member.
  *
  * Usage:
  *
  *   .process(aManifest, aBaseURL, aMemberName);
  *
  */
-/*exported EXPORTED_SYMBOLS*/
-/*globals Components */
-'use strict';
-const {
-  utils: Cu,
-  interfaces: Ci,
-  classes: Cc
-} = Components;
+/* exported EXPORTED_SYMBOLS*/
+/* globals Components */
+"use strict";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-XPCOMUtils.defineLazyGlobalGetters(this, ['URL']);
-const netutil = Cc['@mozilla.org/network/util;1']
-  .getService(Ci.nsINetUtil);
+XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
 
 function ImageObjectProcessor(aConsole, aExtractor) {
   this.console = aConsole;
   this.extractor = aExtractor;
 }
 
 // Static getters
 Object.defineProperties(ImageObjectProcessor, {
-  'decimals': {
-    get: function() {
+  "decimals": {
+    get() {
       return /^\d+$/;
-    }
+    },
   },
-  'anyRegEx': {
-    get: function() {
-      return new RegExp('any', 'i');
-    }
-  }
+  "anyRegEx": {
+    get() {
+      return new RegExp("any", "i");
+    },
+  },
 });
 
 ImageObjectProcessor.prototype.process = function(
   aManifest, aBaseURL, aMemberName
 ) {
   const spec = {
-    objectName: 'manifest',
+    objectName: "manifest",
     object: aManifest,
     property: aMemberName,
-    expectedType: 'array',
-    trim: false
+    expectedType: "array",
+    trim: false,
   };
   const extractor = this.extractor;
   const images = [];
   const value = extractor.extractValue(spec);
   if (Array.isArray(value)) {
     // Filter out images whose "src" is not useful.
     value.filter(item => !!processSrcMember(item, aBaseURL))
       .map(toImageObject)
       .forEach(image => images.push(image));
   }
   return images;
 
   function toImageObject(aImageSpec) {
     return {
-      'src': processSrcMember(aImageSpec, aBaseURL),
-      'type': processTypeMember(aImageSpec),
-      'sizes': processSizesMember(aImageSpec),
+      "src": processSrcMember(aImageSpec, aBaseURL),
+      "type": processTypeMember(aImageSpec),
+      "sizes": processSizesMember(aImageSpec),
     };
   }
 
   function processTypeMember(aImage) {
     const charset = {};
     const hadCharset = {};
     const spec = {
-      objectName: 'image',
+      objectName: "image",
       object: aImage,
-      property: 'type',
-      expectedType: 'string',
-      trim: true
+      property: "type",
+      expectedType: "string",
+      trim: true,
     };
     let value = extractor.extractValue(spec);
     if (value) {
-      value = netutil.parseRequestContentType(value, charset, hadCharset);
+      value = Services.netUtils.parseRequestContentType(value, charset, hadCharset);
     }
     return value || undefined;
   }
 
   function processSrcMember(aImage, aBaseURL) {
     const spec = {
-      objectName: 'image',
+      objectName: "image",
       object: aImage,
-      property: 'src',
-      expectedType: 'string',
-      trim: false
+      property: "src",
+      expectedType: "string",
+      trim: false,
     };
     const value = extractor.extractValue(spec);
     let url;
     if (value && value.length) {
       try {
         url = new URL(value, aBaseURL).href;
       } catch (e) {}
     }
     return url;
   }
 
   function processSizesMember(aImage) {
     const sizes = new Set();
     const spec = {
-      objectName: 'image',
+      objectName: "image",
       object: aImage,
-      property: 'sizes',
-      expectedType: 'string',
-      trim: true
+      property: "sizes",
+      expectedType: "string",
+      trim: true,
     };
     const value = extractor.extractValue(spec);
     if (value) {
       // Split on whitespace and filter out invalid values.
       value.split(/\s+/)
         .filter(isValidSizeValue)
         .reduce((collector, size) => collector.add(size), sizes);
     }
     return (sizes.size) ? Array.from(sizes).join(" ") : undefined;
     // Implementation of HTML's link@size attribute checker.
     function isValidSizeValue(aSize) {
       const size = aSize.toLowerCase();
       if (ImageObjectProcessor.anyRegEx.test(aSize)) {
         return true;
       }
-      if (!size.includes('x') || size.indexOf('x') !== size.lastIndexOf('x')) {
+      if (!size.includes("x") || size.indexOf("x") !== size.lastIndexOf("x")) {
         return false;
       }
       // Split left of x for width, after x for height.
-      const widthAndHeight = size.split('x');
+      const widthAndHeight = size.split("x");
       const w = widthAndHeight.shift();
-      const h = widthAndHeight.join('x');
-      const validStarts = !w.startsWith('0') && !h.startsWith('0');
+      const h = widthAndHeight.join("x");
+      const validStarts = !w.startsWith("0") && !h.startsWith("0");
       const validDecimals = ImageObjectProcessor.decimals.test(w + h);
       return (validStarts && validDecimals);
     }
   }
 };
-var EXPORTED_SYMBOLS = ['ImageObjectProcessor']; // jshint ignore:line
+var EXPORTED_SYMBOLS = ["ImageObjectProcessor"]; // jshint ignore:line
--- a/dom/manifest/Manifest.jsm
+++ b/dom/manifest/Manifest.jsm
@@ -6,18 +6,16 @@
  * and to access the manifest data (including icons).
  *
  * TODO:
  *  - Trigger appropriate app installed events
  */
 
 "use strict";
 
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
 const { ManifestObtainer } =
   ChromeUtils.import("resource://gre/modules/ManifestObtainer.jsm", {});
 const { ManifestIcons } =
   ChromeUtils.import("resource://gre/modules/ManifestIcons.jsm", {});
 
 ChromeUtils.defineModuleGetter(this, "OS",
                                "resource://gre/modules/osfile.jsm");
 ChromeUtils.defineModuleGetter(this, "JSONFile",
@@ -76,33 +74,33 @@ class Manifest {
   }
 
   async prefetch(browser) {
     const manifestData = await ManifestObtainer.browserObtainManifest(browser);
     const icon = await ManifestIcons.browserFetchIcon(browser, manifestData, 192);
     const data = {
       installed: false,
       manifest: manifestData,
-      cached_icon: icon
+      cached_icon: icon,
     };
     return data;
   }
 
   async install() {
     const manifestData = await ManifestObtainer.browserObtainManifest(this._browser);
     this._store.data = {
       installed: true,
-      manifest: manifestData
+      manifest: manifestData,
     };
     Manifests.manifestInstalled(this);
     this._store.saveSoon();
   }
 
   async icon(expectedSize) {
-    if ('cached_icon' in this._store.data) {
+    if ("cached_icon" in this._store.data) {
       return this._store.data.cached_icon;
     }
     const icon = await ManifestIcons
       .browserFetchIcon(this._browser, this._store.data.manifest, expectedSize);
     // Cache the icon so future requests do not go over the network
     this._store.data.cached_icon = icon;
     this._store.saveSoon();
     return icon;
@@ -137,17 +135,17 @@ class Manifest {
   }
 }
 
 /*
  * Manifests maintains the list of installed manifests
  */
 var Manifests = {
 
-  async initialise () {
+  async initialise() {
 
     if (this.started) {
       return this.started;
     }
 
     this.started = (async () => {
 
       // Make sure the manifests have the folder needed to save into
@@ -214,13 +212,13 @@ var Manifests = {
     if (manifestUrl in this.manifestObjs) {
       return this.manifestObjs[manifestUrl];
     }
 
     // Otherwise create a new manifest object
     this.manifestObjs[manifestUrl] = new Manifest(browser, manifestUrl);
     await this.manifestObjs[manifestUrl].initialise();
     return this.manifestObjs[manifestUrl];
-  }
+  },
 
 };
 
 var EXPORTED_SYMBOLS = ["Manifests"]; // jshint ignore:line
--- a/dom/manifest/ManifestFinder.jsm
+++ b/dom/manifest/ManifestFinder.jsm
@@ -1,16 +1,14 @@
 /* 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 https://mozilla.org/MPL/2.0/. */
 /* globals Components, Task, PromiseMessage */
 "use strict";
-const {
-  utils: Cu
-} = Components;
+
 ChromeUtils.import("resource://gre/modules/PromiseMessage.jsm");
 
 var ManifestFinder = {// jshint ignore:line
   /**
   * Check from content process if DOM Window has a conforming
   * manifest link relationship.
   * @param aContent DOM Window to check.
   * @return {Promise<Boolean>}
@@ -31,17 +29,17 @@ var ManifestFinder = {// jshint ignore:l
   async browserHasManifestLink(aBrowser) {
       if (!isXULBrowser(aBrowser)) {
         throw new TypeError("Invalid input.");
       }
       const msgKey = "DOM:WebManifest:hasManifestLink";
       const mm = aBrowser.messageManager;
       const reply = await PromiseMessage.send(mm, msgKey);
       return reply.data.result;
-    }
+    },
 };
 
 function isXULBrowser(aBrowser) {
   if (!aBrowser || !aBrowser.namespaceURI || !aBrowser.localName) {
     return false;
   }
   const XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
   return (aBrowser.namespaceURI === XUL && aBrowser.localName === "browser");
@@ -56,10 +54,10 @@ function checkForManifest(aWindow) {
   // Only if we have an element and a non-empty href attribute.
   if (!elem || !elem.getAttribute("href")) {
     return false;
   }
   return true;
 }
 
 var EXPORTED_SYMBOLS = [// jshint ignore:line
-  "ManifestFinder"
+  "ManifestFinder",
 ];
--- a/dom/manifest/ManifestIcons.jsm
+++ b/dom/manifest/ManifestIcons.jsm
@@ -1,34 +1,28 @@
 "use strict";
 
-const {
-  utils: Cu,
-  classes: Cc,
-  interfaces: Ci
-} = Components;
-
 ChromeUtils.import("resource://gre/modules/PromiseMessage.jsm");
 
 var ManifestIcons = {
 
   async browserFetchIcon(aBrowser, manifest, iconSize) {
     const msgKey = "DOM:WebManifest:fetchIcon";
     const mm = aBrowser.messageManager;
     const {data: {success, result}} =
       await PromiseMessage.send(mm, msgKey, {manifest, iconSize});
     if (!success) {
       throw result;
     }
     return result;
   },
 
   async contentFetchIcon(aWindow, manifest, iconSize) {
-    return await getIcon(aWindow, toIconArray(manifest.icons), iconSize);
-  }
+    return getIcon(aWindow, toIconArray(manifest.icons), iconSize);
+  },
 };
 
 function parseIconSize(size) {
   if (size === "any" || size === "") {
     // We want icons without size specified to sorted
     // as the largest available icons
     return Number.MAX_SAFE_INTEGER;
   }
--- a/dom/manifest/ManifestObtainer.jsm
+++ b/dom/manifest/ManifestObtainer.jsm
@@ -18,28 +18,21 @@
  *
  * e10s IPC message from this components are handled by:
  *   dom/ipc/manifestMessages.js
  *
  * Which is injected into every browser instance via browser.js.
  *
  * exported ManifestObtainer
  */
-/*globals Components, Task, PromiseMessage, XPCOMUtils, ManifestProcessor, BrowserUtils*/
+/* globals Components, Task, PromiseMessage, XPCOMUtils, ManifestProcessor, BrowserUtils*/
 "use strict";
-const {
-  utils: Cu,
-  classes: Cc,
-  interfaces: Ci
-} = Components;
+
 ChromeUtils.import("resource://gre/modules/PromiseMessage.jsm");
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/ManifestProcessor.jsm");
-ChromeUtils.defineModuleGetter(this, "BrowserUtils",  // jshint ignore:line
-  "resource://gre/modules/BrowserUtils.jsm");
 
 var ManifestObtainer = { // jshint ignore:line
   /**
   * Public interface for obtaining a web manifest from a XUL browser, to use
   * on the parent process.
   * @param  {XULBrowser} The browser to check for the manifest.
   * @return {Promise<Object>} The processed manifest.
   */
@@ -67,17 +60,17 @@ var ManifestObtainer = { // jshint ignor
     }
     let manifest;
     try {
       manifest = await fetchManifest(aContent);
     } catch (err) {
       throw err;
     }
     return manifest;
-  }
+  },
 };
 
 function toError(aErrorClone) {
   let error;
   switch (aErrorClone.name) {
   case "TypeError":
     error = new TypeError();
     break;
@@ -110,17 +103,17 @@ const processResponse = async function(a
     const msg =
       `Fetch error: ${aResp.status} - ${aResp.statusText} at ${aResp.url}`;
     throw new Error(msg);
   }
   const text = await aResp.text();
   const args = {
     jsonText: text,
     manifestURL: aResp.url,
-    docURL: aContentWindow.location.href
+    docURL: aContentWindow.location.href,
   };
   const manifest = ManifestProcessor.process(args);
   return manifest;
 };
 
 /**
  * Asynchronously fetches a web manifest.
  * @param {Window} a The content Window from where to extract the manifest.
@@ -134,17 +127,17 @@ const fetchManifest = async function(aWi
   const elem = aWindow.document.querySelector("link[rel~='manifest']");
   if (!elem || !elem.getAttribute("href")) {
     let msg = `No manifest to fetch at ${aWindow.location}`;
     throw new Error(msg);
   }
   // Throws on malformed URLs
   const manifestURL = new aWindow.URL(elem.href, elem.baseURI);
   const reqInit = {
-    mode: "cors"
+    mode: "cors",
   };
   if (elem.crossOrigin === "use-credentials") {
     reqInit.credentials = "include";
   }
   const request = new aWindow.Request(manifestURL, reqInit);
   request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_MANIFEST);
   let response;
   try {
--- a/dom/manifest/ManifestProcessor.jsm
+++ b/dom/manifest/ManifestProcessor.jsm
@@ -14,43 +14,41 @@
  *
  * Depends on ImageObjectProcessor to process things like
  * icons and splash_screens.
  *
  * TODO: The constructor should accept the UA's supported orientations.
  * TODO: The constructor should accept the UA's supported display modes.
  * TODO: hook up developer tools to console. (1086997).
  */
-/*globals Components, ValueExtractor, ImageObjectProcessor, ConsoleAPI*/
-'use strict';
-const {
-  utils: Cu
-} = Components;
+/* globals Components, ValueExtractor, ImageObjectProcessor, ConsoleAPI*/
+"use strict";
+
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyGlobalGetters(this, ['URL']);
-const displayModes = new Set(['fullscreen', 'standalone', 'minimal-ui',
-  'browser'
+XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
+const displayModes = new Set(["fullscreen", "standalone", "minimal-ui",
+  "browser",
 ]);
-const orientationTypes = new Set(['any', 'natural', 'landscape', 'portrait',
-  'portrait-primary', 'portrait-secondary', 'landscape-primary',
-  'landscape-secondary'
+const orientationTypes = new Set(["any", "natural", "landscape", "portrait",
+  "portrait-primary", "portrait-secondary", "landscape-primary",
+  "landscape-secondary",
 ]);
-const textDirections = new Set(['ltr', 'rtl', 'auto']);
+const textDirections = new Set(["ltr", "rtl", "auto"]);
 
-ChromeUtils.import('resource://gre/modules/Console.jsm');
+ChromeUtils.import("resource://gre/modules/Console.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 // ValueExtractor is used by the various processors to get values
 // from the manifest and to report errors.
-ChromeUtils.import('resource://gre/modules/ValueExtractor.jsm');
+ChromeUtils.import("resource://gre/modules/ValueExtractor.jsm");
 // ImageObjectProcessor is used to process things like icons and images
-ChromeUtils.import('resource://gre/modules/ImageObjectProcessor.jsm');
+ChromeUtils.import("resource://gre/modules/ImageObjectProcessor.jsm");
 
 var ManifestProcessor = { // jshint ignore:line
   get defaultDisplayMode() {
-    return 'browser';
+    return "browser";
   },
   get displayModes() {
     return displayModes;
   },
   get orientationTypes() {
     return orientationTypes;
   },
   get textDirections() {
@@ -60,215 +58,215 @@ var ManifestProcessor = { // jshint igno
   // that conforms with the W3C specification. Takes an object
   // expecting the following dictionary items:
   //  * jsonText: the JSON string to be processed.
   //  * manifestURL: the URL of the manifest, to resolve URLs.
   //  * docURL: the URL of the owner doc, for security checks
   process({
     jsonText,
     manifestURL: aManifestURL,
-    docURL: aDocURL
+    docURL: aDocURL,
   }) {
     const domBundle = Services.strings.createBundle("chrome://global/locale/dom/dom.properties");
 
     const console = new ConsoleAPI({
-      prefix: 'Web Manifest'
+      prefix: "Web Manifest",
     });
     const manifestURL = new URL(aManifestURL);
     const docURL = new URL(aDocURL);
     let rawManifest = {};
     try {
       rawManifest = JSON.parse(jsonText);
     } catch (e) {}
-    if (typeof rawManifest !== 'object' || rawManifest === null) {
-      console.warn(domBundle.GetStringFromName('ManifestShouldBeObject'));
+    if (typeof rawManifest !== "object" || rawManifest === null) {
+      console.warn(domBundle.GetStringFromName("ManifestShouldBeObject"));
       rawManifest = {};
     }
     const extractor = new ValueExtractor(console, domBundle);
     const imgObjProcessor = new ImageObjectProcessor(console, extractor);
     const processedManifest = {
-      'dir': processDirMember.call(this),
-      'lang': processLangMember(),
-      'start_url': processStartURLMember(),
-      'display': processDisplayMember.call(this),
-      'orientation': processOrientationMember.call(this),
-      'name': processNameMember(),
-      'icons': imgObjProcessor.process(
-        rawManifest, manifestURL, 'icons'
+      "dir": processDirMember.call(this),
+      "lang": processLangMember(),
+      "start_url": processStartURLMember(),
+      "display": processDisplayMember.call(this),
+      "orientation": processOrientationMember.call(this),
+      "name": processNameMember(),
+      "icons": imgObjProcessor.process(
+        rawManifest, manifestURL, "icons"
       ),
-      'short_name': processShortNameMember(),
-      'theme_color': processThemeColorMember(),
-      'background_color': processBackgroundColorMember(),
+      "short_name": processShortNameMember(),
+      "theme_color": processThemeColorMember(),
+      "background_color": processBackgroundColorMember(),
     };
     processedManifest.scope = processScopeMember();
     return processedManifest;
 
     function processDirMember() {
       const spec = {
-        objectName: 'manifest',
+        objectName: "manifest",
         object: rawManifest,
-        property: 'dir',
-        expectedType: 'string',
+        property: "dir",
+        expectedType: "string",
         trim: true,
       };
       const value = extractor.extractValue(spec);
       if (this.textDirections.has(value)) {
         return value;
       }
-      return 'auto';
+      return "auto";
     }
 
     function processNameMember() {
       const spec = {
-        objectName: 'manifest',
+        objectName: "manifest",
         object: rawManifest,
-        property: 'name',
-        expectedType: 'string',
-        trim: true
+        property: "name",
+        expectedType: "string",
+        trim: true,
       };
       return extractor.extractValue(spec);
     }
 
     function processShortNameMember() {
       const spec = {
-        objectName: 'manifest',
+        objectName: "manifest",
         object: rawManifest,
-        property: 'short_name',
-        expectedType: 'string',
-        trim: true
+        property: "short_name",
+        expectedType: "string",
+        trim: true,
       };
       return extractor.extractValue(spec);
     }
 
     function processOrientationMember() {
       const spec = {
-        objectName: 'manifest',
+        objectName: "manifest",
         object: rawManifest,
-        property: 'orientation',
-        expectedType: 'string',
-        trim: true
+        property: "orientation",
+        expectedType: "string",
+        trim: true,
       };
       const value = extractor.extractValue(spec);
       if (value && typeof value === "string" && this.orientationTypes.has(value.toLowerCase())) {
         return value.toLowerCase();
       }
       return undefined;
     }
 
     function processDisplayMember() {
       const spec = {
-        objectName: 'manifest',
+        objectName: "manifest",
         object: rawManifest,
-        property: 'display',
-        expectedType: 'string',
-        trim: true
+        property: "display",
+        expectedType: "string",
+        trim: true,
       };
       const value = extractor.extractValue(spec);
       if (value && typeof value === "string" && displayModes.has(value.toLowerCase())) {
         return value.toLowerCase();
       }
       return this.defaultDisplayMode;
     }
 
     function processScopeMember() {
       const spec = {
-        objectName: 'manifest',
+        objectName: "manifest",
         object: rawManifest,
-        property: 'scope',
-        expectedType: 'string',
-        trim: false
+        property: "scope",
+        expectedType: "string",
+        trim: false,
       };
       let scopeURL;
       const startURL = new URL(processedManifest.start_url);
       const value = extractor.extractValue(spec);
-      if (value === undefined || value === '') {
+      if (value === undefined || value === "") {
         return undefined;
       }
       try {
         scopeURL = new URL(value, manifestURL);
       } catch (e) {
-        console.warn(domBundle.GetStringFromName('ManifestScopeURLInvalid'));
+        console.warn(domBundle.GetStringFromName("ManifestScopeURLInvalid"));
         return undefined;
       }
       if (scopeURL.origin !== docURL.origin) {
-        console.warn(domBundle.GetStringFromName('ManifestScopeNotSameOrigin'));
+        console.warn(domBundle.GetStringFromName("ManifestScopeNotSameOrigin"));
         return undefined;
       }
       // If start URL is not within scope of scope URL:
       let isSameOrigin = startURL && startURL.origin !== scopeURL.origin;
       if (isSameOrigin || !startURL.pathname.startsWith(scopeURL.pathname)) {
-        console.warn(domBundle.GetStringFromName('ManifestStartURLOutsideScope'));
+        console.warn(domBundle.GetStringFromName("ManifestStartURLOutsideScope"));
         return undefined;
       }
       return scopeURL.href;
     }
 
     function processStartURLMember() {
       const spec = {
-        objectName: 'manifest',
+        objectName: "manifest",
         object: rawManifest,
-        property: 'start_url',
-        expectedType: 'string',
-        trim: false
+        property: "start_url",
+        expectedType: "string",
+        trim: false,
       };
       let result = new URL(docURL).href;
       const value = extractor.extractValue(spec);
-      if (value === undefined || value === '') {
+      if (value === undefined || value === "") {
         return result;
       }
       let potentialResult;
       try {
         potentialResult = new URL(value, manifestURL);
       } catch (e) {
-        console.warn(domBundle.GetStringFromName('ManifestStartURLInvalid'))
+        console.warn(domBundle.GetStringFromName("ManifestStartURLInvalid"));
         return result;
       }
       if (potentialResult.origin !== docURL.origin) {
-        console.warn(domBundle.GetStringFromName('ManifestStartURLShouldBeSameOrigin'));
+        console.warn(domBundle.GetStringFromName("ManifestStartURLShouldBeSameOrigin"));
       } else {
         result = potentialResult.href;
       }
       return result;
     }
 
     function processThemeColorMember() {
       const spec = {
-        objectName: 'manifest',
+        objectName: "manifest",
         object: rawManifest,
-        property: 'theme_color',
-        expectedType: 'string',
-        trim: true
+        property: "theme_color",
+        expectedType: "string",
+        trim: true,
       };
       return extractor.extractColorValue(spec);
     }
 
     function processBackgroundColorMember() {
       const spec = {
-        objectName: 'manifest',
+        objectName: "manifest",
         object: rawManifest,
-        property: 'background_color',
-        expectedType: 'string',
-        trim: true
+        property: "background_color",
+        expectedType: "string",
+        trim: true,
       };
       return extractor.extractColorValue(spec);
     }
 
     function processLangMember() {
       const spec = {
-        objectName: 'manifest',
+        objectName: "manifest",
         object: rawManifest,
-        property: 'lang',
-        expectedType: 'string', trim: true
+        property: "lang",
+        expectedType: "string", trim: true,
       };
       let tag = extractor.extractValue(spec);
       // TODO: Check if tag is structurally valid.
       //       Cannot do this because we don't support Intl API on Android.
       //       https://bugzilla.mozilla.org/show_bug.cgi?id=864843
       //       https://github.com/tc39/ecma402/issues/5
       // TODO: perform canonicalization on the tag.
       //       Can't do this today because there is no direct means to
       //       access canonicalization algorithms through Intl API.
       //       https://github.com/tc39/ecma402/issues/5
       return tag;
     }
-  }
+  },
 };
-var EXPORTED_SYMBOLS = ['ManifestProcessor']; // jshint ignore:line
+var EXPORTED_SYMBOLS = ["ManifestProcessor"]; // jshint ignore:line
--- a/dom/manifest/ValueExtractor.jsm
+++ b/dom/manifest/ValueExtractor.jsm
@@ -1,22 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 /*
  * Helper functions extract values from manifest members
  * and reports conformance violations.
  */
-/*globals Components*/
-'use strict';
-const {
-  classes: Cc,
-  interfaces: Ci,
-  utils: Cu
-} = Components;
+/* globals Components*/
+"use strict";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGlobalGetters(this, ["InspectorUtils"]);
 
 function ValueExtractor(aConsole, aBundle) {
   this.console = aConsole;
   this.domBundle = aBundle;
@@ -30,38 +25,38 @@ ValueExtractor.prototype = {
   //  object: is the object from which to extract the value.
   //  objectName: string used to construct the developer warning.
   //  property: the name of the property being extracted.
   //  trim: boolean, if the value should be trimmed (used by string type).
   extractValue({expectedType, object, objectName, property, trim}) {
     const value = object[property];
     const isArray = Array.isArray(value);
     // We need to special-case "array", as it's not a JS primitive.
-    const type = (isArray) ? 'array' : typeof value;
+    const type = (isArray) ? "array" : typeof value;
     if (type !== expectedType) {
-      if (type !== 'undefined') {
+      if (type !== "undefined") {
         this.console.warn(this.domBundle.formatStringFromName("ManifestInvalidType",
                                                               [objectName, property, expectedType],
                                                               3));
       }
       return undefined;
     }
     // Trim string and returned undefined if the empty string.
-    const shouldTrim = expectedType === 'string' && value && trim;
+    const shouldTrim = expectedType === "string" && value && trim;
     if (shouldTrim) {
       return value.trim() || undefined;
     }
     return value;
   },
   extractColorValue(spec) {
     const value = this.extractValue(spec);
     let color;
     if (InspectorUtils.isValidCSSColor(value)) {
       color = value;
     } else if (value) {
       this.console.warn(this.domBundle.formatStringFromName("ManifestInvalidCSSColor",
                                                             [spec.property, value],
                                                             2));
     }
     return color;
-  }
+  },
 };
-var EXPORTED_SYMBOLS = ['ValueExtractor']; // jshint ignore:line
+var EXPORTED_SYMBOLS = ["ValueExtractor"]; // jshint ignore:line
--- a/dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js
+++ b/dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js
@@ -1,10 +1,10 @@
-//Used by JSHint:
-/*global Cu, BrowserTestUtils, ok, add_task, gBrowser */
+// Used by JSHint:
+/* global Cu, BrowserTestUtils, ok, add_task, gBrowser */
 "use strict";
 const { ManifestFinder } = ChromeUtils.import("resource://gre/modules/ManifestFinder.jsm", {});
 const defaultURL = new URL("http://example.org/browser/dom/manifest/test/resource.sjs");
 defaultURL.searchParams.set("Content-Type", "text/html; charset=utf-8");
 
 const tests = [{
   body: `
     <link rel="manifesto" href='${defaultURL}?body={"name":"fail"}'>
--- a/dom/manifest/test/browser_ManifestIcons_browserFetchIcon.js
+++ b/dom/manifest/test/browser_ManifestIcons_browserFetchIcon.js
@@ -1,56 +1,56 @@
-//Used by JSHint:
-/*global Cu, BrowserTestUtils, ok, add_task, gBrowser */
+// Used by JSHint:
+/* global Cu, BrowserTestUtils, ok, add_task, gBrowser */
 "use strict";
 const { ManifestIcons } = ChromeUtils.import("resource://gre/modules/ManifestIcons.jsm", {});
 const { ManifestObtainer } = ChromeUtils.import("resource://gre/modules/ManifestObtainer.jsm", {});
 
 const defaultURL = new URL("http://example.org/browser/dom/manifest/test/resource.sjs");
 defaultURL.searchParams.set("Content-Type", "application/manifest+json");
 
-const manifest = JSON.stringify({
+const manifestMock = JSON.stringify({
   icons: [{
     sizes: "50x50",
-    src: "red-50.png?Content-type=image/png"
+    src: "red-50.png?Content-type=image/png",
   }, {
     sizes: "150x150",
-    src: "blue-150.png?Content-type=image/png"
-  }]
+    src: "blue-150.png?Content-type=image/png",
+  }],
 });
 
-function makeTestURL(manifest) {
+function makeTestURL() {
   const url = new URL(defaultURL);
-  const body = `<link rel="manifest" href='${defaultURL}&body=${manifest}'>`;
+  const body = `<link rel="manifest" href='${defaultURL}&body=${manifestMock}'>`;
   url.searchParams.set("Content-Type", "text/html; charset=utf-8");
   url.searchParams.set("body", encodeURIComponent(body));
   return url.href;
 }
 
 function getIconColor(icon) {
   return new Promise((resolve, reject) => {
-    const canvas = content.document.createElement('canvas');
+    const canvas = content.document.createElement("canvas");
     const ctx = canvas.getContext("2d");
     const image = new content.Image();
     image.onload = function() {
       ctx.drawImage(image, 0, 0);
       resolve(ctx.getImageData(1, 1, 1, 1).data);
     };
     image.onerror = function() {
       reject(new Error("could not create image"));
     };
     image.src = icon;
   });
 }
 
 add_task(async function() {
-  const tabOptions = {gBrowser, url: makeTestURL(manifest)};
+  const tabOptions = {gBrowser, url: makeTestURL()};
   await BrowserTestUtils.withNewTab(tabOptions, async function(browser) {
     const manifest = await ManifestObtainer.browserObtainManifest(browser);
     let icon = await ManifestIcons.browserFetchIcon(browser, manifest, 25);
     let color = await ContentTask.spawn(browser, icon, getIconColor);
-    is(color[0], 255, 'Fetched red icon');
+    is(color[0], 255, "Fetched red icon");
 
     icon = await ManifestIcons.browserFetchIcon(browser, manifest, 500);
     color = await ContentTask.spawn(browser, icon, getIconColor);
-    is(color[2], 255, 'Fetched blue icon');
+    is(color[2], 255, "Fetched blue icon");
   });
 });
--- a/dom/manifest/test/browser_ManifestObtainer_obtain.js
+++ b/dom/manifest/test/browser_ManifestObtainer_obtain.js
@@ -1,111 +1,111 @@
-//Used by JSHint:
-/*global ok, is, Cu, BrowserTestUtils, add_task, gBrowser, makeTestURL, requestLongerTimeout*/
-'use strict';
-const { ManifestObtainer } = ChromeUtils.import('resource://gre/modules/ManifestObtainer.jsm', {});
-const remoteURL = 'http://mochi.test:8888/browser/dom/manifest/test/resource.sjs';
-const defaultURL = new URL('http://example.org/browser/dom/manifest/test/resource.sjs');
-defaultURL.searchParams.set('Content-Type', 'text/html; charset=utf-8');
+// Used by JSHint:
+/* global ok, is, Cu, BrowserTestUtils, add_task, gBrowser, makeTestURL, requestLongerTimeout*/
+"use strict";
+const { ManifestObtainer } = ChromeUtils.import("resource://gre/modules/ManifestObtainer.jsm", {});
+const remoteURL = "http://mochi.test:8888/browser/dom/manifest/test/resource.sjs";
+const defaultURL = new URL("http://example.org/browser/dom/manifest/test/resource.sjs");
+defaultURL.searchParams.set("Content-Type", "text/html; charset=utf-8");
 requestLongerTimeout(4);
 
 const tests = [
   // Fetch tests.
   {
     body: `
       <link rel="manifesto" href='resource.sjs?body={"name":"fail"}'>
       <link rel="foo bar manifest bar test" href='resource.sjs?body={"name":"pass-1"}'>
       <link rel="manifest" href='resource.sjs?body={"name":"fail"}'>`,
     run(manifest) {
-      is(manifest.name, 'pass-1', 'Manifest is first `link` where @rel contains token manifest.');
-    }
+      is(manifest.name, "pass-1", "Manifest is first `link` where @rel contains token manifest.");
+    },
   }, {
     body: `
       <link rel="foo bar manifest bar test" href='resource.sjs?body={"name":"pass-2"}'>
       <link rel="manifest" href='resource.sjs?body={"name":"fail"}'>
       <link rel="manifest foo bar test" href='resource.sjs?body={"name":"fail"}'>`,
     run(manifest) {
-      is(manifest.name, 'pass-2', 'Manifest is first `link` where @rel contains token manifest.');
+      is(manifest.name, "pass-2", "Manifest is first `link` where @rel contains token manifest.");
     },
   }, {
     body: `<link rel="manifest" href='${remoteURL}?body={"name":"pass-3"}'>`,
     run(err) {
-      is(err.name, 'TypeError', 'By default, manifest cannot load cross-origin.');
+      is(err.name, "TypeError", "By default, manifest cannot load cross-origin.");
     },
   },
   // CORS Tests.
   {
     get body() {
       const body = 'body={"name": "pass-4"}';
       const CORS =
         `Access-Control-Allow-Origin=${defaultURL.origin}`;
       const link =
         `<link
         crossorigin=anonymous
         rel="manifest"
         href='${remoteURL}?${body}&${CORS}'>`;
       return link;
     },
     run(manifest) {
-      is(manifest.name, 'pass-4', 'CORS enabled, manifest must be fetched.');
+      is(manifest.name, "pass-4", "CORS enabled, manifest must be fetched.");
     },
   }, {
     get body() {
       const body = 'body={"name": "fail"}';
-      const CORS = 'Access-Control-Allow-Origin=http://not-here';
+      const CORS = "Access-Control-Allow-Origin=http://not-here";
       const link =
         `<link
         crossorigin
         rel="manifest"
         href='${remoteURL}?${body}&${CORS}'>`;
       return link;
     },
     run(err) {
-      is(err.name, 'TypeError', 'Fetch blocked by CORS - origin does not match.');
+      is(err.name, "TypeError", "Fetch blocked by CORS - origin does not match.");
     },
   }, {
     body: `<link rel="manifest" href='about:whatever'>`,
     run(err) {
-      is(err.name, 'TypeError', 'Trying to load from about:whatever is TypeError.');
+      is(err.name, "TypeError", "Trying to load from about:whatever is TypeError.");
     },
   }, {
     body: `<link rel="manifest" href='file://manifest'>`,
     run(err) {
-      is(err.name, 'TypeError', 'Trying to load from file://whatever is a TypeError.');
+      is(err.name, "TypeError", "Trying to load from file://whatever is a TypeError.");
     },
   },
-  //URL parsing tests
+  // URL parsing tests
   {
     body: `<link rel="manifest" href='http://[12.1212.21.21.12.21.12]'>`,
     run(err) {
-      is(err.name, 'TypeError', 'Trying to load invalid URL is a TypeError.');
+      is(err.name, "TypeError", "Trying to load invalid URL is a TypeError.");
     },
   },
 ];
 
 function makeTestURL({ body }) {
   const url = new URL(defaultURL);
-  url.searchParams.set('body', encodeURIComponent(body));
+  url.searchParams.set("body", encodeURIComponent(body));
   return url.href;
 }
 
 add_task(async function() {
   const promises = tests
     .map(test => ({
       gBrowser,
       testRunner: testObtainingManifest(test),
-      url: makeTestURL(test)
+      url: makeTestURL(test),
     }))
     .reduce((collector, tabOpts) => {
       const promise = BrowserTestUtils.withNewTab(tabOpts, tabOpts.testRunner);
       collector.push(promise);
       return collector;
     }, []);
 
-  const results = await Promise.all(promises);
+  await Promise.all(promises);
 
   function testObtainingManifest(aTest) {
     return async function(aBrowser) {
       try {
         const manifest = await ManifestObtainer.browserObtainManifest(aBrowser);
         aTest.run(manifest);
       } catch (e) {
         aTest.run(e);
@@ -115,17 +115,17 @@ add_task(async function() {
 });
 
 /*
  * e10s race condition tests
  * Open a bunch of tabs and load manifests
  * in each tab. They should all return pass.
  */
 add_task(async function() {
-  const defaultPath = '/browser/dom/manifest/test/manifestLoader.html';
+  const defaultPath = "/browser/dom/manifest/test/manifestLoader.html";
   const tabURLs = [
     `http://example.com:80${defaultPath}`,
     `http://example.org:80${defaultPath}`,
     `http://example.org:8000${defaultPath}`,
     `http://mochi.test:8888${defaultPath}`,
     `http://sub1.test1.example.com:80${defaultPath}`,
     `http://sub1.test1.example.org:80${defaultPath}`,
     `http://sub1.test1.example.org:8000${defaultPath}`,
@@ -150,35 +150,35 @@ add_task(async function() {
     `http://test2.mochi.test:8888${defaultPath}`,
     `http://test:80${defaultPath}`,
     `http://www.example.com:80${defaultPath}`,
   ];
   // Open tabs an collect corresponding browsers
   let browsers = tabURLs.map(url => BrowserTestUtils.addTab(gBrowser, url).linkedBrowser);
 
   // Once all the pages have loaded, run a bunch of tests in "parallel".
-  await Promise.all((function*() {
+  await Promise.all((function* () {
     for (let browser of browsers) {
       yield BrowserTestUtils.browserLoaded(browser);
     }
   })());
   // Flood random browsers with requests. Once promises settle, check that
   // responses all pass.
-  const results = await Promise.all((function*() {
+  const results = await Promise.all((function* () {
     for (let browser of randBrowsers(browsers, 50)) {
       yield ManifestObtainer.browserObtainManifest(browser);
     }
   })());
-  const pass = results.every(manifest => manifest.name === 'pass');
-  ok(pass, 'Expect every manifest to have name equal to `pass`.');
-  //cleanup
+  const pass = results.every(manifest => manifest.name === "pass");
+  ok(pass, "Expect every manifest to have name equal to `pass`.");
+  // cleanup
   browsers
     .map(browser => gBrowser.getTabForBrowser(browser))
     .forEach(tab => gBrowser.removeTab(tab));
 
-  //Helper generator, spits out random browsers
+  // Helper generator, spits out random browsers
   function* randBrowsers(aBrowsers, aMax) {
     for (let i = 0; i < aMax; i++) {
       const randNum = Math.round(Math.random() * (aBrowsers.length - 1));
       yield aBrowsers[randNum];
     }
   }
 });
--- a/dom/manifest/test/browser_Manifest_install.js
+++ b/dom/manifest/test/browser_Manifest_install.js
@@ -1,31 +1,31 @@
-//Used by JSHint:
-/*global Cu, BrowserTestUtils, ok, add_task, gBrowser */
+// Used by JSHint:
+/* global Cu, BrowserTestUtils, ok, add_task, gBrowser */
 "use strict";
 
 const { Manifests } = ChromeUtils.import("resource://gre/modules/Manifest.jsm", {});
 
 const defaultURL = new URL("http://example.org/browser/dom/manifest/test/resource.sjs");
 defaultURL.searchParams.set("Content-Type", "application/manifest+json");
 
-const manifest = JSON.stringify({short_name: "hello World", scope: "/browser/"});
-const manifestUrl = `${defaultURL}&body=${manifest}`;
+const manifestMock = JSON.stringify({short_name: "hello World", scope: "/browser/"});
+const manifestUrl = `${defaultURL}&body=${manifestMock}`;
 
-function makeTestURL(manifest) {
+function makeTestURL() {
   const url = new URL(defaultURL);
   const body = `<link rel="manifest" href='${manifestUrl}'>`;
   url.searchParams.set("Content-Type", "text/html; charset=utf-8");
   url.searchParams.set("body", encodeURIComponent(body));
   return url.href;
 }
 
 add_task(async function() {
 
-  const tabOptions = {gBrowser, url: makeTestURL(manifest)};
+  const tabOptions = {gBrowser, url: makeTestURL()};
 
   await BrowserTestUtils.withNewTab(tabOptions, async function(browser) {
 
     let manifest = await Manifests.getManifest(browser, manifestUrl);
     is(manifest.installed, false, "We havent installed this manifest yet");
 
     await manifest.install(browser);
     is(manifest.name, "hello World", "Manifest has correct name");
@@ -41,9 +41,8 @@ add_task(async function() {
     let foundManifest = Manifests.findManifestUrl("http://example.org/browser/dom/");
     is(foundManifest, manifestUrl, "Finds manifests within scope");
 
     foundManifest = Manifests.findManifestUrl("http://example.org/");
     is(foundManifest, null, "Does not find manifests outside scope");
   });
 
 });
-
--- a/dom/manifest/test/browser_fire_appinstalled_event.js
+++ b/dom/manifest/test/browser_fire_appinstalled_event.js
@@ -1,10 +1,10 @@
-//Used by JSHint:
-/*global Cu, BrowserTestUtils, ok, add_task, gBrowser */
+// Used by JSHint:
+/* global Cu, BrowserTestUtils, ok, add_task, gBrowser */
 "use strict";
 const { PromiseMessage } = ChromeUtils.import("resource://gre/modules/PromiseMessage.jsm", {});
 const testPath = "/browser/dom/manifest/test/file_reg_appinstalled_event.html";
 const defaultURL = new URL("http://example.org/browser/dom/manifest/test/file_testserver.sjs");
 const testURL = new URL(defaultURL);
 testURL.searchParams.append("file", testPath);
 
 // Enable window.onappinstalled, so we can fire events at it.
@@ -34,16 +34,16 @@ async function theTest(aBrowser) {
     ok(false, "AppInstalled event didn't fire: " + err.message);
   }
 }
 
 // Open a tab and run the test
 add_task(async function() {
   await enableOnAppInstalledPref();
   let tabOptions = {
-    gBrowser: gBrowser,
+    gBrowser,
     url: testURL.href,
   };
   await BrowserTestUtils.withNewTab(
     tabOptions,
     theTest
   );
 });
--- a/dom/manifest/test/common.js
+++ b/dom/manifest/test/common.js
@@ -1,22 +1,22 @@
 /**
  * Common infrastructure for manifest tests.
  **/
-/*globals SpecialPowers, ManifestProcessor*/
-'use strict';
+/* globals SpecialPowers, ManifestProcessor*/
+"use strict";
 const {
-  ManifestProcessor
-} = SpecialPowers.Cu.import('resource://gre/modules/ManifestProcessor.jsm');
+  ManifestProcessor,
+} = SpecialPowers.Cu.import("resource://gre/modules/ManifestProcessor.jsm");
 const processor = ManifestProcessor;
-const manifestURL = new URL(document.location.origin + '/manifest.json');
+const manifestURL = new URL(document.location.origin + "/manifest.json");
 const docURL = document.location;
-const seperators = '\u2028\u2029\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000';
-const lineTerminators = '\u000D\u000A\u2028\u2029';
+const seperators = "\u2028\u2029\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000";
+const lineTerminators = "\u000D\u000A\u2028\u2029";
 const whiteSpace = `${seperators}${lineTerminators}`;
 const typeTests = [1, null, {},
-  [], false
+  [], false,
 ];
 const data = {
-  jsonText: '{}',
-  manifestURL: manifestURL,
-  docURL: docURL
+  jsonText: "{}",
+  manifestURL,
+  docURL,
 };
--- a/dom/manifest/test/file_reg_appinstalled_event.html
+++ b/dom/manifest/test/file_reg_appinstalled_event.html
@@ -2,14 +2,14 @@
 <script>
 "use strict";
 window.addEventListener("appinstalled", () => {
   document
     .querySelector("#output")
     .innerHTML = "event received!";
   // Send a custom event back to the browser
   // to acknowledge that we got this
-  const detail = { result: true }
+  const detail = { result: true };
   const ev = new CustomEvent("dom.manifest.onappinstalled", { detail });
   document.dispatchEvent(ev);
 });
 </script>
 <h1 id=output>waiting for event</h1>
--- a/dom/manifest/test/test_ImageObjectProcessor_sizes.html
+++ b/dom/manifest/test/test_ImageObjectProcessor_sizes.html
@@ -9,78 +9,71 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script src="common.js"></script>
   <script>
 /**
  * Image object's sizes member
  * https://w3c.github.io/manifest/#sizes-member
  **/
-'use strict';
+"use strict";
 var validSizes = [{
-  test: '16x16',
-  expect: ['16x16']
+  test: "16x16",
+  expect: ["16x16"],
 }, {
-  test: 'hello 16x16 16x16',
-  expect: ['16x16']
+  test: "hello 16x16 16x16",
+  expect: ["16x16"],
 }, {
-  test: '32x32 16 48x48 12',
-  expect: ['32x32', '48x48']
+  test: "32x32 16 48x48 12",
+  expect: ["32x32", "48x48"],
 }, {
   test: `${whiteSpace}128x128${whiteSpace}512x512 8192x8192 32768x32768${whiteSpace}`,
-  expect: ['128x128', '512x512', '8192x8192', '32768x32768']
+  expect: ["128x128", "512x512", "8192x8192", "32768x32768"],
 }, {
-  test: 'any',
-  expect: ['any']
+  test: "any",
+  expect: ["any"],
 }, {
-  test: 'Any',
-  expect: ['Any']
+  test: "Any",
+  expect: ["Any"],
 }, {
-  test: '16x32',
-  expect: ['16x32']
+  test: "16x32",
+  expect: ["16x32"],
 }, {
-  test: '17x33',
-  expect: ['17x33']
+  test: "17x33",
+  expect: ["17x33"],
 }, {
-  test: '32x32 32x32',
-  expect: ['32x32']
+  test: "32x32 32x32",
+  expect: ["32x32"],
 }, {
-  test: '32X32',
-  expect: ['32X32']
+  test: "32X32",
+  expect: ["32X32"],
 }, {
-  test: 'any 32x32',
-  expect: ['any', '32x32']
+  test: "any 32x32",
+  expect: ["any", "32x32"],
 }];
 
 var testIcon = {
   icons: [{
-    src: 'test',
-    sizes: undefined
-  }]
+    src: "test",
+    sizes: undefined,
+  }],
 };
 
 validSizes.forEach(({test, expect}) => {
   testIcon.icons[0].sizes = test;
   data.jsonText = JSON.stringify(testIcon);
   var result = processor.process(data);
   var sizes = result.icons[0].sizes;
   var expected = `Expect sizes to equal ${expect.join(" ")}`;
   is(sizes, expect.join(" "), expected);
 });
 
-var testIcon = {
-  icons: [{
-    src: 'test',
-    sizes: undefined
-  }]
-};
-
-var invalidSizes = ['invalid', '', ' ', '16 x 16', '32', '21', '16xx16', '16 x x 6'];
+var invalidSizes = ["invalid", "", " ", "16 x 16", "32", "21", "16xx16", "16 x x 6"];
 invalidSizes.forEach((invalidSize) => {
-  var expected = 'Expect invalid sizes to return undefined.';
+  var expected = "Expect invalid sizes to return undefined.";
   testIcon.icons[0].sizes = invalidSize;
   data.jsonText = JSON.stringify(testIcon);
   var result = processor.process(data);
   var sizes = result.icons[0].sizes;
   is(sizes, undefined, expected);
 });
 
 typeTests.forEach((type) => {
--- a/dom/manifest/test/test_ImageObjectProcessor_src.html
+++ b/dom/manifest/test/test_ImageObjectProcessor_src.html
@@ -9,98 +9,102 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script src="common.js"></script>
   <script>
 /**
  * Image object's src member
  * https://w3c.github.io/manifest/#src-member
  **/
-'use strict';
+"use strict";
+
 var noSrc = {
   icons: [{}, {
-    src: []
+    src: [],
   }, {
-    src: {}
+    src: {},
   }, {
-    src: null
+    src: null,
   }, {
-    type: 'image/jpg'
+    type: "image/jpg",
   }, {
-    sizes: '1x1,2x2'
+    sizes: "1x1,2x2",
   }, {
-    sizes: 'any',
-    type: 'image/jpg'
-  }]
+    sizes: "any",
+    type: "image/jpg",
+  }],
 };
 
 var expected = `Expect icons without a src prop to be filtered out.`;
 data.jsonText = JSON.stringify(noSrc);
 var result = processor.process(data);
 is(result.icons.length, 0, expected);
 
 var invalidSrc = {
   icons: [{
-    src: null
+    src: null,
   }, {
-    src: 1
+    src: 1,
   }, {
-    src: []
+    src: [],
   }, {
-    src: {}
+    src: {},
   }, {
-    src: true
+    src: true,
   }, {
-    src: ''
-  }]
+    src: "",
+  }],
 };
 
-var expected = `Expect icons with invalid src prop to be filtered out.`;
-data.jsonText = JSON.stringify(noSrc);
-var result = processor.process(data);
+expected = `Expect icons with invalid src prop to be filtered out.`;
+data.jsonText = JSON.stringify(invalidSrc);
+result = processor.process(data);
 is(result.icons.length, 0, expected);
 
-var expected = `Expect icon's src to be a string.`;
+expected = `Expect icon's src to be a string.`;
 var withSrc = {
   icons: [{
-    src: 'pass'
-  }]
+    src: "pass",
+  }],
 };
 data.jsonText = JSON.stringify(withSrc);
-var result = processor.process(data);
+result = processor.process(data);
 is(typeof result.icons[0].src, "string", expected);
 
-var expected = `Expect only icons with a src prop to be kept.`;
-var withSrc = {
+
+expected = `Expect only icons with a src prop to be kept.`;
+withSrc = {
   icons: [{
-    src: 'pass'
+    src: "pass",
   }, {
-    src: 'pass',
+    src: "pass",
   }, {}, {
-    foo: 'foo'
-  }]
+    foo: "foo",
+  }],
 };
 data.jsonText = JSON.stringify(withSrc);
-var result = processor.process(data);
+result = processor.process(data);
 is(result.icons.length, 2, expected);
 
-var expectedURL = new URL('pass', manifestURL);
+var expectedURL = new URL("pass", manifestURL);
 for (var icon of result.icons) {
-  var expected = `Expect src prop to be ${expectedURL.toString()}`;
+  expected = `Expect src prop to be ${expectedURL.toString()}`;
   is(icon.src.toString(), expectedURL.toString(), expected);
 }
 
-//Resolve URLs relative to manfiest
-var URLs = ['path', '/path', '../../path'];
+
+// Resolve URLs relative to manfiest
+var URLs = ["path", "/path", "../../path"];
 
 URLs.forEach((url) => {
-  var expected = `Resolve icon src URLs relative to manifest.`;
+  expected = `Resolve icon src URLs relative to manifest.`;
   data.jsonText = JSON.stringify({
     icons: [{
-      src: url
-    }]
+      src: url,
+    }],
   });
   var absURL = new URL(url, manifestURL).toString();
-  var result = processor.process(data);
+  result = processor.process(data);
   is(result.icons[0].src.toString(), absURL, expected);
 });
+
   </script>
 </head>
--- a/dom/manifest/test/test_ImageObjectProcessor_type.html
+++ b/dom/manifest/test/test_ImageObjectProcessor_type.html
@@ -10,48 +10,48 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script src="common.js"></script>
   <script>
 /**
  * Image object's type property
  * https://w3c.github.io/manifest/#type-member
  **/
 
-'use strict';
+"use strict";
 var testIcon = {
   icons: [{
-    src: 'test',
-    type: undefined
-  }]
+    src: "test",
+    type: undefined,
+  }],
 };
 
 var invalidMimeTypes = [
-  'application / text',
-  'test;test',
-  ';test?test',
-  'application\\text',
-  'image/jpeg, image/gif'
+  "application / text",
+  "test;test",
+  ";test?test",
+  "application\\text",
+  "image/jpeg, image/gif",
 ];
 invalidMimeTypes.forEach((invalidMime) => {
   var expected = `Expect invalid mime to be treated like undefined.`;
   testIcon.icons[0].type = invalidMime;
   data.jsonText = JSON.stringify(testIcon);
   var result = processor.process(data);
   is(result.icons[0].type, undefined, expected);
 });
 
 var validTypes = [
-  'image/jpeg',
-  'IMAGE/jPeG',
+  "image/jpeg",
+  "IMAGE/jPeG",
   `${whiteSpace}image/jpeg${whiteSpace}`,
-  'image/JPEG; whatever=something',
-  'image/JPEG;whatever'
+  "image/JPEG; whatever=something",
+  "image/JPEG;whatever",
 ];
 
 validTypes.forEach((validMime) => {
   var expected = `Expect valid mime to be parsed to : image/jpeg.`;
   testIcon.icons[0].type = validMime;
   data.jsonText = JSON.stringify(testIcon);
   var result = processor.process(data);
-  is(result.icons[0].type, 'image/jpeg', expected);
+  is(result.icons[0].type, "image/jpeg", expected);
 });
   </script>
 </head>
--- a/dom/manifest/test/test_ManifestProcessor_JSON.html
+++ b/dom/manifest/test/test_ManifestProcessor_JSON.html
@@ -9,18 +9,18 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script src="common.js"></script>
   <script>
 /**
  * JSON parsing/processing tests
  * https://w3c.github.io/manifest/#processing
  **/
-'use strict';
-var invalidJson = ['', ` \t \n ${whiteSpace} `, '{', '{[[}'];
+"use strict";
+var invalidJson = ["", ` \t \n ${whiteSpace} `, "{", "{[[}"];
 invalidJson.forEach((testString) => {
   var expected = `Expect to recover from invalid JSON: ${testString}`;
   data.jsonText = testString;
   var result = processor.process(data);
   SimpleTest.is(result.start_url, docURL.href, expected);
 });
 
 var validButUnhelpful = ["1", 1, "", "[{}]", "null"];
--- a/dom/manifest/test/test_ManifestProcessor_background_color.html
+++ b/dom/manifest/test/test_ManifestProcessor_background_color.html
@@ -9,110 +9,110 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script src="common.js"></script>
   <script>
 /**
  * background_color member
  * https://w3c.github.io/manifest/#background_color-member
  **/
-'use strict';
+"use strict";
 
 typeTests.forEach(type => {
   data.jsonText = JSON.stringify({
-    background_color: type
+    background_color: type,
   });
   var result = processor.process(data);
 
   is(result.background_color, undefined, `Expect non-string background_color to be undefined: ${typeof type}.`);
 });
 
 var validThemeColors = [
-  'maroon',
-  '#f00',
-  '#ff0000',
-  'rgb(255,0,0)',
-  'rgb(255,0,0,1)',
-  'rgb(255,0,0,1.0)',
-  'rgb(255,0,0,100%)',
-  'rgb(255 0 0)',
-  'rgb(255 0 0 / 1)',
-  'rgb(255 0 0 / 1.0)',
-  'rgb(255 0 0 / 100%)',
-  'rgb(100%, 0%, 0%)',
-  'rgb(100%, 0%, 0%, 1)',
-  'rgb(100%, 0%, 0%, 1.0)',
-  'rgb(100%, 0%, 0%, 100%)',
-  'rgb(100% 0% 0%)',
-  'rgb(100% 0% 0% / 1)',
-  'rgb(100%, 0%, 0%, 1.0)',
-  'rgb(100%, 0%, 0%, 100%)',
-  'rgb(300,0,0)',
-  'rgb(300 0 0)',
-  'rgb(255,-10,0)',
-  'rgb(110%, 0%, 0%)',
-  'rgba(255,0,0)',
-  'rgba(255,0,0,1)',
-  'rgba(255 0 0 / 1)',
-  'rgba(100%,0%,0%,1)',
-  'rgba(0,0,255,0.5)',
-  'rgba(100%, 50%, 0%, 0.1)',
-  'hsl(120, 100%, 50%)',
-  'hsl(120 100% 50%)',
-  'hsl(120, 100%, 50%, 1.0)',
-  'hsl(120 100% 50% / 1.0)',
-  'hsla(120, 100%, 50%)',
-  'hsla(120 100% 50%)',
-  'hsla(120, 100%, 50%, 1.0)',
-  'hsla(120 100% 50% / 1.0)',
-  'hsl(120deg, 100%, 50%)',
-  'hsl(133.33333333grad, 100%, 50%)',
-  'hsl(2.0943951024rad, 100%, 50%)',
-  'hsl(0.3333333333turn, 100%, 50%)',
+  "maroon",
+  "#f00",
+  "#ff0000",
+  "rgb(255,0,0)",
+  "rgb(255,0,0,1)",
+  "rgb(255,0,0,1.0)",
+  "rgb(255,0,0,100%)",
+  "rgb(255 0 0)",
+  "rgb(255 0 0 / 1)",
+  "rgb(255 0 0 / 1.0)",
+  "rgb(255 0 0 / 100%)",
+  "rgb(100%, 0%, 0%)",
+  "rgb(100%, 0%, 0%, 1)",
+  "rgb(100%, 0%, 0%, 1.0)",
+  "rgb(100%, 0%, 0%, 100%)",
+  "rgb(100% 0% 0%)",
+  "rgb(100% 0% 0% / 1)",
+  "rgb(100%, 0%, 0%, 1.0)",
+  "rgb(100%, 0%, 0%, 100%)",
+  "rgb(300,0,0)",
+  "rgb(300 0 0)",
+  "rgb(255,-10,0)",
+  "rgb(110%, 0%, 0%)",
+  "rgba(255,0,0)",
+  "rgba(255,0,0,1)",
+  "rgba(255 0 0 / 1)",
+  "rgba(100%,0%,0%,1)",
+  "rgba(0,0,255,0.5)",
+  "rgba(100%, 50%, 0%, 0.1)",
+  "hsl(120, 100%, 50%)",
+  "hsl(120 100% 50%)",
+  "hsl(120, 100%, 50%, 1.0)",
+  "hsl(120 100% 50% / 1.0)",
+  "hsla(120, 100%, 50%)",
+  "hsla(120 100% 50%)",
+  "hsla(120, 100%, 50%, 1.0)",
+  "hsla(120 100% 50% / 1.0)",
+  "hsl(120deg, 100%, 50%)",
+  "hsl(133.33333333grad, 100%, 50%)",
+  "hsl(2.0943951024rad, 100%, 50%)",
+  "hsl(0.3333333333turn, 100%, 50%)",
 ];
 
 validThemeColors.forEach(background_color => {
   data.jsonText = JSON.stringify({
-    background_color: background_color
+    background_color,
   });
   var result = processor.process(data);
 
   is(result.background_color, background_color, `Expect background_color to be returned: ${background_color}.`);
 });
 
 var invalidThemeColors = [
-  'marooon',
-  'f000000',
-  '#ff00000',
-  'rgb(100, 0%, 0%)',
-  'rgb(255,0)',
-  'rbg(255,-10,0)',
-  'rgb(110, 0%, 0%)',
-  '(255,0,0) }',
-  'rgba(255)',
-  ' rgb(100%,0%,0%) }',
-  'hsl(120, 100%, 50)',
-  'hsl(120, 100%, 50.0)',
-  'hsl 120, 100%, 50%',
-  'hsla{120, 100%, 50%, 1}',
-]
+  "marooon",
+  "f000000",
+  "#ff00000",
+  "rgb(100, 0%, 0%)",
+  "rgb(255,0)",
+  "rbg(255,-10,0)",
+  "rgb(110, 0%, 0%)",
+  "(255,0,0) }",
+  "rgba(255)",
+  " rgb(100%,0%,0%) }",
+  "hsl(120, 100%, 50)",
+  "hsl(120, 100%, 50.0)",
+  "hsl 120, 100%, 50%",
+  "hsla{120, 100%, 50%, 1}",
+];
 
 invalidThemeColors.forEach(background_color => {
   data.jsonText = JSON.stringify({
-    background_color: background_color
+    background_color,
   });
   var result = processor.process(data);
 
   is(result.background_color, undefined, `Expect background_color to be undefined: ${background_color}.`);
 });
 
 // Trim tests
 validThemeColors.forEach(background_color => {
   var expandedThemeColor = `${seperators}${lineTerminators}${background_color}${lineTerminators}${seperators}`;
   data.jsonText = JSON.stringify({
-    background_color: expandedThemeColor
+    background_color: expandedThemeColor,
   });
   var result = processor.process(data);
 
   is(result.background_color, background_color, `Expect trimmed background_color to be returned.`);
 });
   </script>
 </head>
--- a/dom/manifest/test/test_ManifestProcessor_dir.html
+++ b/dom/manifest/test/test_ManifestProcessor_dir.html
@@ -9,49 +9,49 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script src="common.js"></script>
   <script>
 /**
  * dir member
  * https://w3c.github.io/manifest/#dir-member
  **/
-'use strict';
-//Type checks
+"use strict";
+// Type checks
 typeTests.forEach((type) => {
   var expected = `Expect non - string dir to default to "auto".`;
   data.jsonText = JSON.stringify({
-    dir: type
+    dir: type,
   });
   var result = processor.process(data);
-  is(result.dir, 'auto', expected);
+  is(result.dir, "auto", expected);
 });
 
-/*Test valid values*/
-var validDirs = ['ltr', 'rtl', 'auto']
+/* Test valid values*/
+var validDirs = ["ltr", "rtl", "auto"];
 validDirs.forEach((dir) => {
   var expected = `Expect dir value to be ${dir}.`;
   data.jsonText = JSON.stringify({dir});
   var result = processor.process(data);
   is(result.dir, dir, expected);
 });
 
-//trim tests
+// trim tests
 validDirs.forEach((dir) => {
   var expected = `Expect trimmed dir to be returned.`;
   var expandeddir =  seperators + lineTerminators + dir + lineTerminators + seperators;
   data.jsonText = JSON.stringify({
-    dir: expandeddir
+    dir: expandeddir,
   });
   var result = processor.process(data);
   is(result.dir, dir, expected);
 });
 
-//Unknown/Invalid directions
-var invalidDirs = ['LTR', 'RtL', `fooo${whiteSpace}rtl`, '', 'bar baz, some value', 'ltr rtl auto', 'AuTo'];
+// Unknown/Invalid directions
+var invalidDirs = ["LTR", "RtL", `fooo${whiteSpace}rtl`, "", "bar baz, some value", "ltr rtl auto", "AuTo"];
 invalidDirs.forEach((dir) => {
   var expected = `Expect default dir "auto" to be returned: '${dir}'`;
   data.jsonText = JSON.stringify({dir});
   var result = processor.process(data);
-  is(result.dir, 'auto', expected);
+  is(result.dir, "auto", expected);
 });
   </script>
 </head>
--- a/dom/manifest/test/test_ManifestProcessor_display.html
+++ b/dom/manifest/test/test_ManifestProcessor_display.html
@@ -9,70 +9,70 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script src="common.js"></script>
   <script>
 /**
  * display member
  * https://w3c.github.io/manifest/#display-member
  **/
-'use strict';
-//Type checks
+"use strict";
+// Type checks
 typeTests.forEach((type) => {
   var expected = `Expect non - string display to default to "browser".`;
   data.jsonText = JSON.stringify({
-    display: type
+    display: type,
   });
   var result = processor.process(data);
-  is(result.display, 'browser', expected);
+  is(result.display, "browser", expected);
 });
 
-/*Test valid modes - case insensitive*/
+/* Test valid modes - case insensitive*/
 var validModes = [
-  'fullscreen',
-  'standalone',
-  'minimal-ui',
-  'browser',
-  'FullScreen',
-  'standAlone',
-  'minimal-UI',
-  'BROWSER',
-]
+  "fullscreen",
+  "standalone",
+  "minimal-ui",
+  "browser",
+  "FullScreen",
+  "standAlone",
+  "minimal-UI",
+  "BROWSER",
+];
 validModes.forEach((mode) => {
   var expected = `Expect display mode to be ${mode.toLowerCase()}.`;
   data.jsonText = JSON.stringify({
-    display: mode
+    display: mode,
   });
   var result = processor.process(data);
   is(result.display, mode.toLowerCase(), expected);
 });
 
-//trim tests
+// trim tests
 validModes.forEach((display) => {
   var expected = `Expect trimmed display mode to be returned.`;
   var expandedDisplay =  seperators + lineTerminators + display + lineTerminators + seperators;
   data.jsonText = JSON.stringify({
-    display: expandedDisplay
+    display: expandedDisplay,
   });
   var result = processor.process(data);
   is(result.display, display.toLowerCase(), expected);
 });
 
-//Unknown modes
+// Unknown modes
 var invalidModes = [
-  'foo',
+  "foo",
   `fooo${whiteSpace}`,
-  '',
-  'fullscreen,standalone',
-  'standalone fullscreen',
-  'FULLSCreENS',
+  "",
+  "fullscreen,standalone",
+  "standalone fullscreen",
+  "FULLSCreENS",
 ];
 
 invalidModes.forEach((invalidMode) => {
   var expected = `Expect default display mode "browser" to be returned: '${invalidMode}'`;
   data.jsonText = JSON.stringify({
-    display: invalidMode
+    display: invalidMode,
   });
   var result = processor.process(data);
-  is(result.display, 'browser', expected);
+  is(result.display, "browser", expected);
 });
   </script>
 </head>
--- a/dom/manifest/test/test_ManifestProcessor_icons.html
+++ b/dom/manifest/test/test_ManifestProcessor_icons.html
@@ -10,21 +10,21 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script src="common.js"></script>
   <script>
 /**
  * Manifest icons member
  * https://w3c.github.io/manifest/#icons-member
  **/
 
-'use strict';
+"use strict";
 
 typeTests.forEach((type) => {
   var expected = `Expect non-array icons to be empty array: ${typeof type}.`;
   data.jsonText = JSON.stringify({
-    icons: type
+    icons: type,
   });
   var result = processor.process(data);
-  var y = SpecialPowers.unwrap(result.icons);
+  SpecialPowers.unwrap(result.icons);
   is(result.icons.length, 0, expected);
 });
   </script>
 </head>
--- a/dom/manifest/test/test_ManifestProcessor_lang.html
+++ b/dom/manifest/test/test_ManifestProcessor_lang.html
@@ -8,105 +8,112 @@ https://bugzilla.mozilla.org/show_bug.cg
 <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 <script src="common.js"></script>
 <script>
 /**
  * lang member
  * https://w3c.github.io/manifest/#lang-member
  **/
-/*globals is, typeTests, data, processor, seperators, lineTerminators, todo_is*/
-'use strict';
+/* globals is, typeTests, data, processor, seperators, lineTerminators, todo_is*/
+"use strict";
 // Type checks: checks that only strings are accepted.
+
 for (var type of typeTests) {
   var expected = `Expect non-string to be undefined.`;
   data.jsonText = JSON.stringify({
-    lang: type
+    lang: type,
   });
   var result = processor.process(data);
   is(result.lang, undefined, expected);
 }
 
 // Test valid language tags - derived from IANA and BCP-47 spec
 // and our Intl.js implementation.
 var validTags = [
-  'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az',
-  'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce',
-  'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee',
-  'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr',
-  'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr',
-  'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ik', 'in', 'io',
-  'is', 'it', 'iu', 'iw', 'ja', 'ji', 'jv', 'jw', 'ka', 'kg', 'ki', 'kj',
-  'kk', 'kl', 'km', 'kn', 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la',
-  'lb', 'lg', 'li', 'ln', 'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk',
-  'ml', 'mn', 'mo', 'mr', 'ms', 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng',
-  'nl', 'nn', 'no', 'nr', 'nv', 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa',
-  'pi', 'pl', 'ps', 'pt', 'qu', 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc',
-  'sd', 'se', 'sg', 'sh', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr',
-  'ss', 'st', 'su', 'sv', 'sw', 'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl',
-  'tn', 'to', 'tr', 'ts', 'tt', 'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've',
-  'vi', 'vo', 'wa', 'wo', 'xh', 'yi', 'yo', 'za', 'zh', 'zu', 'en-US',
-  'jp-JS', 'pt-PT', 'pt-BR', 'de-CH', 'de-DE-1901', 'es-419', 'sl-IT-nedis',
-  'en-US-boont', 'mn-Cyrl-MN', 'x-fr-CH', 'sr-Cyrl', 'sr-Latn',
-  'hy-Latn-IT-arevela', 'zh-TW', 'en-GB-boont-r-extended-sequence-x-private',
-  'zh-nan-hans-bu-variant2-variant1-u-ca-chinese-t-zh-latn-x-private',
-  'zh-cmn-Hans-CN', 'cmn-Hans-CN', 'zh-yue-HK', 'yue-HK',
-  'de-CH-x-phonebk', 'az-Arab-x-AZE-derbend', 'x-whatever',
-  'qaa-Qaaa-QM-x-southern'
+  "aa", "ab", "ae", "af", "ak", "am", "an", "ar", "as", "av", "ay", "az",
+  "ba", "be", "bg", "bh", "bi", "bm", "bn", "bo", "br", "bs", "ca", "ce",
+  "ch", "co", "cr", "cs", "cu", "cv", "cy", "da", "de", "dv", "dz", "ee",
+  "el", "en", "eo", "es", "et", "eu", "fa", "ff", "fi", "fj", "fo", "fr",
+  "fy", "ga", "gd", "gl", "gn", "gu", "gv", "ha", "he", "hi", "ho", "hr",
+  "ht", "hu", "hy", "hz", "ia", "id", "ie", "ig", "ik", "in", "io",
+  "is", "it", "iu", "iw", "ja", "ji", "jv", "jw", "ka", "kg", "ki", "kj",
+  "kk", "kl", "km", "kn", "ko", "kr", "ks", "ku", "kv", "kw", "ky", "la",
+  "lb", "lg", "li", "ln", "lo", "lt", "lu", "lv", "mg", "mh", "mi", "mk",
+  "ml", "mn", "mo", "mr", "ms", "mt", "my", "na", "nb", "nd", "ne", "ng",
+  "nl", "nn", "no", "nr", "nv", "ny", "oc", "oj", "om", "or", "os", "pa",
+  "pi", "pl", "ps", "pt", "qu", "rm", "rn", "ro", "ru", "rw", "sa", "sc",
+  "sd", "se", "sg", "sh", "si", "sk", "sl", "sm", "sn", "so", "sq", "sr",
+  "ss", "st", "su", "sv", "sw", "ta", "te", "tg", "th", "ti", "tk", "tl",
+  "tn", "to", "tr", "ts", "tt", "tw", "ty", "ug", "uk", "ur", "uz", "ve",
+  "vi", "vo", "wa", "wo", "xh", "yi", "yo", "za", "zh", "zu", "en-US",
+  "jp-JS", "pt-PT", "pt-BR", "de-CH", "de-DE-1901", "es-419", "sl-IT-nedis",
+  "en-US-boont", "mn-Cyrl-MN", "x-fr-CH", "sr-Cyrl", "sr-Latn",
+  "hy-Latn-IT-arevela", "zh-TW", "en-GB-boont-r-extended-sequence-x-private",
+  "zh-nan-hans-bu-variant2-variant1-u-ca-chinese-t-zh-latn-x-private",
+  "zh-cmn-Hans-CN", "cmn-Hans-CN", "zh-yue-HK", "yue-HK",
+  "de-CH-x-phonebk", "az-Arab-x-AZE-derbend", "x-whatever",
+  "qaa-Qaaa-QM-x-southern",
 ];
+
+
 for (var tag of validTags) {
-  var expected = `Expect lang to be ${tag}.`;
+  expected = `Expect lang to be ${tag}.`;
   data.jsonText = JSON.stringify({
-    lang: tag
+    lang: tag,
   });
-  var result = processor.process(data);
+  result = processor.process(data);
   is(result.lang, tag, expected);
 }
 
+
+
 // trim tests - check that language tags get trimmed properly.
-for (var tag of validTags) {
-  var expected = `Expect trimmed tag to be returned.`;
+for (tag of validTags) {
+  expected = `Expect trimmed tag to be returned.`;
   var expandedtag = seperators + lineTerminators + tag;
   expandedtag += lineTerminators + seperators;
   data.jsonText = JSON.stringify({
-    lang: expandedtag
+    lang: expandedtag,
   });
-  var result = processor.process(data);
+  result = processor.process(data);
   is(result.lang, tag, expected);
 }
 
-//Invalid language tags, derived from BCP-47 and made up.
+// Invalid language tags, derived from BCP-47 and made up.
 var invalidTags = [
-  'de-419-DE', ' a-DE ', 'ar-a-aaa-b-bbb-a-ccc', 'sdafsdfaadsfdsf', 'i',
-  'i-phone', 'en US', 'EN-*-US-JP', 'JA-INVALID-TAG', '123123123'
+"de-419-DE", " a-DE ", "ar-a-aaa-b-bbb-a-ccc", "sdafsdfaadsfdsf", "i",
+"i-phone", "en US", "EN-*-US-JP", "JA-INVALID-TAG", "123123123",
 ];
+
+
 for (var item of invalidTags) {
-  var expected = `Expect invalid tag (${item}) to be treated as undefined.`;
+  expected = `Expect invalid tag (${item}) to be treated as undefined.`;
   data.jsonText = JSON.stringify({
-    lang: item
+    lang: item,
   });
-  var result = processor.process(data);
+  result = processor.process(data);
   todo_is(result.lang, undefined, expected);
 }
 
 // Canonical form conversion tests. We convert the following tags, which are in
 // canonical form, to upper case and expect the processor to return them
 // in canonical form.
 var canonicalTags = [
-  'jp-JS', 'pt-PT', 'pt-BR', 'de-CH', 'de-DE-1901', 'es-419', 'sl-IT-nedis',
-  'en-US-boont', 'mn-Cyrl-MN', 'x-fr-CH', 'sr-Cyrl', 'sr-Latn',
-  'hy-Latn-IT-arevela', 'zh-TW', 'en-GB-boont-r-extended-sequence-x-private',
-  'zh-cmn-Hans-CN', 'cmn-Hans-CN', 'zh-yue-HK', 'yue-HK',
-  'de-CH-x-phonebk', 'az-Arab-x-AZE-derbend', 'x-whatever',
-  'qaa-Qaaa-QM-x-southern'
+"jp-JS", "pt-PT", "pt-BR", "de-CH", "de-DE-1901", "es-419", "sl-IT-nedis",
+"en-US-boont", "mn-Cyrl-MN", "x-fr-CH", "sr-Cyrl", "sr-Latn",
+"hy-Latn-IT-arevela", "zh-TW", "en-GB-boont-r-extended-sequence-x-private",
+"zh-cmn-Hans-CN", "cmn-Hans-CN", "zh-yue-HK", "yue-HK",
+"de-CH-x-phonebk", "az-Arab-x-AZE-derbend", "x-whatever",
+"qaa-Qaaa-QM-x-southern",
 ];
 
-for (var tag of canonicalTags) {
+for (tag of canonicalTags) {
   var uppedTag = tag.toUpperCase();
-  var expected = `Expect tag (${uppedTag}) to be in canonical form (${tag}).`;
+  expected = `Expect tag (${uppedTag}) to be in canonical form (${tag}).`;
   data.jsonText = JSON.stringify({
-    lang: uppedTag
+    lang: uppedTag,
   });
-  var result = processor.process(data);
+  result = processor.process(data);
   todo_is(result.lang, tag, expected);
 }
 
 </script>
--- a/dom/manifest/test/test_ManifestProcessor_name_and_short_name.html
+++ b/dom/manifest/test/test_ManifestProcessor_name_and_short_name.html
@@ -11,35 +11,35 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script src="common.js"></script>
   <script>
 /**
  * name and short_name members
  * https://w3c.github.io/manifest/#name-member
  * https://w3c.github.io/manifest/#short_name-member
  **/
 
-'use strict';
+"use strict";
 
 var trimNamesTests = [
   `${seperators}pass${seperators}`,
   `${lineTerminators}pass${lineTerminators}`,
   `${whiteSpace}pass${whiteSpace}`,
-  //BOM
-  `\uFEFFpass\uFEFF`
+  // BOM
+  `\uFEFFpass\uFEFF`,
 ];
-var props = ['name', 'short_name'];
+var props = ["name", "short_name"];
 
 props.forEach((prop) => {
   trimNamesTests.forEach((trimmableString) => {
     var assetion = `Expecting ${prop} to be trimmed.`;
     var obj = {};
     obj[prop] = trimmableString;
     data.jsonText = JSON.stringify(obj);
     var result = processor.process(data);
-    is(result[prop], 'pass', assetion);
+    is(result[prop], "pass", assetion);
   });
 });
 
 /*
  * If the object is not a string, it becomes undefined
  */
 props.forEach((prop) => {
   typeTests.forEach((type) => {
@@ -51,23 +51,23 @@ props.forEach((prop) => {
     SimpleTest.ok(result[prop] === undefined, expected);
   });
 });
 
 /**
  * acceptable names - including long names
  */
 var acceptableNames = [
-  'pass',
+  "pass",
   `pass pass pass pass pass pass pass pass pass pass pass pass pass pass
    pass pass pass pass pass pass pass pass pass pass pass pass pass pass
    pass pass pass pass pass pass pass pass pass pass pass pass pass pass
    pass pass pass pass pass pass pass pass pass pass pass pass`,
-  'これは許容できる名前です',
-  'ນີ້ແມ່ນຊື່ທີ່ຍອມຮັບໄດ້'
+  "これは許容できる名前です",
+  "ນີ້ແມ່ນຊື່ທີ່ຍອມຮັບໄດ້",
 ];
 
 props.forEach((prop) => {
   acceptableNames.forEach((name) => {
     var expected = `Expecting name to be acceptable : ${name}`;
     var obj = {};
     obj[prop] = name;
     data.jsonText = JSON.stringify(obj);
--- a/dom/manifest/test/test_ManifestProcessor_orientation.html
+++ b/dom/manifest/test/test_ManifestProcessor_orientation.html
@@ -9,78 +9,78 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script src="common.js"></script>
   <script>
 /**
  * orientation member
  * https://w3c.github.io/manifest/#orientation-member
  **/
-'use strict';
+"use strict";
 
 typeTests.forEach((type) => {
   var expected = `Expect non-string orientation to be empty string : ${typeof type}.`;
   data.jsonText = JSON.stringify({
-    orientation: type
+    orientation: type,
   });
   var result = processor.process(data);
   is(result.orientation, undefined, expected);
 });
 
 var validOrientations = [
-  'any',
-  'natural',
-  'landscape',
-  'portrait',
-  'portrait-primary',
-  'portrait-secondary',
-  'landscape-primary',
-  'landscape-secondary',
-  'aNy',
-  'NaTuRal',
-  'LANDsCAPE',
-  'PORTRAIT',
-  'portrait-PRIMARY',
-  'portrait-SECONDARY',
-  'LANDSCAPE-primary',
-  'LANDSCAPE-secondary',
+  "any",
+  "natural",
+  "landscape",
+  "portrait",
+  "portrait-primary",
+  "portrait-secondary",
+  "landscape-primary",
+  "landscape-secondary",
+  "aNy",
+  "NaTuRal",
+  "LANDsCAPE",
+  "PORTRAIT",
+  "portrait-PRIMARY",
+  "portrait-SECONDARY",
+  "LANDSCAPE-primary",
+  "LANDSCAPE-secondary",
 ];
 
 validOrientations.forEach((orientation) => {
   var expected = `Expect orientation to be returned: ${orientation}.`;
   data.jsonText = JSON.stringify({ orientation });
   var result = processor.process(data);
   is(result.orientation, orientation.toLowerCase(), expected);
 });
 
 var invalidOrientations = [
-  'all',
-  'ANYMany',
-  'NaTuRalle',
-  'portrait-primary portrait-secondary',
-  'portrait-primary,portrait-secondary',
-  'any-natural',
-  'portrait-landscape',
-  'primary-portrait',
-  'secondary-portrait',
-  'landscape-landscape',
-  'secondary-primary'
+  "all",
+  "ANYMany",
+  "NaTuRalle",
+  "portrait-primary portrait-secondary",
+  "portrait-primary,portrait-secondary",
+  "any-natural",
+  "portrait-landscape",
+  "primary-portrait",
+  "secondary-portrait",
+  "landscape-landscape",
+  "secondary-primary",
 ];
 
 invalidOrientations.forEach((orientation) => {
   var expected = `Expect orientation to be empty string: ${orientation}.`;
   data.jsonText = JSON.stringify({ orientation });
   var result = processor.process(data);
   is(result.orientation, undefined, expected);
 });
 
-//Trim tests
+// Trim tests
 validOrientations.forEach((orientation) => {
   var expected = `Expect trimmed orientation to be returned.`;
   var expandedOrientation = `${seperators}${lineTerminators}${orientation}${lineTerminators}${seperators}`;
   data.jsonText = JSON.stringify({
-    orientation: expandedOrientation
+    orientation: expandedOrientation,
   });
   var result = processor.process(data);
   is(result.orientation, orientation.toLowerCase(), expected);
 });
   </script>
 </head>
--- a/dom/manifest/test/test_ManifestProcessor_scope.html
+++ b/dom/manifest/test/test_ManifestProcessor_scope.html
@@ -5,85 +5,87 @@ https://bugzilla.mozilla.org/show_bug.cg
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 1079453</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script src="common.js"></script>
   <script>
+
 /**
  * Manifest scope
  * https://w3c.github.io/manifest/#scope-member
  **/
-'use strict';
-var expected = 'Expect non-string scope to be undefined';
+"use strict";
+
+var expected = "Expect non-string scope to be undefined";
 typeTests.forEach((type) => {
   data.jsonText = JSON.stringify({
-    scope: type
+    scope: type,
   });
   var result = processor.process(data);
   is(result.scope, undefined, expected);
 });
 
-var expected = 'Expect different origin to be treated as undefined';
+expected = "Expect different origin to be treated as undefined";
 data.jsonText = JSON.stringify({
-  scope: 'http://not-same-origin'
-});
-var result = processor.process(data);
-is(result.scope, undefined, expected);
-
-var expected = 'Expect the empty string to be treated as undefined.';
-data.jsonText = JSON.stringify({
-  scope: ''
+  scope: "http://not-same-origin",
 });
 var result = processor.process(data);
 is(result.scope, undefined, expected);
 
-var expected = 'Resolve URLs relative to manifest.';
-var URLs = ['path', '/path', '../../path'];
+expected = "Expect the empty string to be treated as undefined.";
+data.jsonText = JSON.stringify({
+  scope: "",
+});
+result = processor.process(data);
+is(result.scope, undefined, expected);
+
+expected = "Resolve URLs relative to manifest.";
+var URLs = ["path", "/path", "../../path"];
 URLs.forEach((url) => {
   data.jsonText = JSON.stringify({
     scope: url,
-    start_url: "/path"
+    start_url: "/path",
   });
   var absURL = new URL(url, manifestURL).toString();
-  var result = processor.process(data);
+  result = processor.process(data);
   is(result.scope, absURL, expected);
 });
 
-var expected = 'If start URL is not in scope, return undefined.';
+expected = "If start URL is not in scope, return undefined.";
 data.jsonText = JSON.stringify({
-  scope: 'foo',
-  start_url: 'bar'
+  scope: "foo",
+  start_url: "bar",
 });
-var result = processor.process(data);
+result = processor.process(data);
 is(result.scope, undefined, expected);
 
-var expected = 'If start URL is in scope, use the scope.';
+expected = "If start URL is in scope, use the scope.";
 data.jsonText = JSON.stringify({
-  start_url: 'foobar',
-  scope: 'foo'
+  start_url: "foobar",
+  scope: "foo",
 });
-var result = processor.process(data);
-is(result.scope.toString(), new URL('foo', manifestURL).toString(), expected);
+result = processor.process(data);
+is(result.scope.toString(), new URL("foo", manifestURL).toString(), expected);
 
-var expected = 'Expect start_url to be ' + new URL('foobar', manifestURL).toString();
-is(result.start_url.toString(), new URL('foobar', manifestURL).toString(), expected);
+expected = "Expect start_url to be " + new URL("foobar", manifestURL).toString();
+is(result.start_url.toString(), new URL("foobar", manifestURL).toString(), expected);
 
-var expected = 'If start URL is in scope, use the scope.';
+expected = "If start URL is in scope, use the scope.";
 data.jsonText = JSON.stringify({
-  start_url: '/foo/',
-  scope: '/foo/'
+  start_url: "/foo/",
+  scope: "/foo/",
 });
-var result = processor.process(data);
-is(result.scope.toString(), new URL('/foo/', manifestURL).toString(), expected);
+result = processor.process(data);
+is(result.scope.toString(), new URL("/foo/", manifestURL).toString(), expected);
 
-var expected = 'If start URL is in scope, use the scope.';
+expected = "If start URL is in scope, use the scope.";
 data.jsonText = JSON.stringify({
-  start_url: '.././foo/',
-  scope: '../foo/'
+  start_url: ".././foo/",
+  scope: "../foo/",
 });
-var result = processor.process(data);
-is(result.scope.toString(), new URL('/foo/', manifestURL).toString(), expected);
+result = processor.process(data);
+is(result.scope.toString(), new URL("/foo/", manifestURL).toString(), expected);
 
   </script>
 </head>
--- a/dom/manifest/test/test_ManifestProcessor_start_url.html
+++ b/dom/manifest/test/test_ManifestProcessor_start_url.html
@@ -9,51 +9,54 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script src="common.js"></script>
   <script>
 /**
  * Manifest start_url
  * https://w3c.github.io/manifest/#start_url-member
  **/
-'use strict';
+"use strict";
 typeTests.forEach((type) => {
   var expected = `Expect non - string start_url to be doc's url: ${typeof type}.`;
   data.jsonText = JSON.stringify({
-    start_url: type
+    start_url: type,
   });
   var result = processor.process(data);
   is(result.start_url.toString(), docURL.toString(), expected);
 });
 
-//Not same origin
+// Not same origin
 var expected = `Expect different origin URLs to become document's URL.`;
 data.jsonText = JSON.stringify({
-  start_url: 'http://not-same-origin'
-});
-var result = processor.process(data);
-is(result.start_url.toString(), docURL.toString(), expected);
-
-//Empty string test
-var expected = `Expect empty string for start_url to become document's URL.`;
-data.jsonText = JSON.stringify({
-  start_url: ''
+  start_url: "http://not-same-origin",
 });
 var result = processor.process(data);
 is(result.start_url.toString(), docURL.toString(), expected);
 
-//Resolve URLs relative to manfiest
-var URLs = ['path', '/path', '../../path',
+// Empty string test
+expected = `Expect empty string for start_url to become document's URL.`;
+data.jsonText = JSON.stringify({
+  start_url: "",
+});
+result = processor.process(data);
+is(result.start_url.toString(), docURL.toString(), expected);
+
+
+// Resolve URLs relative to manfiest
+var URLs = ["path", "/path", "../../path",
   `${whiteSpace}path${whiteSpace}`,
   `${whiteSpace}/path`,
-  `${whiteSpace}../../path`
+  `${whiteSpace}../../path`,
 ];
+
 URLs.forEach((url) => {
-  var expected = `Resolve URLs relative to manifest.`;
+  expected = `Resolve URLs relative to manifest.`;
   data.jsonText = JSON.stringify({
-    start_url: url
+    start_url: url,
   });
   var absURL = new URL(url, manifestURL).toString();
-  var result = processor.process(data);
+  result = processor.process(data);
   is(result.start_url.toString(), absURL, expected);
 });
+
   </script>
 </head>
--- a/dom/manifest/test/test_ManifestProcessor_theme_color.html
+++ b/dom/manifest/test/test_ManifestProcessor_theme_color.html
@@ -9,110 +9,110 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script src="common.js"></script>
   <script>
 /**
  * theme_color member
  * https://w3c.github.io/manifest/#theme_color-member
  **/
-'use strict';
+"use strict";
 
 typeTests.forEach(type => {
   data.jsonText = JSON.stringify({
-    theme_color: type
+    theme_color: type,
   });
   var result = processor.process(data);
 
   is(result.theme_color, undefined, `Expect non-string theme_color to be undefined: ${typeof type}.`);
 });
 
 var validThemeColors = [
-  'maroon',
-  '#f00',
-  '#ff0000',
-  'rgb(255,0,0)',
-  'rgb(255,0,0,1)',
-  'rgb(255,0,0,1.0)',
-  'rgb(255,0,0,100%)',
-  'rgb(255 0 0)',
-  'rgb(255 0 0 / 1)',
-  'rgb(255 0 0 / 1.0)',
-  'rgb(255 0 0 / 100%)',
-  'rgb(100%, 0%, 0%)',
-  'rgb(100%, 0%, 0%, 1)',
-  'rgb(100%, 0%, 0%, 1.0)',
-  'rgb(100%, 0%, 0%, 100%)',
-  'rgb(100% 0% 0%)',
-  'rgb(100% 0% 0% / 1)',
-  'rgb(100%, 0%, 0%, 1.0)',
-  'rgb(100%, 0%, 0%, 100%)',
-  'rgb(300,0,0)',
-  'rgb(300 0 0)',
-  'rgb(255,-10,0)',
-  'rgb(110%, 0%, 0%)',
-  'rgba(255,0,0)',
-  'rgba(255,0,0,1)',
-  'rgba(255 0 0 / 1)',
-  'rgba(100%,0%,0%,1)',
-  'rgba(0,0,255,0.5)',
-  'rgba(100%, 50%, 0%, 0.1)',
-  'hsl(120, 100%, 50%)',
-  'hsl(120 100% 50%)',
-  'hsl(120, 100%, 50%, 1.0)',
-  'hsl(120 100% 50% / 1.0)',
-  'hsla(120, 100%, 50%)',
-  'hsla(120 100% 50%)',
-  'hsla(120, 100%, 50%, 1.0)',
-  'hsla(120 100% 50% / 1.0)',
-  'hsl(120deg, 100%, 50%)',
-  'hsl(133.33333333grad, 100%, 50%)',
-  'hsl(2.0943951024rad, 100%, 50%)',
-  'hsl(0.3333333333turn, 100%, 50%)',
+  "maroon",
+  "#f00",
+  "#ff0000",
+  "rgb(255,0,0)",
+  "rgb(255,0,0,1)",
+  "rgb(255,0,0,1.0)",
+  "rgb(255,0,0,100%)",
+  "rgb(255 0 0)",
+  "rgb(255 0 0 / 1)",
+  "rgb(255 0 0 / 1.0)",
+  "rgb(255 0 0 / 100%)",
+  "rgb(100%, 0%, 0%)",
+  "rgb(100%, 0%, 0%, 1)",
+  "rgb(100%, 0%, 0%, 1.0)",
+  "rgb(100%, 0%, 0%, 100%)",
+  "rgb(100% 0% 0%)",
+  "rgb(100% 0% 0% / 1)",
+  "rgb(100%, 0%, 0%, 1.0)",
+  "rgb(100%, 0%, 0%, 100%)",
+  "rgb(300,0,0)",
+  "rgb(300 0 0)",
+  "rgb(255,-10,0)",
+  "rgb(110%, 0%, 0%)",
+  "rgba(255,0,0)",
+  "rgba(255,0,0,1)",
+  "rgba(255 0 0 / 1)",
+  "rgba(100%,0%,0%,1)",
+  "rgba(0,0,255,0.5)",
+  "rgba(100%, 50%, 0%, 0.1)",
+  "hsl(120, 100%, 50%)",
+  "hsl(120 100% 50%)",
+  "hsl(120, 100%, 50%, 1.0)",
+  "hsl(120 100% 50% / 1.0)",
+  "hsla(120, 100%, 50%)",
+  "hsla(120 100% 50%)",
+  "hsla(120, 100%, 50%, 1.0)",
+  "hsla(120 100% 50% / 1.0)",
+  "hsl(120deg, 100%, 50%)",
+  "hsl(133.33333333grad, 100%, 50%)",
+  "hsl(2.0943951024rad, 100%, 50%)",
+  "hsl(0.3333333333turn, 100%, 50%)",
 ];
 
 validThemeColors.forEach(theme_color => {
   data.jsonText = JSON.stringify({
-    theme_color: theme_color
+    theme_color,
   });
   var result = processor.process(data);
 
   is(result.theme_color, theme_color, `Expect theme_color to be returned: ${theme_color}.`);
 });
 
 var invalidThemeColors = [
-  'marooon',
-  'f000000',
-  '#ff00000',
-  'rgb(100, 0%, 0%)',
-  'rgb(255,0)',
-  'rbg(255,-10,0)',
-  'rgb(110, 0%, 0%)',
-  '(255,0,0) }',
-  'rgba(255)',
-  ' rgb(100%,0%,0%) }',
-  'hsl(120, 100%, 50)',
-  'hsl(120, 100%, 50.0)',
-  'hsl 120, 100%, 50%',
-  'hsla{120, 100%, 50%, 1}',
-]
+  "marooon",
+  "f000000",
+  "#ff00000",
+  "rgb(100, 0%, 0%)",
+  "rgb(255,0)",
+  "rbg(255,-10,0)",
+  "rgb(110, 0%, 0%)",
+  "(255,0,0) }",
+  "rgba(255)",
+  " rgb(100%,0%,0%) }",
+  "hsl(120, 100%, 50)",
+  "hsl(120, 100%, 50.0)",
+  "hsl 120, 100%, 50%",
+  "hsla{120, 100%, 50%, 1}",
+];
 
 invalidThemeColors.forEach(theme_color => {
   data.jsonText = JSON.stringify({
-    theme_color: theme_color
+    theme_color,
   });
   var result = processor.process(data);
 
   is(result.theme_color, undefined, `Expect theme_color to be undefined: ${theme_color}.`);
 });
 
 // Trim tests
 validThemeColors.forEach(theme_color => {
   var expandedThemeColor = `${seperators}${lineTerminators}${theme_color}${lineTerminators}${seperators}`;
   data.jsonText = JSON.stringify({
-    theme_color: expandedThemeColor
+    theme_color: expandedThemeColor,
   });
   var result = processor.process(data);
 
   is(result.theme_color, theme_color, `Expect trimmed theme_color to be returned.`);
 });
   </script>
 </head>
--- a/dom/manifest/test/test_ManifestProcessor_warnings.html
+++ b/dom/manifest/test/test_ManifestProcessor_warnings.html
@@ -5,85 +5,85 @@ https://bugzilla.mozilla.org/show_bug.cg
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 1086997</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script src="common.js"></script>
   <script>
-'use strict';
+"use strict";
 
 const {
-  ConsoleAPI
-} = SpecialPowers.Cu.import('resource://gre/modules/Console.jsm');
+  ConsoleAPI,
+} = SpecialPowers.Cu.import("resource://gre/modules/Console.jsm");
 
 var warning = null;
 
 var originalWarn = ConsoleAPI.prototype.warn;
 ConsoleAPI.prototype.warn = function(aWarning) {
   warning = aWarning;
 };
 
 [
   {
     func: () => data.jsonText = JSON.stringify(1),
-    warning: 'Manifest should be an object.',
+    warning: "Manifest should be an object.",
   },
   {
     func: () => data.jsonText = JSON.stringify(null),
-    warning: 'Manifest should be an object.',
+    warning: "Manifest should be an object.",
   },
   {
-    func: () => data.jsonText = JSON.stringify('a string'),
-    warning: 'Manifest should be an object.',
+    func: () => data.jsonText = JSON.stringify("a string"),
+    warning: "Manifest should be an object.",
   },
   {
     func: () => data.jsonText = JSON.stringify({
-      scope: 'https://www.mozilla.org',
+      scope: "https://www.mozilla.org",
     }),
-    warning: 'The scope URL must be same origin as document.',
+    warning: "The scope URL must be same origin as document.",
   },
   {
     func: () => data.jsonText = JSON.stringify({
-      scope: 'foo',
-      start_url: 'bar',
+      scope: "foo",
+      start_url: "bar",
     }),
-    warning: 'The start URL is outside the scope, so the scope is invalid.',
+    warning: "The start URL is outside the scope, so the scope is invalid.",
   },
   {
     func: () => data.jsonText = JSON.stringify({
-      start_url: 'https://www.mozilla.org',
+      start_url: "https://www.mozilla.org",
     }),
-    warning: 'The start URL must be same origin as document.',
+    warning: "The start URL must be same origin as document.",
   },
   {
     func: () => data.jsonText = JSON.stringify({
       start_url: 42,
     }),
-    warning: 'Expected the manifest\u2019s start_url member to be a string.',
+    warning: "Expected the manifest\u2019s start_url member to be a string.",
   },
   {
     func: () => data.jsonText = JSON.stringify({
-      theme_color: '42',
+      theme_color: "42",
     }),
-    warning: 'theme_color: 42 is not a valid CSS color.',
+    warning: "theme_color: 42 is not a valid CSS color.",
   },
   {
     func: () => data.jsonText = JSON.stringify({
-      background_color: '42',
+      background_color: "42",
     }),
-    warning: 'background_color: 42 is not a valid CSS color.',
+    warning: "background_color: 42 is not a valid CSS color.",
   },
 ].forEach(function(test) {
   test.func();
 
   processor.process(data);
 
-  is(warning, test.warning, 'Correct warning.');
+  is(warning, test.warning, "Correct warning.");
 
   warning = null;
   data.manifestURL = manifestURL;
   data.docURL = docURL;
 });
 
 ConsoleAPI.prototype.warn = originalWarn;
   </script>
--- a/dom/manifest/test/test_window_onappinstalled_event.html
+++ b/dom/manifest/test/test_window_onappinstalled_event.html
@@ -49,17 +49,18 @@ https://bugzilla.mozilla.org/show_bug.cg
   function checkImplementation(ifrWindow) {
     return new Promise((resolve, reject) => {
       const hasOnAppInstalledProp = ifrWindow.hasOwnProperty("onappinstalled");
       ok(hasOnAppInstalledProp, "window has own onappinstalled property");
 
       // no point in continuing
       if (!hasOnAppInstalledProp) {
         const err = new Error("No 'onappinstalled' IDL attribute. Aborting early.");
-        return reject(err);
+        reject(err);
+        return;
       }
       is(ifrWindow.onappinstalled, null, "window install is initially set to null");
 
       // Check that enumerable, configurable, and has a getter and setter.
       const objDescriptor = Object.getOwnPropertyDescriptor(ifrWindow, "onappinstalled");
       ok(objDescriptor.enumerable, "is enumerable");
       ok(objDescriptor.configurable, "is configurable");
       ok(objDescriptor.hasOwnProperty("get"), "has getter");
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -969,16 +969,24 @@ void MediaDecoder::UpdateVideoDecodeMode
   // If an element is in-tree with UNTRACKED visibility, the visibility is
   // incomplete and don't update the video decode mode.
   if (mIsElementInTree && mElementVisibility == Visibility::UNTRACKED) {
     LOG("UpdateVideoDecodeMode(), early return because we have incomplete "
         "visibility states.");
     return;
   }
 
+  // Seeking is required when leaving suspend mode.
+  if (!mMediaSeekable) {
+    LOG("UpdateVideoDecodeMode(), set Normal because the media is not "
+        "seekable");
+    mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Normal);
+    return;
+  }
+
   // If mHasSuspendTaint is set, never suspend the video decoder.
   if (mHasSuspendTaint) {
     LOG("UpdateVideoDecodeMode(), set Normal because the element has been "
         "tainted.");
     mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Normal);
     return;
   }
 
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -2914,16 +2914,19 @@ void ScriptLoader::ReportErrorToConsole(
   }
 
   bool isScript = !aRequest->IsModuleRequest();
   const char* message;
   if (aResult == NS_ERROR_MALFORMED_URI) {
     message = isScript ? "ScriptSourceMalformed" : "ModuleSourceMalformed";
   } else if (aResult == NS_ERROR_DOM_BAD_URI) {
     message = isScript ? "ScriptSourceNotAllowed" : "ModuleSourceNotAllowed";
+  } else if (aResult == NS_ERROR_TRACKING_URI) {
+    // Tracking protection errors already show their own console messages.
+    return;
   } else {
     message = isScript ? "ScriptSourceLoadFailed" : "ModuleSourceLoadFailed";
   }
 
   NS_ConvertUTF8toUTF16 url(aRequest->mURI->GetSpecOrDefault());
   const char16_t* params[] = {url.get()};
 
   uint32_t lineNo = aRequest->Element()->GetScriptLineNumber();
--- a/dom/serviceworkers/ServiceWorkerInterceptController.cpp
+++ b/dom/serviceworkers/ServiceWorkerInterceptController.cpp
@@ -33,32 +33,35 @@ ServiceWorkerInterceptController::Should
   // window.
   if (!nsContentUtils::IsNonSubresourceRequest(aChannel)) {
     const Maybe<ServiceWorkerDescriptor>& controller =
         loadInfo->GetController();
     *aShouldIntercept = controller.isSome();
     return NS_OK;
   }
 
+  nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateCodebasePrincipal(
+      aURI, loadInfo->GetOriginAttributes());
+
+  // First check with the ServiceWorkerManager for a matching service worker.
+  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+  if (!swm || !swm->IsAvailable(principal, aURI)) {
+    return NS_OK;
+  }
+
+  // Then check to see if we are allowed to control the window.
+  // It is important to check for the availability of the service worker first
+  // to avoid showing warnings about the use of third-party cookies in the UI
+  // unnecessarily when no service worker is being accessed.
   if (nsContentUtils::StorageAllowedForChannel(aChannel) !=
       nsContentUtils::StorageAccess::eAllow) {
     return NS_OK;
   }
 
-  nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateCodebasePrincipal(
-      aURI, loadInfo->GetOriginAttributes());
-
-  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
-  if (!swm) {
-    return NS_OK;
-  }
-
-  // We're allowed to control a window, so check with the ServiceWorkerManager
-  // for a matching service worker.
-  *aShouldIntercept = swm->IsAvailable(principal, aURI);
+  *aShouldIntercept = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ServiceWorkerInterceptController::ChannelIntercepted(
     nsIInterceptedChannel* aChannel) {
   // Note, do not cancel the interception here.  The caller will try to
   // ResetInterception() on error.
--- a/dom/vr/VRDisplay.cpp
+++ b/dom/vr/VRDisplay.cpp
@@ -371,17 +371,17 @@ void VRDisplay::UpdateFrameInfo() {
    * composition and lazily created here in order to receive mid-frame
    * pose-prediction updates while still ensuring conformance to the WebVR spec
    * requirements.
    *
    * If we are not presenting WebVR content, the frame will never end and we
    * should return the latest frame data always.
    */
   if (mFrameInfo.IsDirty() || !mPresentation) {
-    gfx::VRHMDSensorState state = mClient->GetSensorState();
+    const gfx::VRHMDSensorState& state = mClient->GetSensorState();
     const gfx::VRDisplayInfo& info = mClient->GetDisplayInfo();
     mFrameInfo.Update(info, state, mDepthNear, mDepthFar);
   }
 }
 
 bool VRDisplay::GetFrameData(VRFrameData& aFrameData) {
   UpdateFrameInfo();
   if (!(mFrameInfo.mVRState.flags &
--- a/dom/workers/WorkerRef.cpp
+++ b/dom/workers/WorkerRef.cpp
@@ -84,41 +84,41 @@ WorkerRef::~WorkerRef() { NS_ASSERT_OWNI
 void WorkerRef::Notify() {
   MOZ_ASSERT(mHolder);
   NS_ASSERT_OWNINGTHREAD(WorkerRef);
 
   if (!mCallback) {
     return;
   }
 
-  std::function<void()> callback = mCallback;
-  mCallback = nullptr;
+  std::function<void()> callback = std::move(mCallback);
+  MOZ_ASSERT(!mCallback);
 
   callback();
 }
 
 // ----------------------------------------------------------------------------
 // WeakWorkerRef
 
 /* static */ already_AddRefed<WeakWorkerRef> WeakWorkerRef::Create(
-    WorkerPrivate* aWorkerPrivate, const std::function<void()>& aCallback) {
+    WorkerPrivate* aWorkerPrivate, std::function<void()>&& aCallback) {
   MOZ_ASSERT(aWorkerPrivate);
   aWorkerPrivate->AssertIsOnWorkerThread();
 
   RefPtr<WeakWorkerRef> ref = new WeakWorkerRef(aWorkerPrivate);
 
   // This holder doesn't keep the worker alive.
   UniquePtr<Holder> holder(new Holder("WeakWorkerRef::Holder", ref,
                                       WorkerHolder::AllowIdleShutdownStart));
   if (NS_WARN_IF(!holder->HoldWorker(aWorkerPrivate, Canceling))) {
     return nullptr;
   }
 
   ref->mHolder = std::move(holder);
-  ref->mCallback = aCallback;
+  ref->mCallback = std::move(aCallback);
 
   return ref.forget();
 }
 
 WeakWorkerRef::WeakWorkerRef(WorkerPrivate* aWorkerPrivate)
     : WorkerRef(aWorkerPrivate) {}
 
 WeakWorkerRef::~WeakWorkerRef() = default;
@@ -139,31 +139,31 @@ WorkerPrivate* WeakWorkerRef::GetUnsafeP
   return mWorkerPrivate;
 }
 
 // ----------------------------------------------------------------------------
 // StrongWorkerRef
 
 /* static */ already_AddRefed<StrongWorkerRef> StrongWorkerRef::Create(
     WorkerPrivate* aWorkerPrivate, const char* aName,
-    const std::function<void()>& aCallback) {
+    std::function<void()>&& aCallback) {
   MOZ_ASSERT(aWorkerPrivate);
   MOZ_ASSERT(aName);
 
   RefPtr<StrongWorkerRef> ref = new StrongWorkerRef(aWorkerPrivate);
 
   // The worker is kept alive by this holder.
   UniquePtr<Holder> holder(
       new Holder(aName, ref, WorkerHolder::PreventIdleShutdownStart));
   if (NS_WARN_IF(!holder->HoldWorker(aWorkerPrivate, Canceling))) {
     return nullptr;
   }
 
   ref->mHolder = std::move(holder);
-  ref->mCallback = aCallback;
+  ref->mCallback = std::move(aCallback);
 
   return ref.forget();
 }
 
 StrongWorkerRef::StrongWorkerRef(WorkerPrivate* aWorkerPrivate)
     : WorkerRef(aWorkerPrivate) {}
 
 StrongWorkerRef::~StrongWorkerRef() { NS_ASSERT_OWNINGTHREAD(StrongWorkerRef); }
--- a/dom/workers/WorkerRef.h
+++ b/dom/workers/WorkerRef.h
@@ -103,17 +103,17 @@ class WorkerRef {
 
   std::function<void()> mCallback;
 };
 
 class WeakWorkerRef final : public WorkerRef {
  public:
   static already_AddRefed<WeakWorkerRef> Create(
       WorkerPrivate* aWorkerPrivate,
-      const std::function<void()>& aCallback = nullptr);
+      std::function<void()>&& aCallback = nullptr);
 
   WorkerPrivate* GetPrivate() const;
 
   // This can be called on any thread. It's racy and, in general, the wrong
   // choice.
   WorkerPrivate* GetUnsafePrivate() const;
 
  private:
@@ -122,17 +122,17 @@ class WeakWorkerRef final : public Worke
 
   void Notify() override;
 };
 
 class StrongWorkerRef final : public WorkerRef {
  public:
   static already_AddRefed<StrongWorkerRef> Create(
       WorkerPrivate* aWorkerPrivate, const char* aName,
-      const std::function<void()>& aCallback = nullptr);
+      std::function<void()>&& aCallback = nullptr);
 
   WorkerPrivate* Private() const;
 
  private:
   friend class WeakWorkerRef;
   friend class ThreadSafeWorkerRef;
 
   explicit StrongWorkerRef(WorkerPrivate* aWorkerPrivate);
--- a/gfx/tests/crashtests/423110-1.xhtml
+++ b/gfx/tests/crashtests/423110-1.xhtml
@@ -1,1 +1,1 @@
-<html xmlns="http://www.w3.org/1999/xhtml" style="min-width: -moz-max-content; float: left;"><head style="padding: 200%; display: -moz-inline-box; float: inherit;"></head></html>
+<html xmlns="http://www.w3.org/1999/xhtml" style="min-width: max-content; float: left;"><head style="padding: 200%; display: -moz-inline-box; float: inherit;"></head></html>
--- a/gfx/vr/VRDisplayClient.cpp
+++ b/gfx/vr/VRDisplayClient.cpp
@@ -262,17 +262,17 @@ void VRDisplayClient::FireGamepadEvents(
   }
 
   // Note that VRControllerState is asserted to be a POD type and memcpy is
   // safe.
   memcpy(mLastEventControllerState, mDisplayInfo.mControllerState,
          sizeof(VRControllerState) * kVRControllerMaxCount);
 }
 
-VRHMDSensorState VRDisplayClient::GetSensorState() {
+const VRHMDSensorState& VRDisplayClient::GetSensorState() const {
   return mDisplayInfo.GetSensorState();
 }
 
 bool VRDisplayClient::GetIsConnected() const {
   return mDisplayInfo.GetIsConnected();
 }
 
 void VRDisplayClient::NotifyDisconnected() {
--- a/gfx/vr/VRDisplayClient.h
+++ b/gfx/vr/VRDisplayClient.h
@@ -24,17 +24,17 @@ class VRDisplayClient {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRDisplayClient)
 
   explicit VRDisplayClient(const VRDisplayInfo& aDisplayInfo);
 
   void UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo);
   void UpdateSubmitFrameResult(const VRSubmitFrameResultInfo& aResult);
 
   const VRDisplayInfo& GetDisplayInfo() const { return mDisplayInfo; }
-  virtual VRHMDSensorState GetSensorState();
+  virtual const VRHMDSensorState& GetSensorState() const;
   void GetSubmitFrameResult(VRSubmitFrameResultInfo& aResult);
 
   virtual void ZeroSensor();
 
   already_AddRefed<VRDisplayPresentation> BeginPresentation(
       const nsTArray<dom::VRLayer>& aLayers, uint32_t aGroup);
   void PresentationDestroyed();
 
--- a/gfx/vr/VRDisplayHost.h
+++ b/gfx/vr/VRDisplayHost.h
@@ -86,17 +86,17 @@ class VRDisplayHost {
 
   VRDisplayInfo mDisplayInfo;
 
   nsTArray<VRLayerParent*> mLayers;
   // Weak reference to mLayers entries are cleared in
   // VRLayerParent destructor
 
  protected:
-  virtual VRHMDSensorState GetSensorState() = 0;
+  virtual VRHMDSensorState& GetSensorState() = 0;
 
   RefPtr<VRThread> mSubmitThread;
 
  private:
   void SubmitFrameInternal(const layers::SurfaceDescriptor& aTexture,
                            uint64_t aFrameId, const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect);
   void CheckWatchDog();
--- a/gfx/vr/gfxVRExternal.cpp
+++ b/gfx/vr/gfxVRExternal.cpp
@@ -94,17 +94,17 @@ void VRDisplayExternal::Run10msTasks() {
 
 void VRDisplayExternal::ExpireNavigationTransition() {
   if (!mVRNavigationTransitionEnd.IsNull() &&
       TimeStamp::Now() > mVRNavigationTransitionEnd) {
     mBrowserState.navigationTransitionActive = false;
   }
 }
 
-VRHMDSensorState VRDisplayExternal::GetSensorState() {
+VRHMDSensorState& VRDisplayExternal::GetSensorState() {
   return mLastSensorState;
 }
 
 void VRDisplayExternal::StartPresentation() {
   if (mBrowserState.presentationActive) {
     return;
   }
   mTelemetry.Clear();
--- a/gfx/vr/gfxVRExternal.h
+++ b/gfx/vr/gfxVRExternal.h
@@ -26,17 +26,17 @@ namespace mozilla {
 namespace gfx {
 namespace impl {
 
 class VRDisplayExternal : public VRDisplayHost {
  public:
   void ZeroSensor() override;
 
  protected:
-  VRHMDSensorState GetSensorState() override;
+  VRHMDSensorState& GetSensorState() override;
   void StartPresentation() override;
   void StopPresentation() override;
   void StartVRNavigation() override;
   void StopVRNavigation(const TimeDuration& aTimeout) override;
 
   bool SubmitFrame(const layers::SurfaceDescriptor& aTexture, uint64_t aFrameId,
                    const gfx::Rect& aLeftEyeRect,
                    const gfx::Rect& aRightEyeRect) override;
--- a/gfx/vr/gfxVRPuppet.cpp
+++ b/gfx/vr/gfxVRPuppet.cpp
@@ -134,17 +134,17 @@ void VRDisplayPuppet::SetDisplayInfo(con
   memcpy(&state.mEyeTranslation, &aDisplayInfo.mDisplayState.mEyeTranslation,
          sizeof(state.mEyeTranslation[0]) * VRDisplayState::NumEyes);
 }
 
 void VRDisplayPuppet::Destroy() { StopPresentation(); }
 
 void VRDisplayPuppet::ZeroSensor() {}
 
-VRHMDSensorState VRDisplayPuppet::GetSensorState() {
+VRHMDSensorState& VRDisplayPuppet::GetSensorState() {
   mSensorState.inputFrameID = mDisplayInfo.mFrameId;
 
   Matrix4x4 matHeadToEye[2];
   for (uint32_t eye = 0; eye < 2; ++eye) {
     matHeadToEye[eye].PreTranslate(mDisplayInfo.GetEyeTranslation(eye));
   }
   mSensorState.CalcViewMatrices(matHeadToEye);
 
--- a/gfx/vr/gfxVRPuppet.h
+++ b/gfx/vr/gfxVRPuppet.h
@@ -31,17 +31,17 @@ namespace impl {
 
 class VRDisplayPuppet : public VRDisplayLocal {
  public:
   void SetDisplayInfo(const VRDisplayInfo& aDisplayInfo);
   void SetSensorState(const VRHMDSensorState& aSensorState);
   void ZeroSensor() override;
 
  protected:
-  virtual VRHMDSensorState GetSensorState() override;
+  virtual VRHMDSensorState& GetSensorState() override;
   virtual void StartPresentation() override;
   virtual void StopPresentation() override;
 #if defined(XP_WIN)
   virtual bool SubmitFrame(ID3D11Texture2D* aSource, const IntSize& aSize,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) override;
 #elif defined(XP_MACOSX)
   virtual bool SubmitFrame(MacIOSurface* aMacIOSurface, const IntSize& aSize,
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-b0e4a13491f9f780222d3aa374fa8f0c07508e73
+14df2e43424ebc38044158f716c769d26e35b161
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -10,20 +10,21 @@ use clip_scroll_tree::{ClipScrollTree, R
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheAddress};
 use gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders, ZBufferId, ZBufferIdGenerator};
 use gpu_types::{ClipMaskInstance, SplitCompositeInstance};
 use gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, TextureSource};
 use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureSurface};
-use prim_store::{DeferredResolve, PrimitiveTemplateKind, PrimitiveDataStore};
-use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveInstanceKind};
+use prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind};
 use prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
 use prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex};
+use prim_store::image::ImageSource;
+use render_backend::FrameResources;
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
 use scene::FilterOpHelpers;
 use smallvec::SmallVec;
 use std::{f32, i32, usize};
 use tiling::{RenderTargetContext};
@@ -544,20 +545,20 @@ impl AlphaBatchBuilder {
         // Get the clip task address for the global primitive, if one was set.
         let clip_task_address = get_clip_task_address(
             &ctx.scratch.clip_mask_instances,
             prim_instance.clip_task_index,
             0,
             render_tasks,
         ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
-        let prim_data = &ctx.resources.as_common_data(&prim_instance);
+        let prim_common_data = &ctx.resources.as_common_data(&prim_instance);
         let prim_rect = LayoutRect::new(
             prim_instance.prim_origin,
-            prim_data.prim_size,
+            prim_common_data.prim_size,
         );
 
         match prim_instance.kind {
             PrimitiveInstanceKind::Clear { data_handle } => {
                 let prim_data = &ctx.resources.prim_data_store[data_handle];
                 let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
 
                 // TODO(gw): We can abstract some of the common code below into
@@ -597,18 +598,19 @@ impl AlphaBatchBuilder {
                 self.batch_list.push_single_instance(
                     batch_key,
                     bounding_rect,
                     z_id,
                     PrimitiveInstanceData::from(instance),
                 );
             }
             PrimitiveInstanceKind::NormalBorder { data_handle, ref cache_handles, .. } => {
-                let prim_data = &ctx.resources.prim_data_store[data_handle];
-                let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
+                let prim_data = &ctx.resources.normal_border_data_store[data_handle];
+                let common_data = &prim_data.common;
+                let prim_cache_address = gpu_cache.get_address(&common_data.gpu_cache_handle);
                 let cache_handles = &ctx.scratch.border_cache_handles[*cache_handles];
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
                 let mut segment_data: SmallVec<[SegmentInstanceData; 8]> = SmallVec::new();
 
                 // Collect the segment instance data from each render
                 // task for each valid edge / corner of the border.
 
                 for handle in cache_handles {
@@ -619,17 +621,17 @@ impl AlphaBatchBuilder {
                     segment_data.push(
                         SegmentInstanceData {
                             textures: BatchTextures::color(cache_item.texture_id),
                             user_data: cache_item.uv_rect_handle.as_int(gpu_cache),
                         }
                     );
                 }
 
-                let non_segmented_blend_mode = if !prim_data.opacity.is_opaque ||
+                let non_segmented_blend_mode = if !common_data.opacity.is_opaque ||
                     prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
                     transform_kind == TransformedRectKind::Complex
                 {
                     specified_blend_mode
                 } else {
                     BlendMode::None
                 };
 
@@ -653,23 +655,20 @@ impl AlphaBatchBuilder {
                 );
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
                     z_id,
                     batch_params.prim_user_data,
                 );
 
-                let template = match prim_data.kind {
-                    PrimitiveTemplateKind::NormalBorder { ref template, .. } => template,
-                    _ => unreachable!()
-                };
+                let border_data = &prim_data.kind;
                 self.add_segmented_prim_to_batch(
-                    Some(template.brush_segments.as_slice()),
-                    prim_data.opacity,
+                    Some(border_data.brush_segments.as_slice()),
+                    common_data.opacity,
                     &batch_params,
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     clip_task_address,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
@@ -1380,38 +1379,34 @@ impl AlphaBatchBuilder {
                             transforms,
                             root_spatial_node_index,
                             z_generator,
                         );
                     }
                 }
             }
             PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
-                let prim_data = &ctx.resources.prim_data_store[data_handle];
-                let (request, brush_segments) = match &prim_data.kind {
-                    PrimitiveTemplateKind::ImageBorder { request, brush_segments, .. } => {
-                        (request, brush_segments)
-                    }
-                    _ => unreachable!()
-                };
+                let prim_data = &ctx.resources.image_border_data_store[data_handle];
+                let common_data = &prim_data.common;
+                let border_data = &prim_data.kind;
 
                 let cache_item = resolve_image(
-                    *request,
+                    border_data.request,
                     ctx.resource_cache,
                     gpu_cache,
                     deferred_resolves,
                 );
                 if cache_item.texture_id == TextureSource::Invalid {
                     return;
                 }
 
                 let textures = BatchTextures::color(cache_item.texture_id);
-                let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
+                let prim_cache_address = gpu_cache.get_address(&common_data.gpu_cache_handle);
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
-                let non_segmented_blend_mode = if !prim_data.opacity.is_opaque ||
+                let non_segmented_blend_mode = if !common_data.opacity.is_opaque ||
                     prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
                     transform_kind == TransformedRectKind::Complex
                 {
                     specified_blend_mode
                 } else {
                     BlendMode::None
                 };
 
@@ -1437,18 +1432,18 @@ impl AlphaBatchBuilder {
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
                     z_id,
                     batch_params.prim_user_data,
                 );
 
                 self.add_segmented_prim_to_batch(
-                    Some(brush_segments.as_slice()),
-                    prim_data.opacity,
+                    Some(border_data.brush_segments.as_slice()),
+                    common_data.opacity,
                     &batch_params,
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     clip_task_address,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
@@ -1516,36 +1511,30 @@ impl AlphaBatchBuilder {
                     transform_kind,
                     render_tasks,
                     z_id,
                     prim_instance.clip_task_index,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::YuvImage { data_handle, segment_instance_index, .. } => {
-                let prim_data = &ctx.resources.prim_data_store[data_handle];
-                let (format, yuv_key, image_rendering, color_depth, color_space) = match prim_data.kind {
-                    PrimitiveTemplateKind::YuvImage { ref format, yuv_key, ref image_rendering, ref color_depth, ref color_space, .. } => {
-                        (format, yuv_key, image_rendering, color_depth, color_space)
-                    }
-                    _ => unreachable!()
-                };
+                let yuv_image_data = &ctx.resources.yuv_image_data_store[data_handle].kind;
                 let mut textures = BatchTextures::no_texture();
                 let mut uv_rect_addresses = [0; 3];
 
                 //yuv channel
-                let channel_count = format.get_plane_num();
+                let channel_count = yuv_image_data.format.get_plane_num();
                 debug_assert!(channel_count <= 3);
                 for channel in 0 .. channel_count {
-                    let image_key = yuv_key[channel];
+                    let image_key = yuv_image_data.yuv_key[channel];
 
                     let cache_item = resolve_image(
                         ImageRequest {
                             key: image_key,
-                            rendering: *image_rendering,
+                            rendering: yuv_image_data.image_rendering,
                             tile: None,
                         },
                         ctx.resource_cache,
                         gpu_cache,
                         deferred_resolves,
                     );
 
                     if cache_item.texture_id == TextureSource::Invalid {
@@ -1555,53 +1544,53 @@ impl AlphaBatchBuilder {
 
                     textures.colors[channel] = cache_item.texture_id;
                     uv_rect_addresses[channel] = cache_item.uv_rect_handle.as_int(gpu_cache);
                 }
 
                 // All yuv textures should be the same type.
                 let buffer_kind = get_buffer_kind(textures.colors[0]);
                 assert!(
-                    textures.colors[1 .. format.get_plane_num()]
+                    textures.colors[1 .. yuv_image_data.format.get_plane_num()]
                         .iter()
                         .all(|&tid| buffer_kind == get_buffer_kind(tid))
                 );
 
                 let kind = BrushBatchKind::YuvImage(
                     buffer_kind,
-                    *format,
-                    *color_depth,
-                    *color_space,
+                    yuv_image_data.format,
+                    yuv_image_data.color_depth,
+                    yuv_image_data.color_space,
                 );
 
                 let batch_params = BrushBatchParameters::shared(
                     kind,
                     textures,
                     [
                         uv_rect_addresses[0],
                         uv_rect_addresses[1],
                         uv_rect_addresses[2],
                     ],
                     0,
                 );
 
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
 
-                let non_segmented_blend_mode = if !prim_data.opacity.is_opaque ||
+                let non_segmented_blend_mode = if !prim_common_data.opacity.is_opaque ||
                     prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
                     transform_kind == TransformedRectKind::Complex
                 {
                     specified_blend_mode
                 } else {
                     BlendMode::None
                 };
 
                 debug_assert!(segment_instance_index != SegmentInstanceIndex::INVALID);
                 let (prim_cache_address, segments) = if segment_instance_index == SegmentInstanceIndex::UNUSED {
-                    (gpu_cache.get_address(&prim_data.gpu_cache_handle), None)
+                    (gpu_cache.get_address(&prim_common_data.gpu_cache_handle), None)
                 } else {
                     let segment_instance = &ctx.scratch.segment_instances[segment_instance_index];
                     let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
                     (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
@@ -1615,52 +1604,47 @@ impl AlphaBatchBuilder {
                 let prim_header_index = prim_headers.push(
                     &prim_header,
                     z_id,
                     batch_params.prim_user_data,
                 );
 
                 self.add_segmented_prim_to_batch(
                     segments,
-                    prim_data.opacity,
+                    prim_common_data.opacity,
                     &batch_params,
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     clip_task_address,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
                     z_id,
                     prim_instance.clip_task_index,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
-                let prim_data = &ctx.resources.prim_data_store[data_handle];
-                let (source, alpha_type, key, image_rendering) = match prim_data.kind {
-                    PrimitiveTemplateKind::Image { ref source, alpha_type, key, image_rendering, .. } => {
-                        (source, alpha_type, key, image_rendering)
-                    }
-                    _ => unreachable!()
-                };
+                let image_data = &ctx.resources.image_data_store[data_handle].kind;
+                let common_data = &ctx.resources.image_data_store[data_handle].common;
                 let image_instance = &ctx.prim_store.images[image_instance_index];
                 let opacity_binding = ctx.prim_store.get_opacity_binding(image_instance.opacity_binding_index);
-                let specified_blend_mode = match alpha_type {
+                let specified_blend_mode = match image_data.alpha_type {
                     AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
                     AlphaType::Alpha => BlendMode::Alpha,
                 };
                 let request = ImageRequest {
-                    key: key,
-                    rendering: image_rendering,
+                    key: image_data.key,
+                    rendering: image_data.image_rendering,
                     tile: None,
                 };
 
                 if image_instance.visible_tiles.is_empty() {
-                    let cache_item = match *source {
+                    let cache_item = match image_data.source {
                         ImageSource::Default => {
                             resolve_image(
                                 request,
                                 ctx.resource_cache,
                                 gpu_cache,
                                 deferred_resolves,
                             )
                         }
@@ -1676,41 +1660,41 @@ impl AlphaBatchBuilder {
 
                     if cache_item.texture_id == TextureSource::Invalid {
                         return;
                     }
 
                     let textures = BatchTextures::color(cache_item.texture_id);
 
                     let opacity = PrimitiveOpacity::from_alpha(opacity_binding);
-                    let opacity = opacity.combine(prim_data.opacity);
+                    let opacity = opacity.combine(common_data.opacity);
 
                     let non_segmented_blend_mode = if !opacity.is_opaque ||
                         prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
                         transform_kind == TransformedRectKind::Complex
                     {
                         specified_blend_mode
                     } else {
                         BlendMode::None
                     };
 
                     let batch_params = BrushBatchParameters::shared(
                         BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
                         textures,
                         [
-                            ShaderColorMode::Image as i32 | ((alpha_type as i32) << 16),
+                            ShaderColorMode::Image as i32 | ((image_data.alpha_type as i32) << 16),
                             RasterizationSpace::Local as i32,
                             get_shader_opacity(opacity_binding),
                         ],
                         cache_item.uv_rect_handle.as_int(gpu_cache),
                     );
 
                     debug_assert!(image_instance.segment_instance_index != SegmentInstanceIndex::INVALID);
                     let (prim_cache_address, segments) = if image_instance.segment_instance_index == SegmentInstanceIndex::UNUSED {
-                        (gpu_cache.get_address(&prim_data.gpu_cache_handle), None)
+                        (gpu_cache.get_address(&common_data.gpu_cache_handle), None)
                     } else {
                         let segment_instance = &ctx.scratch.segment_instances[image_instance.segment_instance_index];
                         let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
                         (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
                     };
 
                     let prim_header = PrimitiveHeader {
                         local_rect: prim_rect,
@@ -1744,17 +1728,17 @@ impl AlphaBatchBuilder {
                     );
                 } else {
                     for tile in &image_instance.visible_tiles {
                         if let Some((batch_kind, textures, user_data, uv_rect_address)) = get_image_tile_params(
                             ctx.resource_cache,
                             gpu_cache,
                             deferred_resolves,
                             request.with_tile(tile.tile_offset),
-                            alpha_type,
+                            image_data.alpha_type,
                             get_shader_opacity(opacity_binding),
                         ) {
                             let prim_cache_address = gpu_cache.get_address(&tile.handle);
                             let prim_header = PrimitiveHeader {
                                 specific_prim_address: prim_cache_address,
                                 local_rect: tile.local_rect,
                                 local_clip_rect: tile.local_clip_rect,
                                 task_address,
@@ -2266,32 +2250,28 @@ impl BrushBatchParameters {
             ),
         }
     }
 }
 
 impl PrimitiveInstance {
     pub fn is_cacheable(
         &self,
-        prim_data_store: &PrimitiveDataStore,
+        resources: &FrameResources,
         resource_cache: &ResourceCache,
     ) -> bool {
         let image_key = match self.kind {
-            PrimitiveInstanceKind::Image { data_handle, .. } |
+            PrimitiveInstanceKind::Image { data_handle, .. } => {
+                let image_data = &resources.image_data_store[data_handle].kind;
+                image_data.key
+            }
             PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
-                let prim_data = &prim_data_store[data_handle];
-                match prim_data.kind {
-                    PrimitiveTemplateKind::YuvImage { ref yuv_key, .. } => {
-                        yuv_key[0]
-                    }
-                    PrimitiveTemplateKind::Image { key, .. } => {
-                        key
-                    }
-                    _ => unreachable!(),
-                }
+                let yuv_image_data =
+                    &resources.yuv_image_data_store[data_handle].kind;
+                yuv_image_data.yuv_key[0]
             }
             PrimitiveInstanceKind::Picture { .. } |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::Rectangle { .. } |
             PrimitiveInstanceKind::LinearGradient { .. } |
--- a/gfx/wr/webrender/src/border.rs
+++ b/gfx/wr/webrender/src/border.rs
@@ -1,22 +1,24 @@
 /* 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, ColorF, ColorU, DeviceRect, DeviceSize};
 use api::{LayoutSideOffsets, LayoutSizeAu, LayoutPrimitiveInfo, LayoutToDeviceScale};
-use api::{DeviceVector2D, DevicePoint, LayoutRect, LayoutSize, NormalBorder, DeviceIntSize};
+use api::{DeviceVector2D, DevicePoint, LayoutRect, LayoutSize, DeviceIntSize};
 use api::{AuHelpers, LayoutPoint, RepeatMode, TexelRect};
+use api::NormalBorder as ApiNormalBorder;
 use ellipse::Ellipse;
 use euclid::vec2;
 use display_list_flattener::DisplayListFlattener;
 use gpu_types::{BorderInstance, BorderSegment, BrushFlags};
 use prim_store::{BorderSegmentInfo, BrushSegment, NinePatchDescriptor};
-use prim_store::{EdgeAaSegmentMask, ScrollNodeAndClipChain, PrimitiveKeyKind};
+use prim_store::{EdgeAaSegmentMask, ScrollNodeAndClipChain};
+use prim_store::borders::NormalBorderPrim;
 use util::{lerp, RectHelpers};
 
 // Using 2048 as the maximum radius in device space before which we
 // start stretching is up for debate.
 // the value must be chosen so that the corners will not use an
 // unreasonable amount of memory but should allow crisp corners in the
 // common cases.
 
@@ -124,32 +126,32 @@ impl NormalBorderAu {
         b.left.color = color;
         b.right.color = color;
         b.top.color = color;
         b.bottom.color = color;
         b
     }
 }
 
-impl From<NormalBorder> for NormalBorderAu {
-    fn from(border: NormalBorder) -> Self {
+impl From<ApiNormalBorder> for NormalBorderAu {
+    fn from(border: ApiNormalBorder) -> Self {
         NormalBorderAu {
             left: border.left.into(),
             right: border.right.into(),
             top: border.top.into(),
             bottom: border.bottom.into(),
             radius: border.radius.into(),
             do_aa: border.do_aa,
         }
     }
 }
 
-impl From<NormalBorderAu> for NormalBorder {
+impl From<NormalBorderAu> for ApiNormalBorder {
     fn from(border: NormalBorderAu) -> Self {
-        NormalBorder {
+        ApiNormalBorder {
             left: border.left.into(),
             right: border.right.into(),
             top: border.top.into(),
             bottom: border.bottom.into(),
             radius: border.radius.into(),
             do_aa: border.do_aa,
         }
     }
@@ -213,28 +215,28 @@ pub fn ensure_no_corner_overlap(
         bottom_right_radius.height *= ratio;
     }
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn add_normal_border(
         &mut self,
         info: &LayoutPrimitiveInfo,
-        border: &NormalBorder,
+        border: &ApiNormalBorder,
         widths: LayoutSideOffsets,
         clip_and_scroll: ScrollNodeAndClipChain,
     ) {
         let mut border = *border;
         ensure_no_corner_overlap(&mut border.radius, info.rect.size);
 
         self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
-            PrimitiveKeyKind::NormalBorder {
+            NormalBorderPrim {
                 border: border.into(),
                 widths: widths.to_au(),
             },
         );
     }
 }
 
 pub trait BorderSideHelpers {
@@ -645,17 +647,17 @@ fn get_edge_info(
         }
     }
 }
 
 /// Create the set of border segments and render task
 /// cache keys for a given CSS border.
 pub fn create_border_segments(
     size: LayoutSize,
-    border: &NormalBorder,
+    border: &ApiNormalBorder,
     widths: &LayoutSideOffsets,
     border_segments: &mut Vec<BorderSegmentInfo>,
     brush_segments: &mut Vec<BrushSegment>,
 ) {
     let rect = LayoutRect::new(
         LayoutPoint::zero(),
         size,
     );
@@ -1091,17 +1093,17 @@ fn add_edge_segment(
     });
 }
 
 /// Build the set of border instances needed to draw a border
 /// segment into the render task cache.
 pub fn build_border_instances(
     cache_key: &BorderSegmentCacheKey,
     cache_size: DeviceIntSize,
-    border: &NormalBorder,
+    border: &ApiNormalBorder,
     scale: LayoutToDeviceScale,
 ) -> Vec<BorderInstance> {
     let mut instances = Vec::new();
 
     let (side0, side1, flip0, flip1) = match cache_key.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),
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -21,17 +21,19 @@ use hit_test::{HitTestingItem, HitTestin
 use image::simplify_repeated_primitive;
 use intern::{Handle, Internable, InternDebug};
 use internal_types::{FastHashMap, FastHashSet};
 use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PrimitiveList};
 use prim_store::{PrimitiveInstance, PrimitiveKeyKind, PictureCompositeKey};
 use prim_store::{PrimitiveKey, PrimitiveSceneData, PrimitiveInstanceKind, NinePatchDescriptor};
 use prim_store::{PrimitiveStore, PrimitiveStoreStats, LineDecorationCacheKey};
 use prim_store::{ScrollNodeAndClipChain, PictureIndex, register_prim_chase_id, get_line_decoration_sizes};
+use prim_store::borders::{ImageBorder, NormalBorderPrim};
 use prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams};
+use prim_store::image::{Image, YuvImage};
 use prim_store::text_run::TextRun;
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::{DocumentResources, InternerMut};
 use spatial_node::{StickyFrameInfo, ScrollFrameKind, SpatialNodeType};
 use std::{f32, mem, usize};
 use std::collections::vec_deque::VecDeque;
@@ -1815,17 +1817,22 @@ impl<'a> DisplayListFlattener<'a> {
                     };
 
                     // Add any primitives that come after this shadow in the item
                     // list to this shadow.
                     let mut prims = Vec::new();
 
                     for item in &items {
                         match item {
-                            // TODO(djg): ugh. de-duplicate this code.
+                            ShadowItem::Image(ref pending_image) => {
+                                self.add_shadow_prim(&pending_shadow, pending_image, &mut prims)
+                            }
+                            ShadowItem::NormalBorder(ref pending_border) => {
+                                self.add_shadow_prim(&pending_shadow, pending_border, &mut prims)
+                            }
                             ShadowItem::Primitive(ref pending_primitive) => {
                                 self.add_shadow_prim(&pending_shadow, pending_primitive, &mut prims)
                             }
                             ShadowItem::TextRun(ref pending_text_run) => {
                                 self.add_shadow_prim(&pending_shadow, pending_text_run, &mut prims)
                             }
                             _ => {}
                         }
@@ -1892,16 +1899,22 @@ impl<'a> DisplayListFlattener<'a> {
                             pending_shadow.clip_and_scroll.spatial_node_index,
                         );
 
                         // Add the shadow primitive. This must be done before pushing this
                         // picture on to the shadow stack, to avoid infinite recursion!
                         self.add_primitive_to_draw_list(shadow_prim_instance);
                     }
                 }
+                ShadowItem::Image(pending_image) => {
+                    self.add_shadow_prim_to_draw_list(pending_image)
+                },
+                ShadowItem::NormalBorder(pending_border) => {
+                    self.add_shadow_prim_to_draw_list(pending_border)
+                }
                 ShadowItem::Primitive(pending_primitive) => {
                     self.add_shadow_prim_to_draw_list(pending_primitive)
                 },
                 ShadowItem::TextRun(pending_text_run) => {
                     self.add_shadow_prim_to_draw_list(pending_text_run)
                 },
             }
         }
@@ -2104,26 +2117,26 @@ impl<'a> DisplayListFlattener<'a> {
                     repeat_horizontal: border.repeat_horizontal,
                     repeat_vertical: border.repeat_vertical,
                     outset: border.outset.into(),
                     widths: border_item.widths.into(),
                 };
 
                 match border.source {
                     NinePatchBorderSource::Image(image_key) => {
-                        let prim = PrimitiveKeyKind::ImageBorder {
+                        let prim = ImageBorder {
                             request: ImageRequest {
                                 key: image_key,
                                 rendering: ImageRendering::Auto,
                                 tile: None,
                             },
                             nine_patch,
                         };
 
-                        self.add_primitive(
+                        self.add_nonshadowable_primitive(
                             clip_and_scroll,
                             info,
                             Vec::new(),
                             prim,
                         );
                     }
                     NinePatchBorderSource::Gradient(gradient) => {
                         let prim = match self.create_linear_gradient_prim(
@@ -2410,17 +2423,17 @@ impl<'a> DisplayListFlattener<'a> {
                 ),
             )
         });
 
         self.add_primitive(
             clip_and_scroll,
             &info,
             Vec::new(),
-            PrimitiveKeyKind::Image {
+            Image {
                 key: image_key,
                 tile_spacing: tile_spacing.into(),
                 stretch_size: stretch_size.into(),
                 color: color.into(),
                 sub_rect,
                 image_rendering,
                 alpha_type,
             },
@@ -2438,21 +2451,21 @@ impl<'a> DisplayListFlattener<'a> {
     ) {
         let format = yuv_data.get_format();
         let yuv_key = match yuv_data {
             YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::DUMMY],
             YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) => [plane_0, plane_1, plane_2],
             YuvData::InterleavedYCbCr(plane_0) => [plane_0, ImageKey::DUMMY, ImageKey::DUMMY],
         };
 
-        self.add_primitive(
+        self.add_nonshadowable_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
-            PrimitiveKeyKind::YuvImage {
+            YuvImage {
                 color_depth,
                 yuv_key,
                 format,
                 color_space,
                 image_rendering,
             },
         );
     }
@@ -2664,20 +2677,34 @@ pub struct PendingPrimitive<T> {
 /// shadows, and handled at once during pop_all_shadows.
 pub struct PendingShadow {
     shadow: Shadow,
     clip_and_scroll: ScrollNodeAndClipChain,
 }
 
 pub enum ShadowItem {
     Shadow(PendingShadow),
+    Image(PendingPrimitive<Image>),
+    NormalBorder(PendingPrimitive<NormalBorderPrim>),
     Primitive(PendingPrimitive<PrimitiveKeyKind>),
     TextRun(PendingPrimitive<TextRun>),
 }
 
+impl From<PendingPrimitive<Image>> for ShadowItem {
+    fn from(image: PendingPrimitive<Image>) -> Self {
+        ShadowItem::Image(image)
+    }
+}
+
+impl From<PendingPrimitive<NormalBorderPrim>> for ShadowItem {
+    fn from(border: PendingPrimitive<NormalBorderPrim>) -> Self {
+        ShadowItem::NormalBorder(border)
+    }
+}
+
 impl From<PendingPrimitive<PrimitiveKeyKind>> for ShadowItem {
     fn from(container: PendingPrimitive<PrimitiveKeyKind>) -> Self {
         ShadowItem::Primitive(container)
     }
 }
 
 impl From<PendingPrimitive<TextRun>> for ShadowItem {
     fn from(text_run: PendingPrimitive<TextRun>) -> Self {
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -16,17 +16,17 @@ use intern::ItemUid;
 use internal_types::{FastHashMap, PlaneSplitter};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{TransformPalette, TransformPaletteId, UvRectKind};
 use internal_types::FastHashSet;
 use plane_split::{Clipper, Polygon, Splitter};
 use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, VisibleFace, PrimitiveInstanceKind};
 use prim_store::{get_raster_rects, CoordinateSpaceMapping, PointKey};
-use prim_store::{OpacityBindingStorage, PrimitiveTemplateKind, ImageInstanceStorage, OpacityBindingIndex, SizeKey};
+use prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex, SizeKey};
 use print_tree::PrintTreePrinter;
 use render_backend::FrameResources;
 use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle, TileBlit};
 use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use resource_cache::ResourceCache;
 use scene::{FilterOpHelpers, SceneProperties};
 use scene_builder::DocumentResources;
 use smallvec::SmallVec;
@@ -779,17 +779,17 @@ impl TileCache {
         let mut opacity_bindings: SmallVec<[PropertyBindingId; 4]> = SmallVec::new();
         let mut clip_chain_spatial_nodes: SmallVec<[SpatialNodeIndex; 8]> = SmallVec::new();
         let mut clip_chain_uids: SmallVec<[ItemUid; 8]> = SmallVec::new();
         let mut image_keys: SmallVec<[ImageKey; 8]> = SmallVec::new();
         let mut current_clip_chain_id = prim_instance.clip_chain_id;
 
         // Some primitives can not be cached (e.g. external video images)
         let is_cacheable = prim_instance.is_cacheable(
-            &resources.prim_data_store,
+            &resources,
             resource_cache,
         );
 
         match prim_instance.kind {
             PrimitiveInstanceKind::Picture { pic_index,.. } => {
                 // Pictures can depend on animated opacity bindings.
                 let pic = &pictures[pic_index.0];
                 if let Some(PictureCompositeMode::Filter(FilterOp::Opacity(binding, _))) = pic.requested_composite_mode {
@@ -804,48 +804,34 @@ impl TileCache {
                     for binding in &opacity_binding.bindings {
                         if let PropertyBinding::Binding(key, _) = binding {
                             opacity_bindings.push(key.id);
                         }
                     }
                 }
             }
             PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
-                let prim_data = &resources.prim_data_store[data_handle];
+                let image_data = &resources.image_data_store[data_handle].kind;
                 let image_instance = &image_instances[image_instance_index];
                 let opacity_binding_index = image_instance.opacity_binding_index;
 
                 if opacity_binding_index != OpacityBindingIndex::INVALID {
                     let opacity_binding = &opacity_binding_store[opacity_binding_index];
                     for binding in &opacity_binding.bindings {
                         if let PropertyBinding::Binding(key, _) = binding {
                             opacity_bindings.push(key.id);
                         }
                     }
                 }
 
-                match prim_data.kind {
-                    PrimitiveTemplateKind::Image { key, .. } => {
-                        image_keys.push(key);
-                    }
-                    _ => {
-                        unreachable!();
-                    }
-                }
+                image_keys.push(image_data.key);
             }
             PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
-                let prim_data = &resources.prim_data_store[data_handle];
-                match prim_data.kind {
-                    PrimitiveTemplateKind::YuvImage { ref yuv_key, .. } => {
-                        image_keys.extend_from_slice(yuv_key);
-                    }
-                    _ => {
-                        unreachable!();
-                    }
-                }
+                let yuv_image_data = &resources.yuv_image_data_store[data_handle].kind;
+                image_keys.extend_from_slice(&yuv_image_data.yuv_key);
             }
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } |
             PrimitiveInstanceKind::Clear { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::LinearGradient { .. } |
             PrimitiveInstanceKind::RadialGradient { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } => {
@@ -1402,33 +1388,41 @@ impl PrimitiveList {
                 _ => {
                     false
                 }
             };
 
             let prim_data = match prim_instance.kind {
                 PrimitiveInstanceKind::Picture { data_handle, .. } |
                 PrimitiveInstanceKind::LineDecoration { data_handle, .. } |
-                PrimitiveInstanceKind::NormalBorder { data_handle, .. } |
-                PrimitiveInstanceKind::ImageBorder { data_handle, .. } |
                 PrimitiveInstanceKind::Rectangle { data_handle, .. } |
-                PrimitiveInstanceKind::YuvImage { data_handle, .. } |
-                PrimitiveInstanceKind::Image { data_handle, .. } |
                 PrimitiveInstanceKind::Clear { data_handle, .. } => {
                     &resources.prim_interner[data_handle]
                 }
+                PrimitiveInstanceKind::Image { data_handle, .. } => {
+                    &resources.image_interner[data_handle]
+                }
+                PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
+                    &resources.image_border_interner[data_handle]
+                }
                 PrimitiveInstanceKind::LinearGradient { data_handle, .. } => {
                     &resources.linear_grad_interner[data_handle]
                 }
+                PrimitiveInstanceKind::NormalBorder { data_handle, .. } => {
+                    &resources.normal_border_interner[data_handle]
+                }
                 PrimitiveInstanceKind::RadialGradient { data_handle, ..} => {
                     &resources.radial_grad_interner[data_handle]
                 }
                 PrimitiveInstanceKind::TextRun { data_handle, .. } => {
                     &resources.text_run_interner[data_handle]
                 }
+                PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
+                    &resources.yuv_image_interner[data_handle]
+                }
             };
 
             // Get the key for the cluster that this primitive should
             // belong to.
             let key = PrimitiveClusterKey {
                 spatial_node_index: prim_instance.spatial_node_index,
                 is_backface_visible: prim_data.is_backface_visible,
             };
new file mode 100644
--- /dev/null
+++ b/gfx/wr/webrender/src/prim_store/borders.rs
@@ -0,0 +1,392 @@
+/* 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::{
+    AuHelpers, LayoutPrimitiveInfo, LayoutRect, LayoutSideOffsets,
+    LayoutSideOffsetsAu, LayoutSize, NormalBorder, PremultipliedColorF,
+    Shadow
+};
+use border::create_border_segments;
+use border::NormalBorderAu;
+use display_list_flattener::{AsInstanceKind, CreateShadow, IsVisible};
+use frame_builder::{FrameBuildingState};
+use gpu_cache::GpuDataRequest;
+use intern;
+use prim_store::{
+    BorderSegmentInfo, BrushSegment, NinePatchDescriptor, PrimKey,
+    PrimKeyCommonData, PrimTemplate, PrimTemplateCommonData,
+    PrimitiveInstanceKind, PrimitiveOpacity, PrimitiveSceneData,
+    PrimitiveStore
+};
+use resource_cache::ImageRequest;
+use storage;
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct NormalBorderPrim {
+    pub border: NormalBorderAu,
+    pub widths: LayoutSideOffsetsAu,
+}
+
+pub type NormalBorderKey = PrimKey<NormalBorderPrim>;
+
+impl NormalBorderKey {
+    pub fn new(
+        info: &LayoutPrimitiveInfo,
+        prim_relative_clip_rect: LayoutRect,
+        normal_border: NormalBorderPrim,
+    ) -> Self {
+        NormalBorderKey {
+            common: PrimKeyCommonData::with_info(
+                info,
+                prim_relative_clip_rect,
+            ),
+            kind: normal_border,
+        }
+    }
+}
+
+impl intern::InternDebug for NormalBorderKey {}
+
+impl AsInstanceKind<NormalBorderDataHandle> for NormalBorderKey {
+    /// Construct a primitive instance that matches the type
+    /// of primitive key.
+    fn as_instance_kind(
+        &self,
+        data_handle: NormalBorderDataHandle,
+        _: &mut PrimitiveStore,
+    ) -> PrimitiveInstanceKind {
+        PrimitiveInstanceKind::NormalBorder {
+            data_handle,
+            cache_handles: storage::Range::empty(),
+        }
+    }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct NormalBorderData {
+    pub brush_segments: Vec<BrushSegment>,
+    pub border_segments: Vec<BorderSegmentInfo>,
+    pub border: NormalBorder,
+    pub widths: LayoutSideOffsets,
+}
+
+impl NormalBorderData {
+    /// Update the GPU cache for a given primitive template. This may be called multiple
+    /// times per frame, by each primitive reference that refers to this interned
+    /// template. The initial request call to the GPU cache ensures that work is only
+    /// done if the cache entry is invalid (due to first use or eviction).
+    pub fn update(
+        &mut self,
+        common: &mut PrimTemplateCommonData,
+        frame_state: &mut FrameBuildingState,
+    ) {
+        if let Some(ref mut request) = frame_state.gpu_cache.request(&mut common.gpu_cache_handle) {
+            self.write_prim_gpu_blocks(request, common.prim_size);
+            self.write_segment_gpu_blocks(request);
+        }
+
+        common.opacity = PrimitiveOpacity::translucent();
+    }
+
+    fn write_prim_gpu_blocks(
+        &self,
+        request: &mut GpuDataRequest,
+        prim_size: LayoutSize
+    ) {
+        // Border primitives currently used for
+        // image borders, and run through the
+        // normal brush_image shader.
+        request.push(PremultipliedColorF::WHITE);
+        request.push(PremultipliedColorF::WHITE);
+        request.push([
+            prim_size.width,
+            prim_size.height,
+            0.0,
+            0.0,
+        ]);
+    }
+
+    fn write_segment_gpu_blocks(
+        &self,
+        request: &mut GpuDataRequest,
+    ) {
+        for segment in &self.brush_segments {
+            // has to match VECS_PER_SEGMENT
+            request.write_segment(
+                segment.local_rect,
+                segment.extra_data,
+            );
+        }
+    }
+}
+
+pub type NormalBorderTemplate = PrimTemplate<NormalBorderData>;
+
+impl From<NormalBorderKey> for NormalBorderTemplate {
+    fn from(key: NormalBorderKey) -> Self {
+        let common = PrimTemplateCommonData::with_key_common(key.common);
+
+        let mut border: NormalBorder = key.kind.border.into();
+        let widths = LayoutSideOffsets::from_au(key.kind.widths);
+
+        // FIXME(emilio): Is this the best place to do this?
+        border.normalize(&widths);
+
+        let mut brush_segments = Vec::new();
+        let mut border_segments = Vec::new();
+
+        create_border_segments(
+            common.prim_size,
+            &border,
+            &widths,
+            &mut border_segments,
+            &mut brush_segments,
+        );
+
+        NormalBorderTemplate {
+            common,
+            kind: NormalBorderData {
+                brush_segments,
+                border_segments,
+                border,
+                widths,
+            }
+        }
+    }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
+pub struct NormalBorderDataMarker;
+
+pub type NormalBorderDataStore = intern::DataStore<NormalBorderKey, NormalBorderTemplate, NormalBorderDataMarker>;
+pub type NormalBorderDataHandle = intern::Handle<NormalBorderDataMarker>;
+pub type NormalBorderDataUpdateList = intern::UpdateList<NormalBorderKey>;
+pub type NormalBorderDataInterner = intern::Interner<NormalBorderKey, PrimitiveSceneData, NormalBorderDataMarker>;
+
+impl intern::Internable for NormalBorderPrim {
+    type Marker = NormalBorderDataMarker;
+    type Source = NormalBorderKey;
+    type StoreData = NormalBorderTemplate;
+    type InternData = PrimitiveSceneData;
+
+    /// Build a new key from self with `info`.
+    fn build_key(
+        self,
+        info: &LayoutPrimitiveInfo,
+        prim_relative_clip_rect: LayoutRect,
+    ) -> NormalBorderKey {
+        NormalBorderKey::new(
+            info,
+            prim_relative_clip_rect,
+            self,
+        )
+    }
+}
+
+impl CreateShadow for NormalBorderPrim {
+    fn create_shadow(&self, shadow: &Shadow) -> Self {
+        let border = self.border.with_color(shadow.color.into());
+        NormalBorderPrim {
+            border,
+            widths: self.widths,
+        }
+    }
+}
+
+impl IsVisible for NormalBorderPrim {
+    fn is_visible(&self) -> bool {
+        true
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct ImageBorder {
+    pub request: ImageRequest,
+    pub nine_patch: NinePatchDescriptor,
+}
+
+pub type ImageBorderKey = PrimKey<ImageBorder>;
+
+impl ImageBorderKey {
+    pub fn new(
+        info: &LayoutPrimitiveInfo,
+        prim_relative_clip_rect: LayoutRect,
+        image_border: ImageBorder,
+    ) -> Self {
+        ImageBorderKey {
+            common: PrimKeyCommonData::with_info(
+                info,
+                prim_relative_clip_rect,
+            ),
+            kind: image_border,
+        }
+    }
+}
+
+impl intern::InternDebug for ImageBorderKey {}
+
+impl AsInstanceKind<ImageBorderDataHandle> for ImageBorderKey {
+    /// Construct a primitive instance that matches the type
+    /// of primitive key.
+    fn as_instance_kind(
+        &self,
+        data_handle: ImageBorderDataHandle,
+        _: &mut PrimitiveStore,
+    ) -> PrimitiveInstanceKind {
+        PrimitiveInstanceKind::ImageBorder {
+            data_handle
+        }
+    }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ImageBorderData {
+    pub request: ImageRequest,
+    pub brush_segments: Vec<BrushSegment>,
+}
+
+impl ImageBorderData {
+    /// Update the GPU cache for a given primitive template. This may be called multiple
+    /// times per frame, by each primitive reference that refers to this interned
+    /// template. The initial request call to the GPU cache ensures that work is only
+    /// done if the cache entry is invalid (due to first use or eviction).
+    pub fn update(
+        &mut self,
+        common: &mut PrimTemplateCommonData,
+        frame_state: &mut FrameBuildingState,
+    ) {
+        if let Some(ref mut request) = frame_state.gpu_cache.request(&mut common.gpu_cache_handle) {
+            self.write_prim_gpu_blocks(request, &common.prim_size);
+            self.write_segment_gpu_blocks(request);
+        }
+
+        let image_properties = frame_state
+            .resource_cache
+            .get_image_properties(self.request.key);
+
+        common.opacity = if let Some(image_properties) = image_properties {
+            frame_state.resource_cache.request_image(
+                self.request,
+                frame_state.gpu_cache,
+            );
+            PrimitiveOpacity {
+                is_opaque: image_properties.descriptor.is_opaque,
+            }
+        } else {
+            PrimitiveOpacity::opaque()
+        }
+    }
+
+    fn write_prim_gpu_blocks(
+        &self,
+        request: &mut GpuDataRequest,
+        prim_size: &LayoutSize,
+    ) {
+        // Border primitives currently used for
+        // image borders, and run through the
+        // normal brush_image shader.
+        request.push(PremultipliedColorF::WHITE);
+        request.push(PremultipliedColorF::WHITE);
+        request.push([
+            prim_size.width,
+            prim_size.height,
+            0.0,
+            0.0,
+        ]);
+    }
+
+    fn write_segment_gpu_blocks(
+        &self,
+        request: &mut GpuDataRequest,
+    ) {
+        for segment in &self.brush_segments {
+            // has to match VECS_PER_SEGMENT
+            request.write_segment(
+                segment.local_rect,
+                segment.extra_data,
+            );
+        }
+    }
+}
+
+pub type ImageBorderTemplate = PrimTemplate<ImageBorderData>;
+
+impl From<ImageBorderKey> for ImageBorderTemplate {
+    fn from(key: ImageBorderKey) -> Self {
+        let common = PrimTemplateCommonData::with_key_common(key.common);
+
+        let brush_segments = key.kind.nine_patch.create_segments(common.prim_size);
+        ImageBorderTemplate {
+            common,
+            kind: ImageBorderData {
+                request: key.kind.request,
+                brush_segments,
+            }
+        }
+    }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
+pub struct ImageBorderDataMarker;
+
+pub type ImageBorderDataStore = intern::DataStore<ImageBorderKey, ImageBorderTemplate, ImageBorderDataMarker>;
+pub type ImageBorderDataHandle = intern::Handle<ImageBorderDataMarker>;
+pub type ImageBorderDataUpdateList = intern::UpdateList<ImageBorderKey>;
+pub type ImageBorderDataInterner = intern::Interner<ImageBorderKey, PrimitiveSceneData, ImageBorderDataMarker>;
+
+impl intern::Internable for ImageBorder {
+    type Marker = ImageBorderDataMarker;
+    type Source = ImageBorderKey;
+    type StoreData = ImageBorderTemplate;
+    type InternData = PrimitiveSceneData;
+
+    /// Build a new key from self with `info`.
+    fn build_key(
+        self,
+        info: &LayoutPrimitiveInfo,
+        prim_relative_clip_rect: LayoutRect,
+    ) -> ImageBorderKey {
+        ImageBorderKey::new(
+            info,
+            prim_relative_clip_rect,
+            self,
+        )
+    }
+}
+
+impl IsVisible for ImageBorder {
+    fn is_visible(&self) -> bool {
+        true
+    }
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+fn test_struct_sizes() {
+    use std::mem;
+    // The sizes of these structures are critical for performance on a number of
+    // talos stress tests. If you get a failure here on CI, there's two possibilities:
+    // (a) You made a structure smaller than it currently is. Great work! Update the
+    //     test expectations and move on.
+    // (b) You made a structure larger. This is not necessarily a problem, but should only
+    //     be done with care, and after checking if talos performance regresses badly.
+    assert_eq!(mem::size_of::<NormalBorderPrim>(), 84, "NormalBorderPrim size changed");
+    assert_eq!(mem::size_of::<NormalBorderTemplate>(), 240, "NormalBorderTemplate size changed");
+    assert_eq!(mem::size_of::<NormalBorderKey>(), 112, "NormalBorderKey size changed");
+    assert_eq!(mem::size_of::<ImageBorder>(), 92, "ImageBorder size changed");
+    assert_eq!(mem::size_of::<ImageBorderTemplate>(), 104, "ImageBorderTemplate size changed");
+    assert_eq!(mem::size_of::<ImageBorderKey>(), 120, "ImageBorderKey size changed");
+}
new file mode 100644
--- /dev/null
+++ b/gfx/wr/webrender/src/prim_store/image.rs
@@ -0,0 +1,581 @@
+/* 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, ColorDepth, ColorF, ColorU, DeviceIntRect, DeviceIntSideOffsets,
+    DeviceIntSize, ImageRendering, LayoutRect, LayoutSize, LayoutPrimitiveInfo,
+    PremultipliedColorF, Shadow, TileOffset, YuvColorSpace, YuvFormat
+};
+use api::ImageKey as ApiImageKey;
+use display_list_flattener::{AsInstanceKind, CreateShadow, IsVisible};
+use frame_builder::FrameBuildingState;
+use gpu_cache::{GpuCacheHandle, GpuDataRequest};
+use intern::{DataStore, Handle, Internable, Interner, InternDebug, UpdateList};
+use picture::SurfaceIndex;
+use prim_store::{
+    EdgeAaSegmentMask, OpacityBindingIndex, PrimitiveInstanceKind,
+    PrimitiveOpacity, PrimitiveSceneData, PrimKey, PrimKeyCommonData,
+    PrimTemplate, PrimTemplateCommonData, PrimitiveStore, SegmentInstanceIndex,
+    SizeKey
+};
+use render_task::{
+    BlitSource, RenderTask, RenderTaskCacheEntryHandle, RenderTaskCacheKey,
+    RenderTaskCacheKeyKind
+};
+use resource_cache::ImageRequest;
+use util::pack_as_float;
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct VisibleImageTile {
+    pub tile_offset: TileOffset,
+    pub handle: GpuCacheHandle,
+    pub edge_flags: EdgeAaSegmentMask,
+    pub local_rect: LayoutRect,
+    pub local_clip_rect: LayoutRect,
+}
+
+// Key that identifies a unique (partial) image that is being
+// stored in the render task cache.
+#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ImageCacheKey {
+    pub request: ImageRequest,
+    pub texel_rect: Option<DeviceIntRect>,
+}
+
+/// Instance specific fields for an image primitive. These are
+/// currently stored in a separate array to avoid bloating the
+/// size of PrimitiveInstance. In the future, we should be able
+/// to remove this and store the information inline, by:
+/// (a) Removing opacity collapse / binding support completely.
+///     Once we have general picture caching, we don't need this.
+/// (b) Change visible_tiles to use Storage in the primitive
+///     scratch buffer. This will reduce the size of the
+///     visible_tiles field here, and save memory allocation
+///     when image tiling is used. I've left it as a Vec for
+///     now to reduce the number of changes, and because image
+///     tiling is very rare on real pages.
+#[derive(Debug)]
+pub struct ImageInstance {
+    pub opacity_binding_index: OpacityBindingIndex,
+    pub segment_instance_index: SegmentInstanceIndex,
+    pub visible_tiles: Vec<VisibleImageTile>,
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct Image {
+    pub key: ApiImageKey,
+    pub stretch_size: SizeKey,
+    pub tile_spacing: SizeKey,
+    pub color: ColorU,
+    pub sub_rect: Option<DeviceIntRect>,
+    pub image_rendering: ImageRendering,
+    pub alpha_type: AlphaType,
+}
+
+pub type ImageKey = PrimKey<Image>;
+
+impl ImageKey {
+    pub fn new(
+        is_backface_visible: bool,
+        prim_size: LayoutSize,
+        prim_relative_clip_rect: LayoutRect,
+        image: Image,
+    ) -> Self {
+
+        ImageKey {
+            common: PrimKeyCommonData {
+                is_backface_visible,
+                prim_size: prim_size.into(),
+                prim_relative_clip_rect: prim_relative_clip_rect.into(),
+            },
+            kind: image,
+        }
+    }
+}
+
+impl InternDebug for ImageKey {}
+
+impl AsInstanceKind<ImageDataHandle> for ImageKey {
+    /// Construct a primitive instance that matches the type
+    /// of primitive key.
+    fn as_instance_kind(
+        &self,
+        data_handle: ImageDataHandle,
+        prim_store: &mut PrimitiveStore,
+    ) -> PrimitiveInstanceKind {
+        // TODO(gw): Refactor this to not need a separate image
+        //           instance (see ImageInstance struct).
+        let image_instance_index = prim_store.images.push(ImageInstance {
+            opacity_binding_index: OpacityBindingIndex::INVALID,
+            segment_instance_index: SegmentInstanceIndex::INVALID,
+            visible_tiles: Vec::new(),
+        });
+
+        PrimitiveInstanceKind::Image {
+            data_handle,
+            image_instance_index,
+        }
+    }
+}
+
+// Where to find the texture data for an image primitive.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug)]
+pub enum ImageSource {
+    // A normal image - just reference the texture cache.
+    Default,
+    // An image that is pre-rendered into the texture cache
+    // via a render task.
+    Cache {
+        size: DeviceIntSize,
+        handle: Option<RenderTaskCacheEntryHandle>,
+    },
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ImageData {
+    pub key: ApiImageKey,
+    pub stretch_size: LayoutSize,
+    pub tile_spacing: LayoutSize,
+    pub color: ColorF,
+    pub source: ImageSource,
+    pub image_rendering: ImageRendering,
+    pub sub_rect: Option<DeviceIntRect>,
+    pub alpha_type: AlphaType,
+}
+
+impl From<Image> for ImageData {
+    fn from(image: Image) -> Self {
+        ImageData {
+            key: image.key,
+            color: image.color.into(),
+            stretch_size: image.stretch_size.into(),
+            tile_spacing: image.tile_spacing.into(),
+            source: ImageSource::Default,
+            sub_rect: image.sub_rect,
+            image_rendering: image.image_rendering,
+            alpha_type: image.alpha_type,
+        }
+    }
+}
+
+impl ImageData {
+    /// Update the GPU cache for a given primitive template. This may be called multiple
+    /// times per frame, by each primitive reference that refers to this interned
+    /// template. The initial request call to the GPU cache ensures that work is only
+    /// done if the cache entry is invalid (due to first use or eviction).
+    pub fn update(
+        &mut self,
+        // TODO(gw): Passing in surface_index here is not ideal. The primitive template
+        //           code shouldn't depend on current surface state. This is due to a
+        //           limitation in how render task caching works. We should fix this by
+        //           allowing render task caching to assign to surfaces implicitly
+        //           during pass allocation.
+        surface_index: SurfaceIndex,
+        common: &mut PrimTemplateCommonData,
+        frame_state: &mut FrameBuildingState,
+    ) {
+        if let Some(mut request) = frame_state.gpu_cache.request(&mut common.gpu_cache_handle) {
+            self.write_prim_gpu_blocks(&mut request);
+        }
+
+        common.opacity = {
+            let image_properties = frame_state
+                .resource_cache
+                .get_image_properties(self.key);
+
+            match image_properties {
+                Some(image_properties) => {
+                    let is_tiled = image_properties.tiling.is_some();
+
+                    if self.tile_spacing != LayoutSize::zero() && !is_tiled {
+                        self.source = ImageSource::Cache {
+                            // Size in device-pixels we need to allocate in render task cache.
+                            size: image_properties.descriptor.size.to_i32(),
+                            handle: None,
+                        };
+                    }
+
+                    // Work out whether this image is a normal / simple type, or if
+                    // we need to pre-render it to the render task cache.
+                    if let Some(rect) = self.sub_rect {
+                        // We don't properly support this right now.
+                        debug_assert!(!is_tiled);
+                        self.source = ImageSource::Cache {
+                            // Size in device-pixels we need to allocate in render task cache.
+                            size: rect.size,
+                            handle: None,
+                        };
+                    }
+
+                    let mut request_source_image = false;
+                    let mut is_opaque = image_properties.descriptor.is_opaque;
+                    let request = ImageRequest {
+                        key: self.key,
+                        rendering: self.image_rendering,
+                        tile: None,
+                    };
+
+                    // Every frame, for cached items, we need to request the render
+                    // task cache item. The closure will be invoked on the first
+                    // time through, and any time the render task output has been
+                    // evicted from the texture cache.
+                    match self.source {
+                        ImageSource::Cache { ref mut size, ref mut handle } => {
+                            let padding = DeviceIntSideOffsets::new(
+                                0,
+                                (self.tile_spacing.width * size.width as f32 / self.stretch_size.width) as i32,
+                                (self.tile_spacing.height * size.height as f32 / self.stretch_size.height) as i32,
+                                0,
+                            );
+
+                            let inner_size = *size;
+                            size.width += padding.horizontal();
+                            size.height += padding.vertical();
+
+                            is_opaque &= padding == DeviceIntSideOffsets::zero();
+
+                            let image_cache_key = ImageCacheKey {
+                                request,
+                                texel_rect: self.sub_rect,
+                            };
+                            let surfaces = &mut frame_state.surfaces;
+
+                            // Request a pre-rendered image task.
+                            *handle = Some(frame_state.resource_cache.request_render_task(
+                                RenderTaskCacheKey {
+                                    size: *size,
+                                    kind: RenderTaskCacheKeyKind::Image(image_cache_key),
+                                },
+                                frame_state.gpu_cache,
+                                frame_state.render_tasks,
+                                None,
+                                image_properties.descriptor.is_opaque,
+                                |render_tasks| {
+                                    // We need to render the image cache this frame,
+                                    // so will need access to the source texture.
+                                    request_source_image = true;
+
+                                    // Create a task to blit from the texture cache to
+                                    // a normal transient render task surface. This will
+                                    // copy only the sub-rect, if specified.
+                                    let cache_to_target_task = RenderTask::new_blit_with_padding(
+                                        inner_size,
+                                        &padding,
+                                        BlitSource::Image { key: image_cache_key },
+                                    );
+                                    let cache_to_target_task_id = render_tasks.add(cache_to_target_task);
+
+                                    // Create a task to blit the rect from the child render
+                                    // task above back into the right spot in the persistent
+                                        // render target cache.
+                                    let target_to_cache_task = RenderTask::new_blit(
+                                        *size,
+                                        BlitSource::RenderTask {
+                                            task_id: cache_to_target_task_id,
+                                        },
+                                    );
+                                    let target_to_cache_task_id = render_tasks.add(target_to_cache_task);
+
+                                    // Hook this into the render task tree at the right spot.
+                                    surfaces[surface_index.0].tasks.push(target_to_cache_task_id);
+
+                                    // Pass the image opacity, so that the cached render task
+                                    // item inherits the same opacity properties.
+                                    target_to_cache_task_id
+                                }
+                            ));
+                        }
+                        ImageSource::Default => {
+                            // Normal images just reference the source texture each frame.
+                            request_source_image = true;
+                        }
+                    }
+
+                    if request_source_image && !is_tiled {
+                        frame_state.resource_cache.request_image(
+                            request,
+                            frame_state.gpu_cache,
+                        );
+                    }
+
+                    if is_opaque {
+                        PrimitiveOpacity::from_alpha(self.color.a)
+                    } else {
+                        PrimitiveOpacity::translucent()
+                    }
+                }
+                None => {
+                    PrimitiveOpacity::opaque()
+                }
+            }
+        };
+    }
+
+    pub fn write_prim_gpu_blocks(&self, request: &mut GpuDataRequest) {
+        // Images are drawn as a white color, modulated by the total
+        // opacity coming from any collapsed property bindings.
+        request.push(self.color.premultiplied());
+        request.push(PremultipliedColorF::WHITE);
+        request.push([
+            self.stretch_size.width + self.tile_spacing.width,
+            self.stretch_size.height + self.tile_spacing.height,
+            0.0,
+            0.0,
+        ]);
+    }
+}
+
+pub type ImageTemplate = PrimTemplate<ImageData>;
+
+impl From<ImageKey> for ImageTemplate {
+    fn from(image: ImageKey) -> Self {
+        let common = PrimTemplateCommonData::with_key_common(image.common);
+
+        ImageTemplate {
+            common,
+            kind: image.kind.into(),
+        }
+    }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
+pub struct ImageDataMarker;
+
+pub type ImageDataStore = DataStore<ImageKey, ImageTemplate, ImageDataMarker>;
+pub type ImageDataHandle = Handle<ImageDataMarker>;
+pub type ImageDataUpdateList = UpdateList<ImageKey>;
+pub type ImageDataInterner = Interner<ImageKey, PrimitiveSceneData, ImageDataMarker>;
+
+impl Internable for Image {
+    type Marker = ImageDataMarker;
+    type Source = ImageKey;
+    type StoreData = ImageTemplate;
+    type InternData = PrimitiveSceneData;
+
+    /// Build a new key from self with `info`.
+    fn build_key(
+        self,
+        info: &LayoutPrimitiveInfo,
+        prim_relative_clip_rect: LayoutRect,
+    ) -> ImageKey {
+        ImageKey::new(
+            info.is_backface_visible,
+            info.rect.size,
+            prim_relative_clip_rect,
+            self
+        )
+    }
+}
+
+impl CreateShadow for Image {
+    fn create_shadow(&self, shadow: &Shadow) -> Self {
+        Image {
+            tile_spacing: self.tile_spacing,
+            stretch_size: self.stretch_size,
+            key: self.key,
+            sub_rect: self.sub_rect,
+            image_rendering: self.image_rendering,
+            alpha_type: self.alpha_type,
+            color: shadow.color.into(),
+        }
+    }
+}
+
+impl IsVisible for Image {
+    fn is_visible(&self) -> bool {
+        true
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct YuvImage {
+    pub color_depth: ColorDepth,
+    pub yuv_key: [ApiImageKey; 3],
+    pub format: YuvFormat,
+    pub color_space: YuvColorSpace,
+    pub image_rendering: ImageRendering,
+}
+
+pub type YuvImageKey = PrimKey<YuvImage>;
+
+impl YuvImageKey {
+    pub fn new(
+        is_backface_visible: bool,
+        prim_size: LayoutSize,
+        prim_relative_clip_rect: LayoutRect,
+        yuv_image: YuvImage,
+    ) -> Self {
+
+        YuvImageKey {
+            common: PrimKeyCommonData {
+                is_backface_visible,
+                prim_size: prim_size.into(),
+                prim_relative_clip_rect: prim_relative_clip_rect.into(),
+            },
+            kind: yuv_image,
+        }
+    }
+}
+
+impl InternDebug for YuvImageKey {}
+
+impl AsInstanceKind<YuvImageDataHandle> for YuvImageKey {
+    /// Construct a primitive instance that matches the type
+    /// of primitive key.
+    fn as_instance_kind(
+        &self,
+        data_handle: YuvImageDataHandle,
+        _prim_store: &mut PrimitiveStore,
+    ) -> PrimitiveInstanceKind {
+        PrimitiveInstanceKind::YuvImage {
+            data_handle,
+            segment_instance_index: SegmentInstanceIndex::INVALID
+        }
+    }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct YuvImageData {
+    pub color_depth: ColorDepth,
+    pub yuv_key: [ApiImageKey; 3],
+    pub format: YuvFormat,
+    pub color_space: YuvColorSpace,
+    pub image_rendering: ImageRendering,
+}
+
+impl From<YuvImage> for YuvImageData {
+    fn from(image: YuvImage) -> Self {
+        YuvImageData {
+            color_depth: image.color_depth,
+            yuv_key: image.yuv_key,
+            format: image.format,
+            color_space: image.color_space,
+            image_rendering: image.image_rendering,
+        }
+    }
+}
+
+impl YuvImageData {
+    /// Update the GPU cache for a given primitive template. This may be called multiple
+    /// times per frame, by each primitive reference that refers to this interned
+    /// template. The initial request call to the GPU cache ensures that work is only
+    /// done if the cache entry is invalid (due to first use or eviction).
+    pub fn update(
+        &mut self,
+        common: &mut PrimTemplateCommonData,
+        frame_state: &mut FrameBuildingState,
+    ) {
+        if let Some(mut request) = frame_state.gpu_cache.request(&mut common.gpu_cache_handle) {
+            self.write_prim_gpu_blocks(&mut request);
+        };
+
+        let channel_num = self.format.get_plane_num();
+        debug_assert!(channel_num <= 3);
+        for channel in 0 .. channel_num {
+            frame_state.resource_cache.request_image(
+                ImageRequest {
+                    key: self.yuv_key[channel],
+                    rendering: self.image_rendering,
+                    tile: None,
+                },
+                frame_state.gpu_cache,
+            );
+        }
+
+        common.opacity = PrimitiveOpacity::translucent();
+    }
+
+    pub fn write_prim_gpu_blocks(&self, request: &mut GpuDataRequest) {
+        request.push([
+            self.color_depth.rescaling_factor(),
+            pack_as_float(self.color_space as u32),
+            pack_as_float(self.format as u32),
+            0.0
+        ]);
+    }
+}
+
+pub type YuvImageTemplate = PrimTemplate<YuvImageData>;
+
+impl From<YuvImageKey> for YuvImageTemplate {
+    fn from(image: YuvImageKey) -> Self {
+        let common = PrimTemplateCommonData::with_key_common(image.common);
+
+        YuvImageTemplate {
+            common,
+            kind: image.kind.into(),
+        }
+    }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
+pub struct YuvImageDataMarker;
+
+pub type YuvImageDataStore = DataStore<YuvImageKey, YuvImageTemplate, YuvImageDataMarker>;
+pub type YuvImageDataHandle = Handle<YuvImageDataMarker>;
+pub type YuvImageDataUpdateList = UpdateList<YuvImageKey>;
+pub type YuvImageDataInterner = Interner<YuvImageKey, PrimitiveSceneData, YuvImageDataMarker>;
+
+impl Internable for YuvImage {
+    type Marker = YuvImageDataMarker;
+    type Source = YuvImageKey;
+    type StoreData = YuvImageTemplate;
+    type InternData = PrimitiveSceneData;
+
+    /// Build a new key from self with `info`.
+    fn build_key(
+        self,
+        info: &LayoutPrimitiveInfo,
+        prim_relative_clip_rect: LayoutRect,
+    ) -> YuvImageKey {
+        YuvImageKey::new(
+            info.is_backface_visible,
+            info.rect.size,
+            prim_relative_clip_rect,
+            self
+        )
+    }
+}
+
+impl IsVisible for YuvImage {
+    fn is_visible(&self) -> bool {
+        true
+    }
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+fn test_struct_sizes() {
+    use std::mem;
+    // The sizes of these structures are critical for performance on a number of
+    // talos stress tests. If you get a failure here on CI, there's two possibilities:
+    // (a) You made a structure smaller than it currently is. Great work! Update the
+    //     test expectations and move on.
+    // (b) You made a structure larger. This is not necessarily a problem, but should only
+    //     be done with care, and after checking if talos performance regresses badly.
+    assert_eq!(mem::size_of::<Image>(), 56, "Image size changed");
+    assert_eq!(mem::size_of::<ImageTemplate>(), 144, "ImageTemplate size changed");
+    assert_eq!(mem::size_of::<ImageKey>(), 84, "ImageKey size changed");
+    assert_eq!(mem::size_of::<YuvImage>(), 36, "YuvImage size changed");
+    assert_eq!(mem::size_of::<YuvImageTemplate>(), 96, "YuvImageTemplate size changed");
+    assert_eq!(mem::size_of::<YuvImageKey>(), 64, "YuvImageKey size changed");
+}
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -1,57 +1,61 @@
 /* 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, BorderRadius, ClipMode, ColorF, PictureRect, ColorU, LayoutVector2D};
-use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, DeviceRect, LayoutSideOffsetsAu};
-use api::{FilterOp, ImageKey, ImageRendering, TileOffset, RepeatMode, MixBlendMode};
+use api::{BorderRadius, ClipMode, ColorF, PictureRect, ColorU, LayoutVector2D};
+use api::{DeviceIntRect, DevicePixelScale, DeviceRect};
+use api::{FilterOp, ImageRendering, TileOffset, RepeatMode, MixBlendMode};
 use api::{LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize, PropertyBindingId};
-use api::{PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat};
-use api::{DeviceIntSideOffsets, WorldPixel, BoxShadowClipMode, NormalBorder, WorldRect, LayoutToWorldScale};
-use api::{PicturePixel, RasterPixel, ColorDepth, LineStyle, LineOrientation, LayoutSizeAu, AuHelpers};
+use api::{PremultipliedColorF, PropertyBinding, Shadow};
+use api::{WorldPixel, BoxShadowClipMode, WorldRect, LayoutToWorldScale};
+use api::{PicturePixel, RasterPixel, LineStyle, LineOrientation, LayoutSizeAu, AuHelpers};
 use api::LayoutPrimitiveInfo;
 use app_units::Au;
-use border::{get_max_scale_for_border, build_border_instances, create_border_segments};
-use border::{BorderSegmentCacheKey, NormalBorderAu};
+use border::{get_max_scale_for_border, build_border_instances};
+use border::BorderSegmentCacheKey;
 use clip::{ClipStore};
 use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
 use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
 use display_list_flattener::{AsInstanceKind, CreateShadow, IsVisible};
 use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveContext;
 use glyph_rasterizer::GlyphKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use gpu_types::BrushFlags;
-use image::{self, Repetition};
+use image::{Repetition};
 use intern;
 use picture::{PictureCompositeMode, PicturePrimitive, PictureUpdateState, TileCacheUpdateState};
 use picture::{ClusterRange, PrimitiveList, SurfaceIndex, SurfaceInfo, RetainedTiles, RasterConfig};
+use prim_store::borders::{ImageBorderDataHandle, NormalBorderDataHandle};
 use prim_store::gradient::{LinearGradientDataHandle, RadialGradientDataHandle};
+use prim_store::image::{ImageDataHandle, ImageInstance, VisibleImageTile, YuvImageDataHandle};
 use prim_store::text_run::{TextRunDataHandle, TextRunPrimitive};
 #[cfg(debug_assertions)]
 use render_backend::{FrameId};
 use render_backend::FrameResources;
-use render_task::{BlitSource, RenderTask, RenderTaskCacheKey, to_cache_size};
+use render_task::{RenderTask, RenderTaskCacheKey, to_cache_size};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
 use scene::SceneProperties;
 use segment::SegmentBuilder;
 use std::{cmp, fmt, hash, ops, u32, usize, mem};
 #[cfg(debug_assertions)]
 use std::sync::atomic::{AtomicUsize, Ordering};
 use storage;
 use util::{ScaleOffset, MatrixHelpers, MaxRect, recycle_vec};
 use util::{pack_as_float, project_rect, raster_rect_to_device_pixels};
 use smallvec::SmallVec;
 
+pub mod borders;
 pub mod gradient;
+pub mod image;
 pub mod text_run;
 
 /// Counter for unique primitive IDs for debug tracing.
 #[cfg(debug_assertions)]
 static NEXT_PRIM_ID: AtomicUsize = AtomicUsize::new(0);
 
 #[cfg(debug_assertions)]
 static PRIM_CHASE_ID: AtomicUsize = AtomicUsize::new(usize::MAX);
@@ -464,43 +468,19 @@ pub enum PrimitiveKeyKind {
         // that relies on a render task (e.g. wavy). If the
         // cache key is None, it uses a fast path to draw the
         // line decoration as a solid rect.
         cache_key: Option<LineDecorationCacheKey>,
         color: ColorU,
     },
     /// Clear an existing rect, used for special effects on some platforms.
     Clear,
-    NormalBorder {
-        border: NormalBorderAu,
-        widths: LayoutSideOffsetsAu,
-    },
-    ImageBorder {
-        request: ImageRequest,
-        nine_patch: NinePatchDescriptor,
-    },
     Rectangle {
         color: ColorU,
     },
-    YuvImage {
-        color_depth: ColorDepth,
-        yuv_key: [ImageKey; 3],
-        format: YuvFormat,
-        color_space: YuvColorSpace,
-        image_rendering: ImageRendering,
-    },
-    Image {
-        key: ImageKey,
-        stretch_size: SizeKey,
-        tile_spacing: SizeKey,
-        color: ColorU,
-        sub_rect: Option<DeviceIntRect>,
-        image_rendering: ImageRendering,
-        alpha_type: AlphaType,
-    },
     Picture {
         composite_mode_key: PictureCompositeKey,
     },
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, PartialEq)]
@@ -725,16 +705,24 @@ impl PrimKeyCommonData {
             prim_relative_clip_rect: prim_relative_clip_rect.into(),
         }
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct PrimKey<T> {
+    pub common: PrimKeyCommonData,
+    pub kind: T,
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
 pub struct PrimitiveKey {
     pub common: PrimKeyCommonData,
     pub kind: PrimitiveKeyKind,
 }
 
 impl PrimitiveKey {
     pub fn new(
         is_backface_visible: bool,
@@ -756,210 +744,83 @@ impl PrimitiveKey {
 impl intern::InternDebug for PrimitiveKey {}
 
 impl AsInstanceKind<PrimitiveDataHandle> for PrimitiveKey {
     /// Construct a primitive instance that matches the type
     /// of primitive key.
     fn as_instance_kind(
         &self,
         data_handle: PrimitiveDataHandle,
-        prim_store: &mut PrimitiveStore,
+        _: &mut PrimitiveStore,
     ) -> PrimitiveInstanceKind {
         match self.kind {
             PrimitiveKeyKind::LineDecoration { .. } => {
                 PrimitiveInstanceKind::LineDecoration {
                     data_handle,
                     cache_handle: None,
                 }
             }
             PrimitiveKeyKind::Clear => {
                 PrimitiveInstanceKind::Clear {
                     data_handle
                 }
             }
-            PrimitiveKeyKind::NormalBorder { .. } => {
-                PrimitiveInstanceKind::NormalBorder {
-                    data_handle,
-                    cache_handles: storage::Range::empty(),
-                }
-            }
-            PrimitiveKeyKind::ImageBorder { .. } => {
-                PrimitiveInstanceKind::ImageBorder {
-                    data_handle
-                }
-            }
             PrimitiveKeyKind::Rectangle { .. } => {
                 PrimitiveInstanceKind::Rectangle {
                     data_handle,
                     opacity_binding_index: OpacityBindingIndex::INVALID,
                     segment_instance_index: SegmentInstanceIndex::INVALID,
                 }
             }
-            PrimitiveKeyKind::YuvImage { .. } => {
-                PrimitiveInstanceKind::YuvImage {
-                    data_handle,
-                    segment_instance_index: SegmentInstanceIndex::INVALID
-                }
-            }
-            PrimitiveKeyKind::Image { .. } => {
-                // TODO(gw): Refactor this to not need a separate image
-                //           instance (see ImageInstance struct).
-                let image_instance_index = prim_store.images.push(ImageInstance {
-                    opacity_binding_index: OpacityBindingIndex::INVALID,
-                    segment_instance_index: SegmentInstanceIndex::INVALID,
-                    visible_tiles: Vec::new(),
-                });
-
-                PrimitiveInstanceKind::Image {
-                    data_handle,
-                    image_instance_index,
-                }
-            }
             PrimitiveKeyKind::Picture { .. } => {
                 // Should never be hit as this method should not be
                 // called for pictures.
                 unreachable!();
             }
         }
     }
 }
 
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct NormalBorderTemplate {
-    pub brush_segments: Vec<BrushSegment>,
-    pub border_segments: Vec<BorderSegmentInfo>,
-    pub border: NormalBorder,
-    pub widths: LayoutSideOffsets,
-}
-
 /// The shared information for a given primitive. This is interned and retained
 /// both across frames and display lists, by comparing the matching PrimitiveKey.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum PrimitiveTemplateKind {
     LineDecoration {
         cache_key: Option<LineDecorationCacheKey>,
         color: ColorF,
     },
-    NormalBorder {
-        template: Box<NormalBorderTemplate>,
-    },
-    ImageBorder {
-        request: ImageRequest,
-        brush_segments: Vec<BrushSegment>,
-    },
     Rectangle {
         color: ColorF,
     },
-    YuvImage {
-        color_depth: ColorDepth,
-        yuv_key: [ImageKey; 3],
-        format: YuvFormat,
-        color_space: YuvColorSpace,
-        image_rendering: ImageRendering,
-    },
-    Image {
-        key: ImageKey,
-        stretch_size: LayoutSize,
-        tile_spacing: LayoutSize,
-        color: ColorF,
-        source: ImageSource,
-        image_rendering: ImageRendering,
-        sub_rect: Option<DeviceIntRect>,
-        alpha_type: AlphaType,
-    },
     Clear,
     Picture {
 
     },
 }
 
 /// Construct the primitive template data from a primitive key. This
 /// is invoked when a primitive key is created and the interner
 /// doesn't currently contain a primitive with this key.
 impl PrimitiveKeyKind {
-    fn into_template(
-        self,
-        size: LayoutSize,
-    ) -> PrimitiveTemplateKind {
+    fn into_template(self) -> PrimitiveTemplateKind {
         match self {
             PrimitiveKeyKind::Picture { .. } => {
                 PrimitiveTemplateKind::Picture {
 
                 }
             }
             PrimitiveKeyKind::Clear => {
                 PrimitiveTemplateKind::Clear
             }
-            PrimitiveKeyKind::NormalBorder { widths, border, .. } => {
-                let mut border: NormalBorder = border.into();
-                let widths = LayoutSideOffsets::from_au(widths);
-
-                // FIXME(emilio): Is this the best place to do this?
-                border.normalize(&widths);
-
-                let mut brush_segments = Vec::new();
-                let mut border_segments = Vec::new();
-
-                create_border_segments(
-                    size,
-                    &border,
-                    &widths,
-                    &mut border_segments,
-                    &mut brush_segments,
-                );
-
-                PrimitiveTemplateKind::NormalBorder {
-                    template: Box::new(NormalBorderTemplate {
-                        border,
-                        widths,
-                        border_segments,
-                        brush_segments,
-                    })
-                }
-            }
-            PrimitiveKeyKind::ImageBorder {
-                request,
-                ref nine_patch,
-                ..
-            } => {
-                let brush_segments = nine_patch.create_segments(size);
-
-                PrimitiveTemplateKind::ImageBorder {
-                    request,
-                    brush_segments,
-                }
-            }
             PrimitiveKeyKind::Rectangle { color, .. } => {
                 PrimitiveTemplateKind::Rectangle {
                     color: color.into(),
                 }
             }
-            PrimitiveKeyKind::YuvImage { color_depth, yuv_key, format, color_space, image_rendering, .. } => {
-                PrimitiveTemplateKind::YuvImage {
-                    color_depth,
-                    yuv_key,
-                    format,
-                    color_space,
-                    image_rendering,
-                }
-            }
-            PrimitiveKeyKind::Image { alpha_type, key, color, stretch_size, tile_spacing, image_rendering, sub_rect, .. } => {
-                PrimitiveTemplateKind::Image {
-                    key,
-                    color: color.into(),
-                    stretch_size: stretch_size.into(),
-                    tile_spacing: tile_spacing.into(),
-                    source: ImageSource::Default,
-                    sub_rect,
-                    image_rendering,
-                    alpha_type,
-                }
-            }
             PrimitiveKeyKind::LineDecoration { cache_key, color } => {
                 PrimitiveTemplateKind::LineDecoration {
                     cache_key,
                     color: color.into(),
                 }
             }
         }
     }
@@ -988,16 +849,23 @@ impl PrimTemplateCommonData {
             gpu_cache_handle: GpuCacheHandle::new(),
             opacity: PrimitiveOpacity::translucent(),
         }
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PrimTemplate<T> {
+    pub common: PrimTemplateCommonData,
+    pub kind: T,
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveTemplate {
     pub common: PrimTemplateCommonData,
     pub kind: PrimitiveTemplateKind,
 }
 
 impl ops::Deref for PrimitiveTemplate {
     type Target = PrimTemplateCommonData;
     fn deref(&self) -> &Self::Target {
@@ -1009,63 +877,36 @@ impl ops::DerefMut for PrimitiveTemplate
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.common
     }
 }
 
 impl From<PrimitiveKey> for PrimitiveTemplate {
     fn from(item: PrimitiveKey) -> Self {
         let common = PrimTemplateCommonData::with_key_common(item.common);
-        let kind = item.kind.into_template(common.prim_size);
+        let kind = item.kind.into_template();
 
         PrimitiveTemplate { common, kind, }
     }
 }
 
 impl PrimitiveTemplateKind {
     /// Write any GPU blocks for the primitive template to the given request object.
     fn write_prim_gpu_blocks(
         &self,
-        request: &mut GpuDataRequest,
-        prim_size: LayoutSize,
+        request: &mut GpuDataRequest
     ) {
         match *self {
             PrimitiveTemplateKind::Clear => {
                 // Opaque black with operator dest out
                 request.push(PremultipliedColorF::BLACK);
             }
             PrimitiveTemplateKind::Rectangle { ref color, .. } => {
                 request.push(color.premultiplied());
             }
-            PrimitiveTemplateKind::NormalBorder { .. } => {
-                // Border primitives currently used for
-                // image borders, and run through the
-                // normal brush_image shader.
-                request.push(PremultipliedColorF::WHITE);
-                request.push(PremultipliedColorF::WHITE);
-                request.push([
-                    prim_size.width,
-                    prim_size.height,
-                    0.0,
-                    0.0,
-                ]);
-            }
-            PrimitiveTemplateKind::ImageBorder { .. } => {
-                // Border primitives currently used for
-                // image borders, and run through the
-                // normal brush_image shader.
-                request.push(PremultipliedColorF::WHITE);
-                request.push(PremultipliedColorF::WHITE);
-                request.push([
-                    prim_size.width,
-                    prim_size.height,
-                    0.0,
-                    0.0,
-                ]);
-            }
             PrimitiveTemplateKind::LineDecoration { ref cache_key, ref color } => {
                 match cache_key {
                     Some(cache_key) => {
                         request.push(color.premultiplied());
                         request.push(PremultipliedColorF::WHITE);
                         request.push([
                             cache_key.size.width.to_f32_px(),
                             cache_key.size.height.to_f32_px(),
@@ -1073,277 +914,47 @@ impl PrimitiveTemplateKind {
                             0.0,
                         ]);
                     }
                     None => {
                         request.push(color.premultiplied());
                     }
                 }
             }
-            PrimitiveTemplateKind::YuvImage { color_depth, format, color_space, .. } => {
-                request.push([
-                    color_depth.rescaling_factor(),
-                    pack_as_float(color_space as u32),
-                    pack_as_float(format as u32),
-                    0.0
-                ]);
-            }
-            PrimitiveTemplateKind::Image { stretch_size, tile_spacing, color, .. } => {
-                // Images are drawn as a white color, modulated by the total
-                // opacity coming from any collapsed property bindings.
-                request.push(color.premultiplied());
-                request.push(PremultipliedColorF::WHITE);
-                request.push([
-                    stretch_size.width + tile_spacing.width,
-                    stretch_size.height + tile_spacing.height,
-                    0.0,
-                    0.0,
-                ]);
-            }
-            PrimitiveTemplateKind::Picture { .. } => {}
-        }
-    }
-
-    fn write_segment_gpu_blocks(
-        &self,
-        request: &mut GpuDataRequest,
-    ) {
-        match *self {
-            PrimitiveTemplateKind::NormalBorder { ref template, .. } => {
-                for segment in &template.brush_segments {
-                    // has to match VECS_PER_SEGMENT
-                    request.write_segment(
-                        segment.local_rect,
-                        segment.extra_data,
-                    );
-                }
-            }
-            PrimitiveTemplateKind::ImageBorder { ref brush_segments, .. } => {
-                for segment in brush_segments {
-                    // has to match VECS_PER_SEGMENT
-                    request.write_segment(
-                        segment.local_rect,
-                        segment.extra_data,
-                    );
-                }
-            }
-            PrimitiveTemplateKind::Clear |
-            PrimitiveTemplateKind::LineDecoration { .. } |
-            PrimitiveTemplateKind::Image { .. } |
-            PrimitiveTemplateKind::Rectangle { .. } |
-            PrimitiveTemplateKind::YuvImage { .. } |
             PrimitiveTemplateKind::Picture { .. } => {}
         }
     }
 }
 
 impl PrimitiveTemplate {
     /// Update the GPU cache for a given primitive template. This may be called multiple
     /// times per frame, by each primitive reference that refers to this interned
     /// template. The initial request call to the GPU cache ensures that work is only
     /// done if the cache entry is invalid (due to first use or eviction).
     pub fn update(
         &mut self,
-        // TODO(gw): Passing in surface_index here is not ideal. The primitive template
-        //           code shouldn't depend on current surface state. This is due to a
-        //           limitation in how render task caching works. We should fix this by
-        //           allowing render task caching to assign to surfaces implicitly
-        //           during pass allocation.
-        surface_index: SurfaceIndex,
         frame_state: &mut FrameBuildingState,
     ) {
         if let Some(mut request) = frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) {
-            self.kind.write_prim_gpu_blocks(
-                &mut request,
-                self.common.prim_size,
-            );
-            self.kind.write_segment_gpu_blocks(&mut request);
+            self.kind.write_prim_gpu_blocks(&mut request);
         }
 
         self.opacity = match self.kind {
             PrimitiveTemplateKind::Clear => {
                 PrimitiveOpacity::translucent()
             }
             PrimitiveTemplateKind::Rectangle { ref color, .. } => {
                 PrimitiveOpacity::from_alpha(color.a)
             }
-            PrimitiveTemplateKind::NormalBorder { .. } => {
-                // Shouldn't matter, since the segment opacity is used instead
-                PrimitiveOpacity::translucent()
-            }
-            PrimitiveTemplateKind::ImageBorder { request, .. } => {
-                let image_properties = frame_state
-                    .resource_cache
-                    .get_image_properties(request.key);
-
-                if let Some(image_properties) = image_properties {
-                    frame_state.resource_cache.request_image(
-                        request,
-                        frame_state.gpu_cache,
-                    );
-                    PrimitiveOpacity {
-                        is_opaque: image_properties.descriptor.is_opaque,
-                    }
-                } else {
-                    PrimitiveOpacity::opaque()
-                }
-            }
             PrimitiveTemplateKind::LineDecoration { ref cache_key, ref color } => {
                 match cache_key {
                     Some(..) => PrimitiveOpacity::translucent(),
                     None => PrimitiveOpacity::from_alpha(color.a),
                 }
             }
-            PrimitiveTemplateKind::YuvImage { format, yuv_key, image_rendering, .. } => {
-                let channel_num = format.get_plane_num();
-                debug_assert!(channel_num <= 3);
-                for channel in 0 .. channel_num {
-                    frame_state.resource_cache.request_image(
-                        ImageRequest {
-                            key: yuv_key[channel],
-                            rendering: image_rendering,
-                            tile: None,
-                        },
-                        frame_state.gpu_cache,
-                    );
-                }
-
-                PrimitiveOpacity::translucent()
-            }
-            PrimitiveTemplateKind::Image { key, stretch_size, ref color, tile_spacing, ref mut source, sub_rect, image_rendering, .. } => {
-                let image_properties = frame_state
-                    .resource_cache
-                    .get_image_properties(key);
-
-                match image_properties {
-                    Some(image_properties) => {
-                        let is_tiled = image_properties.tiling.is_some();
-
-                        if tile_spacing != LayoutSize::zero() && !is_tiled {
-                            *source = ImageSource::Cache {
-                                // Size in device-pixels we need to allocate in render task cache.
-                                size: image_properties.descriptor.size.to_i32(),
-                                handle: None,
-                            };
-                        }
-
-                        // Work out whether this image is a normal / simple type, or if
-                        // we need to pre-render it to the render task cache.
-                        if let Some(rect) = sub_rect {
-                            // We don't properly support this right now.
-                            debug_assert!(!is_tiled);
-                            *source = ImageSource::Cache {
-                                // Size in device-pixels we need to allocate in render task cache.
-                                size: rect.size,
-                                handle: None,
-                            };
-                        }
-
-                        let mut request_source_image = false;
-                        let mut is_opaque = image_properties.descriptor.is_opaque;
-                        let request = ImageRequest {
-                            key,
-                            rendering: image_rendering,
-                            tile: None,
-                        };
-
-                        // Every frame, for cached items, we need to request the render
-                        // task cache item. The closure will be invoked on the first
-                        // time through, and any time the render task output has been
-                        // evicted from the texture cache.
-                        match *source {
-                            ImageSource::Cache { ref mut size, ref mut handle } => {
-                                let padding = DeviceIntSideOffsets::new(
-                                    0,
-                                    (tile_spacing.width * size.width as f32 / stretch_size.width) as i32,
-                                    (tile_spacing.height * size.height as f32 / stretch_size.height) as i32,
-                                    0,
-                                );
-
-                                let inner_size = *size;
-                                size.width += padding.horizontal();
-                                size.height += padding.vertical();
-
-                                is_opaque &= padding == DeviceIntSideOffsets::zero();
-
-                                let image_cache_key = ImageCacheKey {
-                                    request,
-                                    texel_rect: sub_rect,
-                                };
-                                let surfaces = &mut frame_state.surfaces;
-
-                                // Request a pre-rendered image task.
-                                *handle = Some(frame_state.resource_cache.request_render_task(
-                                    RenderTaskCacheKey {
-                                        size: *size,
-                                        kind: RenderTaskCacheKeyKind::Image(image_cache_key),
-                                    },
-                                    frame_state.gpu_cache,
-                                    frame_state.render_tasks,
-                                    None,
-                                    image_properties.descriptor.is_opaque,
-                                    |render_tasks| {
-                                        // We need to render the image cache this frame,
-                                        // so will need access to the source texture.
-                                        request_source_image = true;
-
-                                        // Create a task to blit from the texture cache to
-                                        // a normal transient render task surface. This will
-                                        // copy only the sub-rect, if specified.
-                                        let cache_to_target_task = RenderTask::new_blit_with_padding(
-                                            inner_size,
-                                            &padding,
-                                            BlitSource::Image { key: image_cache_key },
-                                        );
-                                        let cache_to_target_task_id = render_tasks.add(cache_to_target_task);
-
-                                        // Create a task to blit the rect from the child render
-                                        // task above back into the right spot in the persistent
-                                        // render target cache.
-                                        let target_to_cache_task = RenderTask::new_blit(
-                                            *size,
-                                            BlitSource::RenderTask {
-                                                task_id: cache_to_target_task_id,
-                                            },
-                                        );
-                                        let target_to_cache_task_id = render_tasks.add(target_to_cache_task);
-
-                                        // Hook this into the render task tree at the right spot.
-                                        surfaces[surface_index.0].tasks.push(target_to_cache_task_id);
-
-                                        // Pass the image opacity, so that the cached render task
-                                        // item inherits the same opacity properties.
-                                        target_to_cache_task_id
-                                    }
-                                ));
-                            }
-                            ImageSource::Default => {
-                                // Normal images just reference the source texture each frame.
-                                request_source_image = true;
-                            }
-                        }
-
-                        if request_source_image && !is_tiled {
-                            frame_state.resource_cache.request_image(
-                                request,
-                                frame_state.gpu_cache,
-                            );
-                        }
-
-                        if is_opaque {
-                            PrimitiveOpacity::from_alpha(color.a)
-                        } else {
-                            PrimitiveOpacity::translucent()
-                        }
-                    }
-                    None => {
-                        PrimitiveOpacity::opaque()
-                    }
-                }
-            }
             PrimitiveTemplateKind::Picture { .. } => {
                 PrimitiveOpacity::translucent()
             }
         };
     }
 }
 
 // Type definitions for interning primitives.
@@ -1413,27 +1024,16 @@ impl OpacityBinding {
 
         self.current = new_opacity;
     }
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct VisibleImageTile {
-    pub tile_offset: TileOffset,
-    pub handle: GpuCacheHandle,
-    pub edge_flags: EdgeAaSegmentMask,
-    pub local_rect: LayoutRect,
-    pub local_clip_rect: LayoutRect,
-}
-
-#[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct VisibleMaskImageTile {
     pub tile_offset: TileOffset,
     pub tile_rect: LayoutRect,
 }
 
 #[derive(Debug)]
 pub struct VisibleGradientTile {
     pub handle: GpuCacheHandle,
@@ -1557,51 +1157,26 @@ impl BrushSegment {
             }
             None => {
                 ClipMaskKind::Clipped
             }
         }
     }
 }
 
-// Key that identifies a unique (partial) image that is being
-// stored in the render task cache.
-#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct ImageCacheKey {
-    pub request: ImageRequest,
-    pub texel_rect: Option<DeviceIntRect>,
-}
-
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct LineDecorationCacheKey {
     pub style: LineStyle,
     pub orientation: LineOrientation,
     pub wavy_line_thickness: Au,
     pub size: LayoutSizeAu,
 }
 
-// Where to find the texture data for an image primitive.
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-#[derive(Debug)]
-pub enum ImageSource {
-    // A normal image - just reference the texture cache.
-    Default,
-    // An image that is pre-rendered into the texture cache
-    // via a render task.
-    Cache {
-        size: DeviceIntSize,
-        handle: Option<RenderTaskCacheEntryHandle>,
-    },
-}
-
 #[derive(Debug)]
 #[repr(C)]
 struct ClipRect {
     rect: LayoutRect,
     mode: f32,
 }
 
 #[derive(Debug)]
@@ -1825,20 +1400,16 @@ impl IsVisible for PrimitiveKeyKind {
     // Used to trivially reject non-visible primitives.
     // TODO(gw): Currently, primitives other than those
     //           listed here are handled before the
     //           add_primitive() call. In the future
     //           we should move the logic for all other
     //           primitive types to use this.
     fn is_visible(&self) -> bool {
         match *self {
-            PrimitiveKeyKind::NormalBorder { .. } |
-            PrimitiveKeyKind::ImageBorder { .. } |
-            PrimitiveKeyKind::YuvImage { .. } |
-            PrimitiveKeyKind::Image { .. } |
             PrimitiveKeyKind::Clear |
             PrimitiveKeyKind::Picture { .. } => {
                 true
             }
             PrimitiveKeyKind::Rectangle { ref color, .. } |
             PrimitiveKeyKind::LineDecoration { ref color, .. } => {
                 color.a > 0
             }
@@ -1861,63 +1432,24 @@ impl CreateShadow for PrimitiveKeyKind {
                     cache_key: cache_key.clone(),
                 }
             }
             PrimitiveKeyKind::Rectangle { .. } => {
                 PrimitiveKeyKind::Rectangle {
                     color: shadow.color.into(),
                 }
             }
-            PrimitiveKeyKind::NormalBorder { ref border, widths, .. } => {
-                let border = border.with_color(shadow.color.into());
-                PrimitiveKeyKind::NormalBorder {
-                    border,
-                    widths,
-                }
-            }
-            PrimitiveKeyKind::Image { alpha_type, image_rendering, tile_spacing, stretch_size, key, sub_rect, .. } => {
-                PrimitiveKeyKind::Image {
-                    tile_spacing,
-                    stretch_size,
-                    key,
-                    sub_rect,
-                    image_rendering,
-                    alpha_type,
-                    color: shadow.color.into(),
-                }
-            }
-            PrimitiveKeyKind::ImageBorder { .. } |
-            PrimitiveKeyKind::YuvImage { .. } |
             PrimitiveKeyKind::Picture { .. } |
             PrimitiveKeyKind::Clear => {
                 panic!("bug: this prim is not supported in shadow contexts");
             }
         }
     }
 }
 
-/// Instance specific fields for an image primitive. These are
-/// currently stored in a separate array to avoid bloating the
-/// size of PrimitiveInstance. In the future, we should be able
-/// to remove this and store the information inline, by:
-/// (a) Removing opacity collapse / binding support completely.
-///     Once we have general picture caching, we don't need this.
-/// (b) Change visible_tiles to use Storage in the primitive
-///     scratch buffer. This will reduce the size of the
-///     visible_tiles field here, and save memory allocation
-///     when image tiling is used. I've left it as a Vec for
-///     now to reduce the number of changes, and because image
-///     tiling is very rare on real pages.
-#[derive(Debug)]
-pub struct ImageInstance {
-    pub opacity_binding_index: OpacityBindingIndex,
-    pub segment_instance_index: SegmentInstanceIndex,
-    pub visible_tiles: Vec<VisibleImageTile>,
-}
-
 #[derive(Clone, Copy, Debug, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveDebugId(pub usize);
 
 #[derive(Clone, Debug)]
 pub enum PrimitiveInstanceKind {
     /// Direct reference to a Picture
@@ -1945,37 +1477,37 @@ pub enum PrimitiveInstanceKind {
         //           occur at the same time, we can remove most of
         //           the things we store here in the instance, and
         //           use them directly. This will remove cache_handle,
         //           but also the opacity, clip_task_id etc below.
         cache_handle: Option<RenderTaskCacheEntryHandle>,
     },
     NormalBorder {
         /// Handle to the common interned data for this primitive.
-        data_handle: PrimitiveDataHandle,
+        data_handle: NormalBorderDataHandle,
         cache_handles: storage::Range<RenderTaskCacheEntryHandle>,
     },
     ImageBorder {
         /// Handle to the common interned data for this primitive.
-        data_handle: PrimitiveDataHandle,
+        data_handle: ImageBorderDataHandle,
     },
     Rectangle {
         /// Handle to the common interned data for this primitive.
         data_handle: PrimitiveDataHandle,
         opacity_binding_index: OpacityBindingIndex,
         segment_instance_index: SegmentInstanceIndex,
     },
     YuvImage {
         /// Handle to the common interned data for this primitive.
-        data_handle: PrimitiveDataHandle,
+        data_handle: YuvImageDataHandle,
         segment_instance_index: SegmentInstanceIndex,
     },
     Image {
         /// Handle to the common interned data for this primitive.
-        data_handle: PrimitiveDataHandle,
+        data_handle: ImageDataHandle,
         image_instance_index: ImageInstanceIndex,
     },
     LinearGradient {
         /// Handle to the common interned data for this primitive.
         data_handle: LinearGradientDataHandle,
         visible_tiles_range: GradientTileRange,
     },
     RadialGradient {
@@ -2069,32 +1601,40 @@ impl PrimitiveInstance {
         false
     }
 
     pub fn uid(&self) -> intern::ItemUid {
         match &self.kind {
             PrimitiveInstanceKind::Picture { data_handle, .. } |
             PrimitiveInstanceKind::LineDecoration { data_handle, .. } |
             PrimitiveInstanceKind::Clear { data_handle, .. } |
-            PrimitiveInstanceKind::NormalBorder { data_handle, .. } |
-            PrimitiveInstanceKind::ImageBorder { data_handle, .. } |
-            PrimitiveInstanceKind::Rectangle { data_handle, .. } |
-            PrimitiveInstanceKind::YuvImage { data_handle, .. } |
+            PrimitiveInstanceKind::Rectangle { data_handle, .. } => {
+                data_handle.uid()
+            }
             PrimitiveInstanceKind::Image { data_handle, .. } => {
                 data_handle.uid()
             }
+            PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
+                data_handle.uid()
+            }
             PrimitiveInstanceKind::LinearGradient { data_handle, .. } => {
                 data_handle.uid()
             }
+            PrimitiveInstanceKind::NormalBorder { data_handle, .. } => {
+                data_handle.uid()
+            }
             PrimitiveInstanceKind::RadialGradient { data_handle, .. } => {
                 data_handle.uid()
             }
             PrimitiveInstanceKind::TextRun { data_handle, .. } => {
                 data_handle.uid()
             }
+            PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
+                data_handle.uid()
+            }
         }
     }
 }
 
 #[derive(Debug)]
 pub struct SegmentedInstance {
     pub gpu_cache_handle: GpuCacheHandle,
     pub segments_range: SegmentsRange,
@@ -2761,17 +2301,17 @@ impl PrimitiveStore {
         }
 
         #[cfg(debug_assertions)]
         {
             prim_instance.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
         pic_state.is_cacheable &= prim_instance.is_cacheable(
-            &resources.prim_data_store,
+            &resources,
             frame_state.resource_cache,
         );
 
         match prim_instance.kind {
             PrimitiveInstanceKind::Picture { pic_index, .. } => {
                 let pic = &mut self.pictures[pic_index.0];
                 if pic.prepare_for_render(
                     pic_index,
@@ -2939,20 +2479,17 @@ impl PrimitiveStore {
         let is_chased = prim_instance.is_chased();
 
         match &mut prim_instance.kind {
             PrimitiveInstanceKind::LineDecoration { data_handle, ref mut cache_handle, .. } => {
                 let prim_data = &mut resources.prim_data_store[*data_handle];
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
-                prim_data.update(
-                    pic_context.surface_index,
-                    frame_state,
-                );
+                prim_data.update(frame_state);
 
                 match &prim_data.kind {
                     PrimitiveTemplateKind::LineDecoration { ref cache_key, .. } => {
                         // Work out the device pixel size to be used to cache this line decoration.
                         if is_chased {
                             println!("\tline decoration key={:?}", cache_key);
                         }
 
@@ -3027,51 +2564,43 @@ impl PrimitiveStore {
                     scratch,
                 );
             }
             PrimitiveInstanceKind::Clear { data_handle, .. } => {
                 let prim_data = &mut resources.prim_data_store[*data_handle];
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
-                prim_data.update(
-                    pic_context.surface_index,
-                    frame_state,
-                );
+                prim_data.update(frame_state);
             }
             PrimitiveInstanceKind::NormalBorder { data_handle, ref mut cache_handles, .. } => {
-                let prim_data = &mut resources.prim_data_store[*data_handle];
+                let prim_data = &mut resources.normal_border_data_store[*data_handle];
+                let common_data = &mut prim_data.common;
+                let border_data = &mut prim_data.kind;
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
-                prim_data.update(
-                    pic_context.surface_index,
-                    frame_state,
-                );
-
-                let template = match prim_data.kind {
-                    PrimitiveTemplateKind::NormalBorder { ref template, .. } => template,
-                    _ => unreachable!()
-                };
+                border_data.update(common_data, frame_state);
 
                 // TODO(gw): When drawing in screen raster mode, we should also incorporate a
                 //           scale factor from the world transform to get an appropriately
                 //           sized border task.
                 let world_scale = LayoutToWorldScale::new(1.0);
                 let mut scale = world_scale * frame_context.device_pixel_scale;
-                let max_scale = get_max_scale_for_border(&template.border.radius, &template.widths);
+                let max_scale = get_max_scale_for_border(&border_data.border.radius,
+                                                         &border_data.widths);
                 scale.0 = scale.0.min(max_scale.0);
 
                 // For each edge and corner, request the render task by content key
                 // from the render task cache. This ensures that the render task for
                 // this segment will be available for batching later in the frame.
                 let mut handles: SmallVec<[RenderTaskCacheEntryHandle; 8]> = SmallVec::new();
                 let surfaces = &mut frame_state.surfaces;
 
-                for segment in &template.border_segments {
+                for segment in &border_data.border_segments {
                     // Update the cache key device size based on requested scale.
                     let cache_size = to_cache_size(segment.local_task_size * scale);
                     let cache_key = RenderTaskCacheKey {
                         kind: RenderTaskCacheKeyKind::BorderSegment(segment.cache_key.clone()),
                         size: cache_size,
                     };
 
                     handles.push(frame_state.resource_cache.request_render_task(
@@ -3081,17 +2610,17 @@ impl PrimitiveStore {
                         None,
                         false,          // TODO(gw): We don't calculate opacity for borders yet!
                         |render_tasks| {
                             let task = RenderTask::new_border_segment(
                                 cache_size,
                                 build_border_instances(
                                     &segment.cache_key,
                                     cache_size,
-                                    &template.border,
+                                    &border_data.border,
                                     scale,
                                 ),
                             );
 
                             let task_id = render_tasks.add(task);
 
                             surfaces[pic_context.surface_index.0].tasks.push(task_id);
 
@@ -3100,85 +2629,78 @@ impl PrimitiveStore {
                     ));
                 }
 
                 *cache_handles = scratch
                     .border_cache_handles
                     .extend(handles);
             }
             PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
-                let prim_data = &mut resources.prim_data_store[*data_handle];
+                let prim_data = &mut resources.image_border_data_store[*data_handle];
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
-                prim_data.update(
-                    pic_context.surface_index,
-                    frame_state,
-                );
+                prim_data.kind.update(&mut prim_data.common, frame_state);
             }
             PrimitiveInstanceKind::Rectangle { data_handle, segment_instance_index, opacity_binding_index, .. } => {
                 let prim_data = &mut resources.prim_data_store[*data_handle];
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
-                prim_data.update(
-                    pic_context.surface_index,
-                    frame_state,
-                );
+                prim_data.update(frame_state);
 
                 update_opacity_binding(
                     &mut self.opacity_bindings,
                     *opacity_binding_index,
                     frame_context.scene_properties,
                 );
 
-                write_segment(prim_data, *segment_instance_index, frame_state, scratch);
+                write_segment(*segment_instance_index, frame_state, scratch, |request| {
+                    prim_data.kind.write_prim_gpu_blocks(
+                        request,
+                    );
+                });
             }
             PrimitiveInstanceKind::YuvImage { data_handle, segment_instance_index, .. } => {
-                let prim_data = &mut resources.prim_data_store[*data_handle];
+                let yuv_image_data = &mut resources.yuv_image_data_store[*data_handle];
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
-                prim_data.update(
-                    pic_context.surface_index,
-                    frame_state,
-                );
-
-                write_segment(prim_data, *segment_instance_index, frame_state, scratch);
+                yuv_image_data.kind.update(&mut yuv_image_data.common, frame_state);
+
+                write_segment(*segment_instance_index, frame_state, scratch, |request| {
+                    yuv_image_data.kind.write_prim_gpu_blocks(request);
+                });
             }
             PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
-                let prim_data = &mut resources.prim_data_store[*data_handle];
+                let prim_data = &mut resources.image_data_store[*data_handle];
+                let common_data = &mut prim_data.common;
+                let image_data = &mut prim_data.kind;
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
-                prim_data.update(
+                image_data.update(
                     pic_context.surface_index,
+                    common_data,
                     frame_state,
                 );
 
-                let (key, stretch_size, tile_spacing, image_rendering) = match prim_data.kind {
-                    PrimitiveTemplateKind::Image { ref key, ref stretch_size, ref tile_spacing, ref image_rendering, .. } => {
-                        (key, stretch_size, tile_spacing, image_rendering)
-                    }
-                    _ => unreachable!()
-                };
-
                 let image_instance = &mut self.images[*image_instance_index];
 
                 update_opacity_binding(
                     &mut self.opacity_bindings,
                     image_instance.opacity_binding_index,
                     frame_context.scene_properties,
                 );
 
                 image_instance.visible_tiles.clear();
 
                 let image_properties = frame_state
                     .resource_cache
-                    .get_image_properties(*key);
+                    .get_image_properties(image_data.key);
 
                 if let Some(image_properties) = image_properties {
                     if let Some(tile_size) = image_properties.tiling {
                         let device_image_size = image_properties.descriptor.size;
 
                         // Tighten the clip rect because decomposing the repeated image can
                         // produce primitives that are partially covering the original image
                         // rect and we want to clip these extra parts out.
@@ -3187,41 +2709,41 @@ impl PrimitiveStore {
                             .intersection(&prim_local_rect).unwrap();
 
                         let visible_rect = compute_conservative_visible_rect(
                             prim_context,
                             &pic_context.dirty_world_rect,
                             &tight_clip_rect
                         );
 
-                        let base_edge_flags = edge_flags_for_tile_spacing(tile_spacing);
-
-                        let stride = *stretch_size + *tile_spacing;
-
-                        let repetitions = image::repetitions(
+                        let base_edge_flags = edge_flags_for_tile_spacing(&image_data.tile_spacing);
+
+                        let stride = image_data.stretch_size + image_data.tile_spacing;
+
+                        let repetitions = ::image::repetitions(
                             &prim_local_rect,
                             &visible_rect,
                             stride,
                         );
 
                         let request = ImageRequest {
-                            key: *key,
-                            rendering: *image_rendering,
+                            key: image_data.key,
+                            rendering: image_data.image_rendering,
                             tile: None,
                         };
 
                         for Repetition { origin, edge_flags } in repetitions {
                             let edge_flags = base_edge_flags | edge_flags;
 
                             let image_rect = LayoutRect {
                                 origin,
-                                size: *stretch_size,
+                                size: image_data.stretch_size,
                             };
 
-                            let tiles = image::tiles(
+                            let tiles = ::image::tiles(
                                 &image_rect,
                                 &visible_rect,
                                 &device_image_size,
                                 tile_size as i32,
                             );
 
                             for tile in tiles {
                                 frame_state.resource_cache.request_image(
@@ -3252,17 +2774,19 @@ impl PrimitiveStore {
                             // Clearing the screen rect has the effect of "culling out" the primitive
                             // from the point of view of the batch builder, and ensures we don't hit
                             // assertions later on because we didn't request any image.
                             prim_instance.bounding_rect = None;
                         }
                     }
                 }
 
-                write_segment(prim_data, image_instance.segment_instance_index, frame_state, scratch);
+                write_segment(image_instance.segment_instance_index, frame_state, scratch, |request| {
+                    image_data.write_prim_gpu_blocks(request);
+                });
             }
             PrimitiveInstanceKind::LinearGradient { data_handle, ref mut visible_tiles_range, .. } => {
                 let prim_data = &mut resources.linear_grad_data_store[*data_handle];
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
                 prim_data.update(frame_state);
 
@@ -3343,33 +2867,30 @@ impl PrimitiveStore {
             }
             _ => {
                 unreachable!();
             }
         };
     }
 }
 
-fn write_segment(
-    prim_data: &PrimitiveTemplate,
+fn write_segment<F>(
     segment_instance_index: SegmentInstanceIndex,
     frame_state: &mut FrameBuildingState,
     scratch: &mut PrimitiveScratchBuffer,
-) {
+    f: F,
+) where F: Fn(&mut GpuDataRequest) {
     debug_assert!(segment_instance_index != SegmentInstanceIndex::INVALID);
     if segment_instance_index != SegmentInstanceIndex::UNUSED {
         let segment_instance = &mut scratch.segment_instances[segment_instance_index];
 
         if let Some(mut request) = frame_state.gpu_cache.request(&mut segment_instance.gpu_cache_handle) {
             let segments = &scratch.segments[segment_instance.segments_range];
 
-            prim_data.kind.write_prim_gpu_blocks(
-                &mut request,
-                prim_data.prim_size,
-            );
+            f(&mut request);
 
             for segment in segments {
                 request.write_segment(
                     segment.local_rect,
                     [0.0; 4],
                 );
             }
         }
@@ -3397,17 +2918,17 @@ fn decompose_repeated_primitive(
 
     let visible_rect = compute_conservative_visible_rect(
         prim_context,
         world_rect,
         &tight_clip_rect
     );
     let stride = *stretch_size + *tile_spacing;
 
-    let repetitions = image::repetitions(prim_local_rect, &visible_rect, stride);
+    let repetitions = ::image::repetitions(prim_local_rect, &visible_rect, stride);
     for Repetition { origin, .. } in repetitions {
         let mut handle = GpuCacheHandle::new();
         let rect = LayoutRect {
             origin: origin,
             size: *stretch_size,
         };
 
         if let Some(request) = frame_state.gpu_cache.request(&mut handle) {
@@ -3617,32 +3138,27 @@ impl PrimitiveInstance {
         scratch: &mut PrimitiveScratchBuffer,
     ) {
         let segment_instance_index = match self.kind {
             PrimitiveInstanceKind::Rectangle { ref mut segment_instance_index, .. } |
             PrimitiveInstanceKind::YuvImage { ref mut segment_instance_index, .. } => {
                 segment_instance_index
             }
             PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
-                let prim_data = &resources.prim_data_store[data_handle];
+                let image_data = &resources.image_data_store[data_handle].kind;
                 let image_instance = &mut prim_store.images[image_instance_index];
-                match prim_data.kind {
-                    PrimitiveTemplateKind::Image { key, .. } => {
-                        // tiled images don't support segmentation
-                        if frame_state
-                            .resource_cache
-                            .get_image_properties(key)
-                            .and_then(|properties| properties.tiling)
-                            .is_some() {
-                            image_instance.segment_instance_index = SegmentInstanceIndex::UNUSED;
-                            return;
-                        }
+                // tiled images don't support segmentation
+                if frame_state
+                    .resource_cache
+                    .get_image_properties(image_data.key)
+                    .and_then(|properties| properties.tiling)
+                    .is_some() {
+                        image_instance.segment_instance_index = SegmentInstanceIndex::UNUSED;
+                        return;
                     }
-                    _ => unreachable!(),
-                }
                 &mut image_instance.segment_instance_index
             }
             PrimitiveInstanceKind::Picture { .. } |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::Clear { .. } |
             PrimitiveInstanceKind::LinearGradient { .. } |
@@ -3741,42 +3257,28 @@ impl PrimitiveInstance {
                     return false;
                 }
 
                 let segment_instance = &scratch.segment_instances[segment_instance_index];
 
                 &scratch.segments[segment_instance.segments_range]
             }
             PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
-                let prim_data = &resources.prim_data_store[data_handle];
+                let border_data = &resources.image_border_data_store[data_handle].kind;
 
                 // TODO: This is quite messy - once we remove legacy primitives we
                 //       can change this to be a tuple match on (instance, template)
-                match prim_data.kind {
-                    PrimitiveTemplateKind::ImageBorder { ref brush_segments, .. } => {
-                        brush_segments.as_slice()
-                    }
-                    _ => {
-                        unreachable!();
-                    }
-                }
+                border_data.brush_segments.as_slice()
             }
             PrimitiveInstanceKind::NormalBorder { data_handle, .. } => {
-                let prim_data = &resources.prim_data_store[data_handle];
+                let border_data = &resources.normal_border_data_store[data_handle].kind;
 
                 // TODO: This is quite messy - once we remove legacy primitives we
                 //       can change this to be a tuple match on (instance, template)
-                match prim_data.kind {
-                    PrimitiveTemplateKind::NormalBorder { ref template, .. } => {
-                        template.brush_segments.as_slice()
-                    }
-                    _ => {
-                        unreachable!();
-                    }
-                }
+                border_data.brush_segments.as_slice()
             }
             PrimitiveInstanceKind::LinearGradient { data_handle, .. } => {
                 let prim_data = &resources.linear_grad_data_store[data_handle];
 
                 // TODO: This is quite messy - once we remove legacy primitives we
                 //       can change this to be a tuple match on (instance, template)
                 if prim_data.brush_segments.is_empty() {
                     return false;
@@ -4051,13 +3553,13 @@ fn test_struct_sizes() {
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
     assert_eq!(mem::size_of::<PrimitiveInstance>(), 128, "PrimitiveInstance size changed");
     assert_eq!(mem::size_of::<PrimitiveInstanceKind>(), 40, "PrimitiveInstanceKind size changed");
-    assert_eq!(mem::size_of::<PrimitiveTemplate>(), 144, "PrimitiveTemplate size changed");
-    assert_eq!(mem::size_of::<PrimitiveTemplateKind>(), 88, "PrimitiveTemplateKind size changed");
-    assert_eq!(mem::size_of::<PrimitiveKey>(), 124, "PrimitiveKey size changed");
-    assert_eq!(mem::size_of::<PrimitiveKeyKind>(), 96, "PrimitiveKeyKind size changed");
+    assert_eq!(mem::size_of::<PrimitiveTemplate>(), 96, "PrimitiveTemplate size changed");
+    assert_eq!(mem::size_of::<PrimitiveTemplateKind>(), 36, "PrimitiveTemplateKind size changed");
+    assert_eq!(mem::size_of::<PrimitiveKey>(), 116, "PrimitiveKey size changed");
+    assert_eq!(mem::size_of::<PrimitiveKeyKind>(), 88, "PrimitiveKeyKind size changed");
 }
--- a/gfx/wr/webrender/src/profiler.rs
+++ b/gfx/wr/webrender/src/profiler.rs
@@ -399,19 +399,23 @@ pub struct IpcProfileCounters {
     pub send_time: TimeProfileCounter,
     pub total_time: TimeProfileCounter,
     pub display_lists: ResourceProfileCounter,
 }
 
 #[derive(Clone)]
 pub struct InternProfileCounters {
     pub prims: ResourceProfileCounter,
+    pub images: ResourceProfileCounter,
+    pub image_borders: ResourceProfileCounter,
     pub linear_gradients: ResourceProfileCounter,
+    pub normal_borders: ResourceProfileCounter,
     pub radial_gradients: ResourceProfileCounter,
     pub text_runs: ResourceProfileCounter,
+    pub yuv_images: ResourceProfileCounter,
     pub clips: ResourceProfileCounter,
 }
 
 impl IpcProfileCounters {
     pub fn set(
         &mut self,
         build_start: u64,
         build_end: u64,
@@ -445,19 +449,23 @@ impl BackendProfileCounters {
                 build_time: TimeProfileCounter::new("Display List Build Time", false),
                 consume_time: TimeProfileCounter::new("Display List Consume Time", false),
                 send_time: TimeProfileCounter::new("Display List Send Time", false),
                 total_time: TimeProfileCounter::new("Total Display List Time", false),
                 display_lists: ResourceProfileCounter::new("Display Lists Sent"),
             },
             intern: InternProfileCounters {
                 prims: ResourceProfileCounter::new("Interned primitives"),
+                images: ResourceProfileCounter::new("Interned images"),
+                image_borders: ResourceProfileCounter::new("Interned image borders"),
                 linear_gradients: ResourceProfileCounter::new("Interned linear gradients"),
+                normal_borders: ResourceProfileCounter::new("Interner normal borders"),
                 radial_gradients: ResourceProfileCounter::new("Interned radial gradients"),
                 text_runs: ResourceProfileCounter::new("Interned text runs"),
+                yuv_images: ResourceProfileCounter::new("Interned YUV images"),
                 clips: ResourceProfileCounter::new("Interned clips"),
             },
         }
     }
 
     pub fn reset(&mut self) {
         self.total_time.reset();
         self.ipc.total_time.reset();
@@ -1098,19 +1106,23 @@ impl Profiler {
             true,
             &mut self.draw_state
         );
 
         Profiler::draw_counters(
             &[
                 &backend_profile.intern.clips,
                 &backend_profile.intern.prims,
+                &backend_profile.intern.images,
+                &backend_profile.intern.image_borders,
                 &backend_profile.intern.linear_gradients,
+                &backend_profile.intern.normal_borders,
                 &backend_profile.intern.radial_gradients,
                 &backend_profile.intern.text_runs,
+                &backend_profile.intern.yuv_images,
             ],
             debug_renderer,
             true,
             &mut self.draw_state
         );
 
         Profiler::draw_counters(
             &[
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -28,17 +28,19 @@ use clip_scroll_tree::{SpatialNodeIndex,
 use debug_server;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use hit_test::{HitTest, HitTester};
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
 use picture::RetainedTiles;
 use prim_store::{PrimitiveDataStore, PrimitiveScratchBuffer, PrimitiveInstance};
 use prim_store::{PrimitiveInstanceKind, PrimTemplateCommonData};
+use prim_store::borders::{ImageBorderDataStore, NormalBorderDataStore};
 use prim_store::gradient::{LinearGradientDataStore, RadialGradientDataStore};
+use prim_store::image::{ImageDataStore, YuvImageDataStore};
 use prim_store::text_run::TextRunDataStore;
 use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
 use record::ApiRecordingReceiver;
 use renderer::{AsyncPropertySampler, PipelineInfo};
 use resource_cache::ResourceCache;
 #[cfg(feature = "replay")]
 use resource_cache::PlainCacheOwn;
 #[cfg(any(feature = "capture", feature = "replay"))]
@@ -200,50 +202,66 @@ impl FrameStamp {
 pub struct FrameResources {
     /// The store of currently active / available clip nodes. This is kept
     /// in sync with the clip interner in the scene builder for each document.
     pub clip_data_store: ClipDataStore,
 
     /// Currently active / available primitives. Kept in sync with the
     /// primitive interner in the scene builder, per document.
     pub prim_data_store: PrimitiveDataStore,
+    pub image_data_store: ImageDataStore,
+    pub image_border_data_store: ImageBorderDataStore,
     pub linear_grad_data_store: LinearGradientDataStore,
+    pub normal_border_data_store: NormalBorderDataStore,
     pub radial_grad_data_store: RadialGradientDataStore,
     pub text_run_data_store: TextRunDataStore,
+    pub yuv_image_data_store: YuvImageDataStore,
 }
 
 impl FrameResources {
     pub fn as_common_data(
         &self,
         prim_inst: &PrimitiveInstance
     ) -> &PrimTemplateCommonData {
         match prim_inst.kind {
             PrimitiveInstanceKind::Picture { data_handle, .. } |
             PrimitiveInstanceKind::LineDecoration { data_handle, .. } |
-            PrimitiveInstanceKind::NormalBorder { data_handle, .. } |
-            PrimitiveInstanceKind::ImageBorder { data_handle, .. } |
             PrimitiveInstanceKind::Rectangle { data_handle, .. } |
-            PrimitiveInstanceKind::YuvImage { data_handle, .. } |
-            PrimitiveInstanceKind::Image { data_handle, .. } |
             PrimitiveInstanceKind::Clear { data_handle, .. } => {
                 let prim_data = &self.prim_data_store[data_handle];
                 &prim_data.common
             }
+            PrimitiveInstanceKind::Image { data_handle, .. } => {
+                let prim_data = &self.image_data_store[data_handle];
+                &prim_data.common
+            }
+            PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
+                let prim_data = &self.image_border_data_store[data_handle];
+                &prim_data.common
+            }
             PrimitiveInstanceKind::LinearGradient { data_handle, .. } => {
                 let prim_data = &self.linear_grad_data_store[data_handle];
                 &prim_data.common
             }
+            PrimitiveInstanceKind::NormalBorder { data_handle, .. } => {
+                let prim_data = &self.normal_border_data_store[data_handle];
+                &prim_data.common
+            }
             PrimitiveInstanceKind::RadialGradient { data_handle, .. } =>{
                 let prim_data = &self.radial_grad_data_store[data_handle];
                 &prim_data.common
             }
             PrimitiveInstanceKind::TextRun { data_handle, .. }  => {
                 let prim_data = &self.text_run_data_store[data_handle];
                 &prim_data.common
             }
+            PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
+                let prim_data = &self.yuv_image_data_store[data_handle];
+                &prim_data.common
+            }
         }
     }
 }
 
 struct Document {
     // The latest built scene, usable to build frames.
     // received from the scene builder thread.
     scene: Scene,
@@ -1216,28 +1234,44 @@ impl RenderBackend {
             doc.resources.clip_data_store.apply_updates(
                 updates.clip_updates,
                 &mut profile_counters.intern.clips,
             );
             doc.resources.prim_data_store.apply_updates(
                 updates.prim_updates,
                 &mut profile_counters.intern.prims,
             );
+            doc.resources.image_data_store.apply_updates(
+                updates.image_updates,
+                &mut profile_counters.intern.images,
+            );
+            doc.resources.image_border_data_store.apply_updates(
+                updates.image_border_updates,
+                &mut profile_counters.intern.image_borders,
+            );
             doc.resources.linear_grad_data_store.apply_updates(
                 updates.linear_grad_updates,
                 &mut profile_counters.intern.linear_gradients,
             );
+            doc.resources.normal_border_data_store.apply_updates(
+                updates.normal_border_updates,
+                &mut profile_counters.intern.normal_borders,
+            );
             doc.resources.radial_grad_data_store.apply_updates(
                 updates.radial_grad_updates,
                 &mut profile_counters.intern.radial_gradients,
             );
             doc.resources.text_run_data_store.apply_updates(
                 updates.text_run_updates,
                 &mut profile_counters.intern.text_runs,
             );
+            doc.resources.yuv_image_data_store.apply_updates(
+                updates.yuv_image_updates,
+                &mut profile_counters.intern.yuv_images,
+            );
         }
 
         // TODO: this scroll variable doesn't necessarily mean we scrolled. It is only used
         // for something wrench specific and we should remove it.
         let mut scroll = false;
         for frame_msg in frame_ops {
             let _timer = profile_counters.total_time.timer();
             let op = doc.process_frame_msg(frame_msg);
@@ -1707,9 +1741,8 @@ impl RenderBackend {
 
         if !scenes_to_build.is_empty() {
             self.low_priority_scene_tx.send(
                 SceneBuilderRequest::LoadScenes(scenes_to_build)
             ).unwrap();
         }
     }
 }
-
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -16,17 +16,18 @@ use device::TextureFilter;
 use euclid::{TypedPoint2D, TypedVector2D};
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use glyph_rasterizer::GpuGlyphCacheKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{BorderInstance, ImageSource, UvRectKind};
 use internal_types::{CacheTextureId, FastHashMap, LayerIndex, SavedTargetIndex};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
-use prim_store::{PictureIndex, ImageCacheKey, LineDecorationCacheKey};
+use prim_store::{PictureIndex, LineDecorationCacheKey};
+use prim_store::image::ImageCacheKey;
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
 use render_backend::FrameId;
 use resource_cache::{CacheItem, ResourceCache};
 use surface::SurfaceCacheKey;
 use std::{cmp, ops, mem, usize, f32, i32, u32};
 use texture_cache::{TextureCache, TextureCacheHandle, Eviction};
 use tiling::{RenderPass, RenderTargetIndex};
--- a/gfx/wr/webrender/src/scene_builder.rs
+++ b/gfx/wr/webrender/src/scene_builder.rs
@@ -11,38 +11,50 @@ use capture::CaptureConfig;
 use frame_builder::{FrameBuilderConfig, FrameBuilder};
 use clip::{ClipDataInterner, ClipDataUpdateList};
 use clip_scroll_tree::ClipScrollTree;
 use display_list_flattener::DisplayListFlattener;
 use intern::{Internable, Interner};
 use internal_types::{FastHashMap, FastHashSet};
 use prim_store::{PrimitiveDataInterner, PrimitiveDataUpdateList, PrimitiveKeyKind};
 use prim_store::PrimitiveStoreStats;
+use prim_store::borders::{
+    ImageBorder, ImageBorderDataInterner, ImageBorderDataUpdateList,
+    NormalBorderPrim, NormalBorderDataInterner, NormalBorderDataUpdateList
+};
 use prim_store::gradient::{
     LinearGradient, LinearGradientDataInterner, LinearGradientDataUpdateList,
     RadialGradient, RadialGradientDataInterner, RadialGradientDataUpdateList
 };
+use prim_store::image::{
+    Image, ImageDataInterner, ImageDataUpdateList,
+    YuvImage, YuvImageDataInterner, YuvImageDataUpdateList,
+};
 use prim_store::text_run::{TextRunDataInterner, TextRun, TextRunDataUpdateList};
 use resource_cache::{BlobImageRasterizerEpoch, FontInstanceMap};
 use render_backend::DocumentView;
 use renderer::{PipelineInfo, SceneBuilderHooks};
 use scene::Scene;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::mem::replace;
 use time::precise_time_ns;
 use util::drain_filter;
 use std::thread;
 use std::time::Duration;
 
 pub struct DocumentResourceUpdates {
     pub clip_updates: ClipDataUpdateList,
     pub prim_updates: PrimitiveDataUpdateList,
+    pub image_updates: ImageDataUpdateList,
+    pub image_border_updates: ImageBorderDataUpdateList,
     pub linear_grad_updates: LinearGradientDataUpdateList,
+    pub normal_border_updates: NormalBorderDataUpdateList,
     pub radial_grad_updates: RadialGradientDataUpdateList,
     pub text_run_updates: TextRunDataUpdateList,
+    pub yuv_image_updates: YuvImageDataUpdateList,
 }
 
 /// Represents the work associated to a transaction before scene building.
 pub struct Transaction {
     pub document_id: DocumentId,
     pub display_list_updates: Vec<DisplayListUpdate>,
     pub removed_pipelines: Vec<PipelineId>,
     pub epoch_updates: Vec<(PipelineId, Epoch)>,
@@ -180,52 +192,56 @@ pub enum SceneSwapResult {
 // - Comparison of primitives and pictures between two
 //   display lists is (a) fast (b) done during scene building.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Default)]
 pub struct DocumentResources {
     pub clip_interner: ClipDataInterner,
     pub prim_interner: PrimitiveDataInterner,
+    pub image_interner: ImageDataInterner,
+    pub image_border_interner: ImageBorderDataInterner,
     pub linear_grad_interner: LinearGradientDataInterner,
+    pub normal_border_interner: NormalBorderDataInterner,
     pub radial_grad_interner: RadialGradientDataInterner,
     pub text_run_interner: TextRunDataInterner,
+    pub yuv_image_interner: YuvImageDataInterner,
 }
 
 // Access to `DocumentResources` interners by `Internable`
 pub trait InternerMut<I: Internable>
 {
     fn interner_mut(&mut self) -> &mut Interner<I::Source, I::InternData, I::Marker>;
 }
 
-impl InternerMut<PrimitiveKeyKind> for DocumentResources {
-    fn interner_mut(&mut self) -> &mut PrimitiveDataInterner {
-        &mut self.prim_interner
-    }
-}
-
-impl InternerMut<LinearGradient> for DocumentResources {
-    fn interner_mut(&mut self) -> &mut LinearGradientDataInterner {
-        &mut self.linear_grad_interner
+macro_rules! impl_internet_mut {
+    ($($ty:ident: $mem:ident,)*) => {
+        $(impl InternerMut<$ty> for DocumentResources {
+            fn interner_mut(&mut self) -> &mut Interner<
+                <$ty as Internable>::Source,
+                <$ty as Internable>::InternData,
+                <$ty as Internable>::Marker
+            > {
+                &mut self.$mem
+            }
+        })*
     }
 }
 
-impl InternerMut<RadialGradient> for DocumentResources {
-    fn interner_mut(&mut self) -> &mut RadialGradientDataInterner {
-        &mut self.radial_grad_interner
-    }
+impl_internet_mut! {
+    Image: image_interner,
+    ImageBorder: image_border_interner,
+    LinearGradient: linear_grad_interner,
+    NormalBorderPrim: normal_border_interner,
+    PrimitiveKeyKind: prim_interner,
+    RadialGradient: radial_grad_interner,
+    TextRun: text_run_interner,
+    YuvImage: yuv_image_interner,
 }
 
-impl InternerMut<TextRun> for DocumentResources {
-    fn interner_mut(&mut self) -> &mut TextRunDataInterner {
-        &mut self.text_run_interner
-    }
-}
-
-
 // A document in the scene builder contains the current scene,
 // as well as a persistent clip interner. This allows clips
 // to be de-duplicated, and persisted in the GPU cache between
 // display lists.
 struct Document {
     scene: Scene,
     resources: DocumentResources,
     prim_store_stats: PrimitiveStoreStats,
@@ -386,38 +402,62 @@ impl SceneBuilder {
                     .clip_interner
                     .end_frame_and_get_pending_updates();
 
                 let prim_updates = item
                     .doc_resources
                     .prim_interner
                     .end_frame_and_get_pending_updates();
 
+                let image_updates = item
+                    .doc_resources
+                    .image_interner
+                    .end_frame_and_get_pending_updates();
+
+                let image_border_updates = item
+                    .doc_resources
+                    .image_border_interner
+                    .end_frame_and_get_pending_updates();
+
                 let linear_grad_updates = item
                     .doc_resources
                     .linear_grad_interner
                     .end_frame_and_get_pending_updates();
 
+                let normal_border_updates = item
+                    .doc_resources
+                    .normal_border_interner
+                    .end_frame_and_get_pending_updates();
+
                 let radial_grad_updates = item
                     .doc_resources
                     .radial_grad_interner
                     .end_frame_and_get_pending_updates();
 
                 let text_run_updates = item
                     .doc_resources
                     .text_run_interner
                     .end_frame_and_get_pending_updates();
 
+                let yuv_image_updates = item
+                    .doc_resources
+                    .yuv_image_interner
+                    .end_frame_and_get_pending_updates();
+
                 doc_resource_updates = Some(
                     DocumentResourceUpdates {
                         clip_updates,
                         prim_updates,
+                        image_updates,
+                        image_border_updates,
                         linear_grad_updates,
+                        normal_border_updates,
                         radial_grad_updates,
                         text_run_updates,
+                        yuv_image_updates,
                     }
                 );
 
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
@@ -516,38 +556,62 @@ impl SceneBuilder {
                     .clip_interner
                     .end_frame_and_get_pending_updates();
 
                 let prim_updates = doc
                     .resources
                     .prim_interner
                     .end_frame_and_get_pending_updates();
 
+                let image_updates = doc
+                    .resources
+                    .image_interner
+                    .end_frame_and_get_pending_updates();
+
+                let image_border_updates = doc
+                    .resources
+                    .image_border_interner
+                    .end_frame_and_get_pending_updates();
+
                 let linear_grad_updates = doc
                     .resources
                     .linear_grad_interner
                     .end_frame_and_get_pending_updates();
 
+                let normal_border_updates = doc
+                    .resources
+                    .normal_border_interner
+                    .end_frame_and_get_pending_updates();
+
                 let radial_grad_updates = doc
                     .resources
                     .radial_grad_interner
                     .end_frame_and_get_pending_updates();
 
                 let text_run_updates = doc
                     .resources
                     .text_run_interner
                     .end_frame_and_get_pending_updates();
 
+                let yuv_image_updates = doc
+                    .resources
+                    .yuv_image_interner
+                    .end_frame_and_get_pending_updates();
+
                 doc_resource_updates = Some(
                     DocumentResourceUpdates {
                         clip_updates,
                         prim_updates,
+                        image_updates,
+                        image_border_updates,
                         linear_grad_updates,
+                        normal_border_updates,
                         radial_grad_updates,
                         text_run_updates,
+                        yuv_image_updates,
                     }
                 );
 
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
--- a/intl/l10n/Fluent.jsm
+++ b/intl/l10n/Fluent.jsm
@@ -11,17 +11,17 @@
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 
-/* fluent@fa25466f (October 12, 2018) */
+/* fluent@0.10.0 */
 
 /* global Intl */
 
 /**
  * The `FluentType` class is the base of Fluent's type system.
  *
  * Fluent types wrap JavaScript values and store additional configuration for
  * them, which can then be used in the `toString` method together with a proper
@@ -190,30 +190,17 @@ function values(opts) {
 // Prevent expansion of too long placeables.
 const MAX_PLACEABLE_LENGTH = 2500;
 
 // Unicode bidi isolation characters.
 const FSI = "\u2068";
 const PDI = "\u2069";
 
 
-/**
- * Helper for matching a variant key to the given selector.
- *
- * Used in SelectExpressions and VariantExpressions.
- *
- * @param   {FluentBundle} bundle
- *    Resolver environment object.
- * @param   {FluentType} key
- *    The key of the currently considered variant.
- * @param   {FluentType} selector
- *    The selector based om which the correct variant should be chosen.
- * @returns {FluentType}
- * @private
- */
+// Helper: match a variant key to the given selector.
 function match(bundle, selector, key) {
   if (key === selector) {
     // Both are strings.
     return true;
   }
 
   if (key instanceof FluentNumber
     && selector instanceof FluentNumber
@@ -228,279 +215,102 @@ function match(bundle, selector, key) {
     if (key === category) {
       return true;
     }
   }
 
   return false;
 }
 
-/**
- * Helper for choosing the default value from a set of members.
- *
- * Used in SelectExpressions and Type.
- *
- * @param   {Object} env
- *    Resolver environment object.
- * @param   {Object} members
- *    Hash map of variants from which the default value is to be selected.
- * @param   {Number} star
- *    The index of the default variant.
- * @returns {FluentType}
- * @private
- */
-function DefaultMember(env, members, star) {
-  if (members[star]) {
-    return members[star];
+// Helper: resolve the default variant from a list of variants.
+function getDefault(env, variants, star) {
+  if (variants[star]) {
+    return Type(env, variants[star]);
   }
 
   const { errors } = env;
   errors.push(new RangeError("No default"));
   return new FluentNone();
 }
 
-
-/**
- * Resolve a reference to another message.
- *
- * @param   {Object} env
- *    Resolver environment object.
- * @param   {Object} id
- *    The identifier of the message to be resolved.
- * @param   {String} id.name
- *    The name of the identifier.
- * @returns {FluentType}
- * @private
- */
-function MessageReference(env, {name}) {
-  const { bundle, errors } = env;
-  const message = name.startsWith("-")
-    ? bundle._terms.get(name)
-    : bundle._messages.get(name);
-
-  if (!message) {
-    const err = name.startsWith("-")
-      ? new ReferenceError(`Unknown term: ${name}`)
-      : new ReferenceError(`Unknown message: ${name}`);
-    errors.push(err);
-    return new FluentNone(name);
-  }
-
-  return message;
-}
+// Helper: resolve arguments to a call expression.
+function getArguments(env, args) {
+  const positional = [];
+  const named = {};
 
-/**
- * Resolve a variant expression to the variant object.
- *
- * @param   {Object} env
- *    Resolver environment object.
- * @param   {Object} expr
- *    An expression to be resolved.
- * @param   {Object} expr.ref
- *    An Identifier of a message for which the variant is resolved.
- * @param   {Object} expr.id.name
- *    Name a message for which the variant is resolved.
- * @param   {Object} expr.key
- *    Variant key to be resolved.
- * @returns {FluentType}
- * @private
- */
-function VariantExpression(env, {ref, selector}) {
-  const message = MessageReference(env, ref);
-  if (message instanceof FluentNone) {
-    return message;
-  }
-
-  const { bundle, errors } = env;
-  const sel = Type(env, selector);
-  const value = message.value || message;
-
-  function isVariantList(node) {
-    return Array.isArray(node) &&
-      node[0].type === "select" &&
-      node[0].selector === null;
-  }
-
-  if (isVariantList(value)) {
-    // Match the specified key against keys of each variant, in order.
-    for (const variant of value[0].variants) {
-      const key = Type(env, variant.key);
-      if (match(env.bundle, sel, key)) {
-        return variant;
+  if (args) {
+    for (const arg of args) {
+      if (arg.type === "narg") {
+        named[arg.name] = Type(env, arg.value);
+      } else {
+        positional.push(Type(env, arg));
       }
     }
   }
 
-  errors.push(
-    new ReferenceError(`Unknown variant: ${sel.toString(bundle)}`));
-  return Type(env, message);
-}
-
-
-/**
- * Resolve an attribute expression to the attribute object.
- *
- * @param   {Object} env
- *    Resolver environment object.
- * @param   {Object} expr
- *    An expression to be resolved.
- * @param   {String} expr.ref
- *    An ID of a message for which the attribute is resolved.
- * @param   {String} expr.name
- *    Name of the attribute to be resolved.
- * @returns {FluentType}
- * @private
- */
-function AttributeExpression(env, {ref, name}) {
-  const message = MessageReference(env, ref);
-  if (message instanceof FluentNone) {
-    return message;
-  }
-
-  if (message.attrs) {
-    // Match the specified name against keys of each attribute.
-    for (const attrName in message.attrs) {
-      if (name === attrName) {
-        return message.attrs[name];
-      }
-    }
-  }
-
-  const { errors } = env;
-  errors.push(new ReferenceError(`Unknown attribute: ${name}`));
-  return Type(env, message);
+  return [positional, named];
 }
 
-/**
- * Resolve a select expression to the member object.
- *
- * @param   {Object} env
- *    Resolver environment object.
- * @param   {Object} expr
- *    An expression to be resolved.
- * @param   {String} expr.selector
- *    Selector expression
- * @param   {Array} expr.variants
- *    List of variants for the select expression.
- * @param   {Number} expr.star
- *    Index of the default variant.
- * @returns {FluentType}
- * @private
- */
-function SelectExpression(env, {selector, variants, star}) {
-  if (selector === null) {
-    return DefaultMember(env, variants, star);
-  }
-
-  let sel = Type(env, selector);
-  if (sel instanceof FluentNone) {
-    return DefaultMember(env, variants, star);
-  }
-
-  // Match the selector against keys of each variant, in order.
-  for (const variant of variants) {
-    const key = Type(env, variant.key);
-    if (match(env.bundle, sel, key)) {
-      return variant;
-    }
-  }
-
-  return DefaultMember(env, variants, star);
-}
-
-
-/**
- * Resolve expression to a Fluent type.
- *
- * JavaScript strings are a special case.  Since they natively have the
- * `toString` method they can be used as if they were a Fluent type without
- * paying the cost of creating a instance of one.
- *
- * @param   {Object} env
- *    Resolver environment object.
- * @param   {Object} expr
- *    An expression object to be resolved into a Fluent type.
- * @returns {FluentType}
- * @private
- */
+// Resolve an expression to a Fluent type.
 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.
+  // A fast-path for strings which are the most common case. Since they
+  // natively have the `toString` method they can be used as if they were
+  // a FluentType instance without incurring the cost of creating one.
   if (typeof expr === "string") {
     return env.bundle._transform(expr);
   }
+
+  // A fast-path for `FluentNone` which doesn't require any additional logic.
   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);
   }
 
-
   switch (expr.type) {
+    case "str":
+      return expr.value;
     case "num":
       return new FluentNumber(expr.value);
     case "var":
       return VariableReference(env, expr);
-    case "func":
-      return FunctionReference(env, expr);
-    case "call":
-      return CallExpression(env, expr);
-    case "ref": {
-      const message = MessageReference(env, expr);
-      return Type(env, message);
-    }
-    case "getattr": {
-      const attr = AttributeExpression(env, expr);
-      return Type(env, attr);
-    }
-    case "getvar": {
-      const variant = VariantExpression(env, expr);
-      return Type(env, variant);
-    }
-    case "select": {
-      const member = SelectExpression(env, expr);
-      return Type(env, member);
-    }
+    case "term":
+      return TermReference({...env, args: {}}, expr);
+    case "ref":
+      return expr.args
+        ? FunctionReference(env, expr)
+        : MessageReference(env, expr);
+    case "select":
+      return SelectExpression(env, expr);
     case undefined: {
       // If it's a node with a value, resolve the value.
       if (expr.value !== null && expr.value !== undefined) {
         return Type(env, expr.value);
       }
 
       const { errors } = env;
       errors.push(new RangeError("No value"));
       return new FluentNone();
     }
     default:
       return new FluentNone();
   }
 }
 
-/**
- * Resolve a reference to a variable.
- *
- * @param   {Object} env
- *    Resolver environment object.
- * @param   {Object} expr
- *    An expression to be resolved.
- * @param   {String} expr.name
- *    Name of an argument to be returned.
- * @returns {FluentType}
- * @private
- */
+// Resolve a reference to a variable.
 function VariableReference(env, {name}) {
   const { args, errors } = env;
 
   if (!args || !args.hasOwnProperty(name)) {
     errors.push(new ReferenceError(`Unknown variable: ${name}`));
-    return new FluentNone(name);
+    return new FluentNone(`$${name}`);
   }
 
   const arg = args[name];
 
   // Return early if the argument already is an instance of FluentType.
   if (arg instanceof FluentType) {
     return arg;
   }
@@ -514,101 +324,135 @@ function VariableReference(env, {name}) 
     case "object":
       if (arg instanceof Date) {
         return new FluentDateTime(arg);
       }
     default:
       errors.push(
         new TypeError(`Unsupported variable type: ${name}, ${typeof arg}`)
       );
-      return new FluentNone(name);
+      return new FluentNone(`$${name}`);
   }
 }
 
-/**
- * Resolve a reference to a function.
- *
- * @param   {Object}  env
- *    Resolver environment object.
- * @param   {Object} expr
- *    An expression to be resolved.
- * @param   {String} expr.name
- *    Name of the function to be returned.
- * @returns {Function}
- * @private
- */
-function FunctionReference(env, {name}) {
-  // Some functions are built-in.  Others may be provided by the runtime via
+// Resolve a reference to another message.
+function MessageReference(env, {name, attr}) {
+  const {bundle, errors} = env;
+  const message = bundle._messages.get(name);
+  if (!message) {
+    const err = new ReferenceError(`Unknown message: ${name}`);
+    errors.push(err);
+    return new FluentNone(name);
+  }
+
+  if (attr) {
+    const attribute = message.attrs && message.attrs[attr];
+    if (attribute) {
+      return Type(env, attribute);
+    }
+    errors.push(new ReferenceError(`Unknown attribute: ${attr}`));
+    return Type(env, message);
+  }
+
+  return Type(env, message);
+}
+
+// Resolve a call to a Term with key-value arguments.
+function TermReference(env, {name, attr, selector, args}) {
+  const {bundle, errors} = env;
+
+  const id = `-${name}`;
+  const term = bundle._terms.get(id);
+  if (!term) {
+    const err = new ReferenceError(`Unknown term: ${id}`);
+    errors.push(err);
+    return new FluentNone(id);
+  }
+
+  // Every TermReference has its own args.
+  const [, keyargs] = getArguments(env, args);
+  const local = {...env, args: keyargs};
+
+  if (attr) {
+    const attribute = term.attrs && term.attrs[attr];
+    if (attribute) {
+      return Type(local, attribute);
+    }
+    errors.push(new ReferenceError(`Unknown attribute: ${attr}`));
+    return Type(local, term);
+  }
+
+  const variantList = getVariantList(term);
+  if (selector && variantList) {
+    return SelectExpression(local, {...variantList, selector});
+  }
+
+  return Type(local, term);
+}
+
+// Helper: convert a value into a variant list, if possible.
+function getVariantList(term) {
+  const value = term.value || term;
+  return Array.isArray(value)
+    && value[0].type === "select"
+    && value[0].selector === null
+    ? value[0]
+    : null;
+}
+
+// Resolve a call to a Function with positional and key-value arguments.
+function FunctionReference(env, {name, args}) {
+  // Some functions are built-in. Others may be provided by the runtime via
   // the `FluentBundle` constructor.
-  const { bundle: { _functions }, errors } = env;
+  const {bundle: {_functions}, errors} = env;
   const func = _functions[name] || builtins[name];
 
   if (!func) {
     errors.push(new ReferenceError(`Unknown function: ${name}()`));
     return new FluentNone(`${name}()`);
   }
 
   if (typeof func !== "function") {
     errors.push(new TypeError(`Function ${name}() is not callable`));
     return new FluentNone(`${name}()`);
   }
 
-  return func;
-}
-
-/**
- * Resolve a call to a Function with positional and key-value arguments.
- *
- * @param   {Object} env
- *    Resolver environment object.
- * @param   {Object} expr
- *    An expression to be resolved.
- * @param   {Object} expr.callee
- *    FTL Function object.
- * @param   {Array} expr.args
- *    FTL Function argument list.
- * @returns {FluentType}
- * @private
- */
-function CallExpression(env, {callee, args}) {
-  const func = FunctionReference(env, callee);
-
-  if (func instanceof FluentNone) {
-    return func;
-  }
-
-  const posargs = [];
-  const keyargs = {};
-
-  for (const arg of args) {
-    if (arg.type === "narg") {
-      keyargs[arg.name] = Type(env, arg.value);
-    } else {
-      posargs.push(Type(env, arg));
-    }
-  }
-
   try {
-    return func(posargs, keyargs);
+    return func(...getArguments(env, args));
   } catch (e) {
     // XXX Report errors.
     return new FluentNone();
   }
 }
 
-/**
- * Resolve a pattern (a complex string with placeables).
- *
- * @param   {Object} env
- *    Resolver environment object.
- * @param   {Array} ptn
- *    Array of pattern elements.
- * @returns {Array}
- * @private
- */
+// Resolve a select expression to the member object.
+function SelectExpression(env, {selector, variants, star}) {
+  if (selector === null) {
+    return getDefault(env, variants, star);
+  }
+
+  let sel = Type(env, selector);
+  if (sel instanceof FluentNone) {
+    const variant = getDefault(env, variants, star);
+    return Type(env, variant);
+  }
+
+  // Match the selector against keys of each variant, in order.
+  for (const variant of variants) {
+    const key = Type(env, variant.key);
+    if (match(env.bundle, sel, key)) {
+      return Type(env, variant);
+    }
+  }
+
+  const variant = getDefault(env, variants, star);
+  return Type(env, variant);
+}
+
+// Resolve a pattern (a complex string with placeables).
 function Pattern(env, ptn) {
   const { bundle, dirty, errors } = env;
 
   if (dirty.has(ptn)) {
     errors.push(new RangeError("Cyclic reference"));
     return new FluentNone();
   }
 
@@ -674,55 +518,54 @@ function resolve(bundle, args, message, 
   };
   return Type(env, message).toString(bundle);
 }
 
 class FluentError extends Error {}
 
 // This regex is used to iterate through the beginnings of messages and terms.
 // With the /m flag, the ^ matches at the beginning of every line.
-const RE_MESSAGE_START = /^(-?[a-zA-Z][a-zA-Z0-9_-]*) *= */mg;
+const RE_MESSAGE_START = /^(-?[a-zA-Z][\w-]*) *= */mg;
 
 // Both Attributes and Variants are parsed in while loops. These regexes are
 // used to break out of them.
-const RE_ATTRIBUTE_START = /\.([a-zA-Z][a-zA-Z0-9_-]*) *= */y;
-// [^] matches all characters, including newlines.
-// XXX Use /s (dotall) when it's widely supported.
-const RE_VARIANT_START = /\*?\[[^]*?] */y;
+const RE_ATTRIBUTE_START = /\.([a-zA-Z][\w-]*) *= */y;
+const RE_VARIANT_START = /\*?\[/y;
 
-const RE_IDENTIFIER = /(-?[a-zA-Z][a-zA-Z0-9_-]*)/y;
 const RE_NUMBER_LITERAL = /(-?[0-9]+(\.[0-9]+)?)/y;
+const RE_IDENTIFIER = /([a-zA-Z][\w-]*)/y;
+const RE_REFERENCE = /([$-])?([a-zA-Z][\w-]*)(?:\.([a-zA-Z][\w-]*))?/y;
 
 // A "run" is a sequence of text or string literal characters which don't
-// require any special handling. For TextElements such special characters are:
-// { (starts a placeable), \ (starts an escape sequence), and line breaks which
-// require additional logic to check if the next line is indented. For
-// StringLiterals they are: \ (starts an escape sequence), " (ends the
-// literal), and line breaks which are not allowed in StringLiterals. Also note
-// that string runs may be empty, but text runs may not.
-const RE_TEXT_RUN = /([^\\{\n\r]+)/y;
+// require any special handling. For TextElements such special characters are: {
+// (starts a placeable), and line breaks which require additional logic to check
+// if the next line is indented. For StringLiterals they are: \ (starts an
+// escape sequence), " (ends the literal), and line breaks which are not allowed
+// in StringLiterals. Note that string runs may be empty; text runs may not.
+const RE_TEXT_RUN = /([^{}\n\r]+)/y;
 const RE_STRING_RUN = /([^\\"\n\r]*)/y;
 
 // Escape sequences.
-const RE_UNICODE_ESCAPE = /\\u([a-fA-F0-9]{4})/y;
 const RE_STRING_ESCAPE = /\\([\\"])/y;
-const RE_TEXT_ESCAPE = /\\([\\{])/y;
+const RE_UNICODE_ESCAPE = /\\u([a-fA-F0-9]{4})|\\U([a-fA-F0-9]{6})/y;
 
-// Used for trimming TextElements and indents. With the /m flag, the $ matches
-// the end of every line.
-const RE_TRAILING_SPACES = / +$/mg;
-// CRLFs are normalized to LF.
-const RE_CRLF = /\r\n/g;
+// Used for trimming TextElements and indents.
+const RE_LEADING_NEWLINES = /^\n+/;
+const RE_TRAILING_SPACES = / +$/;
+// Used in makeIndent to strip spaces from blank lines and normalize CRLF to LF.
+const RE_BLANK_LINES = / *\r?\n/g;
+// Used in makeIndent to measure the indentation.
+const RE_INDENT = /( *)$/;
 
 // Common tokens.
 const TOKEN_BRACE_OPEN = /{\s*/y;
 const TOKEN_BRACE_CLOSE = /\s*}/y;
 const TOKEN_BRACKET_OPEN = /\[\s*/y;
-const TOKEN_BRACKET_CLOSE = /\s*]/y;
-const TOKEN_PAREN_OPEN = /\(\s*/y;
+const TOKEN_BRACKET_CLOSE = /\s*] */y;
+const TOKEN_PAREN_OPEN = /\s*\(\s*/y;
 const TOKEN_ARROW = /\s*->\s*/y;
 const TOKEN_COLON = /\s*:\s*/y;
 // Note the optional comma. As a deviation from the Fluent EBNF, the parser
 // doesn't enforce commas between call arguments.
 const TOKEN_COMMA = /\s*,?\s*/y;
 const TOKEN_BLANK = /\s+/y;
 
 // Maximum number of placeables in a single Pattern to protect against Quadratic
@@ -806,138 +649,149 @@ class FluentResource extends Map {
         return true;
       }
       if (errorClass) {
         throw new errorClass(`Expected ${re.toString()}`);
       }
       return false;
     }
 
-    // Execute a regex, advance the cursor, and return the capture group.
+    // Execute a regex, advance the cursor, and return all capture groups.
     function match(re) {
       re.lastIndex = cursor;
       let result = re.exec(source);
       if (result === null) {
         throw new FluentError(`Expected ${re.toString()}`);
       }
       cursor = re.lastIndex;
-      return result[1];
+      return result;
+    }
+
+    // Execute a regex, advance the cursor, and return the capture group.
+    function match1(re) {
+      return match(re)[1];
     }
 
     function parseMessage() {
       let value = parsePattern();
       let attrs = parseAttributes();
 
       if (attrs === null) {
+        if (value === null) {
+          throw new FluentError("Expected message value or attributes");
+        }
         return value;
       }
 
       return {value, attrs};
     }
 
     function parseAttributes() {
       let attrs = {};
-      let hasAttributes = false;
 
       while (test(RE_ATTRIBUTE_START)) {
-        if (!hasAttributes) {
-          hasAttributes = true;
+        let name = match1(RE_ATTRIBUTE_START);
+        let value = parsePattern();
+        if (value === null) {
+          throw new FluentError("Expected attribute value");
         }
-
-        let name = match(RE_ATTRIBUTE_START);
-        attrs[name] = parsePattern();
+        attrs[name] = value;
       }
 
-      return hasAttributes ? attrs : null;
+      return Object.keys(attrs).length > 0 ? attrs : null;
     }
 
     function parsePattern() {
       // First try to parse any simple text on the same line as the id.
       if (test(RE_TEXT_RUN)) {
-        var first = match(RE_TEXT_RUN);
+        var first = match1(RE_TEXT_RUN);
       }
 
-      // If there's a backslash escape or a placeable on the first line, fall
-      // back to parsing a complex pattern.
-      switch (source[cursor]) {
-        case "{":
-        case "\\":
-          return first
-            // Re-use the text parsed above, if possible.
-            ? parsePatternElements(first)
-            : parsePatternElements();
+      // If there's a placeable on the first line, parse a complex pattern.
+      if (source[cursor] === "{" || source[cursor] === "}") {
+        // Re-use the text parsed above, if possible.
+        return parsePatternElements(first ? [first] : [], Infinity);
       }
 
       // RE_TEXT_VALUE stops at newlines. Only continue parsing the pattern if
       // what comes after the newline is indented.
       let indent = parseIndent();
       if (indent) {
-        return first
+        if (first) {
           // If there's text on the first line, the blank block is part of the
-          // translation content.
-          ? parsePatternElements(first, trim(indent))
-          // Otherwise, we're dealing with a block pattern. The blank block is
-          // the leading whitespace; discard it.
-          : parsePatternElements();
+          // translation content in its entirety.
+          return parsePatternElements([first, indent], indent.length);
+        }
+        // Otherwise, we're dealing with a block pattern, i.e. a pattern which
+        // starts on a new line. Discrad the leading newlines but keep the
+        // inline indent; it will be used by the dedentation logic.
+        indent.value = trim(indent.value, RE_LEADING_NEWLINES);
+        return parsePatternElements([indent], indent.length);
       }
 
       if (first) {
         // It was just a simple inline text after all.
-        return trim(first);
+        return trim(first, RE_TRAILING_SPACES);
       }
 
       return null;
     }
 
     // Parse a complex pattern as an array of elements.
-    function parsePatternElements(...elements) {
+    function parsePatternElements(elements = [], commonIndent) {
       let placeableCount = 0;
-      let needsTrimming = false;
 
       while (true) {
         if (test(RE_TEXT_RUN)) {
-          elements.push(match(RE_TEXT_RUN));
-          needsTrimming = true;
+          elements.push(match1(RE_TEXT_RUN));
           continue;
         }
 
         if (source[cursor] === "{") {
           if (++placeableCount > MAX_PLACEABLES) {
             throw new FluentError("Too many placeables");
           }
           elements.push(parsePlaceable());
-          needsTrimming = false;
           continue;
         }
 
+        if (source[cursor] === "}") {
+          throw new FluentError("Unbalanced closing brace");
+        }
+
         let indent = parseIndent();
         if (indent) {
-          elements.push(trim(indent));
-          needsTrimming = false;
-          continue;
-        }
-
-        if (source[cursor] === "\\") {
-          elements.push(parseEscapeSequence(RE_TEXT_ESCAPE));
-          needsTrimming = false;
+          elements.push(indent);
+          commonIndent = Math.min(commonIndent, indent.length);
           continue;
         }
 
         break;
       }
 
-      if (needsTrimming) {
-        // Trim the trailing whitespace of the last element if it's a
-        // TextElement. Use a flag rather than a typeof check to tell
-        // TextElements and StringLiterals apart (both are strings).
-        let lastIndex = elements.length - 1;
-        elements[lastIndex] = trim(elements[lastIndex]);
+      let lastIndex = elements.length - 1;
+      // Trim the trailing spaces in the last element if it's a TextElement.
+      if (typeof elements[lastIndex] === "string") {
+        elements[lastIndex] = trim(elements[lastIndex], RE_TRAILING_SPACES);
       }
 
-      return elements;
+      let baked = [];
+      for (let element of elements) {
+        if (element.type === "indent") {
+          // Dedent indented lines by the maximum common indent.
+          element = element.value.slice(0, element.value.length - commonIndent);
+        } else if (element.type === "str") {
+          // Optimize StringLiterals into their value.
+          element = element.value;
+        }
+        if (element) {
+          baked.push(element);
+        }
+      }
+      return baked;
     }
 
     function parsePlaceable() {
       consumeToken(TOKEN_BRACE_OPEN, FluentError);
 
       // VariantLists are parsed as selector-less SelectExpressions.
       let onlyVariants = parseVariants();
       if (onlyVariants) {
@@ -960,38 +814,30 @@ class FluentResource extends Map {
     }
 
     function parseInlineExpression() {
       if (source[cursor] === "{") {
         // It's a nested placeable.
         return parsePlaceable();
       }
 
-      if (consumeChar("$")) {
-        return {type: "var", name: match(RE_IDENTIFIER)};
-      }
-
-      if (test(RE_IDENTIFIER)) {
-        let ref = {type: "ref", name: match(RE_IDENTIFIER)};
-
-        if (consumeChar(".")) {
-          let name = match(RE_IDENTIFIER);
-          return {type: "getattr", ref, name};
-        }
+      if (test(RE_REFERENCE)) {
+        let [, sigil, name, attr = null] = match(RE_REFERENCE);
+        let type = {"$": "var", "-": "term"}[sigil] || "ref";
 
         if (source[cursor] === "[") {
-          return {type: "getvar", ref, selector: parseVariantKey()};
+          // DEPRECATED VariantExpressions will be removed before 1.0.
+          return {type, name, selector: parseVariantKey()};
         }
 
         if (consumeToken(TOKEN_PAREN_OPEN)) {
-          let callee = {...ref, type: "func"};
-          return {type: "call", callee, args: parseArguments()};
+          return {type, name, attr, args: parseArguments()};
         }
 
-        return ref;
+        return {type, name, attr, args: null};
       }
 
       return parseLiteral();
     }
 
     function parseArguments() {
       let args = [];
       while (true) {
@@ -1030,28 +876,39 @@ class FluentResource extends Map {
       let star;
 
       while (test(RE_VARIANT_START)) {
         if (consumeChar("*")) {
           star = count;
         }
 
         let key = parseVariantKey();
-        cursor = RE_VARIANT_START.lastIndex;
-        variants[count++] = {key, value: parsePattern()};
+        let value = parsePattern();
+        if (value === null) {
+          throw new FluentError("Expected variant value");
+        }
+        variants[count++] = {key, value};
       }
 
-      return count > 0 ? {variants, star} : null;
+      if (count === 0) {
+        return null;
+      }
+
+      if (star === undefined) {
+        throw new FluentError("Expected default variant");
+      }
+
+      return {variants, star};
     }
 
     function parseVariantKey() {
       consumeToken(TOKEN_BRACKET_OPEN, FluentError);
       let key = test(RE_NUMBER_LITERAL)
         ? parseNumberLiteral()
-        : match(RE_IDENTIFIER);
+        : match1(RE_IDENTIFIER);
       consumeToken(TOKEN_BRACKET_CLOSE, FluentError);
       return key;
     }
 
     function parseLiteral() {
       if (test(RE_NUMBER_LITERAL)) {
         return parseNumberLiteral();
       }
@@ -1059,48 +916,54 @@ class FluentResource extends Map {
       if (source[cursor] === "\"") {
         return parseStringLiteral();
       }
 
       throw new FluentError("Invalid expression");
     }
 
     function parseNumberLiteral() {
-      return {type: "num", value: match(RE_NUMBER_LITERAL)};
+      return {type: "num", value: match1(RE_NUMBER_LITERAL)};
     }
 
     function parseStringLiteral() {
       consumeChar("\"", FluentError);
       let value = "";
       while (true) {
-        value += match(RE_STRING_RUN);
+        value += match1(RE_STRING_RUN);
 
         if (source[cursor] === "\\") {
-          value += parseEscapeSequence(RE_STRING_ESCAPE);
+          value += parseEscapeSequence();
           continue;
         }
 
         if (consumeChar("\"")) {
-          return value;
+          return {type: "str", value};
         }
 
         // We've reached an EOL of EOF.
         throw new FluentError("Unclosed string literal");
       }
     }
 
     // Unescape known escape sequences.
-    function parseEscapeSequence(reSpecialized) {
-      if (test(RE_UNICODE_ESCAPE)) {
-        let sequence = match(RE_UNICODE_ESCAPE);
-        return String.fromCodePoint(parseInt(sequence, 16));
+    function parseEscapeSequence() {
+      if (test(RE_STRING_ESCAPE)) {
+        return match1(RE_STRING_ESCAPE);
       }
 
-      if (test(reSpecialized)) {
-        return match(reSpecialized);
+      if (test(RE_UNICODE_ESCAPE)) {
+        let [, codepoint4, codepoint6] = match(RE_UNICODE_ESCAPE);
+        let codepoint = parseInt(codepoint4 || codepoint6, 16);
+        return codepoint <= 0xD7FF || 0xE000 <= codepoint
+          // It's a Unicode scalar value.
+          ? String.fromCodePoint(codepoint)
+          // Lonely surrogates can cause trouble when the parsing result is
+          // saved using UTF-8. Use U+FFFD REPLACEMENT CHARACTER instead.
+          : "�";
       }
 
       throw new FluentError("Unknown escape sequence");
     }
 
     // Parse blank space. Return it if it looks like indent before a pattern
     // line. Skip it othwerwise.
     function parseIndent() {
@@ -1114,36 +977,43 @@ class FluentResource extends Map {
         case "*":
         case "}":
         case undefined: // EOF
           // A special character. End the Pattern.
           return false;
         case "{":
           // Placeables don't require indentation (in EBNF: block-placeable).
           // Continue the Pattern.
-          return source.slice(start, cursor).replace(RE_CRLF, "\n");
+          return makeIndent(source.slice(start, cursor));
       }
 
       // If the first character on the line is not one of the special characters
       // listed above, it's a regular text character. Check if there's at least
       // one space of indent before it.
       if (source[cursor - 1] === " ") {
         // It's an indented text character (in EBNF: indented-char). Continue
         // the Pattern.
-        return source.slice(start, cursor).replace(RE_CRLF, "\n");
+        return makeIndent(source.slice(start, cursor));
       }
 
       // A not-indented text character is likely the identifier of the next
       // message. End the Pattern.
       return false;
     }
 
-    // Trim spaces trailing on every line of text.
-    function trim(text) {
-      return text.replace(RE_TRAILING_SPACES, "");
+    // Trim blanks in text according to the given regex.
+    function trim(text, re) {
+      return text.replace(re, "");
+    }
+
+    // Normalize a blank block and extract the indent details.
+    function makeIndent(blank) {
+      let value = blank.replace(RE_BLANK_LINES, "\n");
+      let length = RE_INDENT.exec(blank)[1].length;
+      return {type: "indent", value, length};
     }
   }
 }
 
 /**
  * Message bundles are single-language stores of translations.  They are
  * responsible for parsing translation resources in the Fluent syntax and can
  * format translation units (entities) to strings.
--- a/intl/l10n/fluent.js.patch
+++ b/intl/l10n/fluent.js.patch
@@ -1,736 +1,1514 @@
---- ./dist/Fluent.jsm	2018-10-19 08:40:36.557032837 -0600
-+++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/Fluent.jsm	2018-10-19 21:22:35.174315857 -0600
+diff --git a/intl/l10n/DOMLocalization.jsm b/intl/l10n/DOMLocalization.jsm
+--- a/intl/l10n/DOMLocalization.jsm
++++ b/intl/l10n/DOMLocalization.jsm
+@@ -15,12 +15,13 @@
+  * limitations under the License.
+  */
+ 
+-/* fluent-dom@fa25466f (October 12, 2018) */
++
++/* fluent-dom@0.4.0 */
+ 
+-const { Localization } =
+-  ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
+-const { Services } =
+-  ChromeUtils.import("resource://gre/modules/Services.jsm", {});
++import Localization from '../../fluent-dom/src/localization.js';
++
++/* eslint no-console: ["error", {allow: ["warn"]}] */
++/* global console */
+ 
+ // Match the opening angle bracket (<) in HTML tags, and HTML entities like
+ // &amp;, &#0038;, &#x0026;.
+@@ -38,7 +39,7 @@ const TEXT_LEVEL_ELEMENTS = {
+   "http://www.w3.org/1999/xhtml": [
+     "em", "strong", "small", "s", "cite", "q", "dfn", "abbr", "data",
+     "time", "code", "var", "samp", "kbd", "sub", "sup", "i", "b", "u",
+-    "mark", "bdi", "bdo", "span", "br", "wbr",
++    "mark", "bdi", "bdo", "span", "br", "wbr"
+   ],
+ };
+ 
+@@ -56,17 +57,16 @@ const LOCALIZABLE_ATTRIBUTES = {
+     track: ["label"],
+     img: ["alt"],
+     textarea: ["placeholder"],
+-    th: ["abbr"],
++    th: ["abbr"]
+   },
+   "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul": {
+     global: [
+-      "accesskey", "aria-label", "aria-valuetext", "aria-moz-hint", "label",
+-      "title", "tooltiptext"],
+-    description: ["value"],
++      "accesskey", "aria-label", "aria-valuetext", "aria-moz-hint", "label"
++    ],
+     key: ["key", "keycode"],
+-    label: ["value"],
+     textbox: ["placeholder"],
+-  },
++    toolbarbutton: ["tooltiptext"],
++  }
+ };
+ 
+ 
+@@ -96,7 +96,6 @@ function translateElement(element, trans
+       const templateElement = element.ownerDocument.createElementNS(
+         "http://www.w3.org/1999/xhtml", "template"
+       );
+-      // eslint-disable-next-line no-unsanitized/property
+       templateElement.innerHTML = value;
+       overlayChildNodes(templateElement.content, element);
+     }
+@@ -350,46 +349,6 @@ function shallowPopulateUsing(fromElemen
+   return toElement;
+ }
+ 
+-/**
+- * Sanitizes a translation before passing them to Node.localize API.
+- *
+- * It returns `false` if the translation contains DOM Overlays and should
+- * not go into Node.localize.
+- *
+- * Note: There's a third item of work that JS DOM Overlays do - removal
+- * of attributes from the previous translation.
+- * This is not trivial to implement for Node.localize scenario, so
+- * at the moment it is not supported.
+- *
+- * @param {{
+- *          localName: string,
+- *          namespaceURI: string,
+- *          type: string || null
+- *          l10nId: string,
+- *          l10nArgs: Array<Object> || null,
+- *          l10nAttrs: string ||null,
+- *        }}                                     l10nItems
+- * @param {{value: string, attrs: Object}} translations
+- * @returns boolean
+- * @private
+- */
+-function sanitizeTranslationForNodeLocalize(l10nItem, translation) {
+-  if (reOverlay.test(translation.value)) {
+-    return false;
+-  }
+-
+-  if (translation.attributes) {
+-    const explicitlyAllowed = l10nItem.l10nAttrs === null ? null :
+-      l10nItem.l10nAttrs.split(",").map(i => i.trim());
+-    for (const [j, {name}] of translation.attributes.entries()) {
+-      if (!isAttrNameLocalizable(name, l10nItem, explicitlyAllowed)) {
+-        translation.attributes.splice(j, 1);
+-      }
+-    }
+-  }
+-  return true;
+-}
+-
+ const L10NID_ATTR_NAME = "data-l10n-id";
+ const L10NARGS_ATTR_NAME = "data-l10n-args";
+ 
+@@ -427,12 +386,12 @@ class DOMLocalization extends Localizati
+       characterData: false,
+       childList: true,
+       subtree: true,
+-      attributeFilter: [L10NID_ATTR_NAME, L10NARGS_ATTR_NAME],
++      attributeFilter: [L10NID_ATTR_NAME, L10NARGS_ATTR_NAME]
+     };
+   }
+ 
+-  onChange(eager = false) {
+-    super.onChange(eager);
++  onChange() {
++    super.onChange();
+     this.translateRoots();
+   }
+ 
+@@ -497,7 +456,7 @@ class DOMLocalization extends Localizati
+   getAttributes(element) {
+     return {
+       id: element.getAttribute(L10NID_ATTR_NAME),
+-      args: JSON.parse(element.getAttribute(L10NARGS_ATTR_NAME) || null),
++      args: JSON.parse(element.getAttribute(L10NARGS_ATTR_NAME) || null)
+     };
+   }
+ 
+@@ -519,17 +478,18 @@ class DOMLocalization extends Localizati
+     }
+ 
+     if (this.windowElement) {
+-      if (this.windowElement !== newRoot.ownerGlobal) {
++      if (this.windowElement !== newRoot.ownerDocument.defaultView) {
+         throw new Error(`Cannot connect a root:
+           DOMLocalization already has a root from a different window.`);
+       }
+     } else {
+-      this.windowElement = newRoot.ownerGlobal;
++      this.windowElement = newRoot.ownerDocument.defaultView;
+       this.mutationObserver = new this.windowElement.MutationObserver(
+         mutations => this.translateMutations(mutations)
+       );
+     }
+ 
++
+     this.roots.add(newRoot);
+     this.mutationObserver.observe(newRoot, this.observerConfig);
+   }
+@@ -572,20 +532,7 @@ class DOMLocalization extends Localizati
+   translateRoots() {
+     const roots = Array.from(this.roots);
+     return Promise.all(
+-      roots.map(async root => {
+-        // We want to first retranslate the UI, and
+-        // then (potentially) flip the directionality.
+-        //
+-        // This means that the DOM alternations and directionality
+-        // are set in the same microtask.
+-        await this.translateFragment(root);
+-        let primaryLocale = Services.locale.appLocaleAsBCP47;
+-        let direction = Services.locale.isAppLocaleRTL ? "rtl" : "ltr";
+-        root.setAttribute("lang", primaryLocale);
+-        root.setAttribute(root.namespaceURI ===
+-          "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+-          ? "localedir" : "dir", direction);
+-      })
++      roots.map(root => this.translateFragment(root))
+     );
+   }
+ 
+@@ -652,10 +599,7 @@ class DOMLocalization extends Localizati
+     if (this.pendingElements.size > 0) {
+       if (this.pendingrAF === null) {
+         this.pendingrAF = this.windowElement.requestAnimationFrame(() => {
+-          // We need to filter for elements that lost their l10n-id while
+-          // waiting for the animation frame.
+-          this.translateElements(Array.from(this.pendingElements)
+-            .filter(elem => elem.hasAttribute("data-l10n-id")));
++          this.translateElements(Array.from(this.pendingElements));
+           this.pendingElements.clear();
+           this.pendingrAF = null;
+         });
+@@ -677,63 +621,6 @@ class DOMLocalization extends Localizati
+    * @returns {Promise}
+    */
+   translateFragment(frag) {
+-    if (frag.localize) {
+-      // This is a temporary fast-path offered by Gecko to workaround performance
+-      // issues coming from Fluent and XBL+Stylo performing unnecesary
+-      // operations during startup.
+-      // For details see bug 1441037, bug 1442262, and bug 1363862.
+-
+-      // A sparse array which will store translations separated out from
+-      // all translations that is needed for DOM Overlay.
+-      const overlayTranslations = [];
+-
+-      const getTranslationsForItems = async l10nItems => {
+-        const keys = l10nItems.map(
+-          l10nItem => ({id: l10nItem.l10nId, args: l10nItem.l10nArgs}));
+-        const translations = await this.formatMessages(keys);
+-
+-        // Here we want to separate out elements that require DOM Overlays.
+-        // Those elements will have to be translated using our JS
+-        // implementation, while everything else is going to use the fast-path.
+-        for (const [i, translation] of translations.entries()) {
+-          if (translation === undefined) {
+-            continue;
+-          }
+-
+-          const hasOnlyText =
+-            sanitizeTranslationForNodeLocalize(l10nItems[i], translation);
+-          if (!hasOnlyText) {
+-            // Removing from translations to make Node.localize skip it.
+-            // We will translate it below using JS DOM Overlays.
+-            overlayTranslations[i] = translations[i];
+-            translations[i] = undefined;
+-          }
+-        }
+-
+-        // We pause translation observing here because Node.localize
+-        // will translate the whole DOM next, using the `translations`.
+-        //
+-        // The observer will be resumed after DOM Overlays are localized
+-        // in the next microtask.
+-        this.pauseObserving();
+-        return translations;
+-      };
+-
+-      return frag.localize(getTranslationsForItems.bind(this))
+-        .then(untranslatedElements => {
+-          for (let i = 0; i < overlayTranslations.length; i++) {
+-            if (overlayTranslations[i] !== undefined &&
+-                untranslatedElements[i] !== undefined) {
+-              translateElement(untranslatedElements[i], overlayTranslations[i]);
+-            }
+-          }
+-          this.resumeObserving();
+-        })
+-        .catch(e => {
+-          this.resumeObserving();
+-          throw e;
+-        });
+-    }
+     return this.translateElements(this.getTranslatables(frag));
+   }
+ 
+@@ -808,10 +695,42 @@ class DOMLocalization extends Localizati
+   getKeysForElement(element) {
+     return {
+       id: element.getAttribute(L10NID_ATTR_NAME),
+-      args: JSON.parse(element.getAttribute(L10NARGS_ATTR_NAME) || null),
++      args: JSON.parse(element.getAttribute(L10NARGS_ATTR_NAME) || null)
+     };
+   }
+ }
+ 
+-this.DOMLocalization = DOMLocalization;
+-var EXPORTED_SYMBOLS = ["DOMLocalization"];
++/* global L10nRegistry, Services */
++
++/**
++ * The default localization strategy for Gecko. It comabines locales
++ * available in L10nRegistry, with locales requested by the user to
++ * generate the iterator over FluentBundles.
++ *
++ * In the future, we may want to allow certain modules to override this
++ * with a different negotitation strategy to allow for the module to
++ * be localized into a different language - for example DevTools.
++ */
++function defaultGenerateBundles(resourceIds) {
++  const requestedLocales = Services.locale.getRequestedLocales();
++  const availableLocales = L10nRegistry.getAvailableLocales();
++  const defaultLocale = Services.locale.defaultLocale;
++  const locales = Services.locale.negotiateLanguages(
++    requestedLocales, availableLocales, defaultLocale,
++  );
++  return L10nRegistry.generateContexts(locales, resourceIds);
++}
++
++
++class GeckoDOMLocalization extends DOMLocalization {
++  constructor(
++    windowElement,
++    resourceIds,
++    generateBundles = defaultGenerateBundles
++  ) {
++    super(windowElement, resourceIds, generateBundles);
++  }
++}
++
++this.DOMLocalization = GeckoDOMLocalization;
++this.EXPORTED_SYMBOLS = ["DOMLocalization"];
+diff --git a/intl/l10n/Fluent.jsm b/intl/l10n/Fluent.jsm
+--- a/intl/l10n/Fluent.jsm
++++ b/intl/l10n/Fluent.jsm
 @@ -16,7 +16,7 @@
   */
  
  
--/* fluent-dom@0.4.0 */
-+/* fluent@fa25466f (October 12, 2018) */
+-/* fluent@0.10.0 */
++/* fluent-dom@0.4.0 */
  
  /* global Intl */
  
-@@ -139,7 +139,53 @@
+@@ -139,53 +139,7 @@ function values(opts) {
    return unwrapped;
  }
  
--/* global Intl */
-+/**
-+ * @overview
-+ *
-+ * The role of the Fluent resolver is to format a translation object to an
-+ * instance of `FluentType` or an array of instances.
-+ *
-+ * Translations can contain references to other messages or variables,
-+ * conditional logic in form of select expressions, traits which describe their
-+ * grammatical features, and can use Fluent builtins which make use of the
-+ * `Intl` formatters to format numbers, dates, lists and more into the
-+ * bundle's language. See the documentation of the Fluent syntax for more
-+ * information.
-+ *
-+ * In case of errors the resolver will try to salvage as much of the
-+ * translation as possible.  In rare situations where the resolver didn't know
-+ * how to recover from an error it will return an instance of `FluentNone`.
-+ *
-+ * `MessageReference`, `VariantExpression`, `AttributeExpression` and
-+ * `SelectExpression` resolve to raw Runtime Entries objects and the result of
-+ * the resolution needs to be passed into `Type` to get their real value.
-+ * This is useful for composing expressions.  Consider:
-+ *
-+ *     brand-name[nominative]
-+ *
-+ * which is a `VariantExpression` with properties `id: MessageReference` and
-+ * `key: Keyword`.  If `MessageReference` was resolved eagerly, it would
-+ * instantly resolve to the value of the `brand-name` message.  Instead, we
-+ * want to get the message object and look for its `nominative` variant.
-+ *
-+ * All other expressions (except for `FunctionReference` which is only used in
-+ * `CallExpression`) resolve to an instance of `FluentType`.  The caller should
-+ * use the `toString` method to convert the instance to a native value.
-+ *
-+ *
-+ * All functions in this file pass around a special object called `env`.
-+ * This object stores a set of elements used by all resolve functions:
-+ *
-+ *  * {FluentBundle} bundle
-+ *      bundle for which the given resolution is happening
-+ *  * {Object} args
-+ *      list of developer provided arguments that can be used
-+ *  * {Array} errors
-+ *      list of errors collected while resolving
-+ *  * {WeakSet} dirty
-+ *      Set of patterns already encountered during this resolution.
-+ *      This is used to prevent cyclic resolutions.
-+ */
+-/**
+- * @overview
+- *
+- * The role of the Fluent resolver is to format a translation object to an
+- * instance of `FluentType` or an array of instances.
+- *
+- * Translations can contain references to other messages or variables,
+- * conditional logic in form of select expressions, traits which describe their
+- * grammatical features, and can use Fluent builtins which make use of the
+- * `Intl` formatters to format numbers, dates, lists and more into the
+- * bundle's language. See the documentation of the Fluent syntax for more
+- * information.
+- *
+- * In case of errors the resolver will try to salvage as much of the
+- * translation as possible.  In rare situations where the resolver didn't know
+- * how to recover from an error it will return an instance of `FluentNone`.
+- *
+- * `MessageReference`, `VariantExpression`, `AttributeExpression` and
+- * `SelectExpression` resolve to raw Runtime Entries objects and the result of
+- * the resolution needs to be passed into `Type` to get their real value.
+- * This is useful for composing expressions.  Consider:
+- *
+- *     brand-name[nominative]
+- *
+- * which is a `VariantExpression` with properties `id: MessageReference` and
+- * `key: Keyword`.  If `MessageReference` was resolved eagerly, it would
+- * instantly resolve to the value of the `brand-name` message.  Instead, we
+- * want to get the message object and look for its `nominative` variant.
+- *
+- * All other expressions (except for `FunctionReference` which is only used in
+- * `CallExpression`) resolve to an instance of `FluentType`.  The caller should
+- * use the `toString` method to convert the instance to a native value.
+- *
+- *
+- * All functions in this file pass around a special object called `env`.
+- * This object stores a set of elements used by all resolve functions:
+- *
+- *  * {FluentBundle} bundle
+- *      bundle for which the given resolution is happening
+- *  * {Object} args
+- *      list of developer provided arguments that can be used
+- *  * {Array} errors
+- *      list of errors collected while resolving
+- *  * {WeakSet} dirty
+- *      Set of patterns already encountered during this resolution.
+- *      This is used to prevent cyclic resolutions.
+- */
++/* global Intl */
  
  // Prevent expansion of too long placeables.
  const MAX_PLACEABLE_LENGTH = 2500;
-@@ -1319,14 +1365,6 @@
+@@ -514,7 +468,7 @@ function Pattern(env, ptn) {
+  */
+ function resolve(bundle, args, message, errors = []) {
+   const env = {
+-    bundle, args, errors, dirty: new WeakSet(),
++    bundle, args, errors, dirty: new WeakSet()
+   };
+   return Type(env, message).toString(bundle);
+ }
+@@ -1064,7 +1018,7 @@ class FluentBundle {
+   constructor(locales, {
+     functions = {},
+     useIsolating = true,
+-    transform = v => v,
++    transform = v => v
+   } = {}) {
+     this.locales = Array.isArray(locales) ? locales : [locales];
+ 
+@@ -1235,6 +1189,14 @@ class FluentBundle {
    }
  }
  
--/*
-- * @module fluent
-- * @overview
-- *
-- * `fluent` is a JavaScript implementation of Project Fluent, a localization
-- * framework designed to unleash the expressive power of the natural language.
-- *
-- */
--
++/*
++ * @module fluent
++ * @overview
++ *
++ * `fluent` is a JavaScript implementation of Project Fluent, a localization
++ * framework designed to unleash the expressive power of the natural language.
++ *
++ */
++
  this.FluentBundle = FluentBundle;
--this.EXPORTED_SYMBOLS = ["FluentBundle"];
-+this.FluentResource = FluentResource;
-+var EXPORTED_SYMBOLS = ["FluentBundle", "FluentResource"];
---- ./dist/Localization.jsm	2018-10-19 08:40:36.773712561 -0600
-+++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/Localization.jsm	2018-10-19 21:20:57.295233460 -0600
-@@ -16,27 +16,34 @@
+-this.FluentResource = FluentResource;
+-var EXPORTED_SYMBOLS = ["FluentBundle", "FluentResource"];
++this.EXPORTED_SYMBOLS = ["FluentBundle"];
+diff --git a/intl/l10n/Localization.jsm b/intl/l10n/Localization.jsm
+--- a/intl/l10n/Localization.jsm
++++ b/intl/l10n/Localization.jsm
+@@ -16,34 +16,27 @@
   */
  
  
--/* fluent-dom@0.4.0 */
-+/* fluent-dom@fa25466f (October 12, 2018) */
-+
-+/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
-+/* global console */
-+
-+const { L10nRegistry } = ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
-+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
-+const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
+-/* fluent-dom@fa25466f (October 12, 2018) */
+-
+-/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
+-/* global console */
+-
+-const { L10nRegistry } = ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
+-const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
+-const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
++/* fluent-dom@0.4.0 */
  
  /*
   * Base CachedIterable class.
   */
  class CachedIterable extends Array {
--    /**
--     * Create a `CachedIterable` instance from an iterable or, if another
--     * instance of `CachedIterable` is passed, return it without any
--     * modifications.
--     *
--     * @param {Iterable} iterable
--     * @returns {CachedIterable}
--     */
--    static from(iterable) {
--        if (iterable instanceof this) {
--            return iterable;
--        }
+-  /**
+-   * Create a `CachedIterable` instance from an iterable or, if another
+-   * instance of `CachedIterable` is passed, return it without any
+-   * modifications.
+-   *
+-   * @param {Iterable} iterable
+-   * @returns {CachedIterable}
+-   */
+-  static from(iterable) {
+-    if (iterable instanceof this) {
+-      return iterable;
++    /**
++     * Create a `CachedIterable` instance from an iterable or, if another
++     * instance of `CachedIterable` is passed, return it without any
++     * modifications.
++     *
++     * @param {Iterable} iterable
++     * @returns {CachedIterable}
++     */
++    static from(iterable) {
++        if (iterable instanceof this) {
++            return iterable;
++        }
++
++        return new this(iterable);
+     }
 -
--        return new this(iterable);
-+  /**
-+   * Create a `CachedIterable` instance from an iterable or, if another
-+   * instance of `CachedIterable` is passed, return it without any
-+   * modifications.
-+   *
-+   * @param {Iterable} iterable
-+   * @returns {CachedIterable}
-+   */
-+  static from(iterable) {
-+    if (iterable instanceof this) {
-+      return iterable;
-     }
-+
-+    return new this(iterable);
-+  }
+-    return new this(iterable);
+-  }
  }
  
  /*
-@@ -46,88 +53,100 @@
+@@ -53,80 +46,88 @@ class CachedIterable extends Array {
   * iterable.
   */
  class CachedAsyncIterable extends CachedIterable {
--    /**
--     * Create an `CachedAsyncIterable` instance.
--     *
--     * @param {Iterable} iterable
--     * @returns {CachedAsyncIterable}
--     */
--    constructor(iterable) {
--        super();
--
--        if (Symbol.asyncIterator in Object(iterable)) {
--            this.iterator = iterable[Symbol.asyncIterator]();
--        } else if (Symbol.iterator in Object(iterable)) {
--            this.iterator = iterable[Symbol.iterator]();
--        } else {
--            throw new TypeError("Argument must implement the iteration protocol.");
--        }
--    }
-+  /**
-+   * Create an `CachedAsyncIterable` instance.
-+   *
-+   * @param {Iterable} iterable
-+   * @returns {CachedAsyncIterable}
-+   */
-+  constructor(iterable) {
-+    super();
+-  /**
+-   * Create an `CachedAsyncIterable` instance.
+-   *
+-   * @param {Iterable} iterable
+-   * @returns {CachedAsyncIterable}
+-   */
+-  constructor(iterable) {
+-    super();
++    /**
++     * Create an `CachedAsyncIterable` instance.
++     *
++     * @param {Iterable} iterable
++     * @returns {CachedAsyncIterable}
++     */
++    constructor(iterable) {
++        super();
++
++        if (Symbol.asyncIterator in Object(iterable)) {
++            this.iterator = iterable[Symbol.asyncIterator]();
++        } else if (Symbol.iterator in Object(iterable)) {
++            this.iterator = iterable[Symbol.iterator]();
++        } else {
++            throw new TypeError("Argument must implement the iteration protocol.");
++        }
++    }
  
--    /**
--     * Synchronous iterator over the cached elements.
--     *
--     * Return a generator object implementing the iterator protocol over the
--     * cached elements of the original (async or sync) iterable.
--     */
--    [Symbol.iterator]() {
--        const cached = this;
--        let cur = 0;
--
--        return {
--            next() {
--                if (cached.length === cur) {
--                    return {value: undefined, done: true};
--                }
--                return cached[cur++];
--            }
--        };
-+    if (Symbol.asyncIterator in Object(iterable)) {
-+      this.iterator = iterable[Symbol.asyncIterator]();
-+    } else if (Symbol.iterator in Object(iterable)) {
-+      this.iterator = iterable[Symbol.iterator]();
-+    } else {
-+      throw new TypeError("Argument must implement the iteration protocol.");
-     }
-+  }
- 
--    /**
--     * Asynchronous iterator caching the yielded elements.
--     *
--     * Elements yielded by the original iterable will be cached and available
--     * synchronously. Returns an async generator object implementing the
--     * iterator protocol over the elements of the original (async or sync)
--     * iterable.
--     */
--    [Symbol.asyncIterator]() {
--        const cached = this;
--        let cur = 0;
--
--        return {
--            async next() {
--                if (cached.length <= cur) {
--                    cached.push(await cached.iterator.next());
--                }
--                return cached[cur++];
--            }
--        };
+-    if (Symbol.asyncIterator in Object(iterable)) {
+-      this.iterator = iterable[Symbol.asyncIterator]();
+-    } else if (Symbol.iterator in Object(iterable)) {
+-      this.iterator = iterable[Symbol.iterator]();
+-    } else {
+-      throw new TypeError("Argument must implement the iteration protocol.");
 -    }
-+  /**
-+   * Synchronous iterator over the cached elements.
-+   *
-+   * Return a generator object implementing the iterator protocol over the
-+   * cached elements of the original (async or sync) iterable.
-+   */
-+  [Symbol.iterator]() {
-+    const cached = this;
-+    let cur = 0;
-+
-+    return {
-+      next() {
-+        if (cached.length === cur) {
-+          return {value: undefined, done: true};
-+        }
-+        return cached[cur++];
-+      }
-+    };
-+  }
+-  }
++    /**
++     * Synchronous iterator over the cached elements.
++     *
++     * Return a generator object implementing the iterator protocol over the
++     * cached elements of the original (async or sync) iterable.
++     */
++    [Symbol.iterator]() {
++        const cached = this;
++        let cur = 0;
+ 
+-  /**
+-   * Asynchronous iterator caching the yielded elements.
+-   *
+-   * Elements yielded by the original iterable will be cached and available
+-   * synchronously. Returns an async generator object implementing the
+-   * iterator protocol over the elements of the original (async or sync)
+-   * iterable.
+-   */
+-  [Symbol.asyncIterator]() {
+-    const cached = this;
+-    let cur = 0;
++        return {
++            next() {
++                if (cached.length === cur) {
++                    return {value: undefined, done: true};
++                }
++                return cached[cur++];
++            }
++        };
++    }
  
--    /**
--     * This method allows user to consume the next element from the iterator
--     * into the cache.
--     *
--     * @param {number} count - number of elements to consume
--     */
--    async touchNext(count = 1) {
--        let idx = 0;
--        while (idx++ < count) {
--            const last = this[this.length - 1];
--            if (last && last.done) {
--                break;
--            }
--            this.push(await this.iterator.next());
-+  /**
-+   * Asynchronous iterator caching the yielded elements.
-+   *
-+   * Elements yielded by the original iterable will be cached and available
-+   * synchronously. Returns an async generator object implementing the
-+   * iterator protocol over the elements of the original (async or sync)
-+   * iterable.
-+   */
-+  [Symbol.asyncIterator]() {
-+    const cached = this;
-+    let cur = 0;
+-    return {
+-      async next() {
+-        if (cached.length <= cur) {
+-          cached.push(cached.iterator.next());
+-        }
+-        return cached[cur++];
+-      },
+-    };
+-  }
++    /**
++     * Asynchronous iterator caching the yielded elements.
++     *
++     * Elements yielded by the original iterable will be cached and available
++     * synchronously. Returns an async generator object implementing the
++     * iterator protocol over the elements of the original (async or sync)
++     * iterable.
++     */
++    [Symbol.asyncIterator]() {
++        const cached = this;
++        let cur = 0;
+ 
+-  /**
+-   * This method allows user to consume the next element from the iterator
+-   * into the cache.
+-   *
+-   * @param {number} count - number of elements to consume
+-   */
+-  async touchNext(count = 1) {
+-    let idx = 0;
+-    while (idx++ < count) {
+-      const last = this[this.length - 1];
+-      if (last && (await last).done) {
+-        break;
+-      }
+-      this.push(this.iterator.next());
++        return {
++            async next() {
++                if (cached.length <= cur) {
++                    cached.push(await cached.iterator.next());
++                }
++                return cached[cur++];
++            }
++        };
+     }
+-    // Return the last cached {value, done} object to allow the calling
+-    // code to decide if it needs to call touchNext again.
+-    return this[this.length - 1];
+-  }
 +
-+    return {
-+      async next() {
-+        if (cached.length <= cur) {
-+          cached.push(await cached.iterator.next());
-         }
--        // Return the last cached {value, done} object to allow the calling
--        // code to decide if it needs to call touchNext again.
--        return this[this.length - 1];
-+        return cached[cur++];
-+      }
-+    };
-+  }
-+
-+  /**
-+   * This method allows user to consume the next element from the iterator
-+   * into the cache.
-+   *
-+   * @param {number} count - number of elements to consume
-+   */
-+  async touchNext(count = 1) {
-+    let idx = 0;
-+    while (idx++ < count) {
-+      const last = this[this.length - 1];
-+      if (last && last.done) {
-+        break;
-+      }
-+      this.push(await this.iterator.next());
-     }
-+    // Return the last cached {value, done} object to allow the calling
-+    // code to decide if it needs to call touchNext again.
-+    return this[this.length - 1];
-+  }
++    /**
++     * This method allows user to consume the next element from the iterator
++     * into the cache.
++     *
++     * @param {number} count - number of elements to consume
++     */
++    async touchNext(count = 1) {
++        let idx = 0;
++        while (idx++ < count) {
++            const last = this[this.length - 1];
++            if (last && last.done) {
++                break;
++            }
++            this.push(await this.iterator.next());
++        }
++        // Return the last cached {value, done} object to allow the calling
++        // code to decide if it needs to call touchNext again.
++        return this[this.length - 1];
++    }
  }
  
--/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
+-/**
+- * The default localization strategy for Gecko. It comabines locales
+- * available in L10nRegistry, with locales requested by the user to
+- * generate the iterator over FluentBundles.
+- *
+- * In the future, we may want to allow certain modules to override this
+- * with a different negotitation strategy to allow for the module to
+- * be localized into a different language - for example DevTools.
+- */
+-function defaultGenerateBundles(resourceIds) {
+-  const appLocales = Services.locale.appLocalesAsBCP47;
+-  return L10nRegistry.generateBundles(appLocales, resourceIds);
+-}
++/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
+ 
+ /**
+  * The `Localization` class is a central high-level API for vanilla
+@@ -142,21 +143,16 @@ class Localization {
+    *
+    * @returns {Localization}
+    */
+-  constructor(resourceIds = [], generateBundles = defaultGenerateBundles) {
++  constructor(resourceIds = [], generateBundles) {
+     this.resourceIds = resourceIds;
+     this.generateBundles = generateBundles;
+     this.bundles = CachedAsyncIterable.from(
+       this.generateBundles(this.resourceIds));
+   }
+ 
+-  /**
+-   * @param {Array<String>} resourceIds - List of resource IDs
+-   * @param {bool}                eager - whether the I/O for new context should
+-   *                                      begin eagerly
+-   */
+-  addResourceIds(resourceIds, eager = false) {
++  addResourceIds(resourceIds) {
+     this.resourceIds.push(...resourceIds);
+-    this.onChange(eager);
++    this.onChange();
+     return this.resourceIds.length;
+   }
+ 
+@@ -188,12 +184,9 @@ class Localization {
+         break;
+       }
+ 
+-      if (AppConstants.NIGHTLY_BUILD || Cu.isInAutomation) {
++      if (typeof console !== "undefined") {
+         const locale = bundle.locales[0];
+         const ids = Array.from(missingIds).join(", ");
+-        if (Cu.isInAutomation) {
+-          throw new Error(`Missing translations in ${locale}: ${ids}`);
+-        }
+         console.warn(`Missing translations in ${locale}: ${ids}`);
+       }
+     }
+@@ -281,64 +274,21 @@ 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":
+-            this.onChange();
+-        }
+-        break;
+-      default:
+-        break;
+-    }
++  handleEvent() {
++    this.onChange();
+   }
+ 
+   /**
+    * This method should be called when there's a reason to believe
+    * that language negotiation or available resources changed.
+-   *
+-   * @param {bool} eager - whether the I/O for new context should begin eagerly
+    */
+-  onChange(eager = false) {
++  onChange() {
+     this.bundles = CachedAsyncIterable.from(
+       this.generateBundles(this.resourceIds));
+-    if (eager) {
+-      // If the first app locale is the same as last fallback
+-      // it means that we have all resources in this locale, and
+-      // we want to eagerly fetch just that one.
+-      // Otherwise, we're in a scenario where the first locale may
+-      // be partial and we want to eagerly fetch a fallback as well.
+-      const appLocale = Services.locale.appLocaleAsBCP47;
+-      const lastFallback = Services.locale.lastFallbackLocale;
+-      const prefetchCount = appLocale === lastFallback ? 1 : 2;
+-      this.bundles.touchNext(prefetchCount);
+-    }
++    this.bundles.touchNext(2);
+   }
+ }
+ 
+-Localization.prototype.QueryInterface = ChromeUtils.generateQI([
+-  Ci.nsISupportsWeakReference,
+-]);
+-
+ /**
+  * Format the value of a message into a string.
+  *
+@@ -430,7 +380,7 @@ function messageFromBundle(bundle, error
+  * See `Localization.formatWithFallback` for more info on how this is used.
+  *
+  * @param {Function}       method
+- * @param {FluentBundle}   bundle
++ * @param {FluentBundle} bundle
+  * @param {Array<string>}  keys
+  * @param {{Array<{value: string, attributes: Object}>}} translations
+  *
+@@ -458,5 +408,44 @@ function keysFromBundle(method, bundle, 
+   return missingIds;
+ }
+ 
+-this.Localization = Localization;
+-var EXPORTED_SYMBOLS = ["Localization"];
++/* global Components */
++/* eslint no-unused-vars: 0 */
++
++const Cu = Components.utils;
++const Cc = Components.classes;
++const Ci = Components.interfaces;
++
++const { L10nRegistry } =
++  Cu.import("resource://gre/modules/L10nRegistry.jsm", {});
++const ObserverService =
++  Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
++const { Services } =
++  Cu.import("resource://gre/modules/Services.jsm", {});
++
 +/**
 + * The default localization strategy for Gecko. It comabines locales
 + * available in L10nRegistry, with locales requested by the user to
 + * generate the iterator over FluentBundles.
 + *
 + * In the future, we may want to allow certain modules to override this
 + * with a different negotitation strategy to allow for the module to
 + * be localized into a different language - for example DevTools.
 + */
 +function defaultGenerateBundles(resourceIds) {
-+  const appLocales = Services.locale.appLocalesAsBCP47;
-+  return L10nRegistry.generateContexts(appLocales, resourceIds);
++  const requestedLocales = Services.locale.getRequestedLocales();
++  const availableLocales = L10nRegistry.getAvailableLocales();
++  const defaultLocale = Services.locale.defaultLocale;
++  const locales = Services.locale.negotiateLanguages(
++    requestedLocales, availableLocales, defaultLocale,
++  );
++  return L10nRegistry.generateContexts(locales, resourceIds);
 +}
- 
- /**
-  * The `Localization` class is a central high-level API for vanilla
-@@ -143,16 +162,21 @@
-    *
-    * @returns {Localization}
-    */
--  constructor(resourceIds = [], generateBundles) {
-+  constructor(resourceIds = [], generateBundles = defaultGenerateBundles) {
-     this.resourceIds = resourceIds;
-     this.generateBundles = generateBundles;
-     this.bundles = CachedAsyncIterable.from(
-       this.generateBundles(this.resourceIds));
-   }
- 
--  addResourceIds(resourceIds) {
-+  /**
-+   * @param {Array<String>} resourceIds - List of resource IDs
-+   * @param {bool}                eager - whether the I/O for new context should
-+   *                                      begin eagerly
-+   */
-+  addResourceIds(resourceIds, eager = false) {
-     this.resourceIds.push(...resourceIds);
--    this.onChange();
-+    this.onChange(eager);
-     return this.resourceIds.length;
-   }
- 
-@@ -184,9 +208,12 @@
-         break;
-       }
- 
--      if (typeof console !== "undefined") {
-+      if (AppConstants.NIGHTLY_BUILD || Cu.isInAutomation) {
-         const locale = bundle.locales[0];
-         const ids = Array.from(missingIds).join(", ");
-+        if (Cu.isInAutomation) {
-+          throw new Error(`Missing translations in ${locale}: ${ids}`);
-+        }
-         console.warn(`Missing translations in ${locale}: ${ids}`);
-       }
-     }
-@@ -274,21 +301,64 @@
-     return val;
-   }
- 
--  handleEvent() {
--    this.onChange();
-+  /**
-+   * 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 {Obje