Merge inbound to mozilla-central. a=merge
authorarthur.iakab <aiakab@mozilla.com>
Wed, 24 Oct 2018 01:18:31 +0300
changeset 442660 c9f6d6242851d25e230bc6a81fb45bfc8f93e6c1
parent 442659 147e9c6a801ca68685ae0512bee2134976edeb29 (diff)
parent 442658 58bcea5ede75c573fc8b8b20380b17d19b6f50ca (current diff)
child 442661 d20fd7486fde6997d38c4337a897fe65b43c4102
child 442717 e2a6c1b95477cba193170f8a9e5b98c8f74d32ce
push id71590
push useraiakab@mozilla.com
push dateTue, 23 Oct 2018 22:22:43 +0000
treeherderautoland@d20fd7486fde [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
c9f6d6242851 / 65.0a1 / 20181023222913 / files
nightly linux64
c9f6d6242851 / 65.0a1 / 20181023222913 / files
nightly mac
c9f6d6242851 / 65.0a1 / 20181023222913 / files
nightly win32
c9f6d6242851 / 65.0a1 / 20181023222913 / files
nightly win64
c9f6d6242851 / 65.0a1 / 20181023222913 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
taskcluster/ci/bouncer-locations-breakpoint/kind.yml
taskcluster/taskgraph/transforms/bouncer_locations_breakpoint.py
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1759,16 +1759,22 @@ pref("app.normandy.shieldLearnMoreUrl", 
 #ifdef MOZ_DATA_REPORTING
 pref("app.shield.optoutstudies.enabled", true);
 #else
 pref("app.shield.optoutstudies.enabled", false);
 #endif
 
 // Multi-lingual preferences
 pref("intl.multilingual.enabled", false);
+// AMO only serves language packs for release versions, so this feature only works on release.
+#ifdef RELEASE
+pref("intl.multilingual.downloadEnabled", true);
+#else
+pref("intl.multilingual.downloadEnabled", false);
+#endif
 
 // Simulate conditions that will happen when the browser
 // is running with Fission enabled. This is meant to assist
 // development and testing of Fission.
 // The current simulated conditions are:
 // - Don't propagate events from subframes to JS child actors
 pref("browser.fission.simulate", false);
 
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -65,16 +65,28 @@
   min-height: 25px;
   min-width: 25px;
 }
 
 toolbar[customizable="true"] {
   -moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar");
 }
 
+%ifdef MENUBAR_CAN_AUTOHIDE
+#toolbar-menubar[autohide="true"] {
+  overflow: hidden;
+}
+
+#toolbar-menubar[autohide="true"][inactive="true"]:not([customizing="true"]) {
+  min-height: 0 !important;
+  height: 0 !important;
+  -moz-appearance: none !important;
+}
+%endif
+
 %ifdef XP_MACOSX
 #toolbar-menubar {
   -moz-binding: none;
 }
 %endif
 
 panelmultiview {
   -moz-box-align: start;
--- a/browser/components/places/content/bookmarkProperties.js
+++ b/browser/components/places/content/bookmarkProperties.js
@@ -360,16 +360,18 @@ var BookmarkPropertiesPanel = {
     delete this._mutationObserver;
 
     window.removeEventListener("resize", this);
 
     // Calling removeEventListener with arguments which do not identify any
     // currently registered EventListener on the EventTarget has no effect.
     this._element("locationField")
         .removeEventListener("input", this);
+    this._element("keywordField")
+        .removeEventListener("input", this);
   },
 
   onDialogAccept() {
     // We must blur current focused element to save its changes correctly
     document.commandDispatcher.focusedElement.blur();
     // We have to uninit the panel first, otherwise late changes could force it
     // to commit more transactions.
     gEditItemOverlay.uninitPanel(true);
--- a/browser/components/places/content/editBookmark.js
+++ b/browser/components/places/content/editBookmark.js
@@ -403,26 +403,39 @@ var gEditItemOverlay = {
       await this._appendFolderItemToMenupopup(menupopup,
                                               this._recentFolders[i].guid,
                                               this._recentFolders[i].title);
     }
 
     let title = (await PlacesUtils.bookmarks.fetch(aSelectedFolderGuid)).title;
     var defaultItem = this._getFolderMenuItem(aSelectedFolderGuid, title);
     this._folderMenuList.selectedItem = defaultItem;
+    // Ensure the selectedGuid attribute is set correctly (the above line wouldn't
+    // necessary trigger a select event, so handle it manually, then add the
+    // listener).
+    this._onFolderListSelected();
 
-    // Set a selectedIndex attribute to show special icons
-    this._folderMenuList.setAttribute("selectedIndex",
-                                      this._folderMenuList.selectedIndex);
+    this._folderMenuList.addEventListener("select", this);
+    this._folderMenuListListenerAdded = true;
 
     // Hide the folders-separator if no folder is annotated as recently-used
     this._element("foldersSeparator").hidden = (menupopup.children.length <= 6);
     this._folderMenuList.disabled = this.readOnly;
   },
 
+  _onFolderListSelected() {
+    // Set a selectedGuid attribute to show special icons
+    let folderGuid = this.selectedFolderGuid;
+    if (folderGuid) {
+      this._folderMenuList.setAttribute("selectedGuid", folderGuid);
+    } else {
+      this._folderMenuList.removeAttribute("selectedGuid");
+    }
+  },
+
   QueryInterface:
   ChromeUtils.generateQI([Ci.nsINavBookmarkObserver]),
 
   _element(aID) {
     return document.getElementById("editBMPanel_" + aID);
   },
 
   uninitPanel(aHideCollapsibleElements) {
@@ -435,19 +448,25 @@ var gEditItemOverlay = {
       // Hide the tag selector if it was previously visible.
       var tagsSelectorRow = this._element("tagsSelectorRow");
       if (!tagsSelectorRow.collapsed)
         this.toggleTagsSelector().catch(Cu.reportError);
     }
 
     if (this._observersAdded) {
       PlacesUtils.bookmarks.removeObserver(this);
+      window.removeEventListener("unload", this);
       this._observersAdded = false;
     }
 
+    if (this._folderMenuListListenerAdded) {
+      this._folderMenuList.removeEventListener("select", this);
+      this._folderMenuListListenerAdded = false;
+    }
+
     this._setPaneInfo(null);
     this._firstEditedField = "";
   },
 
   get selectedFolderGuid() {
     return this._folderMenuList.selectedItem && this._folderMenuList.selectedItem.folderGuid;
   },
 
@@ -676,19 +695,16 @@ var gEditItemOverlay = {
   },
 
   async onFolderMenuListCommand(aEvent) {
     // Check for _paneInfo existing as the dialog may be closing but receiving
     // async updates from unresolved promises.
     if (!this._paneInfo) {
       return;
     }
-    // Set a selectedIndex attribute to show special icons
-    this._folderMenuList.setAttribute("selectedIndex",
-                                      this._folderMenuList.selectedIndex);
 
     if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") {
       // reset the selection back to where it was and expand the tree
       // (this menu-item is hidden when the tree is already visible
       let item = this._getFolderMenuItem(this._paneInfo.parentGuid,
                                          this._paneInfo.title);
       this._folderMenuList.selectedItem = item;
       // XXXmano HACK: setTimeout 100, otherwise focus goes back to the
@@ -865,16 +881,19 @@ var gEditItemOverlay = {
         if (item) {
           this.toggleItemCheckbox(item);
         }
       }
       break;
     case "unload":
       this.uninitPanel(false);
       break;
+    case "select":
+      this._onFolderListSelected();
+      break;
     }
   },
 
   toggleItemCheckbox(item) {
     // Update the tags field when items are checked/unchecked in the listbox
     let tags = this._getTagsArrayFromTagsInputField();
 
     let curTagIndex = tags.indexOf(item.label);
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -32,16 +32,17 @@ support-files =
 [browser_bookmarkProperties_addFolderDefaultButton.js]
 [browser_bookmarkProperties_addKeywordForThisSearch.js]
 skip-if = (verify && debug)
 [browser_bookmarkProperties_bookmarkAllTabs.js]
 skip-if = (verify && debug && (os == 'win' || os == 'mac'))
 [browser_bookmarkProperties_cancel.js]
 [browser_bookmarkProperties_editFolder.js]
 [browser_bookmarkProperties_editTagContainer.js]
+[browser_bookmarkProperties_folderSelection.js]
 [browser_bookmarkProperties_no_user_actions.js]
 [browser_bookmarkProperties_newFolder.js]
 [browser_bookmarkProperties_readOnlyRoot.js]
 [browser_bookmarkProperties_remember_folders.js]
 [browser_bookmarksProperties.js]
 skip-if = (verify && debug && (os == 'win' || os == 'mac'))
 [browser_check_correct_controllers.js]
 [browser_click_bookmarks_on_toolbar.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_folderSelection.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TEST_URL = "about:robots";
+let bookmarkPanel;
+let folders;
+let win;
+
+add_task(async function setup() {
+  await PlacesUtils.bookmarks.eraseEverything();
+
+  win = await BrowserTestUtils.openNewBrowserWindow();
+  await BrowserTestUtils.openNewForegroundTab({
+    gBrowser: win.gBrowser,
+    opening: TEST_URL,
+    waitForStateStop: true,
+  });
+
+  let oldTimeout = win.StarUI._autoCloseTimeout;
+  // Make the timeout something big, so it doesn't iteract badly with tests.
+  win.StarUI._autoCloseTimeout = 6000000;
+
+  bookmarkPanel = win.document.getElementById("editBookmarkPanel");
+  bookmarkPanel.setAttribute("animate", false);
+
+  registerCleanupFunction(async () => {
+    bookmarkPanel = null;
+    win.StarUI._autoCloseTimeout = oldTimeout;
+    // BrowserTestUtils.removeTab(tab);
+    await BrowserTestUtils.closeWindow(win);
+    win = null;
+    await PlacesUtils.bookmarks.eraseEverything();
+  });
+});
+
+add_task(async function test_selectChoose() {
+  await clickBookmarkStar(win);
+
+  // Open folder selector.
+  let menuList = win.document.getElementById("editBMPanel_folderMenuList");
+  let folderTreeRow = win.document.getElementById("editBMPanel_folderTreeRow");
+
+  Assert.equal(menuList.label, PlacesUtils.getString("OtherBookmarksFolderTitle"),
+    "Should have the other bookmarks folder selected by default");
+  Assert.equal(menuList.getAttribute("selectedGuid"), PlacesUtils.bookmarks.unfiledGuid,
+    "Should have the correct default guid selected");
+  Assert.equal(folderTreeRow.collapsed, true,
+    "Should have the folder tree collapsed");
+
+  let promisePopup = BrowserTestUtils.waitForEvent(menuList.menupopup, "popupshown");
+  EventUtils.synthesizeMouseAtCenter(menuList, {}, win);
+  await promisePopup;
+
+  // Click the choose item.
+  EventUtils.synthesizeMouseAtCenter(win.document.getElementById("editBMPanel_chooseFolderMenuItem"), {}, win);
+
+  await TestUtils.waitForCondition(() => !folderTreeRow.collapsed,
+    "Should show the folder tree");
+
+  Assert.equal(menuList.getAttribute("selectedGuid"), PlacesUtils.bookmarks.unfiledGuid,
+    "Should still have the correct selected guid");
+  Assert.equal(menuList.label, PlacesUtils.getString("OtherBookmarksFolderTitle"),
+    "Should have kept the same menu label");
+
+  await hideBookmarksPanel(win);
+});
+
+
+add_task(async function test_selectBookmarksMenu() {
+  await clickBookmarkStar(win);
+
+  // Open folder selector.
+  let menuList = win.document.getElementById("editBMPanel_folderMenuList");
+
+  let promisePopup = BrowserTestUtils.waitForEvent(menuList.menupopup, "popupshown");
+  EventUtils.synthesizeMouseAtCenter(menuList, {}, win);
+  await promisePopup;
+
+  // Click the choose item.
+  EventUtils.synthesizeMouseAtCenter(win.document.getElementById("editBMPanel_bmRootItem"), {}, win);
+
+  await TestUtils.waitForCondition(
+    () => menuList.getAttribute("selectedGuid") == PlacesUtils.bookmarks.menuGuid,
+    "Should select the menu folder item");
+
+  Assert.equal(menuList.label, PlacesUtils.getString("BookmarksMenuFolderTitle"),
+    "Should have updated the menu label");
+
+  await hideBookmarksPanel(win);
+});
--- a/browser/components/places/tests/browser/browser_bookmark_add_tags.js
+++ b/browser/components/places/tests/browser/browser_bookmark_add_tags.js
@@ -18,16 +18,30 @@ async function clickBookmarkStar() {
 }
 
 async function hideBookmarksPanel(callback) {
   let hiddenPromise = promisePopupHidden(bookmarkPanel);
   callback();
   await hiddenPromise;
 }
 
+add_task(function setup() {
+  let oldTimeout = StarUI._autoCloseTimeout;
+
+  bookmarkPanel.setAttribute("animate", false);
+
+  StarUI._autoCloseTimeout = 1000;
+
+  registerCleanupFunction(async () => {
+    StarUI._autoCloseTimeout = oldTimeout;
+    bookmarkPanel.removeAttribute("animate");
+    await PlacesUtils.bookmarks.eraseEverything();
+  });
+});
+
 add_task(async function test_add_bookmark_tags_from_bookmarkProperties() {
   const TEST_URL = "about:robots";
 
   let tab = await BrowserTestUtils.openNewForegroundTab({
     gBrowser,
     opening: TEST_URL,
     waitForStateStop: true,
   });
--- a/browser/components/places/tests/browser/head.js
+++ b/browser/components/places/tests/browser/head.js
@@ -399,21 +399,21 @@ function getToolbarNodeForItemGuid(itemG
     if (itemGuid === child._placesNode.bookmarkGuid) {
       return child;
     }
   }
   return null;
 }
 
 // Open the bookmarks Star UI by clicking the star button on the address bar.
-async function clickBookmarkStar() {
-  let shownPromise = promisePopupShown(document.getElementById("editBookmarkPanel"));
-  BookmarkingUI.star.click();
+async function clickBookmarkStar(win = window) {
+  let shownPromise = promisePopupShown(win.document.getElementById("editBookmarkPanel"));
+  win.BookmarkingUI.star.click();
   await shownPromise;
 }
 
 // Close the bookmarks Star UI by clicking the "Done" button.
-async function hideBookmarksPanel() {
-  let hiddenPromise = promisePopupHidden(document.getElementById("editBookmarkPanel"));
+async function hideBookmarksPanel(win = window) {
+  let hiddenPromise = promisePopupHidden(win.document.getElementById("editBookmarkPanel"));
   // Confirm and close the dialog.
-  document.getElementById("editBookmarkPanelDoneButton").click();
+  win.document.getElementById("editBookmarkPanelDoneButton").click();
   await hiddenPromise;
 }
--- a/browser/components/preferences/browserLanguages.js
+++ b/browser/components/preferences/browserLanguages.js
@@ -288,16 +288,21 @@ function compareItems(a, b) {
   return -1;
 }
 
 var gBrowserLanguagesDialog = {
   _availableLocales: null,
   _requestedLocales: null,
   requestedLocales: null,
 
+  get downloadEnabled() {
+    // Downloading langpacks isn't always supported, check the pref.
+    return Services.prefs.getBoolPref("intl.multilingual.downloadEnabled");
+  },
+
   beforeAccept() {
     this.requestedLocales = this.getRequestedLocales();
     return true;
   },
 
   async onLoad() {
     // Maintain the previously requested locales even if we cancel out.
     let {requesting, search} = window.arguments[0] || {};
@@ -347,16 +352,20 @@ var gBrowserLanguagesDialog = {
     if (search) {
       return this.loadLocalesFromAMO();
     }
 
     return undefined;
   },
 
   async loadLocalesFromAMO() {
+    if (!this.downloadEnabled) {
+      return;
+    }
+
     // Disable the dropdown while we hit the network.
     this._availableLocales.disableWithMessageId("browser-languages-searching");
 
     // Fetch the available langpacks from AMO.
     let availableLangpacks;
     try {
       availableLangpacks = await AddonRepository.getAvailableLangpacks();
     } catch (e) {
@@ -399,20 +408,22 @@ var gBrowserLanguagesDialog = {
   async loadLocalesFromInstalled(available) {
     let items;
     if (available.length > 0) {
       items = getLocaleDisplayInfo(available);
       items.push(await this.createInstalledLabel());
     } else {
       items = [];
     }
-    items.push({
-      label: await document.l10n.formatValue("browser-languages-search"),
-      value: "search",
-    });
+    if (this.downloadEnabled) {
+      items.push({
+        label: await document.l10n.formatValue("browser-languages-search"),
+        value: "search",
+      });
+    }
     this._availableLocales.setItems(items);
   },
 
   async availableLanguageSelected(item) {
     if (Services.locale.availableLocales.includes(item.value)) {
       this.requestLocalLanguage(item);
     } else if (this.availableLangpacks.has(item.value)) {
       await this.requestRemoteLanguage(item);
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -750,25 +750,28 @@ var gMainPane = {
     let fragment = document.createDocumentFragment();
     for (let {code, name} of locales) {
       let menuitem = document.createXULElement("menuitem");
       menuitem.setAttribute("value", code);
       menuitem.setAttribute("label", name);
       fragment.appendChild(menuitem);
     }
 
-    // Add an option to search for more languages.
-    let menuitem = document.createXULElement("menuitem");
-    menuitem.id = "defaultBrowserLanguageSearch";
-    menuitem.setAttribute(
-      "label", await document.l10n.formatValue("browser-languages-search"));
-    menuitem.addEventListener("command", () => {
-      gMainPane.showBrowserLanguages({search: true});
-    });
-    fragment.appendChild(menuitem);
+    // Add an option to search for more languages if downloading is supported.
+    if (Services.prefs.getBoolPref("intl.multilingual.downloadEnabled")) {
+      let menuitem = document.createXULElement("menuitem");
+      menuitem.id = "defaultBrowserLanguageSearch";
+      menuitem.setAttribute(
+        "label", await document.l10n.formatValue("browser-languages-search"));
+      menuitem.setAttribute("value", "search");
+      menuitem.addEventListener("command", () => {
+        gMainPane.showBrowserLanguages({search: true});
+      });
+      fragment.appendChild(menuitem);
+    }
 
     let menulist = document.getElementById("defaultBrowserLanguage");
     let menupopup = menulist.querySelector("menupopup");
     menupopup.textContent = "";
     menupopup.appendChild(fragment);
     menulist.value = requesting;
 
     document.getElementById("browserLanguagesBox").hidden = false;
--- a/browser/components/preferences/in-content/tests/browser_browser_languages_subdialog.js
+++ b/browser/components/preferences/in-content/tests/browser_browser_languages_subdialog.js
@@ -158,16 +158,17 @@ async function openDialog(doc, search = 
     requested: dialogDoc.getElementById("requestedLocales"),
   };
 }
 
 add_task(async function testReorderingBrowserLanguages() {
   await SpecialPowers.pushPrefEnv({
     set: [
       ["intl.multilingual.enabled", true],
+      ["intl.multilingual.downloadEnabled", true],
       ["intl.locale.requested", "pl,en-US"],
     ],
   });
 
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
 
   let doc = gBrowser.contentDocument;
   let messageBar = doc.getElementById("confirmBrowserLanguage");
@@ -214,16 +215,17 @@ add_task(async function testReorderingBr
 
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 add_task(async function testAddAndRemoveRequestedLanguages() {
   await SpecialPowers.pushPrefEnv({
     set: [
       ["intl.multilingual.enabled", true],
+      ["intl.multilingual.downloadEnabled", true],
       ["intl.locale.requested", "en-US"],
       ["extensions.langpacks.signatures.required", false],
     ],
   });
 
   let langpacks = await createTestLangpacks();
   let addons = await Promise.all(langpacks.map(async ([locale, file]) => {
     let install = await AddonTestUtils.promiseInstallFile(file);
@@ -287,16 +289,17 @@ add_task(async function testInstallFromA
   let langpacksFile = await createLanguageToolsFile();
   let langpacksUrl = Services.io.newFileURI(langpacksFile).spec;
   let dictionaryBrowseFile = await createDictionaryBrowseResults();
   let browseApiEndpoint = Services.io.newFileURI(dictionaryBrowseFile).spec;
 
   await SpecialPowers.pushPrefEnv({
     set: [
       ["intl.multilingual.enabled", true],
+      ["intl.multilingual.downloadEnabled", true],
       ["intl.locale.requested", "en-US"],
       ["extensions.getAddons.langpacks.url", langpacksUrl],
       ["extensions.langpacks.signatures.required", false],
       ["extensions.getAddons.get.url", browseApiEndpoint],
     ],
   });
 
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
@@ -355,8 +358,51 @@ add_task(async function testInstallFromA
 
   // Uninstall the langpack and dictionary.
   let installs = await AddonManager.getAddonsByTypes(["locale", "dictionary"]);
   is(installs.length, 2, "There is one langpack and one dictionary installed");
   await Promise.all(installs.map(item => item.uninstall()));
 
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
+
+let hasSearchOption = popup => Array.from(popup.children).some(el => el.value == "search");
+
+add_task(async function testDownloadEnabled() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["intl.multilingual.enabled", true],
+      ["intl.multilingual.downloadEnabled", true],
+    ],
+  });
+
+  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
+  let doc = gBrowser.contentDocument;
+
+  let defaultMenulist = doc.getElementById("defaultBrowserLanguage");
+  ok(hasSearchOption(defaultMenulist.firstChild), "There's a search option in the General pane");
+
+  let { available } = await openDialog(doc, false);
+  ok(hasSearchOption(available.firstChild), "There's a search option in the dialog");
+
+  BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+
+add_task(async function testDownloadDisabled() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["intl.multilingual.enabled", true],
+      ["intl.multilingual.downloadEnabled", false],
+    ],
+  });
+
+  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
+  let doc = gBrowser.contentDocument;
+
+  let defaultMenulist = doc.getElementById("defaultBrowserLanguage");
+  ok(!hasSearchOption(defaultMenulist.firstChild), "There's no search option in the General pane");
+
+  let { available } = await openDialog(doc, false);
+  ok(!hasSearchOption(available.firstChild), "There's no search option in the dialog");
+
+  BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -240,16 +240,20 @@ class UrlbarInput {
     return this.textbox.getAttribute("focused") == "true";
   }
 
   get goButton() {
     return this.document.getAnonymousElementByAttribute(this.textbox, "anonid",
       "urlbar-go-button");
   }
 
+  get textValue() {
+    return this.inputField.value;
+  }
+
   get value() {
     return this._untrimmedValue;
   }
 
   set value(val) {
     this._untrimmedValue = val;
 
     val = this.trimValue(val);
--- a/browser/themes/linux/places/editBookmark.css
+++ b/browser/themes/linux/places/editBookmark.css
@@ -59,22 +59,22 @@
   -moz-box-align: center;
   margin: 0 2px;
   min-width: 13px;
   min-height: 13px;
 }
 
 
 /* Bookmark panel dropdown menu items */
-#editBMPanel_folderMenuList[selectedIndex="0"],
+#editBMPanel_folderMenuList[selectedGuid="toolbar_____"],
 #editBMPanel_toolbarFolderItem {
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.svg") !important;
 }
 
-#editBMPanel_folderMenuList[selectedIndex="1"],
+#editBMPanel_folderMenuList[selectedGuid="menu________"],
 #editBMPanel_bmRootItem {
   list-style-image: url("chrome://browser/skin/places/bookmarksMenu.svg") !important;
 }
 
-#editBMPanel_folderMenuList[selectedIndex="2"],
+#editBMPanel_folderMenuList[selectedGuid="unfiled_____"],
 #editBMPanel_unfiledRootItem {
   list-style-image: url("chrome://browser/skin/places/unfiledBookmarks.svg") !important;
 }
--- a/browser/themes/osx/places/editBookmark.css
+++ b/browser/themes/osx/places/editBookmark.css
@@ -64,22 +64,22 @@
 
 #editBMPanel_tagsSelector > richlistitem[checked="true"] > image {
   background-image: url("chrome://global/skin/checkbox/cbox-check.gif");
 }
 
 
 /* ----- BOOKMARK PANEL DROPDOWN MENU ITEMS ----- */
 
-#editBMPanel_folderMenuList[selectedIndex="0"],
+#editBMPanel_folderMenuList[selectedGuid="toolbar_____"],
 #editBMPanel_toolbarFolderItem {
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.svg") !important;
 }
 
-#editBMPanel_folderMenuList[selectedIndex="1"],
+#editBMPanel_folderMenuList[selectedGuid="menu________"],
 #editBMPanel_bmRootItem {
   list-style-image: url("chrome://browser/skin/places/bookmarksMenu.svg") !important;
 }
 
-#editBMPanel_folderMenuList[selectedIndex="2"],
+#editBMPanel_folderMenuList[selectedGuid="unfiled_____"],
 #editBMPanel_unfiledRootItem {
   list-style-image: url("chrome://browser/skin/places/unfiledBookmarks.svg") !important;
 }
--- a/browser/themes/shared/controlcenter/connection.svg
+++ b/browser/themes/shared/controlcenter/connection.svg
@@ -1,7 +1,6 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="context-fill" fill-opacity="context-fill-opacity">
-  <path d="M12 0a6 6 0 0 0-6 6v5.29a6 6 0 0 0 12 0V6a6 6 0 0 0-6-6zm4 12.25A3.75 3.75 0 0 1 12.25 16h-.5A3.75 3.75 0 0 1 8 12.25v-6.5A3.75 3.75 0 0 1 11.75 2h.5A3.75 3.75 0 0 1 16 5.75z"/>
-  <rect x="3" y="10" width="18" height="13" rx="2" ry="2"/>
+  <path d="M19 10h-1V6A6 6 0 0 0 6 6v4H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2zM8 5.75A3.75 3.75 0 0 1 11.75 2h.5A3.75 3.75 0 0 1 16 5.75V10H8z"/>
 </svg>
--- a/browser/themes/windows/places/editBookmark.css
+++ b/browser/themes/windows/places/editBookmark.css
@@ -67,22 +67,22 @@
   min-width: 13px;
   min-height: 13px;
   background: -moz-Field no-repeat 50% 50%;
 }
 
 
 /* ::::: bookmark panel dropdown icons ::::: */
 
-#editBMPanel_folderMenuList[selectedIndex="0"],
+#editBMPanel_folderMenuList[selectedGuid="toolbar_____"],
 #editBMPanel_toolbarFolderItem {
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.svg") !important;
 }
 
-#editBMPanel_folderMenuList[selectedIndex="1"],
+#editBMPanel_folderMenuList[selectedGuid="menu________"],
 #editBMPanel_bmRootItem {
   list-style-image: url("chrome://browser/skin/places/bookmarksMenu.svg") !important;
 }
 
-#editBMPanel_folderMenuList[selectedIndex="2"],
+#editBMPanel_folderMenuList[selectedGuid="unfiled_____"],
 #editBMPanel_unfiledRootItem {
   list-style-image: url("chrome://browser/skin/places/unfiledBookmarks.svg") !important;
 }
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -278,16 +278,17 @@ devtools.jar:
     content/netmonitor/src/assets/styles/RequestList.css (netmonitor/src/assets/styles/RequestList.css)
     content/netmonitor/src/assets/styles/StatisticsPanel.css (netmonitor/src/assets/styles/StatisticsPanel.css)
     content/netmonitor/src/assets/styles/StatusBar.css (netmonitor/src/assets/styles/StatusBar.css)
     content/netmonitor/src/assets/styles/Toolbar.css (netmonitor/src/assets/styles/Toolbar.css)
     content/netmonitor/src/assets/styles/variables.css (netmonitor/src/assets/styles/variables.css)
     content/netmonitor/src/assets/icons/play.svg (netmonitor/src/assets/icons/play.svg)
     content/netmonitor/src/assets/icons/shield.svg (netmonitor/src/assets/icons/shield.svg)
     content/netmonitor/index.html (netmonitor/index.html)
+    content/netmonitor/src/assets/styles/StatusCode.css (netmonitor/src/assets/styles/StatusCode.css)
 
     # Application panel
     content/application/index.html (application/index.html)
 
     # Devtools-components
     skin/images/devtools-components/arrow.svg (themes/images/devtools-components/arrow.svg)
     skin/images/devtools-components/checkbox.svg (themes/images/devtools-components/checkbox.svg)
 
--- a/devtools/client/netmonitor/src/assets/styles/RequestList.css
+++ b/devtools/client/netmonitor/src/assets/styles/RequestList.css
@@ -175,49 +175,16 @@
 /* Status column */
 
 .requests-list-status {
   min-width: 70px;
    /* Don't ellipsize status codes */
   text-overflow: initial;
 }
 
-.requests-list-status-code {
-  margin-inline-start: 3px;
-  padding: 0 2px;
-  border-radius: 3px;
-  font-family: var(--monospace-font-family);
-  /* prevent status code from having full line height width from .request-list-item */
-  line-height: normal;
-}
-
-.status-code[data-code^="1"] {
-  background-color: var(--status-code-color-1xx);
-}
-
-.status-code[data-code^="2"] {
-  background-color: var(--status-code-color-2xx);
-}
-
-.status-code[data-code^="3"] {
-  background-color: var(--status-code-color-3xx);
-}
-
-.status-code[data-code^="4"] {
-  background-color: var(--status-code-color-4xx);
-}
-
-.status-code[data-code^="5"] {
-  background-color: var(--status-code-color-5xx);
-}
-
-.status-code:not([data-code^="3"]) {
-  color: var(--theme-body-background);
-}
-
 .requests-list-status-icon {
   background: #fff;
   height: 10px;
   width: 10px;
   margin-inline-start: 5px;
   margin-inline-end: 5px;
   border-radius: 10px;
   transition: box-shadow 0.5s ease-in-out;
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/assets/styles/StatusCode.css
@@ -0,0 +1,36 @@
+/* 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/. */
+
+.status-code {
+  margin-inline-start: 3px;
+  padding: 0 2px;
+  border-radius: 3px;
+  font-family: var(--monospace-font-family);
+  /* prevent status code from having full line height width from .request-list-item */
+  line-height: normal;
+}
+
+.status-code[data-code^="1"] {
+  background-color: var(--status-code-color-1xx);
+}
+
+.status-code[data-code^="2"] {
+  background-color: var(--status-code-color-2xx);
+}
+
+.status-code[data-code^="3"] {
+  background-color: var(--status-code-color-3xx);
+}
+
+.status-code[data-code^="4"] {
+  background-color: var(--status-code-color-4xx);
+}
+
+.status-code[data-code^="5"] {
+  background-color: var(--status-code-color-5xx);
+}
+
+.status-code:not([data-code^="3"]) {
+  color: var(--theme-body-background);
+}
--- a/devtools/client/netmonitor/src/assets/styles/netmonitor.css
+++ b/devtools/client/netmonitor/src/assets/styles/netmonitor.css
@@ -15,16 +15,17 @@
 
 /* Network panel components & styles */
 @import "chrome://devtools/content/netmonitor/src/assets/styles/variables.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/Toolbar.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/StatusBar.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/RequestList.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/NetworkDetailsPanel.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/StatisticsPanel.css";
+@import "chrome://devtools/content/netmonitor/src/assets/styles/StatusCode.css";
 
 /* General */
 
 * {
   box-sizing: border-box;
 }
 
 html,
--- a/devtools/client/responsive.html/components/UserAgentInput.js
+++ b/devtools/client/responsive.html/components/UserAgentInput.js
@@ -71,16 +71,17 @@ class UserAgentInput extends PureCompone
   }
 
   render() {
     return (
       dom.label({ id: "user-agent-label" },
         "UA:",
         dom.input({
           id: "user-agent-input",
+          class: "text-input",
           onChange: this.onChange,
           onKeyUp: this.onKeyUp,
           placeholder: getStr("responsive.customUserAgent"),
           type: "text",
           value: this.state.value,
         })
       )
     );
--- a/devtools/client/responsive.html/components/ViewportDimension.js
+++ b/devtools/client/responsive.html/components/ViewportDimension.js
@@ -154,34 +154,34 @@ class ViewportDimension extends PureComp
           "viewport-dimension" +
           (this.state.isEditing ? " editing" : "") +
           (!this.state.isWidthValid || !this.state.isHeightValid ? " invalid" : ""),
       },
       dom.input({
         ref: input => {
           this.widthInput = input;
         },
-        className: "viewport-dimension-input" +
+        className: "text-input viewport-dimension-input" +
                    (this.state.isWidthValid ? "" : " invalid"),
         size: 4,
         value: this.state.width,
         onBlur: this.onInputBlur,
         onChange: this.onInputChange,
         onFocus: this.onInputFocus,
         onKeyDown: this.onInputKeyDown,
         onKeyUp: this.onInputKeyUp,
       }),
       dom.span({
         className: "viewport-dimension-separator",
       }, "×"),
       dom.input({
         ref: input => {
           this.heightInput = input;
         },
-        className: "viewport-dimension-input" +
+        className: "text-input viewport-dimension-input" +
                    (this.state.isHeightValid ? "" : " invalid"),
         size: 4,
         value: this.state.height,
         onBlur: this.onInputBlur,
         onChange: this.onInputChange,
         onFocus: this.onInputFocus,
         onKeyDown: this.onInputKeyDown,
         onKeyUp: this.onInputKeyUp,
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -5,46 +5,63 @@
  * CSS Variables specific to the responsive design mode
  */
 
 :root {
   --rdm-box-shadow: 0 4px 4px 0 rgba(155, 155, 155, 0.26);
   --submit-button-active-background-color: rgba(0,0,0,0.12);
   --submit-button-active-color: var(--theme-body-color);
   --viewport-active-color: #3b3b3b;
+  --input-invalid-border-color: var(--red-60);
 }
 
 :root.theme-dark {
   --rdm-box-shadow: 0 4px 4px 0 rgba(105, 105, 105, 0.26);
   --submit-button-active-background-color: var(--theme-toolbar-hover-active);
   --submit-button-active-color: var(--theme-selection-color);
   --viewport-active-color: #fcfcfc;
+  --input-invalid-border-color: var(--red-50);
 }
 
 * {
   box-sizing: border-box;
 }
 
 :root,
 input,
 button {
   font-size: 12px;
 }
 
+input,
+button {
+  color: var(--theme-toolbar-color);
+  background-color: var(--theme-tab-toolbar-background);
+}
+
 html,
 body,
 #root {
   height: 100%;
   overflow: hidden;
 }
 
-.theme-dark body,
-.theme-dark button,
-.theme-dark input {
-  color: var(--theme-toolbar-color);
+.text-input {
+  border: 1px solid var(--theme-splitter-color);
+  padding: 2px;
+}
+
+.text-input:focus {
+  outline: none;
+  border-color: var(--theme-selection-background);
+  transition: all 0.2s ease-in-out;
+}
+
+.text-input.invalid {
+  border-color: var(--input-invalid-border-color);
 }
 
 #app {
   display: flex;
   flex-direction: column;
   width: 100%;
   height: 100%;
 }
@@ -261,35 +278,20 @@ body,
 
 .viewport-dimension {
   display: flex;
   align-items: center;
   margin: 1px;
 }
 
 .viewport-dimension-input {
-  border: 1px solid var(--theme-splitter-color);
-  outline: none;
   text-align: center;
   width: 3em;
 }
 
-.viewport-dimension-input:focus {
-  border: 1px solid var(--theme-selection-background);
-  transition: all 0.2s ease-in-out;
-}
-
-.viewport-dimension-input.invalid:focus {
-  border: 1px solid #d92215;
-}
-
-.theme-dark .viewport-dimension-input {
-  background-color: var(--theme-tab-toolbar-background);
-}
-
 .viewport-dimension-separator {
   -moz-user-select: none;
   padding: 0 0.3em;
 }
 
 /**
  * Device Modal
  */
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -234,16 +234,18 @@
   --blue-55: #0074e8;
   --blue-60: #0060df;
   --blue-70: #003eaa;
 
   --teal-60: #00c8d7;
   --teal-70: #008ea4;
 
   --red-20: #ffb3d2;
+  --red-50: #ff0039;
+  --red-60: #d70022;
   --red-70: #a4000f;
 
   --green-50: #30e60b;
   --green-60: #12bc00;
   --green-70: #058b00;
 
   --yellow-50: #ffe900;
   --yellow-60: #d7b600;
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -713,47 +713,16 @@ a.learn-more-link.webconsole-learn-more-
   font-style: inherit;
 }
 
 .webconsole-output-wrapper .message.network .status {
   color: var(--theme-highlight-blue);
   font-style: inherit;
 }
 
-.webconsole-output-wrapper .message.network .status .status-info .status-code {
-  padding: 0 2px;
-  border-radius: 3px;
-  text-decoration: none;
-  font-style: normal;
-}
-
-.webconsole-output-wrapper .message.network .status .status-info .status-code:not([data-code^="3"]) {
-  color: var(--theme-body-background);
-}
-
-.status-code[data-code^="1"] {
-  background-color: var(--status-code-color-1xx);
-}
-
-.status-code[data-code^="2"] {
-  background-color: var(--status-code-color-2xx);
-}
-
-.status-code[data-code^="3"] {
-  background-color: var(--status-code-color-3xx);
-}
-
-.status-code[data-code^="4"] {
-  background-color: var(--status-code-color-4xx);
-}
-
-.status-code[data-code^="5"] {
-  background-color: var(--status-code-color-5xx);
-}
-
 .network.message .network-info {
   display: none;
   margin-block-start: 6px;
   margin-block-end: 2px;
   border: solid 1px var(--theme-splitter-color);
 }
 
 .network.message.open .network-info {
--- a/devtools/client/webconsole/index.html
+++ b/devtools/client/webconsole/index.html
@@ -15,16 +15,17 @@
     <link rel="stylesheet" href="chrome://devtools/skin/webconsole.css"/>
     <link rel="stylesheet" href="chrome://devtools/skin/components-frame.css"/>
     <link rel="stylesheet" href="resource://devtools/client/shared/components/reps/reps.css"/>
     <link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/Tabs.css"/>
     <link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/TabBar.css"/>
     <link rel="stylesheet" href="resource://devtools/client/shared/components/NotificationBox.css"/>
     <link rel="stylesheet" href="chrome://devtools/content/netmonitor/src/assets/styles/httpi.css"/>
     <link rel="stylesheet" href="resource://devtools/client/webconsole/components/ReverseSearchInput.css"/>
+    <link rel="stylesheet" href="chrome://devtools/content/netmonitor/src/assets/styles/StatusCode.css"/>
 
     <script src="chrome://devtools/content/shared/theme-switching.js"></script>
     <script type="application/javascript"
             src="resource://devtools/client/webconsole/main.js"></script>
   </head>
   <body class="theme-sidebar" role="application">
     <main id="app-wrapper" class="theme-body" role="document" aria-live="polite">
     </main>
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_object_inspector_local_session_storage.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_object_inspector_local_session_storage.js
@@ -60,29 +60,34 @@ async function checkValues(oi, storageTy
   entriesNode.querySelector(".arrow").click();
   await onMapOiMutation;
 
   nodes = oi.querySelectorAll(".node");
   // There are now 7 nodes, the 5 original ones, and the 2 entries.
   is(nodes.length, 7, "There is the expected number of nodes in the tree");
 
   const title = nodes[0].querySelector(".objectTitle").textContent;
+  const name1 = nodes[1].querySelector(".object-label").textContent;
+  const value1 = nodes[1].querySelector(".objectBox").textContent;
+
   const length = [...nodes[2].querySelectorAll(".object-label,.objectBox")]
                              .map(node => node.textContent);
   const key2 = [...nodes[4].querySelectorAll(".object-label,.nodeName,.objectBox-string")]
                            .map(node => node.textContent);
   const key = [...nodes[5].querySelectorAll(".object-label,.nodeName,.objectBox-string")]
                           .map(node => node.textContent);
 
   is(title, "Storage", `${storageType} object has the expected title`);
   is(length[0], "length", `${storageType} length property name is correct`);
   is(length[1], "2", `${storageType} length property value is correct`);
   is(key2[0], "0", `1st entry of ${storageType} entry has the correct index`);
   is(key2[1], "key2", `1st entry of ${storageType} entry has the correct key`);
 
   const firstValue = storageType === "localStorage" ? `"value2"` : `"value4"`;
+  is(name1, "key2", "Name of short descriptor is correct");
+  is(value1, firstValue, "Value of short descriptor is correct");
   is(key2[2], firstValue, `1st entry of ${storageType} entry has the correct value`);
   is(key[0], "1", `2nd entry of ${storageType} entry has the correct index`);
   is(key[1], "key", `2nd entry of ${storageType} entry has the correct key`);
 
   const secondValue = storageType === "localStorage" ? `"value1"` : `"value3"`;
   is(key[2], secondValue, `2nd entry of ${storageType} entry has the correct value`);
 }
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -640,22 +640,17 @@ const proto = {
         value: e.name,
       };
     }
 
     if (isStorage(this.obj)) {
       if (name === "length") {
         return undefined;
       }
-      return {
-        configurable: true,
-        writable: true,
-        enumerable: true,
-        value: name,
-      };
+      return desc;
     }
 
     if (!desc || onlyEnumerable && !desc.enumerable) {
       return undefined;
     }
 
     const retval = {
       configurable: desc.configurable,
--- a/dom/cache/test/mochitest/test_cache_worker_gc.html
+++ b/dom/cache/test/mochitest/test_cache_worker_gc.html
@@ -24,23 +24,24 @@ function startWorker() {
 }
 
 function gc() {
   return new Promise(resolve => {
     SpecialPowers.exactGC(resolve);
   });
 }
 
-var weakWorker;
+SimpleTest.waitForExplicitFinish();
 
-SimpleTest.waitForExplicitFinish();
-startWorker().then(w => {
-  weakWorker = SpecialPowers.Cu.getWeakReference(w);
+async function test() {
+  let w = await startWorker();
+  let weakWorker = SpecialPowers.Cu.getWeakReference(w);
   ok(weakWorker, 'worker supports weak reference');
-}).then(_ => {
-  return gc();
-}).then(_ => {
+  w = null;
+  await gc();
   ok(!weakWorker.get(), 'worker weak reference should be garbage collected');
   SimpleTest.finish();
-});
+}
+
+test();
 </script>
 </body>
 </html>
--- a/dom/chrome-webidl/Flex.webidl
+++ b/dom/chrome-webidl/Flex.webidl
@@ -34,20 +34,27 @@ interface Flex
   /**
    * The physical direction in which successive flex lines are placed
    * in this flex container (if it is or were multi-line).
    */
   readonly attribute FlexPhysicalDirection crossAxisDirection;
 };
 
 /**
- * Lines with items that have been shrunk are shrinking; with items
- * that have grown are growing, and all others are unchanged.
+ * This indicates which flex factor (flex-grow vs. flex-shrink) the
+ * flex layout algorithm uses internally when resolving flexible sizes
+ * in a given flex line, per flexbox spec section 9.7 step 1. Note that
+ * this value doesn't necessarily mean that any items on this line
+ * are *actually* growing (or shrinking).  This simply indicates what
+ * the layout algorithm "wants" to do, based on the free space -- 
+ * and items will stretch from their flex base size in the corresponding
+ * direction, if permitted by their min/max constraints and their
+ * corresponding flex factor.
  */
-enum FlexLineGrowthState { "unchanged", "shrinking", "growing" };
+enum FlexLineGrowthState { "shrinking", "growing" };
 
 [ChromeOnly]
 interface FlexLineValues
 {
   readonly attribute FlexLineGrowthState growthState;
   readonly attribute double crossStart;
   readonly attribute double crossSize;
 
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -152,17 +152,17 @@ GetAlgorithmName(JSContext* aCx, const O
     if (!alg.Init(aCx, value)) {
       return NS_ERROR_DOM_SYNTAX_ERR;
     }
 
     aName = alg.mName;
   }
 
   if (!NormalizeToken(aName, aName)) {
-    return NS_ERROR_DOM_SYNTAX_ERR;
+    return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
   }
 
   return NS_OK;
 }
 
 template<class T, class OOS>
 static nsresult
 Coerce(JSContext* aCx, T& aTarget, const OOS& aAlgorithm)
@@ -1563,19 +1563,18 @@ public:
       }
 
       // Import the key material
       rv = mKeyData.FromJwkBase64(mJwk.mK.Value());
       if (NS_FAILED(rv)) {
         return NS_ERROR_DOM_DATA_ERR;
       }
     }
-
     // Check that we have valid key data.
-    if (mKeyData.Length() == 0) {
+    if (mKeyData.Length() == 0 && !mAlgName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2)) {
       return NS_ERROR_DOM_DATA_ERR;
     }
 
     // Construct an appropriate KeyAlorithm,
     // and verify that usages are appropriate
     uint32_t length = 8 * mKeyData.Length(); // bytes to bits
     if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
         mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
@@ -2216,17 +2215,16 @@ public:
     mKey = new CryptoKey(aGlobal);
     mKey->SetExtractable(aExtractable);
     mKey->SetType(CryptoKey::SECRET);
 
     // Extract algorithm name
     nsString algName;
     mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
     if (NS_FAILED(mEarlyRv)) {
-      mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
       return;
     }
 
     // Construct an appropriate KeyAlorithm
     uint32_t allowedUsages = 0;
     if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
         algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
         algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
@@ -2245,17 +2243,16 @@ public:
       if (NS_FAILED(mEarlyRv)) {
         mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
         return;
       }
 
       nsString hashName;
       mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
       if (NS_FAILED(mEarlyRv)) {
-        mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
         return;
       }
 
       if (params.mLength.WasPassed()) {
         mLength = params.mLength.Value();
       } else {
         mLength = MapHashAlgorithmNameToBlockSize(hashName);
       }
@@ -2346,17 +2343,16 @@ GenerateAsymmetricKeyTask::GenerateAsymm
 
   // Create an empty key pair and set easy attributes
   mKeyPair->mPrivateKey = new CryptoKey(aGlobal);
   mKeyPair->mPublicKey = new CryptoKey(aGlobal);
 
   // Extract algorithm name
   mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, mAlgName);
   if (NS_FAILED(mEarlyRv)) {
-    mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
     return;
   }
 
   // Construct an appropriate KeyAlorithm
   uint32_t privateAllowedUsages = 0, publicAllowedUsages = 0;
   if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
       mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
       mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
@@ -2369,17 +2365,16 @@ GenerateAsymmetricKeyTask::GenerateAsymm
 
     // Pull relevant info
     uint32_t modulusLength = params.mModulusLength;
     CryptoBuffer publicExponent;
     ATTEMPT_BUFFER_INIT(publicExponent, params.mPublicExponent);
     nsString hashName;
     mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
     if (NS_FAILED(mEarlyRv)) {
-      mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
       return;
     }
 
     // Create algorithm
     if (!mKeyPair->mPublicKey.get()->Algorithm().MakeRsa(mAlgName,
                                                          modulusLength,
                                                          publicExponent,
                                                          hashName)) {
@@ -2752,32 +2747,26 @@ public:
   }
 
   void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey,
             uint32_t aLength)
   {
     Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_PBKDF2);
     CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_PBKDF2);
 
-    // Check that we got a symmetric key
-    if (mSymKey.Length() == 0) {
-      mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
-      return;
-    }
-
     RootedDictionary<Pbkdf2Params> params(aCx);
     mEarlyRv = Coerce(aCx, params, aAlgorithm);
     if (NS_FAILED(mEarlyRv)) {
       mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
       return;
     }
 
     // length must be a multiple of 8 bigger than zero.
     if (aLength == 0 || aLength % 8) {
-      mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+      mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
       return;
     }
 
     // Extract the hash algorithm.
     nsString hashName;
     mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
     if (NS_FAILED(mEarlyRv)) {
       return;
--- a/dom/crypto/test/test-vectors.js
+++ b/dom/crypto/test/test-vectors.js
@@ -654,16 +654,26 @@ tv = {
       "9c13a23bc58a52be8bb4fa1a2cbdff01747265736f7269745f64625f7265636f72645f6964"),
     length: 32 * 8,
     iterations: 1,
 
     derived: util.hex2abv(
       "ef29dd382fa66a83a95be7ccfb71f1ccfee494977855a4c260d90c2f8c91e062")
   },
 
+  pbkdf2_sha256_no_pwd: {
+    password: new Uint8Array(),
+    salt: new TextEncoder("utf-8").encode("saltSALTsaltSALTsaltSALTsaltSALTsalt"),
+    length: 32 * 8,
+    iterations: 1,
+
+    derived: util.hex2abv(
+      "1635fa0f0542cc84f51207ff6cad5284aee3b0264faa55868eca95a7efd2335c")
+  },
+
   broken_pkcs8: {
     // A DH key with parameters p and g, and a private value.
     // This currently fails the key import due to the missing public value.
     // <https://stackoverflow.com/questions/6032675/diffie-hellman-test-vectors>
     dh: util.hex2abv(
       "308201340201003082011506072a8648ce3e02013082010802818100da3a8085" +
       "d372437805de95b88b675122f575df976610c6a844de99f1df82a06848bf7a42" +
       "f18895c97402e81118e01a00d0855d51922f434c022350861d58ddf60d65bc69" +
--- a/dom/crypto/test/test_WebCrypto_PBKDF2.html
+++ b/dom/crypto/test/test_WebCrypto_PBKDF2.html
@@ -230,16 +230,45 @@ TestArray.addTest(
     crypto.subtle.importKey("raw", key, alg, false, ["deriveBits"])
       .then( doDerive, fail )
       .then( memcmp_complete(that, tv.pbkdf2_sha256.derived), fail );
   }
 );
 
 // -----------------------------------------------------------------------------
 TestArray.addTest(
+  "Import raw PBKDF2 zero-length key and derive bits using HMAC-SHA-256",
+  function() {
+    var that = this;
+    var alg = "PBKDF2";
+    var key = tv.pbkdf2_sha256_no_pwd.password;
+
+    function doDerive(x) {
+      if (!hasKeyFields(x)) {
+        throw "Invalid key; missing field(s)";
+      }
+
+      var alg = {
+        name: "PBKDF2",
+        hash: "SHA-256",
+        salt: tv.pbkdf2_sha256_no_pwd.salt,
+        iterations: tv.pbkdf2_sha256_no_pwd.iterations
+      };
+      return crypto.subtle.deriveBits(alg, x, tv.pbkdf2_sha256_no_pwd.length);
+    }
+    function fail(x) { console.log("failing"); error(that)(x); }
+
+    crypto.subtle.importKey("raw", key, alg, false, ["deriveBits"])
+      .then( doDerive, fail )
+      .then( memcmp_complete(that, tv.pbkdf2_sha256_no_pwd.derived), fail );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
   "Import raw PBKDF2 key and derive bits using HMAC-SHA-256 with zero-length salt",
   function() {
     var that = this;
     var importAlg = { name: "PBKDF2", hash: "SHA-256" };
     var key = tv.pbkdf2_sha256_no_salt.password;
 
     function doDerive(x) {
       if (!hasKeyFields(x)) {
--- a/dom/flex/FlexLineValues.cpp
+++ b/dom/flex/FlexLineValues.cpp
@@ -25,28 +25,17 @@ FlexLineValues::FlexLineValues(Flex* aPa
                                const ComputedFlexLineInfo* aLine)
   : mParent(aParent)
 {
   MOZ_ASSERT(aLine,
     "Should never be instantiated with a null ComputedFlexLineInfo.");
 
   // Eagerly copy values from aLine, because we're not
   // going to keep it around.
-  switch (aLine->mGrowthState) {
-    case ComputedFlexLineInfo::GrowthState::SHRINKING:
-      mGrowthState = FlexLineGrowthState::Shrinking;
-      break;
-
-    case ComputedFlexLineInfo::GrowthState::GROWING:
-      mGrowthState = FlexLineGrowthState::Growing;
-      break;
-
-    default:
-      mGrowthState = FlexLineGrowthState::Unchanged;
-  };
+  mGrowthState = aLine->mGrowthState;
 
   // Convert all the app unit values into css pixels.
   mCrossStart = nsPresContext::AppUnitsToDoubleCSSPixels(
     aLine->mCrossStart);
   mCrossSize = nsPresContext::AppUnitsToDoubleCSSPixels(
     aLine->mCrossSize);
   mFirstBaselineOffset = nsPresContext::AppUnitsToDoubleCSSPixels(
     aLine->mFirstBaselineOffset);
--- a/dom/flex/test/chrome/test_flex_lines.html
+++ b/dom/flex/test/chrome/test_flex_lines.html
@@ -92,31 +92,31 @@ function testLineMatchesExpectedValues(l
 }
 
 function runTests() {
   let expectedValues = [
     // items don't fill container, no grow, shrink, or min-width
     [{ crossStart: 0,
        crossSize: 42,
        itemCount: 2,
-       growthState: "unchanged" }],
+       growthState: "growing" }],
     [{ crossStart: 0,
        crossSize: 42,
        itemCount: 3,
-       growthState: "unchanged" }],
+       growthState: "growing" }],
 
     // items don't fill container, no grow, shrink, or min-width, with wrap and align-content:center -->
     [{ crossStart: 13.5,
        crossSize: 15,
        itemCount: 2,
-       growthState: "unchanged" }],
+       growthState: "growing" }],
     [{ crossStart: 13.5,
        crossSize: 15,
        itemCount: 3,
-       growthState: "unchanged" }],
+       growthState: "growing" }],
 
     // items don't fill container, with grow
     [{ crossStart: 0,
        crossSize: 42,
        itemCount: 3,
        growthState: "growing" }],
     [{ crossStart: 0,
        crossSize: 42,
@@ -152,33 +152,33 @@ function runTests() {
     // items overfill container, with shrink and sometimes with wrap
     [{ crossStart: 0,
        crossSize: 42,
        itemCount: 3,
        growthState: "shrinking" }],
     [{ crossStart: 0,
        crossSize: 21,
        itemCount: 2,
-       growthState: "unchanged" },
+       growthState: "growing" },
      { crossStart: 21,
        crossSize: 21,
        itemCount: 1,
-       growthState: "unchanged" }],
+       growthState: "growing" }],
     [{ crossStart: 0,
        crossSize: 42,
        itemCount: 4,
        growthState: "shrinking" }],
     [{ crossStart: 0,
        crossSize: 21,
        itemCount: 2,
-       growthState: "unchanged" },
+       growthState: "growing" },
      { crossStart: 21,
        crossSize: 21,
        itemCount: 2,
-       growthState: "unchanged" }],
+       growthState: "growing" }],
 
     // items overfill container, with wrap and different types of align-content
     [{ crossStart: 0,
        crossSize: 26,
        itemCount: 3 },
      { crossStart: 26,
        crossSize: 16,
        itemCount: 1 }],
--- a/gfx/webrender/res/cs_clip_image.glsl
+++ b/gfx/webrender/res/cs_clip_image.glsl
@@ -1,70 +1,71 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include shared,clip_shared
 
-varying vec3 vLocalPos;
-varying vec3 vClipMaskImageUv;
+varying vec2 vLocalPos;
+varying vec2 vClipMaskImageUv;
 
 flat varying vec4 vClipMaskUvRect;
 flat varying vec4 vClipMaskUvInnerRect;
 flat varying float vLayer;
 
 #ifdef WR_VERTEX_SHADER
 struct ImageMaskData {
-    RectWithSize local_rect;
+    RectWithSize local_mask_rect;
+    RectWithSize local_tile_rect;
 };
 
 ImageMaskData fetch_mask_data(ivec2 address) {
-    vec4 data = fetch_from_gpu_cache_1_direct(address);
-    RectWithSize local_rect = RectWithSize(data.xy, data.zw);
-    ImageMaskData mask_data = ImageMaskData(local_rect);
+    vec4 data[2] = fetch_from_gpu_cache_2_direct(address);
+    RectWithSize mask_rect = RectWithSize(data[0].xy, data[0].zw);
+    RectWithSize tile_rect = RectWithSize(data[1].xy, data[1].zw);
+    ImageMaskData mask_data = ImageMaskData(mask_rect, tile_rect);
     return mask_data;
 }
 
 void main(void) {
     ClipMaskInstance cmi = fetch_clip_item();
     ClipArea area = fetch_clip_area(cmi.render_task_address);
     Transform clip_transform = fetch_transform(cmi.clip_transform_id);
     Transform prim_transform = fetch_transform(cmi.prim_transform_id);
     ImageMaskData mask = fetch_mask_data(cmi.clip_data_address);
-    RectWithSize local_rect = mask.local_rect;
+    RectWithSize local_rect = mask.local_mask_rect;
     ImageResource res = fetch_image_resource_direct(cmi.resource_address);
 
     ClipVertexInfo vi = write_clip_tile_vertex(
         local_rect,
         prim_transform,
         clip_transform,
         area
     );
-    vLocalPos = vi.local_pos;
+    vLocalPos = vi.local_pos.xy / vi.local_pos.z;
     vLayer = res.layer;
-
-    vec2 local_pos = vLocalPos.xy / vLocalPos.z;
-
-    vClipMaskImageUv = vec3((local_pos - local_rect.p0) / local_rect.size, 0.0);
+    vClipMaskImageUv = (vLocalPos - mask.local_tile_rect.p0) / mask.local_tile_rect.size;
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vClipMaskUvRect = vec4(res.uv_rect.p0, res.uv_rect.p1 - res.uv_rect.p0) / texture_size.xyxy;
     // applying a half-texel offset to the UV boundaries to prevent linear samples from the outside
     vec4 inner_rect = vec4(res.uv_rect.p0, res.uv_rect.p1);
     vClipMaskUvInnerRect = (inner_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy;
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 void main(void) {
-    vec2 local_pos = vLocalPos.xy / vLocalPos.z;
+    float alpha = init_transform_fs(vLocalPos);
 
-    float alpha = init_transform_fs(local_pos);
+    // TODO: Handle repeating masks?
+    vec2 clamped_mask_uv = clamp(vClipMaskImageUv, vec2(0.0, 0.0), vec2(1.0, 1.0));
 
-    bool repeat_mask = false; //TODO
-    vec2 clamped_mask_uv = repeat_mask ? fract(vClipMaskImageUv.xy) :
-        clamp(vClipMaskImageUv.xy, vec2(0.0, 0.0), vec2(1.0, 1.0));
+    // Ensure we don't draw outside of our tile.
+    // FIXME(emilio): Can we do this earlier?
+    if (clamped_mask_uv != vClipMaskImageUv)
+        discard;
+
     vec2 source_uv = clamp(clamped_mask_uv * vClipMaskUvRect.zw + vClipMaskUvRect.xy,
         vClipMaskUvInnerRect.xy, vClipMaskUvInnerRect.zw);
     float clip_alpha = texture(sColor0, vec3(source_uv, vLayer)).r; //careful: texture has type A8
-
     oFragColor = vec4(alpha * clip_alpha, 1.0, 1.0, 1.0);
 }
 #endif
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -1,40 +1,38 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, ClipMode, DeviceIntRect, DeviceIntSize, LineStyle};
 use api::{DeviceUintRect, DeviceUintPoint, ExternalImageType, FilterOp, ImageRendering};
-use api::{YuvColorSpace, YuvFormat, WorldPixel, WorldRect, ColorDepth};
+use api::{YuvColorSpace, YuvFormat, WorldRect, ColorDepth};
 use clip::{ClipDataStore, ClipNodeFlags, ClipNodeRange, ClipItem, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
-use euclid::vec3;
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheAddress};
-use gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders};
+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::{PictureCompositeMode, PicturePrimitive, PictureSurface};
-use plane_split::{BspSplitter, Clipper, Polygon, Splitter};
+use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureSurface};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
-use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveIndex};
+use prim_store::{EdgeAaSegmentMask, ImageSource};
 use prim_store::{VisibleGradientTile, PrimitiveInstance};
 use prim_store::{BrushSegment, BorderSource, Primitive, PrimitiveDetails};
 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};
-use util::{MatrixHelpers, TransformedRectKind};
+use util::{TransformedRectKind};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff);
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -124,36 +122,36 @@ impl BatchKey {
 fn textures_compatible(t1: TextureSource, t2: TextureSource) -> bool {
     t1 == TextureSource::Invalid || t2 == TextureSource::Invalid || t1 == t2
 }
 
 pub struct AlphaBatchList {
     pub batches: Vec<PrimitiveBatch>,
     pub item_rects: Vec<Vec<WorldRect>>,
     current_batch_index: usize,
-    current_prim_index: PrimitiveIndex,
+    current_z_id: ZBufferId,
 }
 
 impl AlphaBatchList {
     fn new() -> Self {
         AlphaBatchList {
             batches: Vec::new(),
             item_rects: Vec::new(),
-            current_prim_index: PrimitiveIndex(usize::MAX),
+            current_z_id: ZBufferId::invalid(),
             current_batch_index: usize::MAX,
         }
     }
 
     pub fn set_params_and_get_batch(
         &mut self,
         key: BatchKey,
         bounding_rect: &WorldRect,
-        prim_index: PrimitiveIndex,
+        z_id: ZBufferId,
     ) -> &mut Vec<PrimitiveInstanceData> {
-        if prim_index != self.current_prim_index ||
+        if z_id != self.current_z_id ||
            self.current_batch_index == usize::MAX ||
            !self.batches[self.current_batch_index].key.is_compatible_with(&key) {
             let mut selected_batch_index = None;
 
             match key.blend_mode {
                 BlendMode::SubpixelWithBgColor => {
                     'outer_multipass: for (batch_index, batch) in self.batches.iter().enumerate().rev().take(10) {
                         // Some subpixel batches are drawn in two passes. Because of this, we need
@@ -196,17 +194,17 @@ impl AlphaBatchList {
                 let new_batch = PrimitiveBatch::new(key);
                 selected_batch_index = Some(self.batches.len());
                 self.batches.push(new_batch);
                 self.item_rects.push(Vec::new());
             }
 
             self.current_batch_index = selected_batch_index.unwrap();
             self.item_rects[self.current_batch_index].push(*bounding_rect);
-            self.current_prim_index = prim_index;
+            self.current_z_id = z_id;
         }
 
         &mut self.batches[self.current_batch_index].instances
     }
 }
 
 pub struct OpaqueBatchList {
     pub pixel_area_threshold_for_new_batch: f32,
@@ -294,57 +292,57 @@ impl BatchList {
             opaque_batch_list: OpaqueBatchList::new(batch_area_threshold),
         }
     }
 
     pub fn push_single_instance(
         &mut self,
         key: BatchKey,
         bounding_rect: &WorldRect,
-        prim_index: PrimitiveIndex,
+        z_id: ZBufferId,
         instance: PrimitiveInstanceData,
     ) {
         match key.blend_mode {
             BlendMode::None => {
                 self.opaque_batch_list
                     .set_params_and_get_batch(key, bounding_rect)
                     .push(instance);
             }
             BlendMode::Alpha |
             BlendMode::PremultipliedAlpha |
             BlendMode::PremultipliedDestOut |
             BlendMode::SubpixelConstantTextColor(..) |
             BlendMode::SubpixelWithBgColor |
             BlendMode::SubpixelDualSource => {
                 self.alpha_batch_list
-                    .set_params_and_get_batch(key, bounding_rect, prim_index)
+                    .set_params_and_get_batch(key, bounding_rect, z_id)
                     .push(instance);
             }
         }
     }
 
     pub fn set_params_and_get_batch(
         &mut self,
         key: BatchKey,
         bounding_rect: &WorldRect,
-        prim_index: PrimitiveIndex,
+        z_id: ZBufferId,
     ) -> &mut Vec<PrimitiveInstanceData> {
         match key.blend_mode {
             BlendMode::None => {
                 self.opaque_batch_list
                     .set_params_and_get_batch(key, bounding_rect)
             }
             BlendMode::Alpha |
             BlendMode::PremultipliedAlpha |
             BlendMode::PremultipliedDestOut |
             BlendMode::SubpixelConstantTextColor(..) |
             BlendMode::SubpixelWithBgColor |
             BlendMode::SubpixelDualSource => {
                 self.alpha_batch_list
-                    .set_params_and_get_batch(key, bounding_rect, prim_index)
+                    .set_params_and_get_batch(key, bounding_rect, z_id)
             }
         }
     }
 
     fn finalize(&mut self) {
         self.opaque_batch_list.finalize()
     }
 }
@@ -472,133 +470,34 @@ impl AlphaBatchBuilder {
         task_id: RenderTaskId,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
+        z_generator: &mut ZBufferIdGenerator,
     ) {
         let task_address = render_tasks.get_task_address(task_id);
 
-        // Even though most of the time a splitter isn't used or needed,
-        // they are cheap to construct so we will always pass one down.
-        let mut splitter = BspSplitter::new();
-
         // Add each run in this picture to the batch.
-        for (plane_split_anchor, prim_instance) in pic.prim_instances.iter().enumerate() {
+        for prim_instance in &pic.prim_instances {
             self.add_prim_to_batch(
                 prim_instance,
                 ctx,
                 gpu_cache,
                 render_tasks,
                 task_id,
                 task_address,
                 deferred_resolves,
-                &mut splitter,
                 prim_headers,
                 transforms,
                 root_spatial_node_index,
-                plane_split_anchor,
-            );
-        }
-
-        // Flush the accumulated plane splits onto the task tree.
-        // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
-        for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
-            let prim_instance = &pic.prim_instances[poly.anchor];
-            let prim_index = prim_instance.prim_index;
-            let prim = &ctx.prim_store.primitives[prim_index.0];
-            if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_index) {
-                println!("\t\tsplit polygon {:?}", poly.points);
-            }
-            let transform = transforms.get_world_transform(prim_instance.spatial_node_index).inverse().unwrap();
-            let transform_id = transforms.get_id(
-                prim_instance.spatial_node_index,
-                ROOT_SPATIAL_NODE_INDEX,
-                ctx.clip_scroll_tree,
-            );
-
-            let clip_task_address = prim_instance
-                .clip_task_id
-                .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
-
-            let prim_header = PrimitiveHeader {
-                local_rect: prim.local_rect,
-                local_clip_rect: prim_instance.combined_local_clip_rect,
-                task_address,
-                specific_prim_address: GpuCacheAddress::invalid(),
-                clip_task_address,
-                transform_id,
-            };
-
-            let pic_index = match prim.details {
-                PrimitiveDetails::Brush(ref brush) => {
-                    match brush.kind {
-                        BrushKind::Picture { pic_index, .. } => pic_index,
-                        _ => unreachable!(),
-                    }
-                }
-                PrimitiveDetails::TextRun(..) => {
-                    unreachable!();
-                }
-            };
-            let pic = &ctx.prim_store.pictures[pic_index.0];
-
-            let (uv_rect_address, _) = pic
-                .raster_config
-                .as_ref()
-                .expect("BUG: no raster config")
-                .surface
-                .as_ref()
-                .expect("BUG: no surface")
-                .resolve(
-                    render_tasks,
-                    ctx.resource_cache,
-                    gpu_cache,
-                );
-
-            let prim_header_index = prim_headers.push(&prim_header, [
-                uv_rect_address.as_int(),
-                0,
-                0,
-            ]);
-
-            let mut local_points = [
-                transform.transform_point3d(&poly.points[0].cast()).unwrap(),
-                transform.transform_point3d(&poly.points[1].cast()).unwrap(),
-                transform.transform_point3d(&poly.points[2].cast()).unwrap(),
-                transform.transform_point3d(&poly.points[3].cast()).unwrap(),
-            ];
-            let gpu_blocks = [
-                [local_points[0].x, local_points[0].y, local_points[1].x, local_points[1].y].into(),
-                [local_points[2].x, local_points[2].y, local_points[3].x, local_points[3].y].into(),
-            ];
-
-            let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
-            let key = BatchKey::new(
-                BatchKind::SplitComposite,
-                BlendMode::PremultipliedAlpha,
-                BatchTextures::no_texture(),
-            );
-
-            let gpu_address = gpu_cache.get_address(&gpu_handle);
-
-            let instance = SplitCompositeInstance::new(
-                prim_header_index,
-                gpu_address,
-                prim_headers.z_generator.next(),
-            );
-
-            self.batch_list.push_single_instance(
-                key,
-                &prim_instance.clipped_world_rect.as_ref().expect("bug"),
-                prim_instance.prim_index,
-                PrimitiveInstanceData::from(instance),
+                z_generator,
             );
         }
     }
 
     // Adds a primitive to a batch.
     // It can recursively call itself in some situations, for
     // example if it encounters a picture where the items
     // in that picture are being drawn into the same target.
@@ -606,21 +505,20 @@ impl AlphaBatchBuilder {
         &mut self,
         prim_instance: &PrimitiveInstance,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         task_id: RenderTaskId,
         task_address: RenderTaskAddress,
         deferred_resolves: &mut Vec<DeferredResolve>,
-        splitter: &mut BspSplitter<f64, WorldPixel>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
-        plane_split_anchor: usize,
+        z_generator: &mut ZBufferIdGenerator,
     ) {
         let prim = &ctx.prim_store.primitives[prim_instance.prim_index.0];
 
         if prim_instance.clipped_world_rect.is_none() {
             return;
         }
 
         #[cfg(debug_assertions)] //TODO: why is this needed?
@@ -635,16 +533,17 @@ impl AlphaBatchBuilder {
 
         // TODO(gw): Calculating this for every primitive is a bit
         //           wasteful. We should probably cache this in
         //           the scroll node...
         let transform_kind = transform_id.transform_kind();
         let bounding_rect = prim_instance.clipped_world_rect
                                          .as_ref()
                                          .expect("bug");
+        let z_id = z_generator.next();
 
         // If the primitive is internally decomposed into multiple sub-primitives we may not
         // use some of the per-primitive data and get it from each sub-primitive instead.
         let is_multiple_primitives = match prim.details {
             PrimitiveDetails::Brush(ref brush) => {
                 match brush.kind {
                     BrushKind::Image { ref visible_tiles, .. } => !visible_tiles.is_empty(),
                     BrushKind::LinearGradient { ref visible_tiles, .. } => !visible_tiles.is_empty(),
@@ -664,17 +563,18 @@ impl AlphaBatchBuilder {
         let clip_task_address = prim_instance
             .clip_task_id
             .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
 
         let specified_blend_mode = prim.get_blend_mode();
 
         let non_segmented_blend_mode = if !prim_instance.opacity.is_opaque ||
             prim_instance.clip_task_id.is_some() ||
-            transform_kind == TransformedRectKind::Complex {
+            transform_kind == TransformedRectKind::Complex
+        {
             specified_blend_mode
         } else {
             BlendMode::None
         };
 
         let prim_header = PrimitiveHeader {
             local_rect: prim.local_rect,
             local_clip_rect: prim_instance.combined_local_clip_rect,
@@ -687,70 +587,95 @@ impl AlphaBatchBuilder {
         if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_instance.prim_index) {
             println!("\ttask target {:?}", self.target_rect);
             println!("\t{:?}", prim_header);
         }
 
         match prim.details {
             PrimitiveDetails::Brush(ref brush) => {
                 match brush.kind {
-                    BrushKind::Picture { pic_index, .. } => {
+                    BrushKind::Picture { pic_index } => {
                         let picture = &ctx.prim_store.pictures[pic_index.0];
+                        match picture.context_3d {
+                            // Convert all children of the 3D hierarchy root into batches.
+                            Picture3DContext::In { root_data: Some(ref list), .. } => {
+                                for child in list {
+                                    let prim_instance = &picture.prim_instances[child.anchor];
+                                    let pic_primitive = &ctx.prim_store.primitives[prim_instance.prim_index.0];
+
+                                    let clip_task_address = prim_instance
+                                        .clip_task_id
+                                        .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
 
-                        // If this picture is participating in a 3D rendering context,
-                        // then don't add it to any batches here. Instead, create a polygon
-                        // for it and add it to the current plane splitter.
-                        if picture.is_in_3d_context {
-                            // Push into parent plane splitter.
-                            debug_assert!(picture.raster_config.is_some());
-                            let transform = transforms.get_world_transform(prim_instance.spatial_node_index);
+                                    let prim_header = PrimitiveHeader {
+                                        local_rect: pic_primitive.local_rect,
+                                        local_clip_rect: prim_instance.combined_local_clip_rect,
+                                        task_address,
+                                        specific_prim_address: GpuCacheAddress::invalid(),
+                                        clip_task_address,
+                                        transform_id: child.transform_id,
+                                    };
 
-                            // Apply the local clip rect here, before splitting. This is
-                            // because the local clip rect can't be applied in the vertex
-                            // shader for split composites, since we are drawing polygons
-                            // rather that rectangles. The interpolation still works correctly
-                            // since we determine the UVs by doing a bilerp with a factor
-                            // from the original local rect.
-                            let local_rect = prim
-                                .local_rect
-                                .intersection(&prim_instance.combined_local_clip_rect);
+						            let pic_index = match pic_primitive.details {
+						            	PrimitiveDetails::Brush(ref brush) => {
+						                    match brush.kind {
+						                        BrushKind::Picture { pic_index, .. } => pic_index,
+						                        _ => unreachable!(),
+						                    }
+						                }
+						                PrimitiveDetails::TextRun(..) => {
+						                    unreachable!();
+						                }
+						            };
+						            let pic = &ctx.prim_store.pictures[pic_index.0];
 
-                            if let Some(local_rect) = local_rect {
-                                match transform.transform_kind() {
-                                    TransformedRectKind::AxisAligned => {
-                                        let inv_transform = transforms.get_world_inv_transform(prim_instance.spatial_node_index);
-                                        let polygon = Polygon::from_transformed_rect_with_inverse(
-                                            local_rect.cast(),
-                                            &transform.cast(),
-                                            &inv_transform.cast(),
-                                            plane_split_anchor,
-                                        ).unwrap();
-                                        splitter.add(polygon);
-                                    }
-                                    TransformedRectKind::Complex => {
-                                        let mut clipper = Clipper::new();
-                                        let matrix = transform.cast();
-                                        let results = clipper.clip_transformed(
-                                            Polygon::from_rect(
-                                                local_rect.cast(),
-                                                plane_split_anchor,
-                                            ),
-                                            &matrix,
-                                            Some(bounding_rect.to_f64()),
+                                    let (uv_rect_address, _) = pic
+                                        .raster_config
+                                        .as_ref()
+                                        .expect("BUG: no raster config")
+                                        .surface
+                                        .as_ref()
+                                        .expect("BUG: no surface")
+                                        .resolve(
+                                            render_tasks,
+                                            ctx.resource_cache,
+                                            gpu_cache,
                                         );
-                                        if let Ok(results) = results {
-                                            for poly in results {
-                                                splitter.add(poly);
-                                            }
-                                        }
-                                    }
+
+                                    let prim_header_index = prim_headers.push(&prim_header, z_id, [
+                                        uv_rect_address.as_int(),
+                                        0,
+                                        0,
+                                    ]);
+
+                                    let key = BatchKey::new(
+                                        BatchKind::SplitComposite,
+                                        BlendMode::PremultipliedAlpha,
+                                        BatchTextures::no_texture(),
+                                    );
+
+                                    let instance = SplitCompositeInstance::new(
+                                        prim_header_index,
+                                        child.gpu_address,
+                                        z_id,
+                                    );
+
+                                    self.batch_list.push_single_instance(
+                                        key,
+                                        &prim_instance.clipped_world_rect.as_ref().expect("bug"),
+                                        z_id,
+                                        PrimitiveInstanceData::from(instance),
+                                    );
                                 }
                             }
-
-                            return;
+                            // Ignore the 3D pictures that are not in the root of preserve-3D
+                            // hierarchy, since we process them with the root.
+                            Picture3DContext::In { root_data: None, .. } => return,
+                            // Proceed for non-3D pictures.
+                            Picture3DContext::Out => ()
                         }
 
                         match picture.raster_config {
                             Some(ref raster_config) => {
                                 let surface = raster_config.surface
                                                            .as_ref()
                                                            .expect("bug: surface must be allocated by now");
                                 match raster_config.composite_mode {
@@ -767,17 +692,17 @@ impl AlphaBatchBuilder {
                                                         ctx.resource_cache,
                                                         gpu_cache,
                                                     );
                                                 let key = BatchKey::new(
                                                     kind,
                                                     non_segmented_blend_mode,
                                                     textures,
                                                 );
-                                                let prim_header_index = prim_headers.push(&prim_header, [
+                                                let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                                     ShaderColorMode::Image as i32,
                                                     RasterizationSpace::Screen as i32,
                                                     0,
                                                 ]);
 
                                                 let instance = BrushInstance {
                                                     prim_header_index,
                                                     segment_index: 0,
@@ -785,17 +710,17 @@ impl AlphaBatchBuilder {
                                                     brush_flags: BrushFlags::empty(),
                                                     clip_task_address,
                                                     user_data: uv_rect_address.as_int(),
                                                 };
 
                                                 self.batch_list.push_single_instance(
                                                     key,
                                                     bounding_rect,
-                                                    prim_instance.prim_index,
+                                                    z_id,
                                                     PrimitiveInstanceData::from(instance),
                                                 );
                                             }
                                             FilterOp::DropShadow(offset, ..) => {
                                                 // Draw an instance of the shadow first, following by the content.
 
                                                 // Both the shadow and the content get drawn as a brush image.
                                                 let kind = BatchKind::Brush(
@@ -829,33 +754,36 @@ impl AlphaBatchBuilder {
                                                     .as_int();
                                                 let content_uv_rect_address = render_tasks[secondary_id]
                                                     .get_texture_address(gpu_cache)
                                                     .as_int();
 
                                                 // Get the GPU cache address of the extra data handle.
                                                 let shadow_prim_address = gpu_cache.get_address(&picture.extra_gpu_data_handle);
 
-                                                let content_prim_header_index = prim_headers.push(&prim_header, [
+                                                let z_id_shadow = z_id;
+                                                let z_id_content = z_generator.next();
+
+                                                let content_prim_header_index = prim_headers.push(&prim_header, z_id_content, [
                                                     ShaderColorMode::Image as i32,
                                                     RasterizationSpace::Screen as i32,
                                                     0,
                                                 ]);
 
                                                 let shadow_rect = prim.local_rect.translate(&offset);
                                                 let shadow_clip_rect = prim.local_clip_rect.translate(&offset);
 
                                                 let shadow_prim_header = PrimitiveHeader {
                                                     local_rect: shadow_rect,
                                                     local_clip_rect: shadow_clip_rect,
                                                     specific_prim_address: shadow_prim_address,
                                                     ..prim_header
                                                 };
 
-                                                let shadow_prim_header_index = prim_headers.push(&shadow_prim_header, [
+                                                let shadow_prim_header_index = prim_headers.push(&shadow_prim_header, z_id_shadow, [
                                                     ShaderColorMode::Alpha as i32,
                                                     RasterizationSpace::Screen as i32,
                                                     0,
                                                 ]);
 
                                                 let shadow_instance = BrushInstance {
                                                     prim_header_index: shadow_prim_header_index,
                                                     clip_task_address,
@@ -872,24 +800,24 @@ impl AlphaBatchBuilder {
                                                     edge_flags: EdgeAaSegmentMask::empty(),
                                                     brush_flags: BrushFlags::empty(),
                                                     user_data: content_uv_rect_address,
                                                 };
 
                                                 self.batch_list.push_single_instance(
                                                     shadow_key,
                                                     bounding_rect,
-                                                    prim_instance.prim_index,
+                                                    z_id_shadow,
                                                     PrimitiveInstanceData::from(shadow_instance),
                                                 );
 
                                                 self.batch_list.push_single_instance(
                                                     content_key,
                                                     bounding_rect,
-                                                    prim_instance.prim_index,
+                                                    z_id_content,
                                                     PrimitiveInstanceData::from(content_instance),
                                                 );
                                             }
                                             _ => {
                                                 let filter_mode = match filter {
                                                     FilterOp::Identity => 1, // matches `Contrast(1)`
                                                     FilterOp::Blur(..) => 0,
                                                     FilterOp::Contrast(..) => 1,
@@ -939,17 +867,17 @@ impl AlphaBatchBuilder {
                                                     );
 
                                                 let key = BatchKey::new(
                                                     BatchKind::Brush(BrushBatchKind::Blend),
                                                     BlendMode::PremultipliedAlpha,
                                                     textures,
                                                 );
 
-                                                let prim_header_index = prim_headers.push(&prim_header, [
+                                                let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                                     uv_rect_address.as_int(),
                                                     filter_mode,
                                                     user_data,
                                                 ]);
 
                                                 let instance = BrushInstance {
                                                     prim_header_index,
                                                     clip_task_address,
@@ -957,17 +885,17 @@ impl AlphaBatchBuilder {
                                                     edge_flags: EdgeAaSegmentMask::empty(),
                                                     brush_flags: BrushFlags::empty(),
                                                     user_data: 0,
                                                 };
 
                                                 self.batch_list.push_single_instance(
                                                     key,
                                                     bounding_rect,
-                                                    prim_instance.prim_index,
+                                                    z_id,
                                                     PrimitiveInstanceData::from(instance),
                                                 );
                                             }
                                         }
                                     }
                                     PictureCompositeMode::MixBlend(mode) => {
                                         let cache_task_id = surface.resolve_render_task_id();
                                         let backdrop_id = picture.secondary_render_task_id.expect("no backdrop!?");
@@ -980,17 +908,17 @@ impl AlphaBatchBuilder {
                                                     backdrop_id,
                                                 },
                                             ),
                                             BlendMode::PremultipliedAlpha,
                                             BatchTextures::no_texture(),
                                         );
                                         let backdrop_task_address = render_tasks.get_task_address(backdrop_id);
                                         let source_task_address = render_tasks.get_task_address(cache_task_id);
-                                        let prim_header_index = prim_headers.push(&prim_header, [
+                                        let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                             mode as u32 as i32,
                                             backdrop_task_address.0 as i32,
                                             source_task_address.0 as i32,
                                         ]);
 
                                         let instance = BrushInstance {
                                             prim_header_index,
                                             clip_task_address,
@@ -998,17 +926,17 @@ impl AlphaBatchBuilder {
                                             edge_flags: EdgeAaSegmentMask::empty(),
                                             brush_flags: BrushFlags::empty(),
                                             user_data: 0,
                                         };
 
                                         self.batch_list.push_single_instance(
                                             key,
                                             bounding_rect,
-                                            prim_instance.prim_index,
+                                            z_id,
                                             PrimitiveInstanceData::from(instance),
                                         );
                                     }
                                     PictureCompositeMode::Blit => {
                                         let cache_task_id = surface.resolve_render_task_id();
                                         let kind = BatchKind::Brush(
                                             BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
                                         );
@@ -1016,17 +944,17 @@ impl AlphaBatchBuilder {
                                             kind,
                                             non_segmented_blend_mode,
                                             BatchTextures::render_target_cache(),
                                         );
 
                                         let uv_rect_address = render_tasks[cache_task_id]
                                             .get_texture_address(gpu_cache)
                                             .as_int();
-                                        let prim_header_index = prim_headers.push(&prim_header, [
+                                        let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                             ShaderColorMode::Image as i32,
                                             RasterizationSpace::Screen as i32,
                                             0,
                                         ]);
 
                                         let instance = BrushInstance {
                                             prim_header_index,
                                             clip_task_address,
@@ -1034,17 +962,17 @@ impl AlphaBatchBuilder {
                                             edge_flags: EdgeAaSegmentMask::empty(),
                                             brush_flags: BrushFlags::empty(),
                                             user_data: uv_rect_address,
                                         };
 
                                         self.batch_list.push_single_instance(
                                             key,
                                             bounding_rect,
-                                            prim_instance.prim_index,
+                                            z_id,
                                             PrimitiveInstanceData::from(instance),
                                         );
                                     }
                                 }
                             }
                             None => {
                                 // If this picture is being drawn into an existing target (i.e. with
                                 // no composition operation), recurse and add to the current batch list.
@@ -1053,16 +981,17 @@ impl AlphaBatchBuilder {
                                     task_id,
                                     ctx,
                                     gpu_cache,
                                     render_tasks,
                                     deferred_resolves,
                                     prim_headers,
                                     transforms,
                                     root_spatial_node_index,
+                                    z_generator,
                                 );
                             }
                         }
                     }
                     BrushKind::Image { request, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
                         for tile in visible_tiles {
                             if let Some((batch_kind, textures, user_data, uv_rect_address)) = get_image_tile_params(
                                     ctx.resource_cache,
@@ -1072,86 +1001,87 @@ impl AlphaBatchBuilder {
                             ) {
                                 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,
                                     ..prim_header
                                 };
-                                let prim_header_index = prim_headers.push(&prim_header, user_data);
+                                let prim_header_index = prim_headers.push(&prim_header, z_id, user_data);
 
                                 self.add_image_tile_to_batch(
-                                    prim_instance,
                                     batch_kind,
                                     specified_blend_mode,
                                     textures,
                                     prim_header_index,
                                     clip_task_address,
                                     bounding_rect,
                                     tile.edge_flags,
                                     uv_rect_address,
+                                    z_id,
                                 );
                             }
                         }
                     }
                     BrushKind::LinearGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
                         add_gradient_tiles(
-                            prim_instance,
                             visible_tiles,
                             stops_handle,
                             BrushBatchKind::LinearGradient,
                             specified_blend_mode,
                             bounding_rect,
                             clip_task_address,
                             gpu_cache,
                             &mut self.batch_list,
                             &prim_header,
                             prim_headers,
+                            z_id,
                         );
                     }
                     BrushKind::RadialGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
                         add_gradient_tiles(
-                            prim_instance,
                             visible_tiles,
                             stops_handle,
                             BrushBatchKind::RadialGradient,
                             specified_blend_mode,
                             bounding_rect,
                             clip_task_address,
                             gpu_cache,
                             &mut self.batch_list,
                             &prim_header,
                             prim_headers,
+                            z_id,
                         );
                     }
                     _ => {
                         if let Some(params) = brush.get_batch_params(
                             ctx.resource_cache,
                             gpu_cache,
                             deferred_resolves,
                             ctx.prim_store.chase_id == Some(prim_instance.prim_index),
                         ) {
-                            let prim_header_index = prim_headers.push(&prim_header, params.prim_user_data);
+                            let prim_header_index = prim_headers.push(&prim_header, z_id, params.prim_user_data);
                             if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_instance.prim_index) {
                                 println!("\t{:?} {:?}, task relative bounds {:?}",
                                     params.batch_kind, prim_header_index, bounding_rect);
                             }
 
                             self.add_brush_to_batch(
                                 brush,
                                 &params,
                                 prim_instance,
                                 specified_blend_mode,
                                 non_segmented_blend_mode,
                                 prim_header_index,
                                 clip_task_address,
                                 bounding_rect,
                                 transform_kind,
                                 render_tasks,
+                                z_id,
                             );
                         }
                     }
                 }
             }
             PrimitiveDetails::TextRun(ref text_cpu) => {
                 let subpx_dir = text_cpu.used_font.get_subpx_dir();
 
@@ -1219,25 +1149,25 @@ impl AlphaBatchBuilder {
                             GlyphFormat::ColorBitmap => {
                                 (
                                     BlendMode::PremultipliedAlpha,
                                     ShaderColorMode::ColorBitmap,
                                 )
                             }
                         };
 
-                        let prim_header_index = prim_headers.push(&prim_header, [0; 3]);
+                        let prim_header_index = prim_headers.push(&prim_header, z_id, [0; 3]);
                         let key = BatchKey::new(kind, blend_mode, textures);
                         let base_instance = GlyphInstance::new(
                             prim_header_index,
                         );
                         let batch = alpha_batch_list.set_params_and_get_batch(
                             key,
                             bounding_rect,
-                            prim_instance.prim_index,
+                            z_id,
                         );
 
                         for glyph in glyphs {
                             batch.push(base_instance.build(
                                 glyph.index_in_text_run,
                                 glyph.uv_rect_address.as_int(),
                                 (subpx_dir as u32 as i32) << 16 |
                                 (color_mode as u32 as i32),
@@ -1246,25 +1176,25 @@ impl AlphaBatchBuilder {
                     },
                 );
             }
         }
     }
 
     fn add_image_tile_to_batch(
         &mut self,
-        prim_instance: &PrimitiveInstance,
         batch_kind: BrushBatchKind,
         blend_mode: BlendMode,
         textures: BatchTextures,
         prim_header_index: PrimitiveHeaderIndex,
         clip_task_address: RenderTaskAddress,
         bounding_rect: &WorldRect,
         edge_flags: EdgeAaSegmentMask,
         uv_rect_address: GpuCacheAddress,
+        z_id: ZBufferId,
     ) {
         let base_instance = BrushInstance {
             prim_header_index,
             clip_task_address,
             segment_index: 0,
             edge_flags,
             brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
             user_data: uv_rect_address.as_int(),
@@ -1273,17 +1203,17 @@ impl AlphaBatchBuilder {
         let batch_key = BatchKey {
             blend_mode,
             kind: BatchKind::Brush(batch_kind),
             textures,
         };
         self.batch_list.push_single_instance(
             batch_key,
             bounding_rect,
-            prim_instance.prim_index,
+            z_id,
             PrimitiveInstanceData::from(base_instance),
         );
     }
 
     /// Add a single segment instance to a batch.
     fn add_segment_to_batch(
         &mut self,
         segment: &BrushSegment,
@@ -1291,16 +1221,17 @@ impl AlphaBatchBuilder {
         segment_index: i32,
         batch_kind: BrushBatchKind,
         prim_instance: &PrimitiveInstance,
         prim_header_index: PrimitiveHeaderIndex,
         alpha_blend_mode: BlendMode,
         bounding_rect: &WorldRect,
         transform_kind: TransformedRectKind,
         render_tasks: &RenderTaskTree,
+        z_id: ZBufferId,
     ) {
         let clip_task_address = match segment.clip_task_id {
             BrushSegmentTaskId::RenderTaskId(id) =>
                 render_tasks.get_task_address(id),
             BrushSegmentTaskId::Opaque => OPAQUE_TASK_ADDRESS,
             BrushSegmentTaskId::Empty => return,
         };
 
@@ -1330,17 +1261,17 @@ impl AlphaBatchBuilder {
             blend_mode: if needs_blending { alpha_blend_mode } else { BlendMode::None },
             kind: BatchKind::Brush(batch_kind),
             textures: segment_data.textures,
         };
 
         self.batch_list.push_single_instance(
             batch_key,
             bounding_rect,
-            prim_instance.prim_index,
+            z_id,
             instance,
         );
     }
 
     /// Add any segment(s) from a brush to batches.
     fn add_brush_to_batch(
         &mut self,
         brush: &BrushPrimitive,
@@ -1348,16 +1279,17 @@ impl AlphaBatchBuilder {
         prim_instance: &PrimitiveInstance,
         alpha_blend_mode: BlendMode,
         non_segmented_blend_mode: BlendMode,
         prim_header_index: PrimitiveHeaderIndex,
         clip_task_address: RenderTaskAddress,
         bounding_rect: &WorldRect,
         transform_kind: TransformedRectKind,
         render_tasks: &RenderTaskTree,
+        z_id: ZBufferId,
     ) {
         match (&brush.segment_desc, &params.segment_data) {
             (Some(ref segment_desc), SegmentDataKind::Instanced(ref segment_data)) => {
                 // In this case, we have both a list of segments, and a list of
                 // per-segment instance data. Zip them together to build batches.
                 debug_assert_eq!(segment_desc.segments.len(), segment_data.len());
                 for (segment_index, (segment, segment_data)) in segment_desc.segments
                     .iter()
@@ -1369,16 +1301,17 @@ impl AlphaBatchBuilder {
                         segment_index as i32,
                         params.batch_kind,
                         prim_instance,
                         prim_header_index,
                         alpha_blend_mode,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
+                        z_id,
                     );
                 }
             }
             (Some(ref segment_desc), SegmentDataKind::Shared(ref segment_data)) => {
                 // A list of segments, but the per-segment data is common
                 // between all segments.
                 for (segment_index, segment) in segment_desc.segments
                     .iter()
@@ -1389,16 +1322,17 @@ impl AlphaBatchBuilder {
                         segment_index as i32,
                         params.batch_kind,
                         prim_instance,
                         prim_header_index,
                         alpha_blend_mode,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
+                        z_id,
                     );
                 }
             }
             (None, SegmentDataKind::Shared(ref segment_data)) => {
                 // No segments, and thus no per-segment instance data.
                 let batch_key = BatchKey {
                     blend_mode: non_segmented_blend_mode,
                     kind: BatchKind::Brush(params.batch_kind),
@@ -1410,62 +1344,62 @@ impl AlphaBatchBuilder {
                     clip_task_address,
                     brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
                     prim_header_index,
                     user_data: segment_data.user_data,
                 });
                 self.batch_list.push_single_instance(
                     batch_key,
                     bounding_rect,
-                    prim_instance.prim_index,
+                    z_id,
                     PrimitiveInstanceData::from(instance),
                 );
             }
             (None, SegmentDataKind::Instanced(..)) => {
                 // We should never hit the case where there are no segments,
                 // but a list of segment instance data.
                 unreachable!();
             }
         }
     }
 }
 
 fn add_gradient_tiles(
-    prim_instance: &PrimitiveInstance,
     visible_tiles: &[VisibleGradientTile],
     stops_handle: &GpuCacheHandle,
     kind: BrushBatchKind,
     blend_mode: BlendMode,
     bounding_rect: &WorldRect,
     clip_task_address: RenderTaskAddress,
     gpu_cache: &GpuCache,
     batch_list: &mut BatchList,
     base_prim_header: &PrimitiveHeader,
     prim_headers: &mut PrimitiveHeaders,
+    z_id: ZBufferId,
 ) {
     let batch = batch_list.set_params_and_get_batch(
         BatchKey {
             blend_mode: blend_mode,
             kind: BatchKind::Brush(kind),
             textures: BatchTextures::no_texture(),
         },
         bounding_rect,
-        prim_instance.prim_index,
+        z_id,
     );
 
     let user_data = [stops_handle.as_int(gpu_cache), 0, 0];
 
     for tile in visible_tiles {
         let prim_header = PrimitiveHeader {
             specific_prim_address: gpu_cache.get_address(&tile.handle),
             local_rect: tile.local_rect,
             local_clip_rect: tile.local_clip_rect,
             ..*base_prim_header
         };
-        let prim_header_index = prim_headers.push(&prim_header, user_data);
+        let prim_header_index = prim_headers.push(&prim_header, z_id, user_data);
 
         batch.push(PrimitiveInstanceData::from(
             BrushInstance {
                 prim_header_index,
                 clip_task_address,
                 segment_index: 0,
                 edge_flags: EdgeAaSegmentMask::all(),
                 brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
@@ -2022,47 +1956,61 @@ impl ClipBatcher {
                 render_task_address: task_address,
                 clip_transform_id,
                 prim_transform_id,
                 segment: 0,
                 clip_data_address: GpuCacheAddress::invalid(),
                 resource_address: GpuCacheAddress::invalid(),
             };
 
-            let gpu_address = gpu_cache.get_address(&clip_node.gpu_cache_handle);
-
             match clip_node.item {
-                ClipItem::Image(ref mask, is_valid) => {
-                    if is_valid {
-                        if let Ok(cache_item) = resource_cache.get_cached_image(
-                            ImageRequest {
-                                key: mask.image,
-                                rendering: ImageRendering::Auto,
-                                tile: None,
+                ClipItem::Image { ref mask, ref visible_tiles } => {
+                    let request = ImageRequest {
+                        key: mask.image,
+                        rendering: ImageRendering::Auto,
+                        tile: None,
+                    };
+                    let mut add_image = |request: ImageRequest, clip_data_address: GpuCacheAddress| {
+                        let cache_item = match resource_cache.get_cached_image(request) {
+                            Ok(item) => item,
+                            Err(..) => {
+                                warn!("Warnings: skip a image mask");
+                                debug!("Mask: {:?}, request: {:?}", mask, request);
+                                return;
                             }
-                        ) {
-                            self.images
-                                .entry(cache_item.texture_id)
-                                .or_insert(Vec::new())
-                                .push(ClipMaskInstance {
-                                    clip_data_address: gpu_address,
-                                    resource_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
-                                    ..instance
-                                });
-                        } else {
-                            warn!("Warnings: skip a image mask");
-                            debug!("Key:{:?} Rect::{:?}", mask.image, mask.rect);
-                            continue;
+                        };
+                        self.images
+                            .entry(cache_item.texture_id)
+                            .or_insert(Vec::new())
+                            .push(ClipMaskInstance {
+                                clip_data_address,
+                                resource_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
+                                ..instance
+                            });
+                    };
+
+                    match *visible_tiles {
+                        Some(ref tiles) => {
+                            for tile in tiles {
+                                add_image(
+                                    request.with_tile(tile.tile_offset),
+                                    gpu_cache.get_address(&tile.handle),
+                                )
+                            }
                         }
-                    } else {
-                        warn!("Warnings: clip masks that are tiled blobs are not yet supported (#2852)");
-                        continue;
+                        None => {
+                            let gpu_address =
+                                gpu_cache.get_address(&clip_node.gpu_cache_handle);
+                            add_image(request, gpu_address)
+                        }
                     }
                 }
                 ClipItem::BoxShadow(ref info) => {
+                    let gpu_address =
+                        gpu_cache.get_address(&clip_node.gpu_cache_handle);
                     let rt_handle = info
                         .cache_handle
                         .as_ref()
                         .expect("bug: render task handle not allocated");
                     let rt_cache_entry = resource_cache
                         .get_cached_render_task(rt_handle);
                     let cache_item = resource_cache
                         .get_texture_cache_item(&rt_cache_entry.handle);
@@ -2075,23 +2023,27 @@ impl ClipBatcher {
                             clip_data_address: gpu_address,
                             resource_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
                             ..instance
                         });
                 }
                 ClipItem::Rectangle(_, mode) => {
                     if !clip_instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) ||
                         mode == ClipMode::ClipOut {
+                        let gpu_address =
+                            gpu_cache.get_address(&clip_node.gpu_cache_handle);
                         self.rectangles.push(ClipMaskInstance {
                             clip_data_address: gpu_address,
                             ..instance
                         });
                     }
                 }
                 ClipItem::RoundedRectangle(..) => {
+                    let gpu_address =
+                        gpu_cache.get_address(&clip_node.gpu_cache_handle);
                     self.rectangles.push(ClipMaskInstance {
                         clip_data_address: gpu_address,
                         ..instance
                     });
                 }
             }
         }
     }
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -9,19 +9,20 @@ use api::{PictureRect, LayoutPixel, Worl
 use api::{VoidPtrToSizeFn, LayoutRectAu, ImageKey, AuHelpers};
 use app_units::Au;
 use border::{ensure_no_corner_overlap, BorderRadiusAu};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
 use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use ellipse::Ellipse;
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use gpu_types::{BoxShadowStretchMode};
+use image::{self, Repetition};
 use intern;
 use internal_types::FastHashSet;
-use prim_store::{ClipData, ImageMaskData, SpaceMapper};
+use prim_store::{ClipData, ImageMaskData, SpaceMapper, VisibleMaskImageTile};
 use render_task::to_cache_size;
 use resource_cache::{ImageRequest, ResourceCache};
 use std::{cmp, u32};
 use std::os::raw::c_void;
 use util::{extract_inner_rect_safe, project_rect, ScaleOffset};
 
 /*
 
@@ -96,17 +97,17 @@ use util::{extract_inner_rect_safe, proj
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Clone, Copy, Debug)]
 pub struct ClipDataMarker;
 
 pub type ClipDataStore = intern::DataStore<ClipItemKey, ClipNode, ClipDataMarker>;
 pub type ClipDataHandle = intern::Handle<ClipDataMarker>;
 pub type ClipDataUpdateList = intern::UpdateList<ClipItemKey>;
-pub type ClipDataInterner = intern::Interner<ClipItemKey, ClipDataMarker>;
+pub type ClipDataInterner = intern::Interner<ClipItemKey, ClipItemSceneData, ClipDataMarker>;
 
 // Result of comparing a clip node instance against a local rect.
 #[derive(Debug)]
 enum ClipResult {
     // The clip does not affect the region at all.
     Accept,
     // The clip prevents the region from being drawn.
     Reject,
@@ -140,24 +141,24 @@ impl From<ClipItemKey> for ClipNode {
             ClipItemKey::RoundedRectangle(rect, radius, mode) => {
                 ClipItem::RoundedRectangle(
                     LayoutRect::from_au(rect),
                     radius.into(),
                     mode,
                 )
             }
             ClipItemKey::ImageMask(rect, image, repeat) => {
-                ClipItem::Image(
-                    ImageMask {
+                ClipItem::Image {
+                    mask: ImageMask {
                         image,
                         rect: LayoutRect::from_au(rect),
                         repeat,
                     },
-                    true,
-                )
+                    visible_tiles: None,
+                }
             }
             ClipItemKey::BoxShadow(shadow_rect, shadow_radius, prim_shadow_rect, blur_radius, clip_mode) => {
                 ClipItem::new_box_shadow(
                     LayoutRect::from_au(shadow_rect),
                     shadow_radius.into(),
                     LayoutRect::from_au(prim_shadow_rect),
                     blur_radius.to_f32_px(),
                     clip_mode,
@@ -258,71 +259,104 @@ struct ClipNodeInfo {
 }
 
 impl ClipNode {
     pub fn update(
         &mut self,
         gpu_cache: &mut GpuCache,
         resource_cache: &mut ResourceCache,
         device_pixel_scale: DevicePixelScale,
+        clipped_rect: &LayoutRect,
     ) {
-        if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
-            match self.item {
-                ClipItem::Image(ref mask, ..) => {
-                    let data = ImageMaskData { local_rect: mask.rect };
-                    data.write_gpu_blocks(request);
+        match self.item {
+            ClipItem::Image { ref mask, ref mut visible_tiles } => {
+                let request = ImageRequest {
+                    key: mask.image,
+                    rendering: ImageRendering::Auto,
+                    tile: None,
+                };
+                *visible_tiles = None;
+                if let Some(props) = resource_cache.get_image_properties(mask.image) {
+                    if let Some(tile_size) = props.tiling {
+                        let mut mask_tiles = Vec::new();
+
+                        let device_image_size = props.descriptor.size;
+                        let visible_rect = if mask.repeat {
+                            *clipped_rect
+                        } else {
+                            clipped_rect.intersection(&mask.rect).unwrap()
+                        };
+
+                        let repetitions = image::repetitions(
+                            &mask.rect,
+                            &visible_rect,
+                            mask.rect.size,
+                        );
+
+                        for Repetition { origin, .. } in repetitions {
+                            let image_rect = LayoutRect {
+                                origin,
+                                size: mask.rect.size,
+                            };
+                            let tiles = image::tiles(
+                                &image_rect,
+                                &visible_rect,
+                                &device_image_size,
+                                tile_size as u32,
+                            );
+                            for tile in tiles {
+                                resource_cache.request_image(
+                                    request.with_tile(tile.offset),
+                                    gpu_cache,
+                                );
+                                let mut handle = GpuCacheHandle::new();
+                                if let Some(request) = gpu_cache.request(&mut handle) {
+                                    let data = ImageMaskData {
+                                        local_mask_rect: mask.rect,
+                                        local_tile_rect: tile.rect,
+                                    };
+                                    data.write_gpu_blocks(request);
+                                }
+
+                                mask_tiles.push(VisibleMaskImageTile {
+                                    tile_offset: tile.offset,
+                                    handle,
+                                });
+                            }
+                        }
+                        *visible_tiles = Some(mask_tiles);
+                    } else {
+                        if let Some(request) = gpu_cache.request(&mut self.gpu_cache_handle) {
+                            let data = ImageMaskData {
+                                local_mask_rect: mask.rect,
+                                local_tile_rect: mask.rect,
+                            };
+                            data.write_gpu_blocks(request);
+                        }
+                        resource_cache.request_image(request, gpu_cache);
+                    }
                 }
-                ClipItem::BoxShadow(ref info) => {
+            }
+            ClipItem::BoxShadow(ref mut info) => {
+                if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
                     request.push([
                         info.shadow_rect_alloc_size.width,
                         info.shadow_rect_alloc_size.height,
                         info.clip_mode as i32 as f32,
                         0.0,
                     ]);
                     request.push([
                         info.stretch_mode_x as i32 as f32,
                         info.stretch_mode_y as i32 as f32,
                         0.0,
                         0.0,
                     ]);
                     request.push(info.prim_shadow_rect);
                 }
-                ClipItem::Rectangle(rect, mode) => {
-                    let data = ClipData::uniform(rect, 0.0, mode);
-                    data.write(&mut request);
-                }
-                ClipItem::RoundedRectangle(ref rect, ref radius, mode) => {
-                    let data = ClipData::rounded_rect(rect, radius, mode);
-                    data.write(&mut request);
-                }
-            }
-        }
 
-        match self.item {
-            ClipItem::Image(ref mask, ref mut is_valid) => {
-                if let Some(properties) = resource_cache.get_image_properties(mask.image) {
-                    // Clip masks with tiled blob images are not currently supported.
-                    // This results in them being ignored, which is not ideal, but
-                    // is better than crashing in resource cache!
-                    // See https://github.com/servo/webrender/issues/2852.
-                    *is_valid = properties.tiling.is_none();
-                }
-
-                if *is_valid {
-                    resource_cache.request_image(
-                        ImageRequest {
-                            key: mask.image,
-                            rendering: ImageRendering::Auto,
-                            tile: None,
-                        },
-                        gpu_cache,
-                    );
-                }
-            }
-            ClipItem::BoxShadow(ref mut info) => {
                 // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
                 // "the image that would be generated by applying to the shadow a
                 // Gaussian blur with a standard deviation equal to half the blur radius."
                 let blur_radius_dp = (info.blur_radius * 0.5 * device_pixel_scale.0).round();
 
                 // Create the cache key for this box-shadow render task.
                 let content_scale = LayoutToWorldScale::new(1.0) * device_pixel_scale;
                 let cache_size = to_cache_size(info.shadow_rect_alloc_size * content_scale);
@@ -343,18 +377,28 @@ impl ClipNode {
                         &info.minimal_shadow_rect,
                         &info.shadow_radius,
                         ClipMode::Clip,
                     );
 
                     data.write(&mut request);
                 }
             }
-            ClipItem::Rectangle(..) |
-            ClipItem::RoundedRectangle(..) => {}
+            ClipItem::Rectangle(rect, mode) => {
+                if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
+                    let data = ClipData::uniform(rect, 0.0, mode);
+                    data.write(&mut request);
+                }
+            }
+            ClipItem::RoundedRectangle(ref rect, ref radius, mode) => {
+                if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
+                    let data = ClipData::rounded_rect(rect, radius, mode);
+                    data.write(&mut request);
+                }
+            }
         }
     }
 }
 
 // The main clipping public interface that other modules access.
 pub struct ClipStore {
     pub clip_chain_nodes: Vec<ClipChainNode>,
     clip_node_instances: Vec<ClipNodeInstance>,
@@ -563,16 +607,17 @@ impl ClipStore {
                 ClipResult::Partial => {
                     // Needs a mask -> add to clip node indices
 
                     // TODO(gw): Ensure this only runs once on each node per frame?
                     node.update(
                         gpu_cache,
                         resource_cache,
                         device_pixel_scale,
+                        &local_bounding_rect,
                     );
 
                     // Calculate some flags that are required for the segment
                     // building logic.
                     let flags = match node_info.conversion {
                         ClipSpaceConversion::Local => {
                             ClipNodeFlags::SAME_SPATIAL_NODE | ClipNodeFlags::SAME_COORD_SYSTEM
                         }
@@ -588,17 +633,17 @@ impl ClipStore {
                     // in the same coordinate system as the primitive doesn't need
                     // a clip mask. Instead, it can be handled by the primitive
                     // vertex shader as part of the local clip rect. This is an
                     // important optimization for reducing the number of clip
                     // masks that are allocated on common pages.
                     needs_mask |= match node.item {
                         ClipItem::Rectangle(_, ClipMode::ClipOut) |
                         ClipItem::RoundedRectangle(..) |
-                        ClipItem::Image(..) |
+                        ClipItem::Image { .. } |
                         ClipItem::BoxShadow(..) => {
                             true
                         }
 
                         ClipItem::Rectangle(_, ClipMode::Clip) => {
                             !flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM)
                         }
                     };
@@ -702,16 +747,30 @@ impl ClipRegion<Option<ComplexClipRegion
         ClipRegion {
             main: local_clip.translate(reference_frame_relative_offset),
             image_mask: None,
             complex_clips: None,
         }
     }
 }
 
+/// The information about an interned clip item that
+/// is stored and available in the scene builder
+/// thread.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ClipItemSceneData {
+    // TODO(gw): We will store the local clip rect of
+    //           the clip item here. This will allow
+    //           calculation of the local clip rect
+    //           for a primitive and its clip chain
+    //           during scene building, rather than
+    //           frame building.
+}
+
 // The ClipItemKey is a hashable representation of the contents
 // of a clip item. It is used during interning to de-duplicate
 // clip nodes between frames and display lists. This allows quick
 // comparison of clip node equality by handle, and also allows
 // the uploaded GPU cache handle to be retained between display lists.
 // TODO(gw): Maybe we should consider constructing these directly
 //           in the DL builder?
 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
@@ -762,25 +821,23 @@ impl ClipItemKey {
             shadow_radius.into(),
             prim_shadow_rect.to_au(),
             Au::from_f32_px(blur_radius),
             clip_mode,
         )
     }
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ClipItem {
     Rectangle(LayoutRect, ClipMode),
     RoundedRectangle(LayoutRect, BorderRadius, ClipMode),
-    /// The boolean below is a crash workaround for #2852, will be true unless
-    /// the mask is a tiled blob.
-    Image(ImageMask, bool),
+    Image { mask: ImageMask, visible_tiles: Option<Vec<VisibleMaskImageTile>> },
     BoxShadow(BoxShadowClipSource),
 }
 
 impl ClipItem {
     pub fn new_box_shadow(
         shadow_rect: LayoutRect,
         mut shadow_radius: BorderRadius,
         prim_shadow_rect: LayoutRect,
@@ -883,18 +940,18 @@ impl ClipItem {
     // used to eliminate redundant clips, and reduce the size of
     // any clip mask that eventually gets drawn.
     fn get_local_clip_rect(&self) -> Option<LayoutRect> {
         match *self {
             ClipItem::Rectangle(clip_rect, ClipMode::Clip) => Some(clip_rect),
             ClipItem::Rectangle(_, ClipMode::ClipOut) => None,
             ClipItem::RoundedRectangle(clip_rect, _, ClipMode::Clip) => Some(clip_rect),
             ClipItem::RoundedRectangle(_, _, ClipMode::ClipOut) => None,
-            ClipItem::Image(ref mask, ..) if mask.repeat => None,
-            ClipItem::Image(ref mask, ..) => Some(mask.rect),
+            ClipItem::Image { ref mask, .. } if mask.repeat => None,
+            ClipItem::Image { ref mask, .. } => Some(mask.rect),
             ClipItem::BoxShadow(..) => None,
         }
     }
 
     fn get_clip_result_complex(
         &self,
         transform: &LayoutToWorldTransform,
         prim_world_rect: &WorldRect,
@@ -905,17 +962,17 @@ impl ClipItem {
                 (clip_rect, Some(clip_rect))
             }
             ClipItem::RoundedRectangle(ref clip_rect, ref radius, ClipMode::Clip) => {
                 let inner_clip_rect = extract_inner_rect_safe(clip_rect, radius);
                 (*clip_rect, inner_clip_rect)
             }
             ClipItem::Rectangle(_, ClipMode::ClipOut) |
             ClipItem::RoundedRectangle(_, _, ClipMode::ClipOut) |
-            ClipItem::Image(..) |
+            ClipItem::Image { .. } |
             ClipItem::BoxShadow(..) => {
                 return ClipResult::Partial
             }
         };
 
         let inner_clip_rect = inner_rect.and_then(|ref inner_rect| {
             project_inner_rect(transform, inner_rect)
         });
@@ -1016,17 +1073,17 @@ impl ClipItem {
                     Some(_) => {
                         ClipResult::Partial
                     }
                     None => {
                         ClipResult::Accept
                     }
                 }
             }
-            ClipItem::Image(ref mask, ..) => {
+            ClipItem::Image { ref mask, .. } => {
                 if mask.repeat {
                     ClipResult::Partial
                 } else {
                     match mask.rect.intersection(prim_rect) {
                         Some(..) => {
                             ClipResult::Partial
                         }
                         None => {
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -8,30 +8,30 @@ use api::{ClipId, ColorF, ComplexClipReg
 use api::{DisplayItemRef, ExtendMode, ExternalScrollId};
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, RasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, ColorDepth};
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
-use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
-use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
+use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore, ClipItemSceneData};
+use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
 use euclid::vec2;
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
-use picture::{PictureCompositeMode, PictureIdGenerator, PicturePrimitive};
+use picture::{Picture3DContext, PictureCompositeMode, PictureIdGenerator, PicturePrimitive};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, PrimitiveInstance};
-use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity, PrimitiveKey};
-use prim_store::{BorderSource, BrushSegment, BrushSegmentVec, PrimitiveContainer, PrimitiveStore};
+use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity, PrimitiveKey, PrimitiveSceneData};
+use prim_store::{BorderSource, BrushSegment, BrushSegmentVec, PrimitiveContainer, PrimitiveDataHandle, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive, PictureIndex};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::DocumentResources;
 use spatial_node::{StickyFrameInfo};
 use std::{f32, mem};
 use std::collections::vec_deque::VecDeque;
@@ -794,17 +794,22 @@ impl<'a> DisplayListFlattener<'a> {
         if clip_items.is_empty() {
             parent_clip_chain_id
         } else {
             let mut clip_chain_id = parent_clip_chain_id;
 
             for item in clip_items {
                 // Intern this clip item, and store the handle
                 // in the clip chain node.
-                let handle = self.resources.clip_interner.intern(&item);
+                let handle = self.resources
+                    .clip_interner
+                    .intern(&item, || {
+                        ClipItemSceneData {
+                        }
+                    });
 
                 clip_chain_id = self.clip_store
                                     .add_clip_chain_node(
                                         handle,
                                         spatial_node_index,
                                         clip_chain_id,
                                     );
             }
@@ -818,23 +823,24 @@ impl<'a> DisplayListFlattener<'a> {
     /// sub-primitives.
     pub fn create_primitive(
         &mut self,
         info: &LayoutPrimitiveInfo,
         clip_chain_id: ClipChainId,
         spatial_node_index: SpatialNodeIndex,
         container: PrimitiveContainer,
     ) -> PrimitiveInstance {
-        let stacking_context = self.sc_stack.last().expect("bug: no stacking context!");
+        let prim_key = PrimitiveKey::new(info.is_backface_visible);
 
-        let prim_key = PrimitiveKey::new(
-            info.is_backface_visible && stacking_context.is_backface_visible,
-        );
-
-        let prim_data_handle = self.resources.prim_interner.intern(&prim_key);
+        let prim_data_handle = self.resources
+            .prim_interner
+            .intern(&prim_key, || {
+                PrimitiveSceneData {
+                }
+            });
 
         let prim_index = self.prim_store.add_primitive(
             &info.rect,
             &info.clip_rect,
             container,
         );
 
         PrimitiveInstance::new(
@@ -873,17 +879,17 @@ impl<'a> DisplayListFlattener<'a> {
         &mut self,
         prim_instance: PrimitiveInstance,
     ) {
         // Add primitive to the top-most stacking context on the stack.
         if cfg!(debug_assertions) && self.prim_store.chase_id == Some(prim_instance.prim_index) {
             println!("\tadded to stacking context at {}", self.sc_stack.len());
         }
         let stacking_context = self.sc_stack.last_mut().unwrap();
-        stacking_context.normal_primitives.push(prim_instance);
+        stacking_context.primitives.push(prim_instance);
     }
 
     /// Convenience interface that creates a primitive entry and adds it
     /// to the draw list.
     pub fn add_primitive(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
@@ -948,293 +954,300 @@ impl<'a> DisplayListFlattener<'a> {
             Some(pipeline_id)
         } else {
             None
         };
 
         // Get the transform-style of the parent stacking context,
         // which determines if we *might* need to draw this on
         // an intermediate surface for plane splitting purposes.
-        let parent_transform_style = match self.sc_stack.last() {
-            Some(sc) => sc.transform_style,
-            None => TransformStyle::Flat,
+        let (parent_is_3d, extra_3d_instance) = match self.sc_stack.last_mut() {
+            Some(sc) => {
+                // Cut the sequence of flat children before starting a child stacking context,
+                // so that the relative order between them and our current SC is preserved.
+                let extra_instance = sc.cut_flat_item_sequence(
+                    &mut self.picture_id_generator,
+                    &mut self.prim_store,
+                );
+                (sc.is_3d(), extra_instance)
+            },
+            None => (false, None),
         };
 
+        if let Some(instance) = extra_3d_instance {
+            self.add_primitive_instance_to_3d_root(instance);
+        }
+
         // If this is preserve-3d *or* the parent is, then this stacking
         // context is participating in the 3d rendering context. In that
         // case, hoist the picture up to the 3d rendering context
         // container, so that it's rendered as a sibling with other
         // elements in this context.
         let participating_in_3d_context =
-            composite_ops.count() == 0 &&
-            (parent_transform_style == TransformStyle::Preserve3D ||
-             transform_style == TransformStyle::Preserve3D);
+            composite_ops.is_empty() &&
+            (parent_is_3d || transform_style == TransformStyle::Preserve3D);
 
-        // If this is participating in a 3d context *and* the
-        // parent was not a 3d context, then this must be the
-        // element that establishes a new 3d context.
-        let establishes_3d_context =
-            participating_in_3d_context &&
-            parent_transform_style == TransformStyle::Flat;
+        let context_3d = if participating_in_3d_context {
+            // Find the spatial node index of the containing block, which
+            // defines the context of backface-visibility.
+            let ancestor_context = self.sc_stack
+                .iter()
+                .rfind(|sc| !sc.is_3d());
+            Picture3DContext::In {
+                root_data: if parent_is_3d {
+                    None
+                } else {
+                    Some(Vec::new())
+                },
+                ancestor_index: match ancestor_context {
+                    Some(sc) => sc.spatial_node_index,
+                    None => ROOT_SPATIAL_NODE_INDEX,
+                },
+            }
+        } else {
+            Picture3DContext::Out
+        };
 
         // Force an intermediate surface if the stacking context
         // has a clip node. In the future, we may decide during
         // prepare step to skip the intermediate surface if the
         // clip node doesn't affect the stacking context rect.
         let should_isolate = clipping_node.is_some();
 
-        // preserve-3d's semantics are to hoist all your children to be your siblings
-        // when doing backface-visibility checking, so we need to grab the backface-visibility
-        // of the lowest ancestor which *doesn't* preserve-3d, and AND it in with ours.
-        //
-        // No this isn't obvious or clear, it's just what we worked out over a day of testing.
-        // There's probably a bug in here, but I couldn't find it with the examples and tests
-        // at my disposal!
-        let ancestor_is_backface_visible =
-            self.sc_stack
-                .iter()
-                .rfind(|sc| sc.transform_style == TransformStyle::Flat)
-                .map(|sc| sc.is_backface_visible)
-                .unwrap_or(is_backface_visible);
-
-        let is_backface_visible = is_backface_visible && ancestor_is_backface_visible;
+        let prim_key = PrimitiveKey::new(is_backface_visible);
+        let primitive_data_handle = self.resources
+            .prim_interner
+            .intern(&prim_key, || {
+                PrimitiveSceneData {
+                }
+            }
+        );
 
         // Push the SC onto the stack, so we know how to handle things in
         // pop_stacking_context.
         self.sc_stack.push(FlattenedStackingContext {
-            preserve3d_primitives: Vec::new(),
-            normal_primitives: Vec::new(),
+            primitives: Vec::new(),
             pipeline_id,
-            is_backface_visible,
+            primitive_data_handle,
             requested_raster_space,
             spatial_node_index,
             clip_chain_id,
             frame_output_pipeline_id,
             composite_ops,
             should_isolate,
             transform_style,
-            participating_in_3d_context,
-            establishes_3d_context,
+            context_3d,
         });
     }
 
     pub fn pop_stacking_context(&mut self) {
         let stacking_context = self.sc_stack.pop().unwrap();
 
         // An arbitrary large clip rect. For now, we don't
         // specify a clip specific to the stacking context.
         // However, now that they are represented as Picture
         // primitives, we can apply any kind of clip mask
         // to them, as for a normal primitive. This is needed
         // to correctly handle some CSS cases (see #1957).
         let max_clip = LayoutRect::max_rect();
 
-        // By default, this picture will be collapsed into
-        // the owning target.
-        let mut composite_mode = if stacking_context.should_isolate {
-            Some(PictureCompositeMode::Blit)
-        } else {
-            None
-        };
-
-        // Force an intermediate surface if the stacking context
-        // has a clip node. In the future, we may decide during
-        // prepare step to skip the intermediate surface if the
-        // clip node doesn't affect the stacking context rect.
-        if stacking_context.participating_in_3d_context {
+        let (leaf_context_3d, leaf_composite_mode, leaf_output_pipeline_id) = match stacking_context.context_3d {
             // TODO(gw): For now, as soon as this picture is in
             //           a 3D context, we draw it to an intermediate
             //           surface and apply plane splitting. However,
             //           there is a large optimization opportunity here.
             //           During culling, we can check if there is actually
             //           perspective present, and skip the plane splitting
             //           completely when that is not the case.
-            composite_mode = Some(PictureCompositeMode::Blit);
-        }
-
-        let prim_key = PrimitiveKey::new(true);
-        let prim_data_handle = self.resources.prim_interner.intern(&prim_key);
+            Picture3DContext::In { ancestor_index, .. } => (
+                Picture3DContext::In { root_data: None, ancestor_index },
+                Some(PictureCompositeMode::Blit),
+                None,
+            ),
+            Picture3DContext::Out => (
+                Picture3DContext::Out,
+                if stacking_context.should_isolate {
+                    // Add a dummy composite filter if the SC has to be isolated.
+                    Some(PictureCompositeMode::Blit)
+                } else {
+                    // By default, this picture will be collapsed into
+                    // the owning target.
+                    None
+                },
+                stacking_context.frame_output_pipeline_id
+            ),
+        };
 
         // Add picture for this actual stacking context contents to render into.
         let leaf_picture = PicturePrimitive::new_image(
             self.picture_id_generator.next(),
-            composite_mode,
-            stacking_context.participating_in_3d_context,
+            leaf_composite_mode,
+            leaf_context_3d,
             stacking_context.pipeline_id,
-            stacking_context.frame_output_pipeline_id,
+            leaf_output_pipeline_id,
             true,
             stacking_context.requested_raster_space,
-            stacking_context.normal_primitives,
+            stacking_context.primitives,
         );
         let leaf_pic_index = self.prim_store.create_picture(leaf_picture);
 
         // Create a brush primitive that draws this picture.
         let leaf_prim = BrushPrimitive::new_picture(leaf_pic_index);
 
         // Add the brush to the parent picture.
         let leaf_prim_index = self.prim_store.add_primitive(
             &LayoutRect::zero(),
             &max_clip,
             PrimitiveContainer::Brush(leaf_prim),
         );
 
         // Create a chain of pictures based on presence of filters,
         // mix-blend-mode and/or 3d rendering context containers.
-        let mut current_prim_index = leaf_prim_index;
+
+        if cfg!(debug_assertions) && Some(leaf_prim_index) == self.prim_store.chase_id {
+            println!("\tis a leaf primitive for a stacking context");
+        }
+
         let mut current_pic_index = leaf_pic_index;
+        let mut cur_instance = PrimitiveInstance::new(
+            leaf_prim_index,
+            stacking_context.primitive_data_handle,
+            stacking_context.clip_chain_id,
+            stacking_context.spatial_node_index,
+        );
+
+        // If establishing a 3d context, the `cur_instance` represents
+        // a picture with all the *trailing* immediate children elements.
+        // We append this to the preserve-3D picture set and make a container picture of them.
+        if let Picture3DContext::In { root_data: Some(mut prims), ancestor_index } = stacking_context.context_3d {
+            prims.push(cur_instance.clone());
+
+            // This is the acttual picture representing our 3D hierarchy root.
+            let container_picture = PicturePrimitive::new_image(
+                self.picture_id_generator.next(),
+                None,
+                Picture3DContext::In {
+                    root_data: Some(Vec::new()),
+                    ancestor_index,
+                },
+                stacking_context.pipeline_id,
+                stacking_context.frame_output_pipeline_id,
+                true,
+                stacking_context.requested_raster_space,
+                prims,
+            );
+
+            current_pic_index = self.prim_store.create_picture(container_picture);
+            let container_prim = BrushPrimitive::new_picture(current_pic_index);
+
+            cur_instance.prim_index = self.prim_store.add_primitive(
+                &LayoutRect::zero(),
+                &max_clip,
+                PrimitiveContainer::Brush(container_prim),
+            );
+        }
 
         // For each filter, create a new image with that composite mode.
         for filter in &stacking_context.composite_ops.filters {
             let filter = filter.sanitize();
 
             let filter_picture = PicturePrimitive::new_image(
                 self.picture_id_generator.next(),
                 Some(PictureCompositeMode::Filter(filter)),
-                false,
+                Picture3DContext::Out,
                 stacking_context.pipeline_id,
                 None,
                 true,
                 stacking_context.requested_raster_space,
-                vec![
-                    PrimitiveInstance::new(
-                        current_prim_index,
-                        prim_data_handle,
-                        stacking_context.clip_chain_id,
-                        stacking_context.spatial_node_index,
-                    ),
-                ],
+                vec![cur_instance.clone()],
             );
             let filter_pic_index = self.prim_store.create_picture(filter_picture);
             current_pic_index = filter_pic_index;
 
             let filter_prim = BrushPrimitive::new_picture(filter_pic_index);
 
-            current_prim_index = self.prim_store.add_primitive(
+            cur_instance.prim_index = self.prim_store.add_primitive(
                 &LayoutRect::zero(),
                 &max_clip,
                 PrimitiveContainer::Brush(filter_prim),
             );
 
+            if cfg!(debug_assertions) && Some(cur_instance.prim_index) == self.prim_store.chase_id {
+                println!("\tis a composite picture for a stacking context with {:?}", filter);
+            }
+
             // Run the optimize pass on this picture, to see if we can
             // collapse opacity and avoid drawing to an off-screen surface.
             self.prim_store.optimize_picture_if_possible(current_pic_index);
         }
 
         // Same for mix-blend-mode.
         if let Some(mix_blend_mode) = stacking_context.composite_ops.mix_blend_mode {
             let blend_picture = PicturePrimitive::new_image(
                 self.picture_id_generator.next(),
                 Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
-                false,
+                Picture3DContext::Out,
                 stacking_context.pipeline_id,
                 None,
                 true,
                 stacking_context.requested_raster_space,
-                vec![
-                    PrimitiveInstance::new(
-                        current_prim_index,
-                        prim_data_handle,
-                        stacking_context.clip_chain_id,
-                        stacking_context.spatial_node_index,
-                    ),
-                ],
+                vec![cur_instance.clone()],
             );
             let blend_pic_index = self.prim_store.create_picture(blend_picture);
             current_pic_index = blend_pic_index;
 
             let blend_prim = BrushPrimitive::new_picture(blend_pic_index);
 
-            current_prim_index = self.prim_store.add_primitive(
+            cur_instance.prim_index = self.prim_store.add_primitive(
                 &LayoutRect::zero(),
                 &max_clip,
                 PrimitiveContainer::Brush(blend_prim),
             );
-        }
 
-        if stacking_context.establishes_3d_context {
-            // If establishing a 3d context, we need to add a picture
-            // that will be the container for all the planes and any
-            // un-transformed content.
-            let mut prims = vec![
-                PrimitiveInstance::new(
-                    current_prim_index,
-                    prim_data_handle,
-                    stacking_context.clip_chain_id,
-                    stacking_context.spatial_node_index,
-                ),
-            ];
-            prims.extend(stacking_context.preserve3d_primitives);
-
-            let container_picture = PicturePrimitive::new_image(
-                self.picture_id_generator.next(),
-                None,
-                false,
-                stacking_context.pipeline_id,
-                None,
-                true,
-                stacking_context.requested_raster_space,
-                prims,
-            );
-            let container_pic_index = self.prim_store.create_picture(container_picture);
-            current_pic_index = container_pic_index;
-
-            let container_prim = BrushPrimitive::new_picture(container_pic_index);
-
-            current_prim_index = self.prim_store.add_primitive(
-                &LayoutRect::zero(),
-                &max_clip,
-                PrimitiveContainer::Brush(container_prim),
-            );
-        } else {
-            debug_assert!(stacking_context.preserve3d_primitives.is_empty());
+            if cfg!(debug_assertions) && Some(cur_instance.prim_index) == self.prim_store.chase_id {
+                println!("\tis a mix-blend picture for a stacking context with {:?}", mix_blend_mode);
+            }
         }
 
-        if self.sc_stack.is_empty() {
-            // This must be the root stacking context
-            self.root_pic_index = current_pic_index;
-            return;
-        }
-
-        let sc_count = self.sc_stack.len();
-        let prim_instance = PrimitiveInstance::new(
-            current_prim_index,
-            prim_data_handle,
-            stacking_context.clip_chain_id,
-            stacking_context.spatial_node_index,
-        );
+        let has_mix_blend_on_secondary_framebuffer =
+            stacking_context.composite_ops.mix_blend_mode.is_some() &&
+            self.sc_stack.len() > 2;
 
-        if !stacking_context.establishes_3d_context && stacking_context.participating_in_3d_context {
-            // If we're in a 3D context, we will parent the picture
-            // to the first stacking context we find that is a
-            // 3D rendering context container. This follows the spec
-            // by hoisting these items out into the same 3D context
-            // for plane splitting.
-            let parent_index = self.sc_stack
-                .iter()
-                .rposition(|sc| sc.establishes_3d_context)
-                .unwrap();
-
-            let parent_stacking_context = &mut self.sc_stack[parent_index];
-            parent_stacking_context.preserve3d_primitives.push(prim_instance);
+        // The primitive instance for the remainder of flat children of this SC
+        // if it's a part of 3D hierarchy but not the root of it.
+        let trailing_children_instance = match self.sc_stack.last_mut() {
+            // Preserve3D path (only relevant if there are no filters/mix-blend modes)
+            Some(ref parent_sc) if parent_sc.is_3d() => {
+                Some(cur_instance)
+            }
+            // Regular parenting path
+            Some(ref mut parent_sc) => {
+                // If we have a mix-blend-mode, and we aren't the primary framebuffer,
+                // the stacking context needs to be isolated to blend correctly as per
+                // the CSS spec.
+                // If not already isolated for some other reason,
+                // make this picture as isolated.
+                if has_mix_blend_on_secondary_framebuffer {
+                    parent_sc.should_isolate = true;
+                }
+                parent_sc.primitives.push(cur_instance);
+                None
+            }
+            // This must be the root stacking context
+            None => {
+                self.root_pic_index = current_pic_index;
+                None
+            }
+        };
 
-        } else {
-            let parent_stacking_context = self.sc_stack.last_mut().unwrap();
-
-            // If we have a mix-blend-mode, and we aren't the primary framebuffer,
-            // the stacking context needs to be isolated to blend correctly as per
-            // the CSS spec.
-            // If not already isolated for some other reason,
-            // make this picture as isolated.
-            if stacking_context.composite_ops.mix_blend_mode.is_some() &&
-               sc_count > 2 {
-                parent_stacking_context.should_isolate = true;
-            }
-
-            parent_stacking_context.normal_primitives.push(prim_instance);
-        };
+        // finally, if there any outstanding 3D primitive instances,
+        // find the 3D hierarchy root and add them there.
+        if let Some(instance) = trailing_children_instance {
+            self.add_primitive_instance_to_3d_root(instance);
+        }
 
         assert!(
             self.pending_shadow_items.is_empty(),
             "Found unpopped shadows when popping stacking context!"
         );
     }
 
     pub fn push_reference_frame(
@@ -1324,48 +1337,57 @@ impl<'a> DisplayListFlattener<'a> {
         // handle to a clip chain node, parented to form a chain.
         // TODO(gw): We could re-structure this to share some of the
         //           interning and chaining code.
 
         // Build the clip sources from the supplied region.
         let handle = self
             .resources
             .clip_interner
-            .intern(&ClipItemKey::rectangle(clip_region.main, ClipMode::Clip));
+            .intern(&ClipItemKey::rectangle(clip_region.main, ClipMode::Clip), || {
+                ClipItemSceneData {
+                }
+            });
 
         parent_clip_chain_index = self
             .clip_store
             .add_clip_chain_node(
                 handle,
                 spatial_node,
                 parent_clip_chain_index,
             );
         clip_count += 1;
 
         if let Some(ref image_mask) = clip_region.image_mask {
             let handle = self
                 .resources
                 .clip_interner
-                .intern(&ClipItemKey::image_mask(image_mask));
+                .intern(&ClipItemKey::image_mask(image_mask), || {
+                    ClipItemSceneData {
+                    }
+                });
 
             parent_clip_chain_index = self
                 .clip_store
                 .add_clip_chain_node(
                     handle,
                     spatial_node,
                     parent_clip_chain_index,
                 );
             clip_count += 1;
         }
 
         for region in clip_region.complex_clips {
             let handle = self
                 .resources
                 .clip_interner
-                .intern(&ClipItemKey::rounded_rect(region.rect, region.radii, region.mode));
+                .intern(&ClipItemKey::rounded_rect(region.rect, region.radii, region.mode), || {
+                    ClipItemSceneData {
+                    }
+                });
 
             parent_clip_chain_index = self
                 .clip_store
                 .add_clip_chain_node(
                     handle,
                     spatial_node,
                     parent_clip_chain_index,
                 );
@@ -1494,17 +1516,17 @@ impl<'a> DisplayListFlattener<'a> {
                         // Create a picture that the shadow primitives will be added to. If the
                         // blur radius is 0, the code in Picture::prepare_for_render will
                         // detect this and mark the picture to be drawn directly into the
                         // parent picture, which avoids an intermediate surface and blur.
                         let blur_filter = FilterOp::Blur(std_deviation).sanitize();
                         let mut shadow_pic = PicturePrimitive::new_image(
                             self.picture_id_generator.next(),
                             Some(PictureCompositeMode::Filter(blur_filter)),
-                            false,
+                            Picture3DContext::Out,
                             pipeline_id,
                             None,
                             is_passthrough,
                             raster_space,
                             prims,
                         );
 
                         // Create the primitive to draw the shadow picture into the scene.
@@ -1512,17 +1534,23 @@ impl<'a> DisplayListFlattener<'a> {
                         let shadow_prim = BrushPrimitive::new_picture(shadow_pic_index);
                         let shadow_prim_index = self.prim_store.add_primitive(
                             &LayoutRect::zero(),
                             &max_clip,
                             PrimitiveContainer::Brush(shadow_prim),
                         );
 
                         let shadow_prim_key = PrimitiveKey::new(true);
-                        let shadow_prim_data_handle = self.resources.prim_interner.intern(&shadow_prim_key);
+                        let shadow_prim_data_handle = self.resources
+                            .prim_interner
+                            .intern(&shadow_prim_key, || {
+                                PrimitiveSceneData {
+                                }
+                            }
+                        );
 
                         let shadow_prim_instance = PrimitiveInstance::new(
                             shadow_prim_index,
                             shadow_prim_data_handle,
                             pending_shadow.clip_and_scroll.clip_chain_id,
                             pending_shadow.clip_and_scroll.spatial_node_index,
                         );
 
@@ -2117,30 +2145,42 @@ impl<'a> DisplayListFlattener<'a> {
             self.id_to_index_mapper.get_spatial_node_index(info.scroll_node_id),
             self.id_to_index_mapper.get_clip_chain_id(&info.clip_node_id())
         )
     }
 
     pub fn simple_scroll_and_clip_chain(&mut self, id: &ClipId) -> ScrollNodeAndClipChain {
         self.map_clip_and_scroll(&ClipAndScrollInfo::simple(*id))
     }
+
+    pub fn add_primitive_instance_to_3d_root(&mut self, instance: PrimitiveInstance) {
+        // find the 3D root and append to the children list
+        for sc in self.sc_stack.iter_mut().rev() {
+            match sc.context_3d {
+                Picture3DContext::In { root_data: Some(ref mut prims), .. } => {
+                    prims.push(instance);
+                    break;
+                }
+                Picture3DContext::In { .. } => {}
+                Picture3DContext::Out => panic!("Unable to find 3D root"),
+            }
+        }
+    }
 }
 
 /// Properties of a stacking context that are maintained
 /// during creation of the scene. These structures are
 /// not persisted after the initial scene build.
 struct FlattenedStackingContext {
-    /// The list of un-transformed content being
-    /// added to this stacking context.
-    normal_primitives: Vec<PrimitiveInstance>,
+    /// The list of primitive instances added to this stacking context.
+    primitives: Vec<PrimitiveInstance>,
 
-    /// The list of preserve-3d primitives that
-    /// are being hoisted to this stacking context
-    /// (implies establishes_3d_context).
-    preserve3d_primitives: Vec<PrimitiveInstance>,
+    /// The interned key for all the primitive instances associated with this
+    /// SC (but not its children);
+    primitive_data_handle: PrimitiveDataHandle,
 
     /// Whether or not the caller wants this drawn in
     /// screen space (quality) or local space (performance)
     requested_raster_space: RasterSpace,
 
     /// The positioning node for this stacking context
     spatial_node_index: SpatialNodeIndex,
 
@@ -2157,29 +2197,73 @@ struct FlattenedStackingContext {
 
     /// If true, this stacking context should be
     /// isolated by forcing an off-screen surface.
     should_isolate: bool,
 
     /// Pipeline this stacking context belongs to.
     pipeline_id: PipelineId,
 
-    /// If true, visible when backface is visible.
-    is_backface_visible: bool,
-
     /// CSS transform-style property.
     transform_style: TransformStyle,
 
-    /// If true, this stacking context establishes a new
-    /// 3d rendering context.
-    establishes_3d_context: bool,
+    /// Defines the relationship to a preserve-3D hiearachy.
+    context_3d: Picture3DContext<PrimitiveInstance>,
+}
+
+impl FlattenedStackingContext {
+    /// Return true if the stacking context has a valid preserve-3d property
+    pub fn is_3d(&self) -> bool {
+        self.transform_style == TransformStyle::Preserve3D && self.composite_ops.is_empty()
+    }
 
-    /// If true, this stacking context is part of a
-    /// surrounding 3d rendering context.
-    participating_in_3d_context: bool,
+    /// For a Preserve3D context, cut the sequence of the immediate flat children
+    /// recorded so far and generate a picture from them.
+    pub fn cut_flat_item_sequence(
+        &mut self,
+        picture_id_generator: &mut PictureIdGenerator,
+        prim_store: &mut PrimitiveStore,
+    ) -> Option<PrimitiveInstance> {
+        if !self.is_3d() || self.primitives.is_empty() {
+            return None
+        }
+        let flat_items_context_3d = match self.context_3d {
+            Picture3DContext::In { ancestor_index, .. } => Picture3DContext::In {
+                root_data: None,
+                ancestor_index,
+            },
+            Picture3DContext::Out => panic!("Unexpected out of 3D context"),
+        };
+
+        let container_picture = PicturePrimitive::new_image(
+            picture_id_generator.next(),
+            Some(PictureCompositeMode::Blit),
+            flat_items_context_3d,
+            self.pipeline_id,
+            None,
+            true,
+            self.requested_raster_space,
+            mem::replace(&mut self.primitives, Vec::new()),
+        );
+
+        let pic_index = prim_store.create_picture(container_picture);
+        let container_prim = BrushPrimitive::new_picture(pic_index);
+        let cut_prim_index = prim_store.add_primitive(
+            &LayoutRect::zero(),
+            &LayoutRect::max_rect(),
+            PrimitiveContainer::Brush(container_prim),
+        );
+
+        Some(PrimitiveInstance::new(
+            cut_prim_index,
+            self.primitive_data_handle,
+            self.clip_chain_id,
+            self.spatial_node_index,
+        ))
+    }
 }
 
 /// A primitive that is added while a shadow context is
 /// active is stored as a pending primitive and only
 /// added to pictures during pop_all_shadows.
 struct PendingPrimitive {
     clip_and_scroll: ScrollNodeAndClipChain,
     info: LayoutPrimitiveInfo,
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -4,19 +4,19 @@
 
 use api::{ColorF, DeviceIntPoint, DevicePixelScale, LayoutPixel, PicturePixel, RasterPixel};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FontRenderMode, PictureRect};
 use api::{LayoutPoint, LayoutRect, LayoutSize, PipelineId, RasterSpace, WorldPoint, WorldRect, WorldPixel};
 use clip::{ClipDataStore, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
-use gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind};
+use gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind, ZBufferIdGenerator};
 use hit_test::{HitTester, HitTestingRun};
-use internal_types::{FastHashMap};
+use internal_types::{FastHashMap, PlaneSplitter};
 use picture::{PictureCompositeMode, PictureSurface, RasterConfig};
 use prim_store::{PrimitiveIndex, PrimitiveStore, SpaceMapper, PictureIndex};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::{FrameResources, FrameId};
 use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use segment::SegmentBuilder;
@@ -82,38 +82,50 @@ pub struct FrameBuildingState<'a> {
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub special_render_passes: &'a mut SpecialRenderPasses,
     pub transforms: &'a mut TransformPalette,
     pub resources: &'a mut FrameResources,
     pub segment_builder: SegmentBuilder,
 }
 
+/// Immutable context of a picture when processing children.
+#[derive(Debug)]
 pub struct PictureContext {
     pub pic_index: PictureIndex,
     pub pipeline_id: PipelineId,
     pub apply_local_clip_rect: bool,
     pub inflation_factor: f32,
     pub allow_subpixel_aa: bool,
     pub is_passthrough: bool,
     pub raster_space: RasterSpace,
+    pub local_spatial_node_index: SpatialNodeIndex,
+    pub surface_spatial_node_index: SpatialNodeIndex,
+    pub raster_spatial_node_index: SpatialNodeIndex,
 }
 
-#[derive(Debug)]
+/// Mutable state of a picture that gets modified when
+/// the children are processed.
 pub struct PictureState {
     pub tasks: Vec<RenderTaskId>,
     pub has_non_root_coord_system: bool,
     pub is_cacheable: bool,
     pub local_rect_changed: bool,
+    /// Union rectangle of all the items in this picture.
+    pub rect: PictureRect,
     pub map_local_to_pic: SpaceMapper<LayoutPixel, PicturePixel>,
     pub map_pic_to_world: SpaceMapper<PicturePixel, WorldPixel>,
     pub map_pic_to_raster: SpaceMapper<PicturePixel, RasterPixel>,
     pub map_raster_to_world: SpaceMapper<RasterPixel, WorldPixel>,
-    pub surface_spatial_node_index: SpatialNodeIndex,
-    pub raster_spatial_node_index: SpatialNodeIndex,
+    /// Mapping from local space to the containing block, which is the root for
+    /// plane splitting and affects backface visibility.
+    pub map_local_to_containing_block: SpaceMapper<LayoutPixel, LayoutPixel>,
+    /// If the plane splitter, the primitives get added to it insted of
+    /// batching into their parent pictures.
+    pub plane_splitter: Option<PlaneSplitter>,
 }
 
 pub struct PrimitiveContext<'a> {
     pub spatial_node: &'a SpatialNode,
     pub spatial_node_index: SpatialNodeIndex,
 }
 
 impl<'a> PrimitiveContext<'a> {
@@ -230,44 +242,43 @@ impl FrameBuilder {
         let (pic_context, mut pic_state, mut instances) = self
             .prim_store
             .pictures[self.root_pic_index.0]
             .take_context(
                 self.root_pic_index,
                 &prim_context,
                 root_spatial_node_index,
                 root_spatial_node_index,
+                root_spatial_node_index,
                 true,
                 &mut frame_state,
                 &frame_context,
                 false,
             )
             .unwrap();
 
-        let mut pic_rect = PictureRect::zero();
-
         self.prim_store.prepare_primitives(
             &mut instances,
             &pic_context,
             &mut pic_state,
             &frame_context,
             &mut frame_state,
-            &mut pic_rect,
         );
 
+        let pic_rect = Some(pic_state.rect);
         let pic = &mut self.prim_store.pictures[self.root_pic_index.0];
         pic.restore_context(
             instances,
             pic_context,
             pic_state,
-            Some(pic_rect),
+            pic_rect,
             &mut frame_state,
         );
 
-        let pic_state = pic.take_state();
+        let (pic_state, _) = pic.take_state_and_context();
 
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(self.screen_rect.to_i32()),
             self.screen_rect.size.to_f32(),
             self.root_pic_index,
             DeviceIntPoint::zero(),
             pic_state.tasks,
             UvRectKind::Rect,
@@ -364,16 +375,18 @@ impl FrameBuilder {
                 required_pass_count - 1,
                 &mut passes[2..],
             );
         }
 
         let mut deferred_resolves = vec![];
         let mut has_texture_cache_tasks = false;
         let mut prim_headers = PrimitiveHeaders::new();
+        // Used to generated a unique z-buffer value per primitive.
+        let mut z_generator = ZBufferIdGenerator::new();
         let use_dual_source_blending = self.config.dual_source_blending_is_enabled &&
                                        self.config.dual_source_blending_is_supported;
 
         for pass in &mut passes {
             let mut ctx = RenderTargetContext {
                 device_pixel_scale,
                 prim_store: &self.prim_store,
                 resource_cache,
@@ -385,16 +398,17 @@ impl FrameBuilder {
             pass.build(
                 &mut ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
                 &mut transform_palette,
                 &mut prim_headers,
+                &mut z_generator,
             );
 
             if let RenderPassKind::OffScreen { ref texture_cache, .. } = pass.kind {
                 has_texture_cache_tasks |= !texture_cache.is_empty();
             }
         }
 
         let gpu_cache_frame_id = gpu_cache.end_frame(gpu_cache_profile);
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -7,26 +7,33 @@ use api::{
     PremultipliedColorF, LayoutToPictureTransform, PictureToLayoutTransform, PicturePixel,
     WorldPixel, WorldToLayoutTransform,
 };
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use gpu_cache::{GpuCacheAddress, GpuDataRequest};
 use internal_types::FastHashMap;
 use prim_store::EdgeAaSegmentMask;
 use render_task::RenderTaskAddress;
+use std::i32;
 use util::{TransformedRectKind, MatrixHelpers};
 
 // Contains type that must exactly match the same structures declared in GLSL.
 
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, Debug, PartialEq)]
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ZBufferId(i32);
 
+impl ZBufferId {
+    pub fn invalid() -> Self {
+        ZBufferId(i32::MAX)
+    }
+}
+
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ZBufferIdGenerator {
     next: i32,
 }
 
 impl ZBufferIdGenerator {
@@ -162,45 +169,43 @@ pub struct PrimitiveHeaderIndex(pub i32)
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveHeaders {
     // The integer-type headers for a primitive.
     pub headers_int: Vec<PrimitiveHeaderI>,
     // The float-type headers for a primitive.
     pub headers_float: Vec<PrimitiveHeaderF>,
-    // Used to generated a unique z-buffer value per primitive.
-    pub z_generator: ZBufferIdGenerator,
 }
 
 impl PrimitiveHeaders {
     pub fn new() -> PrimitiveHeaders {
         PrimitiveHeaders {
             headers_int: Vec::new(),
             headers_float: Vec::new(),
-            z_generator: ZBufferIdGenerator::new(),
         }
     }
 
     // Add a new primitive header.
     pub fn push(
         &mut self,
         prim_header: &PrimitiveHeader,
+        z: ZBufferId,
         user_data: [i32; 3],
     ) -> PrimitiveHeaderIndex {
         debug_assert_eq!(self.headers_int.len(), self.headers_float.len());
         let id = self.headers_float.len();
 
         self.headers_float.push(PrimitiveHeaderF {
             local_rect: prim_header.local_rect,
             local_clip_rect: prim_header.local_clip_rect,
         });
 
         self.headers_int.push(PrimitiveHeaderI {
-            z: self.z_generator.next(),
+            z,
             task_address: prim_header.task_address,
             specific_prim_address: prim_header.specific_prim_address.as_int(),
             clip_task_address: prim_header.clip_task_address,
             transform_id: prim_header.transform_id,
             user_data,
         });
 
         PrimitiveHeaderIndex(id as i32)
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -34,17 +34,18 @@ pub struct HitTestClipNode {
 }
 
 impl HitTestClipNode {
     fn new(node: &ClipNode) -> Self {
         let region = match node.item {
             ClipItem::Rectangle(ref rect, mode) => HitTestRegion::Rectangle(*rect, mode),
             ClipItem::RoundedRectangle(ref rect, ref radii, ref mode) =>
                 HitTestRegion::RoundedRectangle(*rect, *radii, *mode),
-            ClipItem::Image(ref mask, ..) => HitTestRegion::Rectangle(mask.rect, ClipMode::Clip),
+            ClipItem::Image { ref mask, .. } =>
+                HitTestRegion::Rectangle(mask.rect, ClipMode::Clip),
             ClipItem::BoxShadow(_) => HitTestRegion::Invalid,
         };
 
         HitTestClipNode {
             region,
         }
     }
 }
--- a/gfx/webrender/src/image.rs
+++ b/gfx/webrender/src/image.rs
@@ -24,28 +24,95 @@ pub fn simplify_repeated_primitive(
         prim_rect.size.width = f32::min(prim_rect.size.width, stretch_size.width);
     }
     if stride.height >= prim_rect.size.height {
         tile_spacing.height = 0.0;
         prim_rect.size.height = f32::min(prim_rect.size.height, stretch_size.height);
     }
 }
 
-pub fn for_each_repetition(
+pub struct Repetition {
+    pub origin: LayoutPoint,
+    pub edge_flags: EdgeAaSegmentMask,
+}
+
+pub struct RepetitionIterator {
+    current_x: i32,
+    x_count: i32,
+    current_y: i32,
+    y_count: i32,
+    row_flags: EdgeAaSegmentMask,
+    current_origin: LayoutPoint,
+    initial_origin: LayoutPoint,
+    stride: LayoutSize,
+}
+
+impl Iterator for RepetitionIterator {
+    type Item = Repetition;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.current_x == self.x_count {
+            self.current_y += 1;
+            if self.current_y >= self.y_count {
+                return None;
+            }
+            self.current_x = 0;
+
+            self.row_flags = EdgeAaSegmentMask::empty();
+            if self.current_y == self.y_count - 1 {
+                self.row_flags |= EdgeAaSegmentMask::BOTTOM;
+            }
+
+            self.current_origin.x = self.initial_origin.x;
+            self.current_origin.y += self.stride.height;
+        }
+
+        let mut edge_flags = self.row_flags;
+        if self.current_x == 0 {
+            edge_flags |= EdgeAaSegmentMask::LEFT;
+        }
+
+        if self.current_x == self.x_count - 1 {
+            edge_flags |= EdgeAaSegmentMask::RIGHT;
+        }
+
+        let repetition = Repetition {
+            origin: self.current_origin,
+            edge_flags: edge_flags,
+        };
+
+        self.current_origin.x += self.stride.width;
+        self.current_x += 1;
+
+        Some(repetition)
+    }
+}
+
+pub fn repetitions(
     prim_rect: &LayoutRect,
     visible_rect: &LayoutRect,
-    stride: &LayoutSize,
-    callback: &mut FnMut(&LayoutPoint, EdgeAaSegmentMask),
-) {
+    stride: LayoutSize,
+) -> RepetitionIterator {
     assert!(stride.width > 0.0);
     assert!(stride.height > 0.0);
 
     let visible_rect = match prim_rect.intersection(&visible_rect) {
-       Some(rect) => rect,
-       None => return,
+        Some(rect) => rect,
+        None => {
+            return RepetitionIterator {
+                current_origin: LayoutPoint::zero(),
+                initial_origin: LayoutPoint::zero(),
+                current_x: 0,
+                current_y: 0,
+                x_count: 0,
+                y_count: 0,
+                stride,
+                row_flags: EdgeAaSegmentMask::empty(),
+            }
+        }
     };
 
     let nx = if visible_rect.origin.x > prim_rect.origin.x {
         f32::floor((visible_rect.origin.x - prim_rect.origin.x) / stride.width)
     } else {
         0.0
     };
 
@@ -53,59 +120,119 @@ pub fn for_each_repetition(
         f32::floor((visible_rect.origin.y - prim_rect.origin.y) / stride.height)
     } else {
         0.0
     };
 
     let x0 = prim_rect.origin.x + nx * stride.width;
     let y0 = prim_rect.origin.y + ny * stride.height;
 
-    let mut p = LayoutPoint::new(x0, y0);
-
     let x_most = visible_rect.max_x();
     let y_most = visible_rect.max_y();
 
     let x_count = f32::ceil((x_most - x0) / stride.width) as i32;
     let y_count = f32::ceil((y_most - y0) / stride.height) as i32;
 
-    for y in 0..y_count {
-        let mut row_flags = EdgeAaSegmentMask::empty();
-        if y == 0 {
-            row_flags |= EdgeAaSegmentMask::TOP;
-        }
-        if y == y_count - 1 {
-            row_flags |= EdgeAaSegmentMask::BOTTOM;
-        }
+    let mut row_flags = EdgeAaSegmentMask::TOP;
+    if y_count == 1 {
+        row_flags |= EdgeAaSegmentMask::BOTTOM;
+    }
 
-        for x in 0..x_count {
-            let mut edge_flags = row_flags;
-            if x == 0 {
-                edge_flags |= EdgeAaSegmentMask::LEFT;
-            }
-            if x == x_count - 1 {
-                edge_flags |= EdgeAaSegmentMask::RIGHT;
-            }
-
-            callback(&p, edge_flags);
-
-            p.x += stride.width;
-        }
-
-        p.x = x0;
-        p.y += stride.height;
+    RepetitionIterator {
+        current_origin: LayoutPoint::new(x0, y0),
+        initial_origin: LayoutPoint::new(x0, y0),
+        current_x: 0,
+        current_y: 0,
+        x_count,
+        y_count,
+        row_flags,
+        stride,
     }
 }
 
-pub fn for_each_tile(
+#[derive(Debug)]
+pub struct Tile {
+    pub rect: LayoutRect,
+    pub offset: TileOffset,
+    pub edge_flags: EdgeAaSegmentMask,
+}
+
+pub struct TileIterator {
+    current_x: u16,
+    x_count: u16,
+    current_y: u16,
+    y_count: u16,
+    origin: TileOffset,
+    tile_size: LayoutSize,
+    leftover_offset: TileOffset,
+    leftover_size: LayoutSize,
+    local_origin: LayoutPoint,
+    row_flags: EdgeAaSegmentMask,
+}
+
+impl Iterator for TileIterator {
+    type Item = Tile;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.current_x == self.x_count {
+            self.current_y += 1;
+            if self.current_y >= self.y_count {
+                return None;
+            }
+            self.current_x = 0;
+            self.row_flags = EdgeAaSegmentMask::empty();
+            if self.current_y == self.y_count - 1 {
+                self.row_flags |= EdgeAaSegmentMask::BOTTOM;
+            }
+        }
+
+        let tile_offset = self.origin + vec2(self.current_x, self.current_y);
+
+        let mut segment_rect = LayoutRect {
+            origin: LayoutPoint::new(
+                self.local_origin.x + tile_offset.x as f32 * self.tile_size.width,
+                self.local_origin.y + tile_offset.y as f32 * self.tile_size.height,
+            ),
+            size: self.tile_size,
+        };
+
+        if tile_offset.x == self.leftover_offset.x {
+            segment_rect.size.width = self.leftover_size.width;
+        }
+
+        if tile_offset.y == self.leftover_offset.y {
+            segment_rect.size.height = self.leftover_size.height;
+        }
+
+        let mut edge_flags = self.row_flags;
+        if self.current_x == 0 {
+            edge_flags |= EdgeAaSegmentMask::LEFT;
+        }
+
+        if self.current_x == self.x_count - 1 {
+            edge_flags |= EdgeAaSegmentMask::RIGHT;
+        }
+
+        let tile = Tile {
+            rect: segment_rect,
+            offset: tile_offset,
+            edge_flags,
+        };
+
+        self.current_x += 1;
+        Some(tile)
+    }
+}
+
+pub fn tiles(
     prim_rect: &LayoutRect,
     visible_rect: &LayoutRect,
     device_image_size: &DeviceUintSize,
     device_tile_size: u32,
-    callback: &mut FnMut(&LayoutRect, TileOffset, EdgeAaSegmentMask),
-) {
+) -> TileIterator {
     // The image resource is tiled. We have to generate an image primitive
     // for each tile.
     // We need to do this because the image is broken up into smaller tiles in the texture
     // cache and the image shader is not able to work with this type of sparse representation.
 
     // The tiling logic works as follows:
     //
     //  ###################-+  -+
@@ -125,18 +252,31 @@ pub fn for_each_tile(
     // each generated segment corresponds to a tile in the texture cache, with the
     // assumption that the smaller tiles with leftover sizes are sized to fit their own
     // irregular size in the texture cache.
 
     // Because we can have very large virtual images we iterate over the visible portion of
     // the image in layer space intead of iterating over device tiles.
 
     let visible_rect = match prim_rect.intersection(&visible_rect) {
-       Some(rect) => rect,
-       None => return,
+        Some(rect) => rect,
+        None => {
+            return TileIterator {
+                current_x: 0,
+                current_y: 0,
+                x_count: 0,
+                y_count: 0,
+                row_flags: EdgeAaSegmentMask::empty(),
+                origin: TileOffset::zero(),
+                tile_size: LayoutSize::zero(),
+                leftover_offset: TileOffset::zero(),
+                leftover_size: LayoutSize::zero(),
+                local_origin: LayoutPoint::zero(),
+            }
+        }
     };
 
     let device_tile_size_f32 = device_tile_size as f32;
 
     // Ratio between (image space) tile size and image size .
     let tile_dw = device_tile_size_f32 / (device_image_size.width as f32);
     let tile_dh = device_tile_size_f32 / (device_image_size.height as f32);
 
@@ -178,56 +318,31 @@ pub fn for_each_tile(
         } else {
             0
         },
     );
 
     let x_count = f32::ceil((visible_rect.max_x() - prim_rect.origin.x) / layer_tile_size.width) as u16 - t0.x;
     let y_count = f32::ceil((visible_rect.max_y() - prim_rect.origin.y) / layer_tile_size.height) as u16 - t0.y;
 
-    for y in 0..y_count {
-
-        let mut row_flags = EdgeAaSegmentMask::empty();
-        if y == 0 {
-            row_flags |= EdgeAaSegmentMask::TOP;
-        }
-        if y == y_count - 1 {
-            row_flags |= EdgeAaSegmentMask::BOTTOM;
-        }
-
-        for x in 0..x_count {
-            let tile_offset = t0 + vec2(x, y);
-
-
-            let mut segment_rect = LayoutRect {
-                origin: LayoutPoint::new(
-                    prim_rect.origin.x + tile_offset.x as f32 * layer_tile_size.width,
-                    prim_rect.origin.y + tile_offset.y as f32 * layer_tile_size.height,
-                ),
-                size: layer_tile_size,
-            };
-
-            if tile_offset.x == leftover_offset.x {
-                segment_rect.size.width = leftover_layer_size.width;
-            }
-
-            if tile_offset.y == leftover_offset.y {
-                segment_rect.size.height = leftover_layer_size.height;
-            }
-
-            let mut edge_flags = row_flags;
-            if x == 0 {
-                edge_flags |= EdgeAaSegmentMask::LEFT;
-            }
-            if x == x_count - 1 {
-                edge_flags |= EdgeAaSegmentMask::RIGHT;
-            }
-
-            callback(&segment_rect, tile_offset, edge_flags);
-        }
+    let mut row_flags = EdgeAaSegmentMask::TOP;
+    if y_count == 1 {
+        row_flags |= EdgeAaSegmentMask::BOTTOM;
+    }
+    TileIterator {
+        current_x: 0,
+        current_y: 0,
+        x_count,
+        y_count,
+        row_flags,
+        origin: t0,
+        tile_size: layer_tile_size,
+        leftover_offset,
+        leftover_size: leftover_layer_size,
+        local_origin: prim_rect.origin,
     }
 }
 
 pub fn compute_tile_range(
     visible_area: &DeviceUintRect,
     tile_size: u16,
 ) -> TileRange {
     // Tile dimensions in normalized coordinates.
@@ -247,17 +362,17 @@ pub fn compute_tile_range(
     TileRange {
         origin: t0,
         size: (t1 - t0).to_size(),
     }
 }
 
 pub fn for_each_tile_in_range(
     range: &TileRange,
-    callback: &mut FnMut(TileOffset),
+    mut callback: impl FnMut(TileOffset),
 ) {
     for y in 0..range.size.height {
         for x in 0..range.size.width {
             callback(range.origin + vec2(x, y));
         }
     }
 }
 
@@ -272,30 +387,30 @@ mod tests {
     fn checked_for_each_tile(
         prim_rect: &LayoutRect,
         visible_rect: &LayoutRect,
         device_image_size: &DeviceUintSize,
         device_tile_size: u32,
         callback: &mut FnMut(&LayoutRect, TileOffset, EdgeAaSegmentMask),
     ) {
         let mut coverage = LayoutRect::zero();
-        let mut tiles = HashSet::new();
-        for_each_tile(prim_rect,
-                      visible_rect,
-                      device_image_size,
-                      device_tile_size,
-                      &mut |tile_rect, tile_offset, tile_flags| {
-                          // make sure we don't get sent duplicate tiles
-                          assert!(!tiles.contains(&tile_offset));
-                          tiles.insert(tile_offset);
-                          coverage = coverage.union(tile_rect);
-                          assert!(prim_rect.contains_rect(&tile_rect));
-                          callback(tile_rect, tile_offset, tile_flags);
-                      },
-        );
+        let mut seen_tiles = HashSet::new();
+        for tile in tiles(
+            prim_rect,
+            visible_rect,
+            device_image_size,
+            device_tile_size,
+        ) {
+            // make sure we don't get sent duplicate tiles
+            assert!(!seen_tiles.contains(&tile.offset));
+            seen_tiles.insert(tile.offset);
+            coverage = coverage.union(&tile.rect);
+            assert!(prim_rect.contains_rect(&tile.rect));
+            callback(&tile.rect, tile.offset, tile.edge_flags);
+        }
         assert!(prim_rect.contains_rect(&coverage));
         assert!(coverage.contains_rect(&visible_rect.intersection(&prim_rect).unwrap_or(LayoutRect::zero())));
     }
 
     #[test]
     fn basic() {
         let mut count = 0;
         checked_for_each_tile(&rect(0., 0., 1000., 1000.),
--- a/gfx/webrender/src/intern.rs
+++ b/gfx/webrender/src/intern.rs
@@ -168,77 +168,79 @@ impl<S, T, M> ops::IndexMut<Handle<M>> f
 
 /// The main interning data structure. This lives in the
 /// scene builder thread, and handles hashing and interning
 /// unique data structures. It also manages a free-list for
 /// the items in the data store, which is synchronized via
 /// an update list of additions / removals.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct Interner<S : Eq + Hash + Clone + Debug, M> {
+pub struct Interner<S : Eq + Hash + Clone + Debug, D, M> {
     /// Uniquely map an interning key to a handle
     map: FastHashMap<S, Handle<M>>,
     /// List of free slots in the data store for re-use.
     free_list: Vec<usize>,
-    /// The next index to append items to if free-list is empty.
-    next_index: usize,
     /// Pending list of updates that need to be applied.
     updates: Vec<Update<S>>,
     /// The current epoch for the interner.
     current_epoch: Epoch,
+    /// The information associated with each interned
+    /// item that can be accessed by the interner.
+    local_data: Vec<Item<D>>,
 }
 
-impl<S, M> Interner<S, M> where S: Eq + Hash + Clone + Debug, M: Copy + Debug {
+impl<S, D, M> Interner<S, D, M> where S: Eq + Hash + Clone + Debug, M: Copy + Debug {
     /// Construct a new interner
     pub fn new() -> Self {
         Interner {
             map: FastHashMap::default(),
             free_list: Vec::new(),
-            next_index: 0,
             updates: Vec::new(),
             current_epoch: Epoch(1),
+            local_data: Vec::new(),
         }
     }
 
     /// Intern a data structure, and return a handle to
     /// that data. The handle can then be stored in the
     /// frame builder, and safely accessed via the data
     /// store that lives in the frame builder thread.
-    pub fn intern(
+    /// The provided closure is invoked to build the
+    /// local data about an interned structure if the
+    /// key isn't already interned.
+    pub fn intern<F>(
         &mut self,
         data: &S,
-    ) -> Handle<M> {
+        f: F,
+    ) -> Handle<M> where F: FnOnce() -> D {
         // Use get_mut rather than entry here to avoid
         // cloning the (sometimes large) key in the common
         // case, where the data already exists in the interner.
         if let Some(handle) = self.map.get_mut(data) {
             // Update the epoch in the data store. This
             // is not strictly needed for correctness, but
             // is used to ensure items are only accessed
             // via valid handles.
             if handle.epoch != self.current_epoch {
                 self.updates.push(Update {
                     index: handle.index,
                     kind: UpdateKind::UpdateEpoch,
-                })
+                });
+                self.local_data[handle.index].epoch = self.current_epoch;
             }
             handle.epoch = self.current_epoch;
             return *handle;
         }
 
         // We need to intern a new data item. First, find out
         // if there is a spare slot in the free-list that we
         // can use. Otherwise, append to the end of the list.
         let index = match self.free_list.pop() {
             Some(index) => index,
-            None => {
-                let index = self.next_index;
-                self.next_index += 1;
-                index
-            }
+            None => self.local_data.len(),
         };
 
         // Add a pending update to insert the new data.
         self.updates.push(Update {
             index,
             kind: UpdateKind::Insert(data.clone()),
         });
 
@@ -248,16 +250,28 @@ impl<S, M> Interner<S, M> where S: Eq + 
             epoch: self.current_epoch,
             _marker: PhantomData,
         };
 
         // Store this handle so the next time it is
         // interned, it gets re-used.
         self.map.insert(data.clone(), handle);
 
+        // Create the local data for this item that is
+        // being interned.
+        let local_item = Item {
+            epoch: self.current_epoch,
+            data: f(),
+        };
+        if self.local_data.len() == index {
+            self.local_data.push(local_item);
+        } else {
+            self.local_data[index] = local_item;
+        }
+
         handle
     }
 
     /// Retrieve the pending list of updates for an interner
     /// that need to be applied to the data store. Also run
     /// a GC step that removes old entries.
     pub fn end_frame_and_get_pending_updates(&mut self) -> UpdateList<S> {
         let mut updates = mem::replace(&mut self.updates, Vec::new());
@@ -294,8 +308,18 @@ impl<S, M> Interner<S, M> where S: Eq + 
         };
 
         // Begin the next epoch
         self.current_epoch = Epoch(self.current_epoch.0 + 1);
 
         updates
     }
 }
+
+/// Retrieve the local data for an item from the interner via handle
+impl<S, D, M> ops::Index<Handle<M>> for Interner<S, D, M> where S: Eq + Clone + Hash + Debug, M: Copy + Debug {
+    type Output = D;
+    fn index(&self, handle: Handle<M>) -> &D {
+        let item = &self.local_data[handle.index];
+        assert_eq!(item.epoch, handle.epoch);
+        &item.data
+    }
+}
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -1,18 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{DebugCommand, DeviceUintRect, DocumentId, ExternalImageData, ExternalImageId};
-use api::{ImageFormat, NotificationRequest};
+use api::{ImageFormat, WorldPixel, NotificationRequest};
 use device::TextureFilter;
 use renderer::PipelineInfo;
 use gpu_cache::GpuCacheUpdateList;
 use fxhash::FxHasher;
+use plane_split::BspSplitter;
 use profiler::BackendProfileCounters;
 use std::{usize, i32};
 use std::collections::{HashMap, HashSet};
 use std::f32;
 use std::hash::BuildHasherDefault;
 use std::path::PathBuf;
 use std::sync::Arc;
 
@@ -20,16 +21,19 @@ use std::sync::Arc;
 use capture::{CaptureConfig, ExternalCaptureImage};
 #[cfg(feature = "replay")]
 use capture::PlainExternalImage;
 use tiling;
 
 pub type FastHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FxHasher>>;
 pub type FastHashSet<K> = HashSet<K, BuildHasherDefault<FxHasher>>;
 
+/// A concrete plane splitter type used in WebRender.
+pub type PlaneSplitter = BspSplitter<f64, WorldPixel>;
+
 /// An ID for a texture that is owned by the `texture_cache` module.
 ///
 /// This can include atlases or standalone textures allocated via the texture
 /// cache (e.g.  if an image is too large to be added to an atlas). The texture
 /// cache manages the allocation and freeing of these IDs, and the rendering
 /// thread maintains a map from cache texture ID to native texture.
 ///
 /// We never reuse IDs, so we use a u64 here to be safe.
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -3,23 +3,26 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{DeviceRect, FilterOp, MixBlendMode, PipelineId, PremultipliedColorF, PictureRect, PicturePoint};
 use api::{DeviceIntRect, DeviceIntSize, DevicePoint, LayoutRect, PictureToRasterTransform};
 use api::{DevicePixelScale, PictureIntPoint, PictureIntRect, PictureIntSize, RasterRect, RasterSpace};
 use api::{PicturePixel, RasterPixel, WorldPixel};
 use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip::ClipNodeCollector;
-use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
-use euclid::TypedScale;
+use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
+use euclid::{TypedScale, vec3};
+use internal_types::PlaneSplitter;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
 use frame_builder::{PictureContext, PrimitiveContext};
-use gpu_cache::{GpuCacheHandle};
-use gpu_types::UvRectKind;
-use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, get_raster_rects};
+use gpu_cache::{GpuCacheAddress, GpuCacheHandle};
+use gpu_types::{TransformPalette, TransformPaletteId, UvRectKind};
+use plane_split::{Clipper, Polygon, Splitter};
+use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper};
+use prim_store::{get_raster_rects};
 use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle};
 use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use scene::{FilterOpHelpers, SceneProperties};
 use std::mem;
 use tiling::RenderTargetKind;
 use util::{TransformedRectKind, MatrixHelpers, MaxRect};
 
 /*
@@ -148,21 +151,47 @@ pub struct PictureCacheKey {
 
     // Ensure that if the overall size of the picture
     // changes, the cache key will not match. This can
     // happen, for example, during zooming or changes
     // in device-pixel-ratio.
     unclipped_size: DeviceIntSize,
 }
 
-#[derive(Debug)]
+/// Enum value describing the place of a picture in a 3D context.
+#[derive(Clone, Debug)]
+pub enum Picture3DContext<C> {
+    /// The picture is not a part of 3D context sub-hierarchy.
+    Out,
+    /// The picture is a part of 3D context.
+    In {
+        /// Additional data per child for the case of this a root of 3D hierarchy.
+        root_data: Option<Vec<C>>,
+        /// The spatial node index of an "ancestor" element, i.e. one
+        /// that establishes the transformed element’s containing block.
+        ///
+        /// See CSS spec draft for more details:
+        /// https://drafts.csswg.org/css-transforms-2/#accumulated-3d-transformation-matrix-computation
+        ancestor_index: SpatialNodeIndex,
+    },
+}
+
+/// Information about a preserve-3D hierarchy child that has been plane-split
+/// and ordered according to the view direction.
+#[derive(Clone, Debug)]
+pub struct OrderedPictureChild {
+    pub anchor: usize,
+    pub transform_id: TransformPaletteId,
+    pub gpu_address: GpuCacheAddress,
+}
+
 pub struct PicturePrimitive {
     // List of primitive runs that make up this picture.
     pub prim_instances: Vec<PrimitiveInstance>,
-    pub state: Option<PictureState>,
+    pub state: Option<(PictureState, PictureContext)>,
 
     // The pipeline that the primitives on this picture belong to.
     pub pipeline_id: PipelineId,
 
     // If true, apply the local clip rect to primitive drawn
     // in this picture.
     pub apply_local_clip_rect: bool,
 
@@ -176,18 +205,18 @@ pub struct PicturePrimitive {
     /// How this picture should be composited.
     /// If None, don't composite - just draw directly on parent surface.
     pub requested_composite_mode: Option<PictureCompositeMode>,
     /// Requested rasterization space for this picture. It is
     /// a performance hint only.
     pub requested_raster_space: RasterSpace,
 
     pub raster_config: Option<RasterConfig>,
-    // If true, this picture is part of a 3D context.
-    pub is_in_3d_context: bool,
+    pub context_3d: Picture3DContext<OrderedPictureChild>,
+
     // If requested as a frame output (for rendering
     // pages to a texture), this is the pipeline this
     // picture is the root of.
     pub frame_output_pipeline_id: Option<PipelineId>,
     // An optional cache handle for storing extra data
     // in the GPU cache, depending on the type of
     // picture.
     pub extra_gpu_data_handle: GpuCacheHandle,
@@ -211,43 +240,44 @@ impl PicturePrimitive {
             }
             _ => true,
         }
     }
 
     pub fn new_image(
         id: PictureId,
         requested_composite_mode: Option<PictureCompositeMode>,
-        is_in_3d_context: bool,
+        context_3d: Picture3DContext<OrderedPictureChild>,
         pipeline_id: PipelineId,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
         requested_raster_space: RasterSpace,
         prim_instances: Vec<PrimitiveInstance>,
     ) -> Self {
         PicturePrimitive {
             prim_instances,
             state: None,
             secondary_render_task_id: None,
             requested_composite_mode,
             raster_config: None,
-            is_in_3d_context,
+            context_3d,
             frame_output_pipeline_id,
             extra_gpu_data_handle: GpuCacheHandle::new(),
             apply_local_clip_rect,
             pipeline_id,
             id,
             requested_raster_space,
         }
     }
 
     pub fn take_context(
         &mut self,
         pic_index: PictureIndex,
         prim_context: &PrimitiveContext,
+        parent_spatial_node_index: SpatialNodeIndex,
         surface_spatial_node_index: SpatialNodeIndex,
         raster_spatial_node_index: SpatialNodeIndex,
         parent_allows_subpixel_aa: bool,
         frame_state: &mut FrameBuildingState,
         frame_context: &FrameBuildingContext,
         is_chased: bool,
     ) -> Option<(PictureContext, PictureState, Vec<PrimitiveInstance>)> {
         if !self.resolve_scene_properties(frame_context.scene_properties) {
@@ -299,17 +329,17 @@ impl PicturePrimitive {
         let raster_spatial_node_index = if establishes_raster_root {
             surface_spatial_node_index
         } else {
             raster_spatial_node_index
         };
 
         if has_surface {
             frame_state.clip_store
-                       .push_surface(surface_spatial_node_index);
+                .push_surface(surface_spatial_node_index);
         }
 
         let map_pic_to_world = SpaceMapper::new_with_target(
             ROOT_SPATIAL_NODE_INDEX,
             surface_spatial_node_index,
             frame_context.world_rect,
             frame_context.clip_scroll_tree,
         );
@@ -323,35 +353,53 @@ impl PicturePrimitive {
         );
 
         let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
             surface_spatial_node_index,
             raster_spatial_node_index,
             frame_context,
         );
 
+        let (containing_block_index, plane_splitter) = match self.context_3d {
+            Picture3DContext::Out => {
+                (parent_spatial_node_index, None)
+            }
+            Picture3DContext::In { root_data: Some(_), ancestor_index } => {
+                (ancestor_index, Some(PlaneSplitter::new()))
+            }
+            Picture3DContext::In { root_data: None, ancestor_index } => {
+                (ancestor_index, None)
+            }
+        };
+
+        let map_local_to_containing_block = SpaceMapper::new(
+            containing_block_index,
+            LayoutRect::zero(), // bounds aren't going to be used for this mapping
+        );
+
         self.raster_config = actual_composite_mode.map(|composite_mode| {
             RasterConfig {
                 composite_mode,
                 surface: None,
                 raster_spatial_node_index,
             }
         });
 
         let state = PictureState {
             tasks: Vec::new(),
             has_non_root_coord_system: false,
             is_cacheable: true,
             local_rect_changed: false,
-            raster_spatial_node_index,
-            surface_spatial_node_index,
+            rect: PictureRect::zero(),
             map_local_to_pic,
             map_pic_to_world,
             map_pic_to_raster,
             map_raster_to_world,
+            map_local_to_containing_block,
+            plane_splitter,
         };
 
         // Disallow subpixel AA if an intermediate surface is needed.
         // TODO(lsalzman): allow overriding parent if intermediate surface is opaque
         let allow_subpixel_aa = parent_allows_subpixel_aa &&
             self.raster_config.is_none();
 
         let inflation_factor = match self.raster_config {
@@ -368,34 +416,34 @@ impl PicturePrimitive {
         let context = PictureContext {
             pic_index,
             pipeline_id: self.pipeline_id,
             apply_local_clip_rect: self.apply_local_clip_rect,
             inflation_factor,
             allow_subpixel_aa,
             is_passthrough: self.raster_config.is_none(),
             raster_space,
+            local_spatial_node_index: prim_context.spatial_node_index,
+            raster_spatial_node_index,
+            surface_spatial_node_index,
         };
 
         let instances = mem::replace(&mut self.prim_instances, Vec::new());
 
         Some((context, state, instances))
     }
 
     pub fn restore_context(
         &mut self,
         prim_instances: Vec<PrimitiveInstance>,
         context: PictureContext,
         state: PictureState,
         local_rect: Option<PictureRect>,
         frame_state: &mut FrameBuildingState,
     ) -> (LayoutRect, Option<ClipNodeCollector>) {
-        self.prim_instances = prim_instances;
-        self.state = Some(state);
-
         let local_rect = match local_rect {
             Some(local_rect) => {
                 let local_content_rect = LayoutRect::from_untyped(&local_rect.to_untyped());
 
                 match self.raster_config {
                     Some(RasterConfig { composite_mode: PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)), .. }) => {
                         let inflate_size = (blur_radius * BLUR_SAMPLE_SCALE).ceil();
                         local_content_rect.inflate(inflate_size, inflate_size)
@@ -426,358 +474,475 @@ impl PicturePrimitive {
         };
 
         let clip_node_collector = if context.is_passthrough {
             None
         } else {
             Some(frame_state.clip_store.pop_surface())
         };
 
+        self.prim_instances = prim_instances;
+        self.state = Some((state, context));
+
         (local_rect, clip_node_collector)
     }
 
-    pub fn take_state(&mut self) -> PictureState {
+    pub fn take_state_and_context(&mut self) -> (PictureState, PictureContext) {
         self.state.take().expect("bug: no state present!")
     }
 
+    /// Add a primitive instance to the plane splitter. The function would generate
+    /// an appropriate polygon, clip it against the frustum, and register with the
+    /// given plane splitter.
+    pub fn add_split_plane(
+        splitter: &mut PlaneSplitter,
+        transforms: &TransformPalette,
+        prim_instance: &PrimitiveInstance,
+        original_local_rect: LayoutRect,
+        plane_split_anchor: usize,
+    ) -> bool {
+        let transform = transforms
+            .get_world_transform(prim_instance.spatial_node_index);
+        let matrix = transform.cast();
+
+        // Apply the local clip rect here, before splitting. This is
+        // because the local clip rect can't be applied in the vertex
+        // shader for split composites, since we are drawing polygons
+        // rather that rectangles. The interpolation still works correctly
+        // since we determine the UVs by doing a bilerp with a factor
+        // from the original local rect.
+        let local_rect = match original_local_rect
+            .intersection(&prim_instance.combined_local_clip_rect)
+        {
+            Some(rect) => rect.cast(),
+            None => return false,
+        };
+
+        match transform.transform_kind() {
+            TransformedRectKind::AxisAligned => {
+                let inv_transform = transforms
+                    .get_world_inv_transform(prim_instance.spatial_node_index);
+                let polygon = Polygon::from_transformed_rect_with_inverse(
+                    local_rect,
+                    &matrix,
+                    &inv_transform.cast(),
+                    plane_split_anchor,
+                ).unwrap();
+                splitter.add(polygon);
+            }
+            TransformedRectKind::Complex => {
+                let mut clipper = Clipper::new();
+                let results = clipper.clip_transformed(
+                    Polygon::from_rect(
+                        local_rect,
+                        plane_split_anchor,
+                    ),
+                    &matrix,
+                    prim_instance.clipped_world_rect.map(|r| r.to_f64()),
+                );
+                if let Ok(results) = results {
+                    for poly in results {
+                        splitter.add(poly);
+                    }
+                }
+            }
+        }
+
+        true
+    }
+
+    pub fn resolve_split_planes(
+        &mut self,
+        splitter: &mut PlaneSplitter,
+        frame_state: &mut FrameBuildingState,
+        clip_scroll_tree: &ClipScrollTree,
+    ) {
+        let ordered = match self.context_3d {
+            Picture3DContext::In { root_data: Some(ref mut list), .. } => list,
+            _ => panic!("Expected to find 3D context root"),
+        };
+        ordered.clear();
+
+        // Process the accumulated split planes and order them for rendering.
+        // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
+        for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
+            let spatial_node_index = self.prim_instances[poly.anchor].spatial_node_index;
+
+            let transform = frame_state.transforms.get_world_inv_transform(spatial_node_index);
+            let transform_id = frame_state.transforms.get_id(
+                spatial_node_index,
+                ROOT_SPATIAL_NODE_INDEX,
+                clip_scroll_tree,
+            );
+
+            let local_points = [
+                transform.transform_point3d(&poly.points[0].cast()).unwrap(),
+                transform.transform_point3d(&poly.points[1].cast()).unwrap(),
+                transform.transform_point3d(&poly.points[2].cast()).unwrap(),
+                transform.transform_point3d(&poly.points[3].cast()).unwrap(),
+            ];
+            let gpu_blocks = [
+                [local_points[0].x, local_points[0].y, local_points[1].x, local_points[1].y].into(),
+                [local_points[2].x, local_points[2].y, local_points[3].x, local_points[3].y].into(),
+            ];
+            let gpu_handle = frame_state.gpu_cache.push_per_frame_blocks(&gpu_blocks);
+            let gpu_address = frame_state.gpu_cache.get_address(&gpu_handle);
+
+            ordered.push(OrderedPictureChild {
+                anchor: poly.anchor,
+                transform_id,
+                gpu_address,
+            });
+        }
+    }
+
     pub fn prepare_for_render(
         &mut self,
         pic_index: PictureIndex,
         prim_instance: &PrimitiveInstance,
         prim_local_rect: &LayoutRect,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) -> bool {
-        let mut pic_state_for_children = self.take_state();
+        let (mut pic_state_for_children, pic_context) = self.take_state_and_context();
+
+        if let Some(ref mut splitter) = pic_state_for_children.plane_splitter {
+            self.resolve_split_planes(
+                splitter,
+                frame_state,
+                frame_context.clip_scroll_tree,
+            );
+        }
+
+        let raster_config = match self.raster_config {
+            Some(ref mut raster_config) => raster_config,
+            None => {
+                pic_state.tasks.extend(pic_state_for_children.tasks);
+                return true
+            }
+        };
+
+        let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
+            prim_instance.spatial_node_index,
+            raster_config.raster_spatial_node_index,
+            frame_context,
+        );
+
+        let pic_rect = PictureRect::from_untyped(&prim_local_rect.to_untyped());
 
-        match self.raster_config {
-            Some(ref mut raster_config) => {
-                let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
-                    prim_instance.spatial_node_index,
-                    raster_config.raster_spatial_node_index,
-                    frame_context,
+        let (clipped, unclipped, transform) = match get_raster_rects(
+            pic_rect,
+            &map_pic_to_raster,
+            &map_raster_to_world,
+            prim_instance.clipped_world_rect.expect("bug1"),
+            frame_context.device_pixel_scale,
+        ) {
+            Some(info) => info,
+            None => return false,
+        };
+
+        // TODO(gw): Almost all of the Picture types below use extra_gpu_cache_data
+        //           to store the same type of data. The exception is the filter
+        //           with a ColorMatrix, which stores the color matrix here. It's
+        //           probably worth tidying this code up to be a bit more consistent.
+        //           Perhaps store the color matrix after the common data, even though
+        //           it's not used by that shader.
+
+        match raster_config.composite_mode {
+            PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)) => {
+                let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
+                let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
+
+                // The clipped field is the part of the picture that is visible
+                // on screen. The unclipped field is the screen-space rect of
+                // the complete picture, if no screen / clip-chain was applied
+                // (this includes the extra space for blur region). To ensure
+                // that we draw a large enough part of the picture to get correct
+                // blur results, inflate that clipped area by the blur range, and
+                // then intersect with the total screen rect, to minimize the
+                // allocation size.
+                let device_rect = clipped
+                    .inflate(blur_range, blur_range)
+                    .intersection(&unclipped.to_i32())
+                    .unwrap();
+
+                let uv_rect_kind = calculate_uv_rect_kind(
+                    &pic_rect,
+                    &transform,
+                    &device_rect,
+                    frame_context.device_pixel_scale,
                 );
 
-                let pic_rect = PictureRect::from_untyped(&prim_local_rect.to_untyped());
+                // If we are drawing a blur that has primitives or clips that contain
+                // a complex coordinate system, don't bother caching them (for now).
+                // It's likely that they are animating and caching may not help here
+                // anyway. In the future we should relax this a bit, so that we can
+                // cache tasks with complex coordinate systems if we detect the
+                // relevant transforms haven't changed from frame to frame.
+                let surface = if pic_state_for_children.has_non_root_coord_system ||
+                                 !pic_state_for_children.is_cacheable {
+                    let picture_task = RenderTask::new_picture(
+                        RenderTaskLocation::Dynamic(None, device_rect.size),
+                        unclipped.size,
+                        pic_index,
+                        device_rect.origin,
+                        pic_state_for_children.tasks,
+                        uv_rect_kind,
+                        pic_context.raster_spatial_node_index,
+                    );
 
-                let (clipped, unclipped, transform) = match get_raster_rects(
-                    pic_rect,
-                    &map_pic_to_raster,
-                    &map_raster_to_world,
-                    prim_instance.clipped_world_rect.expect("bug1"),
-                    frame_context.device_pixel_scale,
-                ) {
-                    Some(info) => info,
-                    None => return false,
-                };
+                    let picture_task_id = frame_state.render_tasks.add(picture_task);
 
-                // TODO(gw): Almost all of the Picture types below use extra_gpu_cache_data
-                //           to store the same type of data. The exception is the filter
-                //           with a ColorMatrix, which stores the color matrix here. It's
-                //           probably worth tidying this code up to be a bit more consistent.
-                //           Perhaps store the color matrix after the common data, even though
-                //           it's not used by that shader.
+                    let blur_render_task = RenderTask::new_blur(
+                        blur_std_deviation,
+                        picture_task_id,
+                        frame_state.render_tasks,
+                        RenderTargetKind::Color,
+                        ClearMode::Transparent,
+                    );
 
-                match raster_config.composite_mode {
-                    PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)) => {
-                        let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
-                        let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
+                    let render_task_id = frame_state.render_tasks.add(blur_render_task);
+
+                    pic_state.tasks.push(render_task_id);
 
-                        // The clipped field is the part of the picture that is visible
-                        // on screen. The unclipped field is the screen-space rect of
-                        // the complete picture, if no screen / clip-chain was applied
-                        // (this includes the extra space for blur region). To ensure
-                        // that we draw a large enough part of the picture to get correct
-                        // blur results, inflate that clipped area by the blur range, and
-                        // then intersect with the total screen rect, to minimize the
-                        // allocation size.
-                        let device_rect = clipped
-                            .inflate(blur_range, blur_range)
-                            .intersection(&unclipped.to_i32())
-                            .unwrap();
+                    PictureSurface::RenderTask(render_task_id)
+                } else {
+                    // Get the relative clipped rect within the overall prim rect, that
+                    // forms part of the cache key.
+                    let pic_relative_render_rect = PictureIntRect::new(
+                        PictureIntPoint::new(
+                            device_rect.origin.x - unclipped.origin.x as i32,
+                            device_rect.origin.y - unclipped.origin.y as i32,
+                        ),
+                        PictureIntSize::new(
+                            device_rect.size.width,
+                            device_rect.size.height,
+                        ),
+                    );
 
-                        let uv_rect_kind = calculate_uv_rect_kind(
-                            &pic_rect,
-                            &transform,
-                            &device_rect,
-                            frame_context.device_pixel_scale,
-                        );
+                    // Request a render task that will cache the output in the
+                    // texture cache.
+                    let cache_item = frame_state.resource_cache.request_render_task(
+                        RenderTaskCacheKey {
+                            size: device_rect.size,
+                            kind: RenderTaskCacheKeyKind::Picture(PictureCacheKey {
+                                scene_id: frame_context.scene_id,
+                                picture_id: self.id,
+                                unclipped_size: unclipped.size.to_i32(),
+                                pic_relative_render_rect,
+                            }),
+                        },
+                        frame_state.gpu_cache,
+                        frame_state.render_tasks,
+                        None,
+                        false,
+                        |render_tasks| {
+                            let child_tasks = mem::replace(&mut pic_state_for_children.tasks, Vec::new());
 
-                        // If we are drawing a blur that has primitives or clips that contain
-                        // a complex coordinate system, don't bother caching them (for now).
-                        // It's likely that they are animating and caching may not help here
-                        // anyway. In the future we should relax this a bit, so that we can
-                        // cache tasks with complex coordinate systems if we detect the
-                        // relevant transforms haven't changed from frame to frame.
-                        let surface = if pic_state_for_children.has_non_root_coord_system ||
-                                         !pic_state_for_children.is_cacheable {
                             let picture_task = RenderTask::new_picture(
                                 RenderTaskLocation::Dynamic(None, device_rect.size),
                                 unclipped.size,
                                 pic_index,
                                 device_rect.origin,
-                                pic_state_for_children.tasks,
+                                child_tasks,
                                 uv_rect_kind,
-                                pic_state_for_children.raster_spatial_node_index,
+                                pic_context.raster_spatial_node_index,
                             );
 
-                            let picture_task_id = frame_state.render_tasks.add(picture_task);
+                            let picture_task_id = render_tasks.add(picture_task);
 
                             let blur_render_task = RenderTask::new_blur(
                                 blur_std_deviation,
                                 picture_task_id,
-                                frame_state.render_tasks,
+                                render_tasks,
                                 RenderTargetKind::Color,
                                 ClearMode::Transparent,
                             );
 
-                            let render_task_id = frame_state.render_tasks.add(blur_render_task);
+                            let render_task_id = render_tasks.add(blur_render_task);
 
                             pic_state.tasks.push(render_task_id);
 
-                            PictureSurface::RenderTask(render_task_id)
-                        } else {
-                            // Get the relative clipped rect within the overall prim rect, that
-                            // forms part of the cache key.
-                            let pic_relative_render_rect = PictureIntRect::new(
-                                PictureIntPoint::new(
-                                    device_rect.origin.x - unclipped.origin.x as i32,
-                                    device_rect.origin.y - unclipped.origin.y as i32,
-                                ),
-                                PictureIntSize::new(
-                                    device_rect.size.width,
-                                    device_rect.size.height,
-                                ),
-                            );
+                            render_task_id
+                        }
+                    );
+
+                    PictureSurface::TextureCache(cache_item)
+                };
+
+                raster_config.surface = Some(surface);
+            }
+            PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color)) => {
+                let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
+                let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
 
-                            // Request a render task that will cache the output in the
-                            // texture cache.
-                            let cache_item = frame_state.resource_cache.request_render_task(
-                                RenderTaskCacheKey {
-                                    size: device_rect.size,
-                                    kind: RenderTaskCacheKeyKind::Picture(PictureCacheKey {
-                                        scene_id: frame_context.scene_id,
-                                        picture_id: self.id,
-                                        unclipped_size: unclipped.size.to_i32(),
-                                        pic_relative_render_rect,
-                                    }),
-                                },
-                                frame_state.gpu_cache,
-                                frame_state.render_tasks,
-                                None,
-                                false,
-                                |render_tasks| {
-                                    let child_tasks = mem::replace(&mut pic_state_for_children.tasks, Vec::new());
+                // The clipped field is the part of the picture that is visible
+                // on screen. The unclipped field is the screen-space rect of
+                // the complete picture, if no screen / clip-chain was applied
+                // (this includes the extra space for blur region). To ensure
+                // that we draw a large enough part of the picture to get correct
+                // blur results, inflate that clipped area by the blur range, and
+                // then intersect with the total screen rect, to minimize the
+                // allocation size.
+                let device_rect = clipped
+                    .inflate(blur_range, blur_range)
+                    .intersection(&unclipped.to_i32())
+                    .unwrap();
 
-                                    let picture_task = RenderTask::new_picture(
-                                        RenderTaskLocation::Dynamic(None, device_rect.size),
-                                        unclipped.size,
-                                        pic_index,
-                                        device_rect.origin,
-                                        child_tasks,
-                                        uv_rect_kind,
-                                        pic_state_for_children.raster_spatial_node_index,
-                                    );
-
-                                    let picture_task_id = render_tasks.add(picture_task);
+                let uv_rect_kind = calculate_uv_rect_kind(
+                    &pic_rect,
+                    &transform,
+                    &device_rect,
+                    frame_context.device_pixel_scale,
+                );
 
-                                    let blur_render_task = RenderTask::new_blur(
-                                        blur_std_deviation,
-                                        picture_task_id,
-                                        render_tasks,
-                                        RenderTargetKind::Color,
-                                        ClearMode::Transparent,
-                                    );
+                let mut picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, device_rect.size),
+                    unclipped.size,
+                    pic_index,
+                    device_rect.origin,
+                    pic_state_for_children.tasks,
+                    uv_rect_kind,
+                    pic_context.raster_spatial_node_index,
+                );
+                picture_task.mark_for_saving();
 
-                                    let render_task_id = render_tasks.add(blur_render_task);
-
-                                    pic_state.tasks.push(render_task_id);
+                let picture_task_id = frame_state.render_tasks.add(picture_task);
 
-                                    render_task_id
-                                }
-                            );
+                let blur_render_task = RenderTask::new_blur(
+                    blur_std_deviation.round(),
+                    picture_task_id,
+                    frame_state.render_tasks,
+                    RenderTargetKind::Color,
+                    ClearMode::Transparent,
+                );
 
-                            PictureSurface::TextureCache(cache_item)
-                        };
+                self.secondary_render_task_id = Some(picture_task_id);
 
-                        raster_config.surface = Some(surface);
-                    }
-                    PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color)) => {
-                        let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
-                        let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
+                let render_task_id = frame_state.render_tasks.add(blur_render_task);
+                pic_state.tasks.push(render_task_id);
+                raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
 
-                        // The clipped field is the part of the picture that is visible
-                        // on screen. The unclipped field is the screen-space rect of
-                        // the complete picture, if no screen / clip-chain was applied
-                        // (this includes the extra space for blur region). To ensure
-                        // that we draw a large enough part of the picture to get correct
-                        // blur results, inflate that clipped area by the blur range, and
-                        // then intersect with the total screen rect, to minimize the
-                        // allocation size.
-                        let device_rect = clipped
-                            .inflate(blur_range, blur_range)
-                            .intersection(&unclipped.to_i32())
-                            .unwrap();
-
-                        let uv_rect_kind = calculate_uv_rect_kind(
-                            &pic_rect,
-                            &transform,
-                            &device_rect,
-                            frame_context.device_pixel_scale,
-                        );
+                // If the local rect of the contents changed, force the cache handle
+                // to be invalidated so that the primitive data below will get
+                // uploaded to the GPU this frame. This can occur during property
+                // animation.
+                if pic_state.local_rect_changed {
+                    frame_state.gpu_cache.invalidate(&mut self.extra_gpu_data_handle);
+                }
 
-                        let mut picture_task = RenderTask::new_picture(
-                            RenderTaskLocation::Dynamic(None, device_rect.size),
-                            unclipped.size,
-                            pic_index,
-                            device_rect.origin,
-                            pic_state_for_children.tasks,
-                            uv_rect_kind,
-                            pic_state_for_children.raster_spatial_node_index,
-                        );
-                        picture_task.mark_for_saving();
+                if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
+                    // TODO(gw): This is very hacky code below! It stores an extra
+                    //           brush primitive below for the special case of a
+                    //           drop-shadow where we need a different local
+                    //           rect for the shadow. To tidy this up in future,
+                    //           we could consider abstracting the code in prim_store.rs
+                    //           that writes a brush primitive header.
 
-                        let picture_task_id = frame_state.render_tasks.add(picture_task);
+                    // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
+                    //  [brush specific data]
+                    //  [segment_rect, segment data]
+                    let shadow_rect = prim_local_rect.translate(&offset);
 
-                        let blur_render_task = RenderTask::new_blur(
-                            blur_std_deviation.round(),
-                            picture_task_id,
-                            frame_state.render_tasks,
-                            RenderTargetKind::Color,
-                            ClearMode::Transparent,
-                        );
+                    // ImageBrush colors
+                    request.push(color.premultiplied());
+                    request.push(PremultipliedColorF::WHITE);
+                    request.push([
+                        prim_local_rect.size.width,
+                        prim_local_rect.size.height,
+                        0.0,
+                        0.0,
+                    ]);
 
-                        self.secondary_render_task_id = Some(picture_task_id);
-
-                        let render_task_id = frame_state.render_tasks.add(blur_render_task);
-                        pic_state.tasks.push(render_task_id);
-                        raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
-
-                        // If the local rect of the contents changed, force the cache handle
-                        // to be invalidated so that the primitive data below will get
-                        // uploaded to the GPU this frame. This can occur during property
-                        // animation.
-                        if pic_state.local_rect_changed {
-                            frame_state.gpu_cache.invalidate(&mut self.extra_gpu_data_handle);
-                        }
+                    // segment rect / extra data
+                    request.push(shadow_rect);
+                    request.push([0.0, 0.0, 0.0, 0.0]);
+                }
+            }
+            PictureCompositeMode::MixBlend(..) => {
+                let uv_rect_kind = calculate_uv_rect_kind(
+                    &pic_rect,
+                    &transform,
+                    &clipped,
+                    frame_context.device_pixel_scale,
+                );
 
-                        if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
-                            // TODO(gw): This is very hacky code below! It stores an extra
-                            //           brush primitive below for the special case of a
-                            //           drop-shadow where we need a different local
-                            //           rect for the shadow. To tidy this up in future,
-                            //           we could consider abstracting the code in prim_store.rs
-                            //           that writes a brush primitive header.
-
-                            // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
-                            //  [brush specific data]
-                            //  [segment_rect, segment data]
-                            let shadow_rect = prim_local_rect.translate(&offset);
+                let picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, clipped.size),
+                    unclipped.size,
+                    pic_index,
+                    clipped.origin,
+                    pic_state_for_children.tasks,
+                    uv_rect_kind,
+                    pic_context.raster_spatial_node_index,
+                );
 
-                            // ImageBrush colors
-                            request.push(color.premultiplied());
-                            request.push(PremultipliedColorF::WHITE);
-                            request.push([
-                                prim_local_rect.size.width,
-                                prim_local_rect.size.height,
-                                0.0,
-                                0.0,
-                            ]);
+                let readback_task_id = frame_state.render_tasks.add(
+                    RenderTask::new_readback(clipped)
+                );
+
+                self.secondary_render_task_id = Some(readback_task_id);
+                pic_state.tasks.push(readback_task_id);
 
-                            // segment rect / extra data
-                            request.push(shadow_rect);
-                            request.push([0.0, 0.0, 0.0, 0.0]);
+                let render_task_id = frame_state.render_tasks.add(picture_task);
+                pic_state.tasks.push(render_task_id);
+                raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
+            }
+            PictureCompositeMode::Filter(filter) => {
+                if let FilterOp::ColorMatrix(m) = filter {
+                    if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
+                        for i in 0..5 {
+                            request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
                         }
                     }
-                    PictureCompositeMode::MixBlend(..) => {
-                        let uv_rect_kind = calculate_uv_rect_kind(
-                            &pic_rect,
-                            &transform,
-                            &clipped,
-                            frame_context.device_pixel_scale,
-                        );
+                }
 
-                        let picture_task = RenderTask::new_picture(
-                            RenderTaskLocation::Dynamic(None, clipped.size),
-                            unclipped.size,
-                            pic_index,
-                            clipped.origin,
-                            pic_state_for_children.tasks,
-                            uv_rect_kind,
-                            pic_state_for_children.raster_spatial_node_index,
-                        );
+                let uv_rect_kind = calculate_uv_rect_kind(
+                    &pic_rect,
+                    &transform,
+                    &clipped,
+                    frame_context.device_pixel_scale,
+                );
 
-                        let readback_task_id = frame_state.render_tasks.add(
-                            RenderTask::new_readback(clipped)
-                        );
-
-                        self.secondary_render_task_id = Some(readback_task_id);
-                        pic_state.tasks.push(readback_task_id);
-
-                        let render_task_id = frame_state.render_tasks.add(picture_task);
-                        pic_state.tasks.push(render_task_id);
-                        raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
-                    }
-                    PictureCompositeMode::Filter(filter) => {
-                        if let FilterOp::ColorMatrix(m) = filter {
-                            if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
-                                for i in 0..5 {
-                                    request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
-                                }
-                            }
-                        }
+                let picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, clipped.size),
+                    unclipped.size,
+                    pic_index,
+                    clipped.origin,
+                    pic_state_for_children.tasks,
+                    uv_rect_kind,
+                    pic_context.raster_spatial_node_index,
+                );
 
-                        let uv_rect_kind = calculate_uv_rect_kind(
-                            &pic_rect,
-                            &transform,
-                            &clipped,
-                            frame_context.device_pixel_scale,
-                        );
-
-                        let picture_task = RenderTask::new_picture(
-                            RenderTaskLocation::Dynamic(None, clipped.size),
-                            unclipped.size,
-                            pic_index,
-                            clipped.origin,
-                            pic_state_for_children.tasks,
-                            uv_rect_kind,
-                            pic_state_for_children.raster_spatial_node_index,
-                        );
+                let render_task_id = frame_state.render_tasks.add(picture_task);
+                pic_state.tasks.push(render_task_id);
+                raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
+            }
+            PictureCompositeMode::Blit => {
+                let uv_rect_kind = calculate_uv_rect_kind(
+                    &pic_rect,
+                    &transform,
+                    &clipped,
+                    frame_context.device_pixel_scale,
+                );
 
-                        let render_task_id = frame_state.render_tasks.add(picture_task);
-                        pic_state.tasks.push(render_task_id);
-                        raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
-                    }
-                    PictureCompositeMode::Blit => {
-                        let uv_rect_kind = calculate_uv_rect_kind(
-                            &pic_rect,
-                            &transform,
-                            &clipped,
-                            frame_context.device_pixel_scale,
-                        );
+                let picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, clipped.size),
+                    unclipped.size,
+                    pic_index,
+                    clipped.origin,
+                    pic_state_for_children.tasks,
+                    uv_rect_kind,
+                    pic_context.raster_spatial_node_index,
+                );
 
-                        let picture_task = RenderTask::new_picture(
-                            RenderTaskLocation::Dynamic(None, clipped.size),
-                            unclipped.size,
-                            pic_index,
-                            clipped.origin,
-                            pic_state_for_children.tasks,
-                            uv_rect_kind,
-                            pic_state_for_children.raster_spatial_node_index,
-                        );
-
-                        let render_task_id = frame_state.render_tasks.add(picture_task);
-                        pic_state.tasks.push(render_task_id);
-                        raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
-                    }
-                }
-            }
-            None => {
-                pic_state.tasks.extend(pic_state_for_children.tasks);
+                let render_task_id = frame_state.render_tasks.add(picture_task);
+                pic_state.tasks.push(render_task_id);
+                raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
             }
         }
 
         true
     }
 }
 
 // Calculate a single screen-space UV for a picture.
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -15,28 +15,29 @@ use clip_scroll_tree::{ClipScrollTree, C
 use clip::{ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
 use euclid::{TypedTransform3D, TypedRect, TypedScale};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveContext;
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::BrushFlags;
-use image::{for_each_tile, for_each_repetition};
+use image::{self, Repetition};
 use intern;
 use picture::{PictureCompositeMode, PicturePrimitive};
 #[cfg(debug_assertions)]
 use render_backend::FrameId;
 use render_task::{BlitSource, 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 std::{cmp, fmt, mem, usize};
-use util::{ScaleOffset, MatrixHelpers, pack_as_float, project_rect, raster_rect_to_device_pixels};
+use std::{cmp, fmt, mem, ops, usize};
+use util::{ScaleOffset, MatrixHelpers};
+use util::{pack_as_float, project_rect, raster_rect_to_device_pixels};
 use smallvec::SmallVec;
 
 
 const MIN_BRUSH_SPLIT_AREA: f32 = 256.0 * 256.0;
 pub const VECS_PER_SEGMENT: usize = 2;
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub struct ScrollNodeAndClipChain {
@@ -72,16 +73,32 @@ impl PrimitiveOpacity {
 
     pub fn from_alpha(alpha: f32) -> PrimitiveOpacity {
         PrimitiveOpacity {
             is_opaque: alpha >= 1.0,
         }
     }
 }
 
+#[derive(Debug, Copy, Clone)]
+pub enum VisibleFace {
+    Front,
+    Back,
+}
+
+impl ops::Not for VisibleFace {
+    type Output = Self;
+    fn not(self) -> Self {
+        match self {
+            VisibleFace::Front => VisibleFace::Back,
+            VisibleFace::Back => VisibleFace::Front,
+        }
+    }
+}
+
 #[derive(Debug)]
 pub enum CoordinateSpaceMapping<F, T> {
     Local,
     ScaleOffset(ScaleOffset),
     Transform(TypedTransform3D<f32, F, T>),
 }
 
 #[derive(Debug)]
@@ -194,16 +211,30 @@ impl<F, T> SpaceMapper<F, T> where F: fm
                     None => {
                         warn!("parent relative transform can't transform the primitive rect for {:?}", rect);
                         None
                     }
                 }
             }
         }
     }
+
+    pub fn visible_face(&self) -> VisibleFace {
+        match self.kind {
+            CoordinateSpaceMapping::Local => VisibleFace::Front,
+            CoordinateSpaceMapping::ScaleOffset(_) => VisibleFace::Front,
+            CoordinateSpaceMapping::Transform(ref transform) => {
+                if transform.is_backface_visible() {
+                    VisibleFace::Back
+                } else {
+                    VisibleFace::Front
+                }
+            }
+        }
+    }
 }
 
 /// For external images, it's not possible to know the
 /// UV coords of the image (or the image data itself)
 /// until the render thread receives the frame and issues
 /// callbacks to the client application. For external
 /// images that are visible, a DeferredResolve is created
 /// that is stored in the frame. This allows the render
@@ -239,16 +270,29 @@ impl GpuCacheAddress {
     pub fn as_int(&self) -> i32 {
         // TODO(gw): Temporarily encode GPU Cache addresses as a single int.
         //           In the future, we can change the PrimitiveInstanceData struct
         //           to use 2x u16 for the vertex attribute instead of an i32.
         self.v as i32 * MAX_VERTEX_TEXTURE_WIDTH as i32 + self.u as i32
     }
 }
 
+/// The information about an interned primitive that
+/// is stored and available in the scene builder
+/// thread.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PrimitiveSceneData {
+    // TODO(gw): We will store the local clip rect of
+    //           the primitive here. This will allow
+    //           fast calculation of the tight local
+    //           bounding rect of a primitive during
+    //           picture traversal.
+}
+
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
 pub struct PrimitiveKey {
     pub is_backface_visible: bool,
 }
 
 impl PrimitiveKey {
@@ -279,17 +323,17 @@ impl From<PrimitiveKey> for PrimitiveTem
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Clone, Copy, Debug)]
 pub struct PrimitiveDataMarker;
 
 pub type PrimitiveDataStore = intern::DataStore<PrimitiveKey, PrimitiveTemplate, PrimitiveDataMarker>;
 pub type PrimitiveDataHandle = intern::Handle<PrimitiveDataMarker>;
 pub type PrimitiveDataUpdateList = intern::UpdateList<PrimitiveKey>;
-pub type PrimitiveDataInterner = intern::Interner<PrimitiveKey, PrimitiveDataMarker>;
+pub type PrimitiveDataInterner = intern::Interner<PrimitiveKey, PrimitiveSceneData, PrimitiveDataMarker>;
 
 // Maintains a list of opacity bindings that have been collapsed into
 // the color of a single primitive. This is an important optimization
 // that avoids allocating an intermediate surface for most common
 // uses of opacity filters.
 #[derive(Debug)]
 pub struct OpacityBinding {
     bindings: Vec<PropertyBinding<f32>>,
@@ -323,25 +367,35 @@ impl OpacityBinding {
         let changed = new_opacity != self.current;
         self.current = new_opacity;
 
         changed
     }
 }
 
 #[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 handle: GpuCacheHandle,
+}
+
+#[derive(Debug)]
 pub struct VisibleGradientTile {
     pub handle: GpuCacheHandle,
     pub local_rect: LayoutRect,
     pub local_clip_rect: LayoutRect,
 }
 
 /// Information about how to cache a border segment,
 /// along with the current render task cache entry.
@@ -358,17 +412,16 @@ pub enum BorderSource {
     Image(ImageRequest),
     Border {
         segments: SmallVec<[BorderSegmentInfo; 8]>,
         border: NormalBorder,
         widths: LayoutSideOffsets,
     },
 }
 
-#[derive(Debug)]
 pub enum BrushKind {
     Solid {
         color: ColorF,
         opacity_binding: OpacityBinding,
     },
     Clear,
     Picture {
         pic_index: PictureIndex,
@@ -504,16 +557,18 @@ impl BrushKind {
 
 bitflags! {
     /// Each bit of the edge AA mask is:
     /// 0, when the edge of the primitive needs to be considered for AA
     /// 1, when the edge of the segment needs to be considered for AA
     ///
     /// *Note*: the bit values have to match the shader logic in
     /// `write_transform_vertex()` function.
+    #[cfg_attr(feature = "capture", derive(Serialize))]
+    #[cfg_attr(feature = "replay", derive(Deserialize))]
     pub struct EdgeAaSegmentMask: u8 {
         const LEFT = 0x1;
         const TOP = 0x2;
         const RIGHT = 0x4;
         const BOTTOM = 0x8;
     }
 }
 
@@ -616,17 +671,16 @@ impl BrushSegment {
 
 pub type BrushSegmentVec = SmallVec<[BrushSegment; 8]>;
 
 #[derive(Debug)]
 pub struct BrushSegmentDescriptor {
     pub segments: BrushSegmentVec,
 }
 
-#[derive(Debug)]
 pub struct BrushPrimitive {
     pub kind: BrushKind,
     pub segment_desc: Option<BrushSegmentDescriptor>,
 }
 
 impl BrushPrimitive {
     pub fn new(
         kind: BrushKind,
@@ -1228,22 +1282,26 @@ impl ClipCorner {
             inner_radius_y: inner_radius,
         }
     }
 }
 
 #[derive(Debug)]
 #[repr(C)]
 pub struct ImageMaskData {
-    pub local_rect: LayoutRect,
+    /// The local rect of the whole masked area.
+    pub local_mask_rect: LayoutRect,
+    /// The local rect of an individual tile.
+    pub local_tile_rect: LayoutRect,
 }
 
 impl ToGpuBlocks for ImageMaskData {
     fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
-        request.push(self.local_rect);
+        request.push(self.local_mask_rect);
+        request.push(self.local_tile_rect);
     }
 }
 
 #[derive(Debug)]
 pub struct ClipData {
     rect: ClipRect,
     top_left: ClipCorner,
     top_right: ClipCorner,
@@ -1363,17 +1421,16 @@ impl ClipData {
             &self.bottom_left,
             &self.bottom_right,
         ] {
             corner.write(request);
         }
     }
 }
 
-#[derive(Debug)]
 pub enum PrimitiveContainer {
     TextRun(TextRunPrimitive),
     Brush(BrushPrimitive),
 }
 
 impl PrimitiveContainer {
     // Return true if the primary primitive is visible.
     // Used to trivially reject non-visible primitives.
@@ -1499,17 +1556,17 @@ pub enum PrimitiveDetails {
 }
 
 pub struct Primitive {
     pub local_rect: LayoutRect,
     pub local_clip_rect: LayoutRect,
     pub details: PrimitiveDetails,
 }
 
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct PrimitiveInstance {
     /// Index into the prim store containing information about
     /// the specific primitive. This will be removed once all
     /// primitive data is interned.
     pub prim_index: PrimitiveIndex,
 
     /// Handle to the common interned data for this primitive.
     pub prim_data_handle: PrimitiveDataHandle,
@@ -1748,34 +1805,35 @@ impl PrimitiveStore {
         &mut self,
         prim_instance: &mut PrimitiveInstance,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         display_list: &BuiltDisplayList,
+        plane_split_anchor: usize,
         is_chased: bool,
-        current_pic_rect: &mut PictureRect,
     ) -> bool {
         // If we have dependencies, we need to prepare them first, in order
         // to know the actual rect of this primitive.
         // For example, scrolling may affect the location of an item in
         // local space, which may force us to render this item on a larger
         // picture target, if being composited.
         let pic_info = {
             match self.primitives[prim_instance.prim_index.0].details {
                 PrimitiveDetails::Brush(BrushPrimitive { kind: BrushKind::Picture { pic_index, .. }, .. }) => {
                     let pic = &mut self.pictures[pic_index.0];
 
                     match pic.take_context(
                         pic_index,
                         prim_context,
-                        pic_state.surface_spatial_node_index,
-                        pic_state.raster_spatial_node_index,
+                        pic_context.local_spatial_node_index,
+                        pic_context.surface_spatial_node_index,
+                        pic_context.raster_spatial_node_index,
                         pic_context.allow_subpixel_aa,
                         frame_state,
                         frame_context,
                         is_chased,
                     ) {
                         Some(info) => Some(info),
                         None => return false,
                     }
@@ -1789,31 +1847,29 @@ impl PrimitiveStore {
 
         let (is_passthrough, clip_node_collector) = match pic_info {
             Some((pic_context_for_children, mut pic_state_for_children, mut prim_instances)) => {
                 // Mark whether this picture has a complex coordinate system.
                 let is_passthrough = pic_context_for_children.is_passthrough;
                 pic_state_for_children.has_non_root_coord_system |=
                     prim_context.spatial_node.coordinate_system_id != CoordinateSystemId::root();
 
-                let mut pic_rect = PictureRect::zero();
                 self.prepare_primitives(
                     &mut prim_instances,
                     &pic_context_for_children,
                     &mut pic_state_for_children,
                     frame_context,
                     frame_state,
-                    &mut pic_rect,
                 );
 
                 let pic_rect = if is_passthrough {
-                    *current_pic_rect = current_pic_rect.union(&pic_rect);
+                    pic_state.rect = pic_state.rect.union(&pic_state_for_children.rect);
                     None
                 } else {
-                    Some(pic_rect)
+                    Some(pic_state_for_children.rect)
                 };
 
                 if !pic_state_for_children.is_cacheable {
                     pic_state.is_cacheable = false;
                 }
 
                 // Restore the dependencies (borrow check dance)
                 let (new_local_rect, clip_node_collector) = self
@@ -1920,18 +1976,20 @@ impl PrimitiveStore {
             let pic_rect = match pic_state.map_local_to_pic
                                           .map(&prim.local_rect) {
                 Some(pic_rect) => pic_rect,
                 None => return false,
             };
 
             // Check if the clip bounding rect (in pic space) is visible on screen
             // This includes both the prim bounding rect + local prim clip rect!
-            let world_rect = match pic_state.map_pic_to_world
-                                            .map(&clip_chain.pic_clip_rect) {
+            let world_rect = match pic_state
+                .map_pic_to_world
+                .map(&clip_chain.pic_clip_rect)
+            {
                 Some(world_rect) => world_rect,
                 None => {
                     return false;
                 }
             };
 
             let clipped_world_rect = match world_rect.intersection(&frame_context.world_rect) {
                 Some(rect) => rect,
@@ -1941,106 +1999,124 @@ impl PrimitiveStore {
             };
 
             prim_instance.clipped_world_rect = Some(clipped_world_rect);
 
             prim.update_clip_task(
                 prim_instance,
                 prim_context,
                 clipped_world_rect,
-                pic_state.raster_spatial_node_index,
+                pic_context.raster_spatial_node_index,
                 &clip_chain,
                 pic_state,
                 frame_context,
                 frame_state,
                 is_chased,
                 &clip_node_collector,
             );
 
             if cfg!(debug_assertions) && is_chased {
                 println!("\tconsidered visible and ready with local rect {:?}", local_rect);
             }
 
-            *current_pic_rect = current_pic_rect.union(&pic_rect);
+            pic_state.rect = pic_state.rect.union(&pic_rect);
         }
 
         prim.prepare_prim_for_render_inner(
             prim_instance,
             prim_context,
             pic_context,
             pic_state,
             &mut self.pictures,
             frame_context,
             frame_state,
             display_list,
+            plane_split_anchor,
             is_chased,
         );
 
         true
     }
 
     pub fn prepare_primitives(
         &mut self,
-        prim_instances: &mut Vec<PrimitiveInstance>,
+        prim_instances: &mut [PrimitiveInstance],
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
-        current_pic_rect: &mut PictureRect,
     ) {
         let display_list = &frame_context
             .pipelines
             .get(&pic_context.pipeline_id)
             .expect("No display list?")
             .display_list;
 
-        for prim_instance in prim_instances {
+        for (plane_split_anchor, prim_instance) in prim_instances.iter_mut().enumerate() {
             prim_instance.clipped_world_rect = None;
 
             let prim_index = prim_instance.prim_index;
             let is_chased = Some(prim_index) == self.chase_id;
 
             if is_chased {
                 println!("\tpreparing prim {:?} in pipeline {:?}",
                     prim_instance.prim_index, pic_context.pipeline_id);
             }
 
-            let is_backface_visible = frame_state
+            // Do some basic checks first, that can early out
+            // without even knowing the local rect.
+            if !frame_state
                 .resources
                 .prim_data_store[prim_instance.prim_data_handle]
-                .is_backface_visible;
+                .is_backface_visible
+            {
+                pic_state.map_local_to_containing_block.set_target_spatial_node(
+                    prim_instance.spatial_node_index,
+                    frame_context.clip_scroll_tree,
+                );
+
+                match pic_state.map_local_to_containing_block.visible_face() {
+                    VisibleFace::Back => {
+                        if cfg!(debug_assertions) && is_chased {
+                            println!("\tculled for not having visible back faces, transform {:?}",
+                                pic_state.map_local_to_containing_block);
+                        }
+                        continue;
+                    }
+                    VisibleFace::Front => {
+                        if cfg!(debug_assertions) && is_chased {
+                            println!("\tprim {:?} is not culled for visible face {:?}, transform {:?}",
+                                prim_index,
+                                pic_state.map_local_to_containing_block.visible_face(),
+                                pic_state.map_local_to_containing_block);
+                            println!("\tpicture context {:?}", pic_context);
+                        }
+                    }
+                }
+            }
 
             let spatial_node = &frame_context
                 .clip_scroll_tree
                 .spatial_nodes[prim_instance.spatial_node_index.0];
 
+            if !spatial_node.invertible {
+                if cfg!(debug_assertions) && is_chased {
+                    println!("\tculled for the scroll node transform being invertible");
+                }
+                continue;
+            }
+
             // TODO(gw): Although constructing these is cheap, they are often
             //           the same for many consecutive primitives, so it may
             //           be worth caching the most recent context.
             let prim_context = PrimitiveContext::new(
                 spatial_node,
                 prim_instance.spatial_node_index,
             );
 
-            // Do some basic checks first, that can early out
-            // without even knowing the local rect.
-            if !is_backface_visible && spatial_node.world_content_transform.is_backface_visible() {
-                if cfg!(debug_assertions) && is_chased {
-                    println!("\tculled for not having visible back faces");
-                }
-                continue;
-            }
-
-            if !spatial_node.invertible {
-                if cfg!(debug_assertions) && is_chased {
-                    println!("\tculled for the scroll node transform being invertible");
-                }
-                continue;
-            }
-
             // Mark whether this picture contains any complex coordinate
             // systems, due to either the scroll node or the clip-chain.
             pic_state.has_non_root_coord_system |=
                 spatial_node.coordinate_system_id != CoordinateSystemId::root();
 
             pic_state.map_local_to_pic.set_target_spatial_node(
                 prim_instance.spatial_node_index,
                 frame_context.clip_scroll_tree,
@@ -2049,18 +2125,18 @@ impl PrimitiveStore {
             if self.prepare_prim_for_render(
                 prim_instance,
                 &prim_context,
                 pic_context,
                 pic_state,
                 frame_context,
                 frame_state,
                 display_list,
+                plane_split_anchor,
                 is_chased,
-                current_pic_rect,
             ) {
                 frame_state.profile_counters.visible_primitives.inc();
             }
         }
     }
 }
 
 fn build_gradient_stops_request(
@@ -2107,38 +2183,34 @@ fn decompose_repeated_primitive(
 
     let visible_rect = compute_conservative_visible_rect(
         prim_context,
         clipped_world_rect,
         &tight_clip_rect
     );
     let stride = *stretch_size + *tile_spacing;
 
-    for_each_repetition(
-        prim_local_rect,
-        &visible_rect,
-        &stride,
-        &mut |origin, _| {
-
-            let mut handle = GpuCacheHandle::new();
-            let rect = LayoutRect {
-                origin: *origin,
-                size: *stretch_size,
-            };
-            if let Some(request) = frame_state.gpu_cache.request(&mut handle) {
-                callback(&rect, request);
-            }
-
-            visible_tiles.push(VisibleGradientTile {
-                local_rect: rect,
-                local_clip_rect: tight_clip_rect,
-                handle
-            });
+    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) {
+            callback(&rect, request);
         }
-    );
+
+        visible_tiles.push(VisibleGradientTile {
+            local_rect: rect,
+            local_clip_rect: tight_clip_rect,
+            handle
+        });
+    }
 
     if visible_tiles.is_empty() {
         // At this point if we don't have tiles to show it means we could probably
         // have done a better a job at culling during an earlier stage.
         // 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.
         instance.clipped_world_rect = None;
@@ -2277,17 +2349,17 @@ impl BrushPrimitive {
                             -0.5 * info.shadow_rect_alloc_size.width,
                             -0.5 * info.shadow_rect_alloc_size.height,
                         ),
                         inner_clip_mode,
                     );
 
                     continue;
                 }
-                ClipItem::Image(..) => {
+                ClipItem::Image { .. } => {
                     rect_clips_only = false;
                     continue;
                 }
             };
 
             segment_builder.push_clip_rect(local_clip_rect, radius, mode);
         }
 
@@ -2456,16 +2528,17 @@ impl Primitive {
         prim_instance: &mut PrimitiveInstance,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         pictures: &mut [PicturePrimitive],
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         display_list: &BuiltDisplayList,
+        plane_split_anchor: usize,
         is_chased: bool,
     ) {
         let mut is_tiled = false;
         #[cfg(debug_assertions)]
         {
             prim_instance.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
@@ -2633,59 +2706,60 @@ impl Primitive {
                                 );
 
                                 let base_edge_flags = edge_flags_for_tile_spacing(tile_spacing);
 
                                 let stride = stretch_size + *tile_spacing;
 
                                 visible_tiles.clear();
 
-                                for_each_repetition(
+                                let repetitions = image::repetitions(
                                     &self.local_rect,
                                     &visible_rect,
-                                    &stride,
-                                    &mut |origin, edge_flags| {
-                                        let edge_flags = base_edge_flags | edge_flags;
-
-                                        let image_rect = LayoutRect {
-                                            origin: *origin,
-                                            size: stretch_size,
-                                        };
-
-                                        for_each_tile(
-                                            &image_rect,
-                                            &visible_rect,
-                                            &device_image_size,
-                                            tile_size as u32,
-                                            &mut |tile_rect, tile_offset, tile_flags| {
-
-                                                frame_state.resource_cache.request_image(
-                                                    request.with_tile(tile_offset),
-                                                    frame_state.gpu_cache,
-                                                );
-
-                                                let mut handle = GpuCacheHandle::new();
-                                                if let Some(mut request) = frame_state.gpu_cache.request(&mut handle) {
-                                                    request.push(ColorF::new(1.0, 1.0, 1.0, opacity_binding.current).premultiplied());
-                                                    request.push(PremultipliedColorF::WHITE);
-                                                    request.push([tile_rect.size.width, tile_rect.size.height, 0.0, 0.0]);
-                                                    request.write_segment(*tile_rect, [0.0; 4]);
-                                                }
-
-                                                visible_tiles.push(VisibleImageTile {
-                                                    tile_offset,
-                                                    handle,
-                                                    edge_flags: tile_flags & edge_flags,
-                                                    local_rect: *tile_rect,
-                                                    local_clip_rect: tight_clip_rect,
-                                                });
-                                            }
+                                    stride,
+                                );
+
+                                for Repetition { origin, edge_flags } in repetitions {
+                                    let edge_flags = base_edge_flags | edge_flags;
+
+                                    let image_rect = LayoutRect {
+                                        origin,
+                                        size: stretch_size,
+                                    };
+
+                                    let tiles = image::tiles(
+                                        &image_rect,
+                                        &visible_rect,
+                                        &device_image_size,
+                                        tile_size as u32,
+                                    );
+
+                                    for tile in tiles {
+                                        frame_state.resource_cache.request_image(
+                                            request.with_tile(tile.offset),
+                                            frame_state.gpu_cache,
                                         );
+
+                                        let mut handle = GpuCacheHandle::new();
+                                        if let Some(mut request) = frame_state.gpu_cache.request(&mut handle) {
+                                            request.push(ColorF::new(1.0, 1.0, 1.0, opacity_binding.current).premultiplied());
+                                            request.push(PremultipliedColorF::WHITE);
+                                            request.push([tile.rect.size.width, tile.rect.size.height, 0.0, 0.0]);
+                                            request.write_segment(tile.rect, [0.0; 4]);
+                                        }
+
+                                        visible_tiles.push(VisibleImageTile {
+                                            tile_offset: tile.offset,
+                                            handle,
+                                            edge_flags: tile.edge_flags & edge_flags,
+                                            local_rect: tile.rect,
+                                            local_clip_rect: tight_clip_rect,
+                                        });
                                     }
-                                );
+                                }
 
                                 if visible_tiles.is_empty() {
                                     // At this point if we don't have tiles to show it means we could probably
                                     // have done a better a job at culling during an earlier stage.
                                     // 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.clipped_world_rect = None;
@@ -2965,24 +3039,34 @@ impl Primitive {
                                     ]);
                                     request.write_segment(*rect, [0.0; 4]);
                                 }
                             );
                         }
                     }
                     BrushKind::Picture { pic_index, .. } => {
                         let pic = &mut pictures[pic_index.0];
-                        if !pic.prepare_for_render(
+                        if pic.prepare_for_render(
                             pic_index,
                             prim_instance,
                             &self.local_rect,
                             pic_state,
                             frame_context,
                             frame_state,
                         ) {
+                            if let Some(ref mut splitter) = pic_state.plane_splitter {
+                                PicturePrimitive::add_split_plane(
+                                    splitter,
+                                    frame_state.transforms,
+                                    prim_instance,
+                                    self.local_rect,
+                                    plane_split_anchor,
+                                );
+                            }
+                        } else {
                             prim_instance.clipped_world_rect = None;
                         }
                     }
                     BrushKind::Solid { ref color, ref mut opacity_binding, .. } => {
                         // If the opacity changed, invalidate the GPU cache so that
                         // the new color for this primitive gets uploaded. Also update
                         // the opacity field that controls which batches this primitive
                         // will be added to.
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -549,17 +549,17 @@ impl RenderTask {
                             children.push(root_task_id);
 
                             root_task_id
                         }
                     ));
                 }
                 ClipItem::Rectangle(..) |
                 ClipItem::RoundedRectangle(..) |
-                ClipItem::Image(..) => {}
+                ClipItem::Image { .. } => {}
             }
         }
 
         RenderTask::with_dynamic_location(
             outer_rect.size,
             children,
             RenderTaskKind::CacheMask(CacheMaskTask {
                 actual_rect: outer_rect,
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -1077,17 +1077,17 @@ impl ResourceCache {
                         tiles.size.width -= 2;
                         tiles.origin.x += 1;
                     } else {
                         tiles.size.height -= 2;
                         tiles.origin.y += 1;
                     }
                 }
 
-                for_each_tile_in_range(&tiles, &mut|tile| {
+                for_each_tile_in_range(&tiles, |tile| {
                     let descriptor = BlobImageDescriptor {
                         offset: DevicePoint::new(
                             tile.x as f32 * tile_size as f32,
                             tile.y as f32 * tile_size as f32,
                         ),
                         size: compute_tile_size(
                             &template.descriptor,
                             tile_size,
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -8,17 +8,17 @@ use api::{MixBlendMode, PipelineId, Devi
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
 use clip::ClipStore;
 use clip_scroll_tree::{ClipScrollTree};
 use device::{FrameId, Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use gpu_cache::{GpuCache};
 use gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
-use gpu_types::{TransformData, TransformPalette};
+use gpu_types::{TransformData, TransformPalette, ZBufferIdGenerator};
 use internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex, TextureSource};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use prim_store::{PrimitiveStore, DeferredResolve};
 use profiler::FrameProfileCounters;
 use render_backend::FrameResources;
 use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree, ScalingTask};
@@ -119,16 +119,17 @@ pub trait RenderTarget {
     fn build(
         &mut self,
         _ctx: &mut RenderTargetContext,
         _gpu_cache: &mut GpuCache,
         _render_tasks: &mut RenderTaskTree,
         _deferred_resolves: &mut Vec<DeferredResolve>,
         _prim_headers: &mut PrimitiveHeaders,
         _transforms: &mut TransformPalette,
+        _z_generator: &mut ZBufferIdGenerator,
     ) {
     }
 
     /// Associates a `RenderTask` with this target. That task must be assigned
     /// to a region returned by invoking `allocate()` on this target.
     ///
     /// TODO(gw): It's a bit odd that we need the deferred resolves and mutable
     /// GPU cache here. They are typically used by the build step above. They
@@ -212,28 +213,30 @@ impl<T: RenderTarget> RenderTargetList<T
         &mut self,
         ctx: &mut RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
         saved_index: Option<SavedTargetIndex>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
+        z_generator: &mut ZBufferIdGenerator,
     ) {
         debug_assert_eq!(None, self.saved_index);
         self.saved_index = saved_index;
 
         for target in &mut self.targets {
             target.build(
                 ctx,
                 gpu_cache,
                 render_tasks,
                 deferred_resolves,
                 prim_headers,
                 transforms,
+                z_generator,
             );
         }
     }
 
     fn add_task(
         &mut self,
         task_id: RenderTaskId,
         ctx: &RenderTargetContext,
@@ -394,16 +397,17 @@ impl RenderTarget for ColorRenderTarget 
     fn build(
         &mut self,
         ctx: &mut RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
+        z_generator: &mut ZBufferIdGenerator,
     ) {
         let mut merged_batches = AlphaBatchContainer::new(None);
 
         for task_id in &self.alpha_tasks {
             let task = &render_tasks[*task_id];
 
             match task.kind {
                 RenderTaskKind::Picture(ref pic_task) => {
@@ -422,16 +426,17 @@ impl RenderTarget for ColorRenderTarget 
                         *task_id,
                         ctx,
                         gpu_cache,
                         render_tasks,
                         deferred_resolves,
                         prim_headers,
                         transforms,
                         pic_task.root_spatial_node_index,
+                        z_generator,
                     );
 
                     if let Some(batch_container) = batch_builder.build(&mut merged_batches) {
                         self.alpha_batch_containers.push(batch_container);
                     }
                 }
                 _ => {
                     unreachable!();
@@ -890,16 +895,17 @@ impl RenderPass {
         &mut self,
         ctx: &mut RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
         clip_store: &ClipStore,
         transforms: &mut TransformPalette,
         prim_headers: &mut PrimitiveHeaders,
+        z_generator: &mut ZBufferIdGenerator,
     ) {
         profile_scope!("RenderPass::build");
 
         match self.kind {
             RenderPassKind::MainFramebuffer(ref mut target) => {
                 for &task_id in &self.tasks {
                     assert_eq!(render_tasks[task_id].target_kind(), RenderTargetKind::Color);
                     target.add_task(
@@ -914,16 +920,17 @@ impl RenderPass {
                 }
                 target.build(
                     ctx,
                     gpu_cache,
                     render_tasks,
                     deferred_resolves,
                     prim_headers,
                     transforms,
+                    z_generator,
                 );
             }
             RenderPassKind::OffScreen { ref mut color, ref mut alpha, ref mut texture_cache } => {
                 let saved_color = if self.tasks.iter().any(|&task_id| {
                     let t = &render_tasks[task_id];
                     t.target_kind() == RenderTargetKind::Color && t.saved_index.is_some()
                 }) {
                     Some(render_tasks.save_target())
@@ -1018,25 +1025,27 @@ impl RenderPass {
                 color.build(
                     ctx,
                     gpu_cache,
                     render_tasks,
                     deferred_resolves,
                     saved_color,
                     prim_headers,
                     transforms,
+                    z_generator,
                 );
                 alpha.build(
                     ctx,
                     gpu_cache,
                     render_tasks,
                     deferred_resolves,
                     saved_alpha,
                     prim_headers,
                     transforms,
+                    z_generator,
                 );
             }
         }
     }
 }
 
 #[derive(Debug, Clone, Default)]
 pub struct CompositeOps {
@@ -1050,18 +1059,18 @@ pub struct CompositeOps {
 impl CompositeOps {
     pub fn new(filters: Vec<FilterOp>, mix_blend_mode: Option<MixBlendMode>) -> Self {
         CompositeOps {
             filters,
             mix_blend_mode,
         }
     }
 
-    pub fn count(&self) -> usize {
-        self.filters.len() + if self.mix_blend_mode.is_some() { 1 } else { 0 }
+    pub fn is_empty(&self) -> bool {
+        self.filters.is_empty() && self.mix_blend_mode.is_none()
     }
 }
 
 /// A rendering-oriented representation of the frame built by the render backend
 /// and presented to the renderer.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct Frame {
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -567,17 +567,20 @@ impl<Src, Dst> FastTransform<Src, Dst> {
                 FastTransform::with_transform(transform.pre_translate(other_offset.to_3d()))
         }
     }
 
     #[inline(always)]
     pub fn is_backface_visible(&self) -> bool {
         match *self {
             FastTransform::Offset(..) => false,
-            FastTransform::Transform { ref transform, .. } => transform.is_backface_visible(),
+            FastTransform::Transform { inverse: None, .. } => false,
+            //TODO: fix this properly by taking "det|M33| * det|M34| > 0"
+            // see https://www.w3.org/Bugs/Public/show_bug.cgi?id=23014
+            FastTransform::Transform { inverse: Some(ref inverse), .. } => inverse.m33 < 0.0,
         }
     }
 
     #[inline(always)]
     pub fn transform_point2d(&self, point: &TypedPoint2D<f32, Src>) -> Option<TypedPoint2D<f32, Dst>> {
         match *self {
             FastTransform::Offset(offset) => {
                 let new_point = *point + offset;
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-9464adaf2634f8d11408b915323d8006474f02e2
+a7052abfe8e41bcc8904cb5b3add99735fedcd1f
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -67,43 +67,60 @@ fn broadcast<T: Clone>(base_vals: &[T], 
         if vals.len() == num_items {
             break;
         }
         vals.extend_from_slice(base_vals);
     }
     vals
 }
 
+enum CheckerboardKind {
+    BlackGrey,
+    BlackTransparent,
+}
+
 fn generate_checkerboard_image(
     border: u32,
     tile_x_size: u32,
     tile_y_size: u32,
     tile_x_count: u32,
     tile_y_count: u32,
+    kind: CheckerboardKind,
 ) -> (ImageDescriptor, ImageData) {
     let width = 2 * border + tile_x_size * tile_x_count;
     let height = 2 * border + tile_y_size * tile_y_count;
     let mut pixels = Vec::new();
 
     for y in 0 .. height {
         for x in 0 .. width {
             if y < border || y >= (height - border) ||
                x < border || x >= (width - border) {
                 pixels.push(0);
                 pixels.push(0);
                 pixels.push(0xff);
                 pixels.push(0xff);
             } else {
                 let xon = ((x - border) % (2 * tile_x_size)) < tile_x_size;
                 let yon = ((y - border) % (2 * tile_y_size)) < tile_y_size;
-                let value = if xon ^ yon { 0xff } else { 0x7f };
-                pixels.push(value);
-                pixels.push(value);
-                pixels.push(value);
-                pixels.push(0xff);
+                match kind {
+                    CheckerboardKind::BlackGrey => {
+                        let value = if xon ^ yon { 0xff } else { 0x7f };
+                        pixels.push(value);
+                        pixels.push(value);
+                        pixels.push(value);
+                        pixels.push(0xff);
+                    }
+                    CheckerboardKind::BlackTransparent => {
+                        let value = if xon ^ yon { 0xff } else { 0x00 };
+                        pixels.push(value);
+                        pixels.push(value);
+                        pixels.push(value);
+                        pixels.push(value);
+                    }
+                }
             }
         }
     }
 
     (
         ImageDescriptor::new(width, height, ImageFormat::BGRA8, true, false),
         ImageData::new(pixels),
     )
@@ -400,20 +417,20 @@ impl YamlFrameReader {
                 }
             }
             _ => None,
         }
 
     }
 
     pub fn add_or_get_image(
-            &mut self,
-            file: &Path,
-            tiling: Option<i64>,
-            wrench: &mut Wrench,
+        &mut self,
+        file: &Path,
+        tiling: Option<i64>,
+        wrench: &mut Wrench,
     ) -> (ImageKey, LayoutSize) {
         let key = (file.to_owned(), tiling);
         if let Some(k) = self.image_map.get(&key) {
             return *k;
         }
 
         if self.list_resources { println!("{}", file.to_string_lossy()); }
         let (descriptor, image_data) = match image::open(file) {
@@ -471,17 +488,18 @@ impl YamlFrameReader {
                     ("solid-color", args, _) => generate_solid_color_image(
                         args.get(0).unwrap_or(&"255").parse::<u8>().unwrap(),
                         args.get(1).unwrap_or(&"255").parse::<u8>().unwrap(),
                         args.get(2).unwrap_or(&"255").parse::<u8>().unwrap(),
                         args.get(3).unwrap_or(&"255").parse::<u8>().unwrap(),
                         args.get(4).unwrap_or(&"1000").parse::<u32>().unwrap(),
                         args.get(5).unwrap_or(&"1000").parse::<u32>().unwrap(),
                     ),
-                    ("checkerboard", args, _) => {
+                    (name @ "transparent-checkerboard", args, _) |
+                    (name @ "checkerboard", args, _) => {
                         let border = args.get(0).unwrap_or(&"4").parse::<u32>().unwrap();
 
                         let (x_size, y_size, x_count, y_count) = match args.len() {
                             3 => {
                                 let size = args.get(1).unwrap_or(&"32").parse::<u32>().unwrap();
                                 let count = args.get(2).unwrap_or(&"8").parse::<u32>().unwrap();
                                 (size, size, count, count)
                             }
@@ -492,22 +510,29 @@ impl YamlFrameReader {
                                 let y_count = args.get(4).unwrap_or(&"8").parse::<u32>().unwrap();
                                 (x_size, y_size, x_count, y_count)
                             }
                             _ => {
                                 panic!("invalid checkerboard function");
                             }
                         };
 
+                        let kind = if name == "transparent-checkerboard" {
+                            CheckerboardKind::BlackTransparent
+                        } else {
+                            CheckerboardKind::BlackGrey
+                        };
+
                         generate_checkerboard_image(
                             border,
                             x_size,
                             y_size,
                             x_count,
                             y_count,
+                            kind,
                         )
                     }
                     _ => {
                         panic!("Failed to load image {:?}", file.to_str());
                     }
                 }
             }
         };
@@ -595,18 +620,19 @@ impl YamlFrameReader {
                 file
             }
             None => {
                 warn!("No image provided for the image-mask!");
                 return None;
             }
         };
 
+        let tiling = item["tile-size"].as_i64();
         let (image_key, image_dims) =
-            self.add_or_get_image(&file, None, wrench);
+            self.add_or_get_image(&file, tiling, wrench);
         let image_rect = item["rect"]
             .as_rect()
             .unwrap_or(LayoutRect::new(LayoutPoint::zero(), image_dims));
         let image_repeat = item["repeat"].as_bool().expect("Expected boolean");
         Some(ImageMask {
             image: image_key,
             rect: image_rect,
             repeat: image_repeat,
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -67,17 +67,16 @@ enum ThreadType {
     THREAD_TYPE_PARSE,          // 4
     THREAD_TYPE_COMPRESS,       // 5
     THREAD_TYPE_GCHELPER,       // 6
     THREAD_TYPE_GCPARALLEL,     // 7
     THREAD_TYPE_PROMISE_TASK,   // 8
     THREAD_TYPE_ION_FREE,       // 9
     THREAD_TYPE_WASM_TIER2,     // 10
     THREAD_TYPE_WORKER,         // 11
-    THREAD_TYPE_CURRENT,        // 12 -- Special code to only track the current thread.
     THREAD_TYPE_MAX             // Used to check shell function arguments
 };
 
 namespace oom {
 
 /*
  * Theads are tagged only in certain debug contexts.  Notably, to make testing
  * OOM in certain helper threads more effective, we allow restricting the OOM
@@ -88,23 +87,20 @@ namespace oom {
  * is in jsutil.cpp.
  */
 # if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 
 // Define the range of threads tested by simulated OOM testing and the
 // like. Testing worker threads is not supported.
 const ThreadType FirstThreadTypeToTest = THREAD_TYPE_MAIN;
 const ThreadType LastThreadTypeToTest = THREAD_TYPE_WASM_TIER2;
-const ThreadType WorkerFirstThreadTypeToTest = THREAD_TYPE_CURRENT;
-const ThreadType WorkerLastThreadTypeToTest = THREAD_TYPE_CURRENT;
 
 extern bool InitThreadType(void);
 extern void SetThreadType(ThreadType);
 extern JS_FRIEND_API(uint32_t) GetThreadType(void);
-extern JS_FRIEND_API(uint32_t) GetSimulatingThreadType(void);
 
 # else
 
 inline bool InitThreadType(void) { return true; }
 inline void SetThreadType(ThreadType t) {};
 inline uint32_t GetThreadType(void) { return 0; }
 inline uint32_t GetAllocationThreadType(void) { return 0; }
 inline uint32_t GetStackCheckThreadType(void) { return 0; }
@@ -156,17 +152,17 @@ class FailureSimulator
     }
     uint64_t counter() const {
         return counter_;
     }
     uint32_t targetThread() const {
         return targetThread_;
     }
     bool isThreadSimulatingAny() const {
-        return targetThread_ && targetThread_ == js::oom::GetSimulatingThreadType();
+        return targetThread_ && targetThread_ == js::oom::GetThreadType();
     }
     bool isThreadSimulating(Kind kind) const {
         return kind_ == kind && isThreadSimulatingAny();
     }
     bool isSimulatedFailure(Kind kind) const {
         if (!isThreadSimulating(kind)) {
             return false;
         }
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1880,16 +1880,20 @@ struct IterativeFailureSimulator
 bool
 RunIterativeFailureTest(JSContext* cx, const IterativeFailureTestParams& params,
                         IterativeFailureSimulator& simulator)
 {
     if (disableOOMFunctions) {
         return true;
     }
 
+    if (!CheckCanSimulateOOM(cx)) {
+        return false;
+    }
+
     // Disallow nested tests.
     if (cx->runningOOMTest) {
         JS_ReportErrorASCII(cx, "Nested call to iterative failure test is not allowed.");
         return false;
     }
     cx->runningOOMTest = true;
 
     MOZ_ASSERT(!cx->isExceptionPending());
@@ -2040,32 +2044,25 @@ ParseIterativeFailureTestParams(JSContex
     }
 
     // There are some places where we do fail without raising an exception, so
     // we can't expose this to the fuzzers by default.
     if (fuzzingSafe) {
         params->expectExceptionOnFailure = false;
     }
 
-    // Test all threads by default except worker threads, except if we are
-    // running in a worker thread in which case only the worker thread which
-    // requested the simulation is tested.
-    if (js::oom::GetThreadType() == js::THREAD_TYPE_WORKER) {
-        params->threadStart = oom::WorkerFirstThreadTypeToTest;
-        params->threadEnd = oom::WorkerLastThreadTypeToTest;
-    } else {
-        params->threadStart = oom::FirstThreadTypeToTest;
-        params->threadEnd = oom::LastThreadTypeToTest;
-    }
+    // Test all threads by default except worker threads.
+    params->threadStart = oom::FirstThreadTypeToTest;
+    params->threadEnd = oom::LastThreadTypeToTest;
 
     // Test a single thread type if specified by the OOM_THREAD environment variable.
     int threadOption = 0;
     if (EnvVarAsInt("OOM_THREAD", &threadOption)) {
-        if (threadOption < oom::FirstThreadTypeToTest || threadOption > oom::LastThreadTypeToTest ||
-            threadOption != js::THREAD_TYPE_CURRENT)
+        if (threadOption < oom::FirstThreadTypeToTest ||
+            threadOption > oom::LastThreadTypeToTest)
         {
             JS_ReportErrorASCII(cx, "OOM_THREAD value out of range.");
             return false;
         }
 
         params->threadStart = threadOption;
         params->threadEnd = threadOption;
     }
--- a/js/src/jit-test/tests/self-test/oom-test-bug1497906.js
+++ b/js/src/jit-test/tests/self-test/oom-test-bug1497906.js
@@ -1,27 +1,18 @@
 // |jit-test| skip-if: !('oomTest' in this && 'stackTest' in this) || helperThreadCount() === 0
 
-// Check the we can run a stack test on all threads except worker threads while
-// a worker thread is running a OOM test at the same time.
-//
-// This test case uses setSharedObject to "synchronize" between a worker thread
-// and the main thread. This worker thread enters the oomTest section, while the
-// main thread waits. Then the worker thread remains in the oomTest section
-// until the main thread finish entering and existing its stackTest section.
+// Check that oomTest throws an exception on worker threads.
 
 setSharedObject(0);
 evalInWorker(`
-    oomTest(() => {
-        if (getSharedObject() < 2) {
+    try {
+        oomTest(crash);
+    } catch (e) {
+        if (e.toString().includes("main thread")) {
             setSharedObject(1);
-            while (getSharedObject() != 2) {
-            }
         }
-    });
+    }
 `);
 
 while (getSharedObject() != 1) {
     // poor-man wait condition.
 }
-
-stackTest(() => 42);
-setSharedObject(2);
--- a/js/src/jsutil.cpp
+++ b/js/src/jsutil.cpp
@@ -30,86 +30,69 @@ using mozilla::Maybe;
 namespace js {
 
 mozilla::Atomic<AutoEnterOOMUnsafeRegion*> AutoEnterOOMUnsafeRegion::owner_;
 
 namespace oom {
 
 JS_PUBLIC_DATA(FailureSimulator) simulator;
 static MOZ_THREAD_LOCAL(uint32_t) threadType;
-static MOZ_THREAD_LOCAL(bool) isSimulatingThread;
 
 bool
 InitThreadType()
 {
-    return threadType.init() && isSimulatingThread.init();
+    return threadType.init();
 }
 
 void
 SetThreadType(ThreadType type)
 {
     threadType.set(type);
-    isSimulatingThread.set(false);
 }
 
 uint32_t
 GetThreadType(void)
 {
     return threadType.get();
 }
 
-uint32_t
-GetSimulatingThreadType()
-{
-    if (isSimulatingThread.get()) {
-        return js::THREAD_TYPE_CURRENT;
-    }
-    return threadType.get();
-}
-
-
 static inline bool
 IsHelperThreadType(uint32_t thread)
 {
-    return thread != THREAD_TYPE_NONE && thread != THREAD_TYPE_MAIN &&
-        thread != THREAD_TYPE_CURRENT;
+    return thread != THREAD_TYPE_NONE && thread != THREAD_TYPE_MAIN;
 }
 
 void
 FailureSimulator::simulateFailureAfter(Kind kind, uint64_t checks, uint32_t thread,
                                        bool always)
 {
     Maybe<AutoLockHelperThreadState> lock;
     if (IsHelperThreadType(targetThread_) || IsHelperThreadType(thread)) {
         lock.emplace();
         HelperThreadState().waitForAllThreadsLocked(lock.ref());
     }
 
     MOZ_ASSERT(counter_ + checks > counter_);
     MOZ_ASSERT(thread > js::THREAD_TYPE_NONE && thread < js::THREAD_TYPE_MAX);
     targetThread_ = thread;
-    if (thread == js::THREAD_TYPE_CURRENT) {
-        isSimulatingThread.set(true);
-    }
     maxChecks_ = counter_ + checks;
     failAlways_ = always;
     kind_ = kind;
 }
 
 void
 FailureSimulator::reset()
 {
     Maybe<AutoLockHelperThreadState> lock;
     if (IsHelperThreadType(targetThread_)) {
         lock.emplace();
         HelperThreadState().waitForAllThreadsLocked(lock.ref());
     }
 
     targetThread_ = THREAD_TYPE_NONE;
-    isSimulatingThread.set(false);
     maxChecks_ = UINT64_MAX;
     failAlways_ = false;
     kind_ = Kind::Nothing;
 }
 
 } // namespace oom
 } // namespace js
 #endif // defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -443,23 +443,21 @@ skip script test262/intl402/Segmenter/pr
 skip include test262/intl402/RelativeTimeFormat/prototype/formatToParts/jstests.list
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1483545
 skip script test262/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-always.js
 skip script test262/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-auto.js
 skip script test262/intl402/RelativeTimeFormat/prototype/format/en-us-style-short.js
 skip script test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-long.js
 skip script test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-narrow.js
+skip script test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-short.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1473230
 skip script test262/intl402/RelativeTimeFormat/prototype/format/unit-plural.js
 
-# https://bugzilla.mozilla.org/show_bug.cgi?id=1483547
-skip script test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-short.js
-
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1483548
 skip script test262/intl402/RelativeTimeFormat/prototype/format/value-non-finite.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1499933
 skip script test262/intl402/DateTimeFormat/prototype/resolvedOptions/order.js
 skip script test262/intl402/PluralRules/prototype/resolvedOptions/order.js
 skip script test262/intl402/NumberFormat/prototype/resolvedOptions/order.js
 
--- a/layout/generic/nsColumnSetFrame.cpp
+++ b/layout/generic/nsColumnSetFrame.cpp
@@ -310,22 +310,18 @@ nsColumnSetFrame::ClampUsedColumnWidth(c
              "This should only be called when column-width is a <length>!");
 
   // Per spec, used values will be clamped to a minimum of 1px.
   return std::max(CSSPixel::ToAppUnits(1), aColumnWidth.GetCoordValue());
 }
 
 nsColumnSetFrame::ReflowConfig
 nsColumnSetFrame::ChooseColumnStrategy(const ReflowInput& aReflowInput,
-                                       bool aForceAuto = false,
-                                       nscoord aFeasibleBSize = NS_INTRINSICSIZE,
-                                       nscoord aInfeasibleBSize = 0)
+                                       bool aForceAuto = false)
 {
-  nscoord knownFeasibleBSize = aFeasibleBSize;
-  nscoord knownInfeasibleBSize = aInfeasibleBSize;
   WritingMode wm = aReflowInput.GetWritingMode();
 
   const nsStyleColumn* colStyle = StyleColumn();
   nscoord availContentISize = GetAvailableContentISize(aReflowInput);
   if (aReflowInput.ComputedISize() != NS_INTRINSICSIZE) {
     availContentISize = aReflowInput.ComputedISize();
   }
 
@@ -451,19 +447,28 @@ nsColumnSetFrame::ChooseColumnStrategy(c
                  " colBSize=%d, colGap=%d",
                  __func__,
                  numColumns,
                  colISize,
                  expectedISizeLeftOver,
                  colBSize,
                  colGap);
 
-  ReflowConfig config = { numColumns, colISize, expectedISizeLeftOver, colGap,
-                          colBSize, isBalancing, knownFeasibleBSize,
-                          knownInfeasibleBSize, computedBSize, consumedBSize };
+  ReflowConfig config;
+  config.mBalanceColCount = numColumns;
+  config.mColISize = colISize;
+  config.mExpectedISizeLeftOver = expectedISizeLeftOver;
+  config.mColGap = colGap;
+  config.mColMaxBSize = colBSize;
+  config.mIsBalancing = isBalancing;
+  config.mKnownFeasibleBSize = NS_INTRINSICSIZE;
+  config.mKnownInfeasibleBSize = 0;
+  config.mComputedBSize = computedBSize;
+  config.mConsumedBSize = consumedBSize;
+
   return config;
 }
 
 static void
 MarkPrincipalChildrenDirty(nsIFrame* aFrame)
 {
   for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
     childFrame->AddStateBits(NS_FRAME_IS_DIRTY);
@@ -1230,17 +1235,16 @@ nsColumnSetFrame::Reflow(nsPresContext* 
   // what the average column height should be, because we can measure
   // the heights of all the columns and sum them up. But don't do this
   // if we have a next in flow because we don't want to suck all its
   // content back here and then have to push it out again!
   nsIFrame* nextInFlow = GetNextInFlow();
   bool unboundedLastColumn = config.mIsBalancing && !nextInFlow;
   nsCollapsingMargin carriedOutBottomMargin;
   ColumnBalanceData colData;
-  colData.mHasExcessBSize = false;
 
   bool feasible = ReflowColumns(aDesiredSize, aReflowInput, aStatus, config,
                                 unboundedLastColumn, &carriedOutBottomMargin,
                                 colData);
 
   // If we're not balancing, then we're already done, since we should have
   // reflown all of our children, and there is no need for a binary search to
   // determine proper column height.
--- a/layout/generic/nsColumnSetFrame.h
+++ b/layout/generic/nsColumnSetFrame.h
@@ -97,71 +97,75 @@ protected:
   nsReflowStatus mLastFrameStatus;
 
   /**
    * These are the parameters that control the layout of columns.
    */
   struct ReflowConfig {
     // The number of columns that we want to balance across. If we're not
     // balancing, this will be set to INT32_MAX.
-    int32_t mBalanceColCount;
+    int32_t mBalanceColCount = INT32_MAX;
 
     // The inline-size of each individual column.
-    nscoord mColISize;
+    nscoord mColISize = NS_INTRINSICSIZE;
 
     // The amount of inline-size that is expected to be left over after all the
     // columns and column gaps are laid out.
-    nscoord mExpectedISizeLeftOver;
+    nscoord mExpectedISizeLeftOver = 0;
 
     // The width (inline-size) of each column gap.
-    nscoord mColGap;
+    nscoord mColGap = NS_INTRINSICSIZE;
 
     // The maximum bSize of any individual column during a reflow iteration.
     // This parameter is set during each iteration of the binary search for
     // the best column block-size.
-    nscoord mColMaxBSize;
+    nscoord mColMaxBSize = NS_INTRINSICSIZE;
 
     // A boolean controlling whether or not we are balancing. This should be
     // equivalent to mBalanceColCount == INT32_MAX.
-    bool mIsBalancing;
+    bool mIsBalancing = false;
 
     // The last known column block-size that was 'feasible'. A column bSize is
     // feasible if all child content fits within the specified bSize.
-    nscoord mKnownFeasibleBSize;
+    nscoord mKnownFeasibleBSize = NS_INTRINSICSIZE;
 
     // The last known block-size that was 'infeasible'. A column bSize is
     // infeasible if not all child content fits within the specified bSize.
-    nscoord mKnownInfeasibleBSize;
+    nscoord mKnownInfeasibleBSize = 0;
 
     // block-size of the column set frame
-    nscoord mComputedBSize;
+    nscoord mComputedBSize = NS_INTRINSICSIZE;
 
     // The block-size "consumed" by previous-in-flows.
     // The computed block-size should be equal to the block-size of the element
     // (i.e. the computed block-size itself) plus the consumed block-size.
-    nscoord mConsumedBSize;
+    nscoord mConsumedBSize = 0;
   };
 
   /**
    * Some data that is better calculated during reflow
    */
   struct ColumnBalanceData {
     // The maximum "content block-size" of any column
-    nscoord mMaxBSize;
+    nscoord mMaxBSize = 0;
+
     // The sum of the "content block-size" for all columns
-    nscoord mSumBSize;
+    nscoord mSumBSize = 0;
+
     // The "content block-size" of the last column
-    nscoord mLastBSize;
+    nscoord mLastBSize = 0;
+
     // The maximum "content block-size" of all columns that overflowed
     // their available block-size
-    nscoord mMaxOverflowingBSize;
+    nscoord mMaxOverflowingBSize = 0;
+
     // This flag determines whether the last reflow of children exceeded the
     // computed block-size of the column set frame. If so, we set the bSize to
     // this maximum allowable bSize, and continue reflow without balancing.
-    bool mHasExcessBSize;
+    bool mHasExcessBSize = false;
 
     void Reset() {
       mMaxBSize = mSumBSize = mLastBSize = mMaxOverflowingBSize = 0;
       mHasExcessBSize = false;
     }
   };
 
   bool ReflowColumns(ReflowOutput& aDesiredSize,
@@ -175,18 +179,17 @@ protected:
   /**
    * The basic reflow strategy is to call this function repeatedly to
    * obtain specific parameters that determine the layout of the
    * columns. This function will compute those parameters from the CSS
    * style. This function will also be responsible for implementing
    * the state machine that controls column balancing.
    */
   ReflowConfig ChooseColumnStrategy(const ReflowInput& aReflowInput,
-                                    bool aForceAuto, nscoord aFeasibleBSize,
-                                    nscoord aInfeasibleBSize);
+                                    bool aForceAuto);
 
   /**
    * Perform the binary search for the best balance height for this column set.
    *
    * @param aReflowInput The input parameters for the current reflow iteration.
    * @param aPresContext The presentation context in which the current reflow
    *        iteration is occurring.
    * @param aConfig The ReflowConfig object associated with this column set
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -2638,16 +2638,22 @@ FlexLine::ResolveFlexibleLengths(nscoord
       aLineInfo->mItems[itemIndex].mMainDeltaSize = 0;
     }
   }
 
   // Determine whether we're going to be growing or shrinking items.
   const bool isUsingFlexGrow =
     (mTotalOuterHypotheticalMainSize < aFlexContainerMainSize);
 
+  if (aLineInfo) {
+    aLineInfo->mGrowthState = isUsingFlexGrow ?
+                              mozilla::dom::FlexLineGrowthState::Growing :
+                              mozilla::dom::FlexLineGrowthState::Shrinking;
+  }
+
   // Do an "early freeze" for flex items that obviously can't flex in the
   // direction we've chosen:
   FreezeItemsEarly(isUsingFlexGrow, aLineInfo);
 
   if ((mNumFrozenItems == mNumItems) && !aLineInfo) {
     // All our items are frozen, so we have no flexible lengths to resolve,
     // and we aren't being asked to generate computed line info.
     return;
@@ -2867,40 +2873,16 @@ FlexLine::ResolveFlexibleLengths(nscoord
               // actually changed, since the size of the item will be clamped
               // to min and max values later in this pass. That's intentional,
               // since we want to report the value that the sizing algorithm
               // tried to stretch or shrink the item.
               nscoord deltaSize = item->GetMainSize() -
                 aLineInfo->mItems[itemIndex].mMainBaseSize;
 
               aLineInfo->mItems[itemIndex].mMainDeltaSize = deltaSize;
-              // If any (unfrozen) item on the line is growing, we mark the
-              // aLineInfo structure; likewise if any item is shrinking.
-              // (Note: a line can't contain a mix of items that are growing
-              // and shrinking. Also, the sign of any delta should match the
-              // type of flex factor we're using [grow vs shrink].)
-              if (deltaSize > 0) {
-                MOZ_ASSERT(isUsingFlexGrow,
-                           "Unfrozen items can only grow if we're "
-                           "distributing (positive) space with flex-grow");
-                MOZ_ASSERT(aLineInfo->mGrowthState !=
-                           ComputedFlexLineInfo::GrowthState::SHRINKING,
-                           "shouldn't flip flop from shrinking to growing");
-                aLineInfo->mGrowthState =
-                  ComputedFlexLineInfo::GrowthState::GROWING;
-              } else if (deltaSize < 0) {
-                MOZ_ASSERT(!isUsingFlexGrow,
-                           "Unfrozen items can only shrink if we're "
-                           "distributing (negative) space with flex-shrink");
-                MOZ_ASSERT(aLineInfo->mGrowthState !=
-                           ComputedFlexLineInfo::GrowthState::GROWING,
-                           "shouldn't flip flop from growing to shrinking");
-                aLineInfo->mGrowthState =
-                  ComputedFlexLineInfo::GrowthState::SHRINKING;
-              }
             }
           }
         }
       }
     }
 
     // Fix min/max violations:
     nscoord totalViolation = 0; // keeps track of adjustments for min/max
@@ -4765,26 +4747,20 @@ nsFlexContainerFrame::DoFlexLayout(nsPre
       ConvertAxisOrientationTypeToAPIEnum(mainAxis);
     containerInfo->mCrossAxisDirection =
       ConvertAxisOrientationTypeToAPIEnum(crossAxis);
 
     for (const FlexLine* line = lines.getFirst(); line;
          line = line->getNext()) {
       ComputedFlexLineInfo* lineInfo =
         containerInfo->mLines.AppendElement();
-      // Most lineInfo properties will be set later, but we set
-      // mGrowthState to UNCHANGED here because it may be later
-      // modified by ResolveFlexibleLengths().
-      lineInfo->mGrowthState =
-        ComputedFlexLineInfo::GrowthState::UNCHANGED;
-
-      // The remaining lineInfo properties will be filled out at the
-      // end of this function, when we have real values. But we still
-      // add all the items here, so we can capture computed data for
-      // each item.
+      // Most of the remaining lineInfo properties will be filled out at the
+      // end of this function (some will be provided by other functions),
+      // when we have real values. But we still add all the items here, so
+      // we can capture computed data for each item as we proceed.
       for (const FlexItem* item = line->GetFirstItem(); item;
            item = item->getNext()) {
         nsIFrame* frame = item->Frame();
 
         // The frame may be for an element, or it may be for an
         // anonymous flex item, e.g. wrapping one or more text nodes.
         // DevTools wants the content node for the actual child in
         // the DOM tree, so we descend through anonymous boxes.
--- a/layout/generic/nsFlexContainerFrame.h
+++ b/layout/generic/nsFlexContainerFrame.h
@@ -58,21 +58,17 @@ struct ComputedFlexItemInfo
 
 struct ComputedFlexLineInfo
 {
   nsTArray<ComputedFlexItemInfo> mItems;
   nscoord mCrossStart;
   nscoord mCrossSize;
   nscoord mFirstBaselineOffset;
   nscoord mLastBaselineOffset;
-  enum GrowthState {
-    UNCHANGED,
-    SHRINKING,
-    GROWING,
-  } mGrowthState;
+  mozilla::dom::FlexLineGrowthState mGrowthState;
 };
 
 struct ComputedFlexContainerInfo
 {
   nsTArray<ComputedFlexLineInfo> mLines;
   mozilla::dom::FlexPhysicalDirection mMainAxisDirection;
   mozilla::dom::FlexPhysicalDirection mCrossAxisDirection;
 };
--- a/layout/generic/test/mochitest.ini
+++ b/layout/generic/test/mochitest.ini
@@ -12,16 +12,17 @@ support-files =
   plugin_focus_helper.html
   file_BrokenImageReference.png
   file_Dolske.png
   file_IconTestServer.sjs
   file_LoadingImageReference.png
   file_SlowImage.sjs
   file_SlowPage.sjs
   bug1174521.html
+  !/gfx/layers/apz/test/mochitest/apz_test_utils.js
 
 [test_bug240933.html]
 [test_bug263683.html]
 [test_bug288789.html]
 [test_bug290397.html]
 [test_bug323656.html]
 [test_bug344830.html]
 support-files = bug344830_testembed.svg
--- a/layout/generic/test/test_bug784410.html
+++ b/layout/generic/test/test_bug784410.html
@@ -1,15 +1,16 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test bug 784410</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
   <script src="/tests/SimpleTest/paint_listener.js"></script>
+  <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="outer" style="overflow:auto; height:200px; border:2px dotted black; transform: translateY(1px)" onscroll="doneScroll()">
   <div id="d" style="overflow:auto; height:102px;" onscroll="doneScroll()">
     <div id="inner" style="height:100.1px; border:1px solid black; background:yellow;">Hello</div>
   </div>
@@ -35,23 +36,26 @@ function doneScroll() {
   switch (step) {
   case 1:
     is(innerScrollOffset(), innerStartScrollOffset, "Inner element should not have scrolled down");
     ok(outer.scrollTop > 0, "Outer element should have scrolled down");
 
     outer.scrollTop = 0;
     break;
   case 2:
-    // Wait for paints to flush, so APZ is notified of the new scroll offset.
-    sendWheelAndPaint(inner, 4, 4,
-      { deltaMode: WheelEvent.DOM_DELTA_LINE, deltaY: 1 },
-      function() {
-          wheelAndPaintDone = true;
-          doneTest();
-      });
+    // wait until APZ is ready to handle the wheel event, then send it
+    waitForApzFlushedRepaints(() => {
+      // Wait for paints to flush, so APZ is notified of the new scroll offset.
+      sendWheelAndPaint(inner, 4, 4,
+        { deltaMode: WheelEvent.DOM_DELTA_LINE, deltaY: 1 },
+        function() {
+            wheelAndPaintDone = true;
+            doneTest();
+        });
+    });
     break;
   case 3:
     is(innerScrollOffset(), innerStartScrollOffset, "Inner element should not have scrolled down");
     ok(outer.scrollTop > 0, "Outer element should have scrolled down");
     
     doneTest();
     break;
   }
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_cli_arguments.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_cli_arguments.py
@@ -1,17 +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 http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import
 
 import copy
 
-from marionette_harness import MarionetteTestCase, skip
+from marionette_harness import MarionetteTestCase
 
 
 class TestCommandLineArguments(MarionetteTestCase):
 
     def setUp(self):
         super(TestCommandLineArguments, self).setUp()
 
         self.orig_arguments = copy.copy(self.marionette.instance.app_args)
@@ -31,17 +31,16 @@ class TestCommandLineArguments(Marionett
         with self.marionette.using_context("chrome"):
             safe_mode = self.marionette.execute_script("""
               Cu.import("resource://gre/modules/Services.jsm");
 
               return Services.appinfo.inSafeMode;
             """)
             self.assertTrue(safe_mode, "Safe Mode has not been enabled")
 
-    @skip("Bug 1430717 - Causes '1000s of no output' failures")
     def test_startup_timeout(self):
         startup_timeout = self.marionette.startup_timeout
 
         # Use a timeout which always cause an IOError
         self.marionette.startup_timeout = .1
         msg = "Process killed after {}s".format(self.marionette.startup_timeout)
 
         try:
--- a/testing/mozbase/mozprofile/setup.py
+++ b/testing/mozbase/mozprofile/setup.py
@@ -2,17 +2,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import
 
 from setuptools import setup
 
 PACKAGE_NAME = 'mozprofile'
-PACKAGE_VERSION = '2.0.0'
+PACKAGE_VERSION = '2.1.0'
 
 deps = [
     'mozfile>=1.2',
     'mozlog==3.*',
     'six>=1.10.0,<2',
 ]
 
 setup(name=PACKAGE_NAME,
--- a/testing/raptor/requirements.txt
+++ b/testing/raptor/requirements.txt
@@ -1,5 +1,5 @@
 mozrunner ~= 7.0
-mozprofile ~= 2.0.0
+mozprofile ~= 2.1
 manifestparser >= 1.1
 wptserve ~= 1.4.0
 mozdevice >= 1.1.1
\ No newline at end of file
--- a/testing/tps/setup.py
+++ b/testing/tps/setup.py
@@ -9,17 +9,17 @@ import sys
 version = '0.6'
 
 deps = ['httplib2 == 0.9.2',
         'mozfile >= 1.2',
         'mozhttpd == 0.7',
         'mozinfo >= 0.10',
         'mozinstall == 1.16',
         'mozprocess == 0.26',
-        'mozprofile == 2.0.0',
+        'mozprofile ~= 2.1',
         'mozrunner == 7.0.2',
         'mozversion == 1.5',
        ]
 
 # we only support python 2.6+ right now
 assert sys.version_info[0] == 2
 assert sys.version_info[1] >= 6
 
--- a/testing/web-platform/meta/WebCryptoAPI/derive_bits_keys/pbkdf2.https.any.js.ini
+++ b/testing/web-platform/meta/WebCryptoAPI/derive_bits_keys/pbkdf2.https.any.js.ini
@@ -1,11262 +1,42 @@
 [pbkdf2.https.any.html?1001-2000]
-  [Derived key of type name: AES-GCM length: 192  using short password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [short password, long salt, SHA-384, with 1 iterations with null length]
-    expected: FAIL
-
-  [short password, long salt, SHA-384, with 1 iterations with 0 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-384, with 1 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [short password, long salt, SHA-384, with 1000 iterations with null length]
-    expected: FAIL
-
-  [short password, long salt, SHA-384, with 1000 iterations with 0 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-384, with 1000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [short password, long salt, SHA-384, with 100000 iterations with null length]
-    expected: FAIL
-
-  [short password, long salt, SHA-384, with 100000 iterations with 0 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-384, with 100000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [short password, long salt, SHA-512, with 1 iterations with null length]
-    expected: FAIL
-
-  [short password, long salt, SHA-512, with 1 iterations with 0 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-512, with 1 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [short password, long salt, SHA-512, with 1000 iterations with null length]
-    expected: FAIL
-
-  [short password, long salt, SHA-512, with 1000 iterations with 0 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-512, with 1000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [short password, long salt, SHA-512, with 100000 iterations with null length]
-    expected: FAIL
-
-  [short password, long salt, SHA-512, with 100000 iterations with 0 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-512, with 100000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [short password, long salt, SHA-1, with 1 iterations with null length]
-    expected: FAIL
-
-  [short password, long salt, SHA-1, with 1 iterations with 0 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-1, with 1 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [short password, long salt, SHA-1, with 1000 iterations with null length]
-    expected: FAIL
-
-  [short password, long salt, SHA-1, with 1000 iterations with 0 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-1, with 1000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [short password, long salt, SHA-1, with 100000 iterations with null length]
-    expected: FAIL
-
-  [short password, long salt, SHA-1, with 100000 iterations with 0 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-1, with 100000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [short password, long salt, SHA-256, with 1 iterations with null length]
-    expected: FAIL
-
-  [short password, long salt, SHA-256, with 1 iterations with 0 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-256, with 1 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [short password, long salt, SHA-256, with 1000 iterations with null length]
-    expected: FAIL
-
-  [short password, long salt, SHA-256, with 1000 iterations with 0 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-256, with 1000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [short password, long salt, SHA-256, with 100000 iterations with null length]
-    expected: FAIL
-
-  [short password, long salt, SHA-256, with 100000 iterations with 0 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-256, with 100000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, long salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, empty salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, empty salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, empty salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, empty salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, empty salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, empty salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, empty salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, empty salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, empty salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, empty salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, empty salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, empty salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, empty salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, empty salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
 
 [pbkdf2.https.any.html?3001-4000]
-  [Derived key of type name: AES-CTR length: 192  using long password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using long password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using long password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using long password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using long password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using long password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using long password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using long password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using long password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using long password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using long password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using long password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [long password, short salt, SHA-384, with 1000 iterations with null length]
-    expected: FAIL
-
-  [long password, short salt, SHA-384, with 1000 iterations with 0 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-384, with 1000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [long password, short salt, SHA-384, with 100000 iterations with null length]
-    expected: FAIL
-
-  [long password, short salt, SHA-384, with 100000 iterations with 0 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-384, with 100000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [long password, short salt, SHA-512, with 1 iterations with null length]
-    expected: FAIL
-
-  [long password, short salt, SHA-512, with 1 iterations with 0 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-512, with 1 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [long password, short salt, SHA-512, with 1000 iterations with null length]
-    expected: FAIL
-
-  [long password, short salt, SHA-512, with 1000 iterations with 0 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-512, with 1000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [long password, short salt, SHA-512, with 100000 iterations with null length]
-    expected: FAIL
-
-  [long password, short salt, SHA-512, with 100000 iterations with 0 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-512, with 100000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [long password, short salt, SHA-1, with 1 iterations with null length]
-    expected: FAIL
-
-  [long password, short salt, SHA-1, with 1 iterations with 0 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-1, with 1 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [long password, short salt, SHA-1, with 1000 iterations with null length]
-    expected: FAIL
-
-  [long password, short salt, SHA-1, with 1000 iterations with 0 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-1, with 1000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [long password, short salt, SHA-1, with 100000 iterations with null length]
-    expected: FAIL
-
-  [long password, short salt, SHA-1, with 100000 iterations with 0 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-1, with 100000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-1, with 100000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [long password, short salt, SHA-256, with 1 iterations with null length]
-    expected: FAIL
-
-  [long password, short salt, SHA-256, with 1 iterations with 0 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-256, with 1 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-256, with 1 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [long password, short salt, SHA-256, with 1000 iterations with null length]
-    expected: FAIL
-
-  [long password, short salt, SHA-256, with 1000 iterations with 0 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-256, with 1000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-256, with 1000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [long password, short salt, SHA-256, with 100000 iterations with null length]
-    expected: FAIL
-
-  [long password, short salt, SHA-256, with 100000 iterations with 0 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-256, with 100000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [long password, short salt, SHA-256, with 100000 iterations with bad hash name SHA256]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [long password, long salt, SHA-384, with 1 iterations with null length]
-    expected: FAIL
-
-  [long password, long salt, SHA-384, with 1 iterations with 0 length]
-    expected: FAIL
-
-  [long password, long salt, SHA-384, with 1 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [long password, long salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using long password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using long password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using long password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using long password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using long password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using long password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using long password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using long password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using long password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using long password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using long password, long salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
 
 [pbkdf2.https.any.html?1-1000]
-  [Derived key of type name: AES-CBC length: 128  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [short password, short salt, SHA-384, with 1 iterations with null length]
-    expected: FAIL
-
-  [short password, short salt, SHA-384, with 1 iterations with 0 length]
-    expected: FAIL
-
-  [short password, short salt, SHA-384, with 1 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, short salt, SHA-384, with 1 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [short password, short salt, SHA-384, with 1000 iterations with null length]
-    expected: FAIL
-
-  [short password, short salt, SHA-384, with 1000 iterations with 0 length]
-    expected: FAIL
-
-  [short password, short salt, SHA-384, with 1000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, short salt, SHA-384, with 1000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [short password, short salt, SHA-384, with 100000 iterations with null length]
-    expected: FAIL
-
-  [short password, short salt, SHA-384, with 100000 iterations with 0 length]
-    expected: FAIL
-
-  [short password, short salt, SHA-384, with 100000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, short salt, SHA-384, with 100000 iterations with bad hash name SHA384]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [short password, short salt, SHA-512, with 1 iterations with null length]
-    expected: FAIL
-
-  [short password, short salt, SHA-512, with 1 iterations with 0 length]
-    expected: FAIL
-
-  [short password, short salt, SHA-512, with 1 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, short salt, SHA-512, with 1 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [short password, short salt, SHA-512, with 1000 iterations with null length]
-    expected: FAIL
-
-  [short password, short salt, SHA-512, with 1000 iterations with 0 length]
-    expected: FAIL
-
-  [short password, short salt, SHA-512, with 1000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, short salt, SHA-512, with 1000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [short password, short salt, SHA-512, with 100000 iterations with null length]
-    expected: FAIL
-
-  [short password, short salt, SHA-512, with 100000 iterations with 0 length]
-    expected: FAIL
-
-  [short password, short salt, SHA-512, with 100000 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, short salt, SHA-512, with 100000 iterations with bad hash name SHA512]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [short password, short salt, SHA-1, with 1 iterations with null length]
-    expected: FAIL
-
-  [short password, short salt, SHA-1, with 1 iterations with 0 length]
-    expected: FAIL
-
-  [short password, short salt, SHA-1, with 1 iterations with non-multiple of 8 length]
-    expected: FAIL
-
-  [short password, short salt, SHA-1, with 1 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 128  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 192  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CBC length: 256  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 128  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 192  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-CTR length: 256  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 128  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 192  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-GCM length: 256  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 128  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 192  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: AES-KW length: 256  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-1 length: 256  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-256 length: 256  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-384 length: 256  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [Derived key of type name: HMAC hash: SHA-512 length: 256  using short password, short salt, SHA-1, with 1000 iterations with bad hash name SHA1]
-    expected: FAIL
-
-  [short password, short salt, SHA-1, with 1000 iterations with null length]
-    expected: FAIL
<