Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
authorRazvan Maries <rmaries@mozilla.com>
Tue, 27 Nov 2018 11:48:45 +0200
changeset 504710 f3361f0816dd2a7e63d1acb92e23b74f0e8f0f30
parent 504709 70abf246cac81298bb518e1dcaf07ed24cd1512e (current diff)
parent 504630 ce39a152428a7f8ba5a4c82455dcf501c76c031b (diff)
child 504711 4f9a88ca20ebd876c51948bc171269502865c3ee
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
dom/ipc/ContentProcessHost.cpp
dom/ipc/ContentProcessHost.h
dom/media/platforms/apple/AppleCMFunctions.h
dom/media/platforms/apple/AppleVTFunctions.h
testing/web-platform/meta/fetch/api/request/destination/fetch-destination.https.html.ini
--- a/browser/base/content/browser-ctrlTab.js
+++ b/browser/base/content/browser-ctrlTab.js
@@ -425,35 +425,40 @@ var ctrlTab = {
     for (let preview of this.previews) {
       this.updatePreview(preview, null);
     }
   },
 
   onKeyDown(event) {
     if (event.keyCode != event.DOM_VK_TAB ||
         !event.ctrlKey ||
-        !this.isOpen && event.shiftKey ||
         event.altKey ||
         event.metaKey) {
       return;
     }
 
     event.preventDefault();
     event.stopPropagation();
 
     if (this.isOpen) {
       this.advanceFocus(!event.shiftKey);
-    } else {
-      let tabs = gBrowser.visibleTabs;
-      if (tabs.length > 2) {
-        this.open();
-      } else if (tabs.length == 2) {
-        let index = tabs[0].selected ? 1 : 0;
-        gBrowser.selectedTab = tabs[index];
-      }
+      return;
+    }
+
+    if (event.shiftKey) {
+      this.showAllTabs();
+      return;
+    }
+
+    let tabs = gBrowser.visibleTabs;
+    if (tabs.length > 2) {
+      this.open();
+    } else if (tabs.length == 2) {
+      let index = tabs[0].selected ? 1 : 0;
+      gBrowser.selectedTab = tabs[index];
     }
   },
 
   onKeyPress(event) {
     if (!this.isOpen ||
         !event.ctrlKey) {
       return;
     }
@@ -588,18 +593,10 @@ var ctrlTab = {
       PageThumbs.addExpirationFilter(this);
     else
       PageThumbs.removeExpirationFilter(this);
 
     // If we're not running, hide the "Show All Tabs" menu item,
     // as Shift+Ctrl+Tab will be handled by the tab bar.
     document.getElementById("menu_showAllTabs").hidden = !enable;
     document.getElementById("menu_viewPopup")[toggleEventListener]("popupshowing", this);
-
-    // Also disable the <key> to ensure Shift+Ctrl+Tab never triggers
-    // Show All Tabs.
-    var key_showAllTabs = document.getElementById("key_showAllTabs");
-    if (enable)
-      key_showAllTabs.removeAttribute("disabled");
-    else
-      key_showAllTabs.setAttribute("disabled", "true");
   },
 };
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -262,17 +262,17 @@
     <key id="key_fullZoomReduce"  key="&fullZoomReduceCmd.commandkey;"   command="cmd_fullZoomReduce"  modifiers="accel"/>
     <key                          key="&fullZoomReduceCmd.commandkey2;"  command="cmd_fullZoomReduce"  modifiers="accel"/>
     <key id="key_fullZoomEnlarge" key="&fullZoomEnlargeCmd.commandkey;"  command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key                          key="&fullZoomEnlargeCmd.commandkey2;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key                          key="&fullZoomEnlargeCmd.commandkey3;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key id="key_fullZoomReset"   key="&fullZoomResetCmd.commandkey;"    command="cmd_fullZoomReset"   modifiers="accel"/>
     <key                          key="&fullZoomResetCmd.commandkey2;"   command="cmd_fullZoomReset"   modifiers="accel"/>
 
-    <key id="key_showAllTabs" command="Browser:ShowAllTabs" keycode="VK_TAB" modifiers="control,shift" disabled="true"/>
+    <key id="key_showAllTabs" keycode="VK_TAB" modifiers="control,shift"/>
 
     <key id="key_switchTextDirection" key="&bidiSwitchTextDirectionItem.commandkey;" command="cmd_switchTextDirection" modifiers="accel,shift" />
 
     <key id="key_privatebrowsing" command="Tools:PrivateBrowsing" key="&privateBrowsingCmd.commandkey;"
          modifiers="accel,shift" reserved="true"/>
     <key id="key_sanitize" command="Tools:Sanitize" keycode="VK_DELETE" modifiers="accel,shift"/>
 #ifdef XP_MACOSX
     <key id="key_sanitize_mac" command="Tools:Sanitize" keycode="VK_BACK" modifiers="accel,shift"/>
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -961,17 +961,25 @@ var gIdentityHandler = {
             });
           }
         }
       }
     }
 
     let hasBlockedPopupIndicator = false;
     for (let permission of permissions) {
+      if (permission.id == "storage-access") {
+        // Ignore storage access permissions here, they are made visible inside
+        // the Content Blocking UI.
+        continue;
+      }
       let item = this._createPermissionItem(permission);
+      if (!item) {
+        continue;
+      }
       this._permissionList.appendChild(item);
 
       if (permission.id == "popup" &&
           gBrowser.selectedBrowser.blockedPopups &&
           gBrowser.selectedBrowser.blockedPopups.length) {
         this._createBlockedPopupIndicator();
         hasBlockedPopupIndicator = true;
       }
@@ -1030,17 +1038,21 @@ var gIdentityHandler = {
           imgBlink.startTime = sharingIconBlink.startTime;
         }
       });
     }
 
     let nameLabel = document.createXULElement("label");
     nameLabel.setAttribute("flex", "1");
     nameLabel.setAttribute("class", "identity-popup-permission-label");
-    nameLabel.textContent = SitePermissions.getPermissionLabel(aPermission.id);
+    let label = SitePermissions.getPermissionLabel(aPermission.id);
+    if (label === null) {
+      return null;
+    }
+    nameLabel.textContent = label;
     let nameLabelId = "identity-popup-permission-label-" + aPermission.id;
     nameLabel.setAttribute("id", nameLabelId);
 
     let isPolicyPermission = [
       SitePermissions.SCOPE_POLICY, SitePermissions.SCOPE_GLOBAL,
     ].includes(aPermission.scope);
 
     if (aPermission.id == "popup" && !isPolicyPermission) {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -972,16 +972,18 @@ xmlns="http://www.w3.org/1999/xhtml"
                   <image id="eme-notification-icon" class="notification-anchor-icon drm-icon" role="button"
                          tooltiptext="&urlbar.emeNotificationAnchor.tooltip;"/>
                   <image id="persistent-storage-notification-icon" class="notification-anchor-icon persistent-storage-icon" role="button"
                          tooltiptext="&urlbar.persistentStorageNotificationAnchor.tooltip;"/>
                   <image id="midi-notification-icon" class="notification-anchor-icon midi-icon" role="button"
                          tooltiptext="&urlbar.midiNotificationAnchor.tooltip;"/>
                   <image id="webauthn-notification-icon" class="notification-anchor-icon" role="button"
                          tooltiptext="&urlbar.webAuthnAnchor.tooltip;"/>
+                  <image id="storage-access-notification-icon" class="notification-anchor-icon storage-access-icon" role="button"
+                         tooltiptext="&urlbar.storageAccessAnchor.tooltip;"/>
                 </box>
                 <image id="connection-icon"/>
                 <image id="extension-icon"/>
                 <image id="remote-control-icon"
                        tooltiptext="&urlbar.remoteControlNotificationAnchor.tooltip;"/>
                 <hbox id="identity-icon-labels">
                   <label id="identity-icon-label" class="plain" flex="1"/>
                   <label id="identity-icon-country-label" class="plain"/>
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -7,21 +7,23 @@
 ChromeUtils.import("resource:///modules/SitePermissions.jsm");
 ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm");
 
 var gPermURI;
 var gPermPrincipal;
 var gUsageRequest;
 
 // Array of permissionIDs sorted alphabetically by label.
-var gPermissions = SitePermissions.listPermissions().sort((a, b) => {
-  let firstLabel = SitePermissions.getPermissionLabel(a);
-  let secondLabel = SitePermissions.getPermissionLabel(b);
-  return firstLabel.localeCompare(secondLabel);
-});
+var gPermissions = SitePermissions.listPermissions()
+  .filter(p => SitePermissions.getPermissionLabel(p) != null)
+  .sort((a, b) => {
+    let firstLabel = SitePermissions.getPermissionLabel(a);
+    let secondLabel = SitePermissions.getPermissionLabel(b);
+    return firstLabel.localeCompare(secondLabel);
+  });
 
 var permissionObserver = {
   observe(aSubject, aTopic, aData) {
     if (aTopic == "perm-changed") {
       var permission = aSubject.QueryInterface(Ci.nsIPermission);
       if (permission.matchesURI(gPermURI, true) && gPermissions.includes(permission.type)) {
           initRow(permission.type);
       }
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -98,8 +98,23 @@
           <hbox id="cfr-notification-footer-filled-stars"/>
           <hbox id="cfr-notification-footer-empty-stars"/>
           <label id="cfr-notification-footer-users"/>
           <spacer id="cfr-notification-footer-spacer" hidden="true"/>
           <label id="cfr-notification-footer-learn-more-link" class="text-link"/>
         </hbox>
       </popupnotificationfooter>
     </popupnotification>
+
+    <popupnotification id="storage-access-notification" hidden="true">
+      <popupnotificationcontent class="storage-access-notification-content">
+        <xul:vbox flex="1">
+          <!-- These need to be on the same line to avoid creating
+               whitespace between them (whitespace is added in the
+               localization file, if necessary). -->
+          <xul:description class="storage-access-perm-text"><html:span
+            id="storage-access-perm-label"/><html:a id="storage-access-perm-learnmore"
+            onclick="openTrustedLinkIn(this.href, 'tab'); return false;"
+            class="text-link popup-notification-learnmore-link"/><html:span
+            id="storage-access-perm-endlabel"/></xul:description>
+        </xul:vbox>
+      </popupnotificationcontent>
+    </popupnotification>
--- a/browser/base/content/test/sync/browser_contextmenu_sendtab.js
+++ b/browser/base/content/test/sync/browser_contextmenu_sendtab.js
@@ -116,16 +116,20 @@ add_task(async function test_tab_context
 
   updateTabContextMenu(testTab);
   is(document.getElementById("context_sendTabToDevice").hidden, true, "Send tab to device is hidden");
 
   getter.restore();
   [...document.querySelectorAll(".sync-ui-item")].forEach(e => e.hidden = false);
 });
 
+add_task(async function teardown() {
+  Weave.Service.clientsEngine.getClientType.restore();
+});
+
 async function openTabContextMenu(openSubmenuId = null) {
   const contextMenu = document.getElementById("tabContextMenu");
   is(contextMenu.state, "closed", "checking if popup is closed");
 
   const awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
   EventUtils.synthesizeMouseAtCenter(gBrowser.selectedTab, {type: "contextmenu", button: 2});
   await awaitPopupShown;
 
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -3130,16 +3130,19 @@ const ContentPermissionIntegration = {
         return new PermissionUI.PersistentStoragePermissionPrompt(request);
       }
       case "midi": {
         return new PermissionUI.MIDIPermissionPrompt(request);
       }
       case "autoplay-media": {
         return new PermissionUI.AutoplayPermissionPrompt(request);
       }
+      case "storage-access": {
+        return new PermissionUI.StorageAccessPermissionPrompt(request);
+      }
     }
     return undefined;
   },
 };
 
 function ContentPermissionPrompt() {}
 
 ContentPermissionPrompt.prototype = {
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -127,22 +127,22 @@
                 <description control="contentBlockingDisableTrackingProtectionExtension" flex="1"/>
                 <button id="contentBlockingDisableTrackingProtectionExtension"
                         class="extension-controlled-button accessory-button"
                         data-l10n-id="disable-extension" hidden="true"/>
               </hbox>
                 <hbox class="custom-option">
                   <checkbox id="contentBlockingTrackingProtectionCheckbox"
                             class="content-blocking-checkbox" flex="1"
-                            data-l10n-id="content-blocking-tracking-protection-trackers-label"
+                            data-l10n-id="content-blocking-trackers-label"
                             aria-describedby="contentBlockingCustomDesc"/>
                   <vbox>
                     <menulist id="trackingProtectionMenu">
                       <menupopup>
-                        <menuitem data-l10n-id="content-blocking-tracking-protection-option-private" value="private"/>
+                        <menuitem data-l10n-id="content-blocking-option-private" value="private"/>
                         <menuitem data-l10n-id="content-blocking-tracking-protection-option-all-windows" value="always"/>
                       </menupopup>
                     </menulist>
                   </vbox>
                 </hbox>
                 <label id="changeBlockListLink"
                        data-l10n-id="content-blocking-tracking-protection-change-block-list"
                        class="text-link"
@@ -158,20 +158,20 @@
                   <vbox>
                     <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
                     <hbox>
                       <menulist id="blockCookiesMenu"
                                 preference="network.cookie.cookieBehavior"
                                 onsyncfrompreference="return gPrivacyPane.readBlockCookiesFrom();"
                                 onsynctopreference="return gPrivacyPane.writeBlockCookiesFrom();">
                         <menupopup>
-                          <menuitem data-l10n-id="sitedata-block-trackers-option" value="trackers"/>
-                          <menuitem data-l10n-id="sitedata-block-unvisited-option" value="unvisited"/>
-                          <menuitem data-l10n-id="sitedata-block-all-third-party-option" value="all-third-parties"/>
-                          <menuitem data-l10n-id="sitedata-block-all-option" value="always"/>
+                          <menuitem data-l10n-id="sitedata-option-block-trackers" value="trackers"/>
+                          <menuitem data-l10n-id="sitedata-option-block-unvisited" value="unvisited"/>
+                          <menuitem data-l10n-id="sitedata-option-block-all-third-party" value="all-third-parties"/>
+                          <menuitem data-l10n-id="sitedata-option-block-all" value="always"/>
                         </menupopup>
                       </menulist>
                     </hbox>
                   </vbox>
                 </hbox>
               <vbox class="content-blocking-warning">
                 <vbox class="indent">
                   <hbox>
--- a/browser/components/search/content/searchbar.js
+++ b/browser/components/search/content/searchbar.js
@@ -459,16 +459,21 @@ class MozSearchbar extends MozXULElement
         return;
       }
 
       // Ignore clicks on the search go button.
       if (event.originalTarget.classList.contains("search-go-button")) {
         return;
       }
 
+      // Ignore clicks on menu items in the input's context menu.
+      if (event.originalTarget.localName == "menuitem") {
+        return;
+      }
+
       let isIconClick = event.originalTarget.classList.contains("searchbar-search-button");
 
       // Ignore clicks on the icon if they were made to close the popup
       if (isIconClick && this._clickClosedPopup) {
         return;
       }
 
       // Open the suggestions whenever clicking on the search icon or if there
--- a/browser/locales/en-US/browser/preferences/preferences.ftl
+++ b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -765,23 +765,23 @@ sitedata-disallow-cookies-option =
     .label = Block cookies and site data
     .accesskey = B
 
 # This label means 'type of content that is blocked', and is followed by a drop-down list with content types below.
 # The list items are the strings named sitedata-block-*-option*.
 sitedata-block-desc = Type blocked
     .accesskey = T
 
-sitedata-block-trackers-option =
+sitedata-option-block-trackers =
     .label = Third-party trackers
-sitedata-block-unvisited-option =
+sitedata-option-block-unvisited =
     .label = Cookies from unvisited websites
-sitedata-block-all-third-party-option =
+sitedata-option-block-all-third-party =
     .label = All third-party cookies (may cause websites to break)
-sitedata-block-all-option =
+sitedata-option-block-all =
     .label = All cookies (will cause websites to break)
 
 sitedata-clear =
     .label = Clear Data…
     .accesskey = l
 
 sitedata-settings =
     .label = Manage Data…
@@ -837,26 +837,23 @@ content-blocking-private-trackers = Know
 content-blocking-third-party-cookies = Third-party tracking cookies
 content-blocking-all-windows-trackers = Known trackers in all windows
 content-blocking-all-third-party-cookies = All third-party cookies
 
 content-blocking-warning-title = Heads up!
 content-blocking-warning-desc = Blocking cookies and trackers can cause some websites to break. It’s easy to disable blocking for sites you trust.
 content-blocking-learn-how = Learn how
 
-content-blocking-tracking-protection-trackers-label =
+content-blocking-trackers-label =
   .label = Trackers
   .accesskey = T
-content-blocking-tracking-protection-all-detected-trackers-label =
-  .label = All Detected Trackers
-  .accesskey = T
 content-blocking-tracking-protection-option-all-windows =
   .label = In all windows
   .accesskey = A
-content-blocking-tracking-protection-option-private =
+content-blocking-option-private =
   .label = Only in Private Windows
   .accesskey = p
 content-blocking-tracking-protection-change-block-list = Change block list
 
 content-blocking-cookies-label =
   .label = Cookies
   .accesskey = C
 
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -277,16 +277,17 @@ These should match what Safari and other
 <!ENTITY urlbar.canvasNotificationAnchor.tooltip          "Manage canvas extraction permission">
 <!ENTITY urlbar.indexedDBNotificationAnchor.tooltip       "Open offline storage message panel">
 <!ENTITY urlbar.passwordNotificationAnchor.tooltip        "Open save password message panel">
 <!ENTITY urlbar.pluginsNotificationAnchor.tooltip         "Manage plug-in use">
 <!ENTITY urlbar.webNotificationAnchor.tooltip             "Change whether you can receive notifications from the site">
 <!ENTITY urlbar.persistentStorageNotificationAnchor.tooltip     "Store data in Persistent Storage">
 <!ENTITY urlbar.remoteControlNotificationAnchor.tooltip   "Browser is under remote control">
 <!ENTITY urlbar.webAuthnAnchor.tooltip                    "Open Web Authentication panel">
+<!ENTITY urlbar.storageAccessAnchor.tooltip               "Open browsing activity permission panel">
 
 <!ENTITY urlbar.webRTCShareDevicesNotificationAnchor.tooltip      "Manage sharing your camera and/or microphone with the site">
 <!ENTITY urlbar.webRTCShareMicrophoneNotificationAnchor.tooltip   "Manage sharing your microphone with the site">
 <!ENTITY urlbar.webRTCShareScreenNotificationAnchor.tooltip       "Manage sharing your windows or screen with the site">
 
 <!ENTITY urlbar.servicesNotificationAnchor.tooltip        "Open install message panel">
 <!ENTITY urlbar.translateNotificationAnchor.tooltip       "Translate this page">
 <!ENTITY urlbar.translatedNotificationAnchor.tooltip      "Manage page translation">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -960,16 +960,34 @@ autoplay.remember-private = Remember for
 # LOCALIZATION NOTE (autoplay.message): %S is the name of the site URL (https://...) trying to autoplay media
 autoplay.message = Will you allow %S to autoplay media with sound?
 autoplay.messageWithFile = Will you allow this file to autoplay media with sound?
 # LOCALIZATION NOTE (panel.back):
 # This is used by screen readers to label the "back" button in various browser
 # popup panels, including the sliding subviews of the main menu.
 panel.back = Back
 
+storageAccess.Allow.label = Allow Access
+storageAccess.Allow.accesskey = A
+storageAccess.AllowOnAnySite.label = Allow access on any site
+storageAccess.AllowOnAnySite.accesskey = w
+storageAccess.DontAllow.label = Block Access
+storageAccess.DontAllow.accesskey = B
+# LOCALIZATION NOTE (storageAccess.message):
+# %1$S is the name of the site URL (www.site1.example) trying to track the user's activity.
+# %2$S is the name of the site URL (www.site2.example) that the user is visiting.  This is the same domain name displayed in the address bar.
+storageAccess.message = Will you give %1$S access to track your browsing activity on %2$S?
+# LOCALIZATION NOTE (storageAccess.description.label):
+# %1$S is the name of the site URL (www.site1.example) trying to track the user's activity.
+# %2$S will be replaced with the localized version of storageAccess.description.learnmore.  This text will be converted into a hyper-link linking to the SUMO page explaining the concept of third-party trackers.
+storageAccess.description.label = You may want to block %1$S on this site if you don’t recognize or trust it. Learn more about %2$S
+# LOCALIZATION NOTE (storageAccess.description.learnmore):
+# The value of this string is embedded inside storageAccess.description.label.  See the localization note for storageAccess.description.label.
+storageAccess.description.learnmore = third-party trackers
+
 confirmationHint.sendToDevice.label = Sent!
 confirmationHint.sendToDeviceOffline.label = Queued (offline)
 confirmationHint.copyURL.label = Copied to clipboard!
 confirmationHint.pageBookmarked.label = Saved to Library!
 confirmationHint.addSearchEngine.label = Search engine added!
 
 # LOCALIZATION NOTE (livebookmarkMigration.title):
 # Used by the export of user's live bookmarks to an OPML file as a title for the file.
--- a/browser/locales/en-US/chrome/browser/sitePermissions.properties
+++ b/browser/locales/en-US/chrome/browser/sitePermissions.properties
@@ -39,9 +39,9 @@ permission.install.label = Install Add-o
 permission.popup.label = Open Pop-up Windows
 permission.geo.label = Access Your Location
 permission.shortcuts.label = Override Keyboard Shortcuts
 permission.focus-tab-by-prompt.label = Switch to this Tab
 permission.persistent-storage.label = Store Data in Persistent Storage
 permission.canvas.label = Extract Canvas Data
 permission.flash-plugin.label = Run Adobe Flash
 permission.midi.label = Access MIDI Devices
-permission.midi-sysex.label = Access MIDI Devices with SysEx Support
\ No newline at end of file
+permission.midi-sysex.label = Access MIDI Devices with SysEx Support
--- a/browser/modules/AsyncTabSwitcher.jsm
+++ b/browser/modules/AsyncTabSwitcher.jsm
@@ -131,16 +131,18 @@ class AsyncTabSwitcher {
     // For telemetry, keeps track of what most recently cleared
     // the loadTimer, which can tell us something about the cause
     // of tab switch spinners.
     this._loadTimerClearedBy = "none";
 
     this._useDumpForLogging = false;
     this._logInit = false;
 
+    this._tabSwitchStopWatchRunning = false;
+
     this.window.addEventListener("MozAfterPaint", this);
     this.window.addEventListener("MozLayerTreeReady", this);
     this.window.addEventListener("MozLayerTreeCleared", this);
     this.window.addEventListener("TabRemotenessChange", this);
     this.window.addEventListener("sizemodechange", this);
     this.window.addEventListener("occlusionstatechange", this);
     this.window.addEventListener("SwapDocShells", this, true);
     this.window.addEventListener("EndSwapDocShells", this, true);
@@ -430,16 +432,17 @@ class AsyncTabSwitcher {
             // completion.
             this.switchPaintId = this.window.windowUtils.lastTransactionId + 1;
           } else {
             // We're making the tab visible even though we haven't yet got layers for it.
             // It's hard to know which composite the layers will first be available in (and
             // the parent process might not even get MozAfterPaint delivered for it), so just
             // give up measuring this for now. :(
             TelemetryStopwatch.cancel("FX_TAB_SWITCH_COMPOSITE_E10S_MS", this.window);
+            this._tabSwitchStopWatchRunning = false;
           }
 
           this.tabbrowser._adjustFocusAfterTabSwitch(showTab);
           this.maybeActivateDocShell(this.requestedTab);
         }
       }
 
       // This doesn't necessarily exist if we're a new window and haven't switched tabs yet
@@ -735,20 +738,22 @@ class AsyncTabSwitcher {
   }
 
   // Fires when we paint the screen. Any tab switches we initiated
   // previously are done, so there's no need to keep the old layers
   // around.
   onPaint(event) {
     if (this.switchPaintId != -1 &&
         event.transactionId >= this.switchPaintId) {
-      let time = TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_COMPOSITE_E10S_MS", this.window);
-      if (time != -1) {
-        TelemetryStopwatch.finish("FX_TAB_SWITCH_COMPOSITE_E10S_MS", this.window);
-        this.log("DEBUG: tab switch time including compositing = " + time);
+      if (this._tabSwitchStopWatchRunning) {
+        let time = TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_COMPOSITE_E10S_MS", this.window);
+        if (time != -1) {
+          TelemetryStopwatch.finish("FX_TAB_SWITCH_COMPOSITE_E10S_MS", this.window);
+          this.log("DEBUG: tab switch time including compositing = " + time);
+        }
       }
       this.addMarker("AsyncTabSwitch:Composited");
       this.switchPaintId = -1;
     }
 
     this.maybeVisibleTabs.clear();
   }
 
@@ -1070,18 +1075,21 @@ class AsyncTabSwitcher {
    * Telemetry and Profiler related helpers for recording tab switch
    * timing.
    */
 
   startTabSwitch() {
     TelemetryStopwatch.cancel("FX_TAB_SWITCH_TOTAL_E10S_MS", this.window);
     TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_E10S_MS", this.window);
 
-    TelemetryStopwatch.cancel("FX_TAB_SWITCH_COMPOSITE_E10S_MS", this.window);
+    if (this._tabSwitchStopWatchRunning) {
+      TelemetryStopwatch.cancel("FX_TAB_SWITCH_COMPOSITE_E10S_MS", this.window);
+    }
     TelemetryStopwatch.start("FX_TAB_SWITCH_COMPOSITE_E10S_MS", this.window);
+    this._tabSwitchStopWatchRunning = true;
     this.addMarker("AsyncTabSwitch:Start");
     this.switchInProgress = true;
   }
 
   /**
    * Something has occurred that might mean that we've completed
    * the tab switch (layers are ready, paints are done, spinners
    * are hidden). This checks to make sure all conditions are
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -252,18 +252,24 @@ var PermissionPromptPrototype = {
    * If the prompt will be shown to the user, this callback will
    * be called just before. Subclasses may want to override this
    * in order to, for example, bump a counter Telemetry probe for
    * how often a particular permission request is seen.
    */
   onBeforeShow() {},
 
   /**
-   * If the prompt was be shown to the user, this callback will
-   * be called just after its been hidden.
+   * If the prompt was shown to the user, this callback will be called just
+   * after it's been shown.
+   */
+  onShown() {},
+
+  /**
+   * If the prompt was shown to the user, this callback will be called just
+   * after it's been hidden.
    */
   onAfterShow() {},
 
   /**
    * Will determine if a prompt should be shown to the user, and if so,
    * will show it.
    *
    * If a permissionKey is defined prompt() might automatically
@@ -418,16 +424,20 @@ var PermissionPromptPrototype = {
     options.hideClose = !Services.prefs.getBoolPref("privacy.permissionPrompts.showCloseButton");
     options.eventCallback = (topic) => {
       // When the docshell of the browser is aboout to be swapped to another one,
       // the "swapping" event is called. Returning true causes the notification
       // to be moved to the new browser.
       if (topic == "swapping") {
         return true;
       }
+      // The prompt has been shown, notify the PermissionUI.
+      if (topic == "shown") {
+        this.onShown();
+      }
       // The prompt has been removed, notify the PermissionUI.
       if (topic == "removed") {
         this.onAfterShow();
       }
       return false;
     };
 
     this.onBeforeShow();
@@ -467,18 +477,18 @@ var PermissionPromptForRequestPrototype 
   get principal() {
     return this.request.principal;
   },
 
   cancel() {
     this.request.cancel();
   },
 
-  allow() {
-    this.request.allow();
+  allow(choices) {
+    this.request.allow(choices);
   },
 };
 
 PermissionUI.PermissionPromptForRequestPrototype =
   PermissionPromptForRequestPrototype;
 
 /**
  * Creates a PermissionPrompt for a nsIContentPermissionRequest for
@@ -900,8 +910,104 @@ AutoplayPermissionPrompt.prototype = {
       }
     };
     this.browser.addEventListener(
       "DOMAudioPlaybackStarted", this.handlePlaybackStart);
   },
 };
 
 PermissionUI.AutoplayPermissionPrompt = AutoplayPermissionPrompt;
+
+function StorageAccessPermissionPrompt(request) {
+  this.request = request;
+}
+
+StorageAccessPermissionPrompt.prototype = {
+  __proto__: PermissionPromptForRequestPrototype,
+
+  get usePermissionManager() {
+    return false;
+  },
+
+  get permissionKey() {
+    // Make sure this name is unique per each third-party tracker
+    return "storage-access-" + this.principal.origin;
+  },
+
+  get popupOptions() {
+    return {
+      displayURI: false,
+      name: this.principal.URI.hostPort,
+      secondName: this.topLevelPrincipal.URI.hostPort,
+    };
+  },
+
+  onShown() {
+    let document = this.browser.ownerDocument;
+    let label =
+      gBrowserBundle.formatStringFromName("storageAccess.description.label",
+                                          [this.request.principal.URI.hostPort, "<>"], 2);
+    let parts = label.split("<>");
+    if (parts.length == 1) {
+      parts.push("");
+    }
+    let map = {
+      "storage-access-perm-label": parts[0],
+      "storage-access-perm-learnmore":
+        gBrowserBundle.GetStringFromName("storageAccess.description.learnmore"),
+      "storage-access-perm-endlabel": parts[1],
+    };
+    for (let id in map) {
+      let str = map[id];
+      document.getElementById(id).textContent = str;
+    }
+    let learnMoreURL =
+      Services.urlFormatter.formatURLPref("app.support.baseURL") + "third-party-cookies";
+    document.getElementById("storage-access-perm-learnmore")
+            .href = learnMoreURL;
+  },
+
+  get notificationID() {
+    return "storage-access";
+  },
+
+  get anchorID() {
+    return "storage-access-notification-icon";
+  },
+
+  get message() {
+    return gBrowserBundle.formatStringFromName("storageAccess.message", ["<>", "<>"], 2);
+  },
+
+  get promptActions() {
+    let self = this;
+    return [{
+        label: gBrowserBundle.GetStringFromName("storageAccess.DontAllow.label"),
+        accessKey: gBrowserBundle.GetStringFromName("storageAccess.DontAllow.accesskey"),
+        action: Ci.nsIPermissionManager.DENY_ACTION,
+        callback(state) {
+          self.cancel();
+        },
+      },
+      {
+        label: gBrowserBundle.GetStringFromName("storageAccess.Allow.label"),
+        accessKey: gBrowserBundle.GetStringFromName("storageAccess.Allow.accesskey"),
+        action: Ci.nsIPermissionManager.ALLOW_ACTION,
+        callback(state) {
+          self.allow({"storage-access": "allow"});
+        },
+      },
+      {
+        label: gBrowserBundle.GetStringFromName("storageAccess.AllowOnAnySite.label"),
+        accessKey: gBrowserBundle.GetStringFromName("storageAccess.AllowOnAnySite.accesskey"),
+        action: Ci.nsIPermissionManager.ALLOW_ACTION,
+        callback(state) {
+          self.allow({"storage-access": "allow-on-any-site"});
+        },
+    }];
+  },
+
+  get topLevelPrincipal() {
+    return this.request.topLevelPrincipal;
+  },
+};
+
+PermissionUI.StorageAccessPermissionPrompt = StorageAccessPermissionPrompt;
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -350,17 +350,17 @@ var SitePermissions = {
    *        The browser to fetch permission for.
    *
    * @return {Array<Object>} a list of objects with the keys:
    *           - id: the permissionID of the permission
    *           - state: a constant representing the current permission state
    *             (e.g. SitePermissions.ALLOW)
    *           - scope: a constant representing how long the permission will
    *             be kept.
-   *           - label: the localized label
+   *           - label: the localized label, or null if none is available.
    */
   getAllPermissionDetailsForBrowser(browser) {
     return this.getAllForBrowser(browser).map(({id, scope, state}) =>
       ({id, scope, state, label: this.getPermissionLabel(id)}));
   },
 
   /**
    * Checks whether a UI for managing permissions should be exposed for a given
@@ -648,19 +648,28 @@ var SitePermissions = {
 
   /**
    * Returns the localized label for the permission with the given ID, to be
    * used in a UI for managing permissions.
    *
    * @param {string} permissionID
    *        The permission to get the label for.
    *
-   * @return {String} the localized label.
+   * @return {String} the localized label or null if none is available.
    */
   getPermissionLabel(permissionID) {
+    if (!(permissionID in gPermissionObject)) {
+      // Permission can't be found.
+      return null;
+    }
+    if ("labelID" in gPermissionObject[permissionID] &&
+        gPermissionObject[permissionID].labelID === null) {
+      // Permission doesn't support having a label.
+      return null;
+    }
     let labelID = gPermissionObject[permissionID].labelID || permissionID;
     return gStringBundle.GetStringFromName("permission." + labelID + ".label");
   },
 
   /**
    * Returns the localized label for the given permission state, to be used in
    * a UI for managing permissions.
    *
@@ -847,16 +856,23 @@ var gPermissionObject = {
 
   "midi": {
     exactHostMatch: true,
   },
 
   "midi-sysex": {
     exactHostMatch: true,
   },
+
+  "storage-access": {
+    labelID: null,
+    getDefault() {
+      return SitePermissions.UNKNOWN;
+    },
+  },
 };
 
 if (!Services.prefs.getBoolPref("dom.webmidi.enabled")) {
   // ESLint gets angry about array versus dot notation here, but some permission
   // names use hyphens. Disabling rule for line to keep things consistent.
   // eslint-disable-next-line dot-notation
   delete gPermissionObject["midi"];
   delete gPermissionObject["midi-sysex"];
--- a/browser/modules/test/browser/browser_PermissionUI_prompts.js
+++ b/browser/modules/test/browser/browser_PermissionUI_prompts.js
@@ -32,67 +32,82 @@ add_task(async function test_midi_permis
 
 // Tests that AutoplayPermissionPrompt works as expected
 add_task(async function test_autoplay_permission_prompt() {
   Services.prefs.setIntPref("media.autoplay.default", Ci.nsIAutoplay.PROMPT);
   await testPrompt(PermissionUI.AutoplayPermissionPrompt);
   Services.prefs.clearUserPref("media.autoplay.default");
 });
 
+// Tests that AutoplayPermissionPrompt works as expected
+add_task(async function test_storage_access_permission_prompt() {
+  await testPrompt(PermissionUI.StorageAccessPermissionPrompt);
+});
+
 async function testPrompt(Prompt) {
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: "http://example.com",
   }, async function(browser) {
     let mockRequest = makeMockPermissionRequest(browser);
     let principal = mockRequest.principal;
     let TestPrompt = new Prompt(mockRequest);
     let permissionKey = TestPrompt.usePermissionManager &&
                         TestPrompt.permissionKey;
 
     registerCleanupFunction(function() {
-      SitePermissions.remove(principal.URI, permissionKey);
+      if (permissionKey) {
+        SitePermissions.remove(principal.URI, permissionKey);
+      }
     });
 
     let shownPromise =
       BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
     TestPrompt.prompt();
     await shownPromise;
     let notification =
       PopupNotifications.getNotification(TestPrompt.notificationID, browser);
     Assert.ok(notification, "Should have gotten the notification");
 
-    let curPerm = SitePermissions.get(principal.URI, permissionKey, browser);
-    Assert.equal(curPerm.state, SitePermissions.UNKNOWN,
-                 "Should be no permission set to begin with.");
+    let curPerm;
+    if (permissionKey) {
+      curPerm = SitePermissions.get(principal.URI, permissionKey, browser);
+      Assert.equal(curPerm.state, SitePermissions.UNKNOWN,
+                   "Should be no permission set to begin with.");
+    }
 
     // First test denying the permission request without the checkbox checked.
     let popupNotification = getPopupNotificationNode();
     popupNotification.checkbox.checked = false;
 
     let isNotificationPrompt = Prompt == PermissionUI.DesktopNotificationPermissionPrompt;
     let isPersistentStoragePrompt = Prompt == PermissionUI.PersistentStoragePermissionPrompt;
+    let isStorageAccessPrompt = Prompt == PermissionUI.StorageAccessPermissionPrompt;
 
-    let expectedSecondaryActionsCount = isNotificationPrompt || isPersistentStoragePrompt ? 2 : 1;
+    let expectedSecondaryActionsCount = isNotificationPrompt ||
+                                        isPersistentStoragePrompt ||
+                                        isStorageAccessPrompt ? 2 : 1;
     Assert.equal(notification.secondaryActions.length, expectedSecondaryActionsCount,
                  "There should only be " + expectedSecondaryActionsCount + " secondary action(s)");
     await clickSecondaryAction();
-    curPerm = SitePermissions.get(principal.URI, permissionKey, browser);
-    Assert.deepEqual(curPerm, {
-                       state: SitePermissions.BLOCK,
-                       scope: SitePermissions.SCOPE_TEMPORARY,
-                     }, "Should have denied the action temporarily");
+    if (permissionKey) {
+      curPerm = SitePermissions.get(principal.URI, permissionKey, browser);
+      Assert.deepEqual(curPerm, {
+                         state: SitePermissions.BLOCK,
+                         scope: SitePermissions.SCOPE_TEMPORARY,
+                       }, "Should have denied the action temporarily");
 
-    Assert.ok(mockRequest._cancelled,
-              "The request should have been cancelled");
-    Assert.ok(!mockRequest._allowed,
-              "The request should not have been allowed");
+      Assert.ok(mockRequest._cancelled,
+                "The request should have been cancelled");
+      Assert.ok(!mockRequest._allowed,
+                "The request should not have been allowed");
 
-    SitePermissions.remove(principal.URI, permissionKey, browser);
-    mockRequest._cancelled = false;
+      SitePermissions.remove(principal.URI, permissionKey, browser);
+      mockRequest._cancelled = false;
+    }
 
     // Bring the PopupNotification back up now...
     shownPromise =
       BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
     TestPrompt.prompt();
     await shownPromise;
 
     // Test denying the permission request with the checkbox checked (for geolocation)
@@ -103,44 +118,50 @@ async function testPrompt(Prompt) {
       secondaryActionToClickIndex = 1;
     } else {
       popupNotification.checkbox.checked = true;
     }
 
     Assert.equal(notification.secondaryActions.length, expectedSecondaryActionsCount,
                  "There should only be " + expectedSecondaryActionsCount + " secondary action(s)");
     await clickSecondaryAction(secondaryActionToClickIndex);
-    curPerm = SitePermissions.get(principal.URI, permissionKey);
-    Assert.deepEqual(curPerm, {
-                       state: SitePermissions.BLOCK,
-                       scope: SitePermissions.SCOPE_PERSISTENT,
-                     }, "Should have denied the action permanently");
-    Assert.ok(mockRequest._cancelled,
-              "The request should have been cancelled");
-    Assert.ok(!mockRequest._allowed,
-              "The request should not have been allowed");
+    if (permissionKey) {
+      curPerm = SitePermissions.get(principal.URI, permissionKey);
+      Assert.deepEqual(curPerm, {
+                         state: SitePermissions.BLOCK,
+                         scope: SitePermissions.SCOPE_PERSISTENT,
+                       }, "Should have denied the action permanently");
+      Assert.ok(mockRequest._cancelled,
+                "The request should have been cancelled");
+      Assert.ok(!mockRequest._allowed,
+                "The request should not have been allowed");
+    }
 
-    SitePermissions.remove(principal.URI, permissionKey);
-    mockRequest._cancelled = false;
+    if (permissionKey) {
+      SitePermissions.remove(principal.URI, permissionKey);
+      mockRequest._cancelled = false;
+    }
 
     // Bring the PopupNotification back up now...
     shownPromise =
       BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
     TestPrompt.prompt();
     await shownPromise;
 
     // Test allowing the permission request with the checkbox checked.
     popupNotification = getPopupNotificationNode();
     popupNotification.checkbox.checked = true;
 
     await clickMainAction();
-    curPerm = SitePermissions.get(principal.URI, permissionKey);
-    Assert.deepEqual(curPerm, {
-                       state: SitePermissions.ALLOW,
-                       scope: SitePermissions.SCOPE_PERSISTENT,
-                     }, "Should have allowed the action permanently");
-    Assert.ok(!mockRequest._cancelled,
-              "The request should not have been cancelled");
-    Assert.ok(mockRequest._allowed,
-              "The request should have been allowed");
+    if (permissionKey) {
+      curPerm = SitePermissions.get(principal.URI, permissionKey);
+      Assert.deepEqual(curPerm, {
+                         state: SitePermissions.ALLOW,
+                         scope: SitePermissions.SCOPE_PERSISTENT,
+                       }, "Should have allowed the action permanently");
+      Assert.ok(!mockRequest._cancelled,
+                "The request should not have been cancelled");
+      Assert.ok(mockRequest._allowed,
+                "The request should have been allowed");
+    }
   });
 }
 
--- a/browser/modules/test/browser/head.js
+++ b/browser/modules/test/browser/head.js
@@ -176,16 +176,17 @@ function makeMockPermissionRequest(brows
     options: Cc["@mozilla.org/array;1"].createInstance(Ci.nsIArray),
     QueryInterface: ChromeUtils.generateQI([Ci.nsIContentPermissionType]),
   };
   let types = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
   types.appendElement(type);
   let result = {
     types,
     principal: browser.contentPrincipal,
+    topLevelPrincipal: browser.contentPrincipal,
     requester: null,
     _cancelled: false,
     cancel() {
       this._cancelled = true;
     },
     _allowed: false,
     allow() {
       this._allowed = true;
--- a/browser/modules/test/unit/test_SitePermissions.js
+++ b/browser/modules/test/unit/test_SitePermissions.js
@@ -7,17 +7,17 @@ ChromeUtils.import("resource:///modules/
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 const RESIST_FINGERPRINTING_ENABLED = Services.prefs.getBoolPref("privacy.resistFingerprinting");
 const MIDI_ENABLED = Services.prefs.getBoolPref("dom.webmidi.enabled");
 
 add_task(async function testPermissionsListing() {
   let expectedPermissions = ["autoplay-media", "camera", "cookie", "desktop-notification", "focus-tab-by-prompt",
      "geo", "image", "install", "microphone", "plugin:flash", "popup", "screen", "shortcuts",
-     "persistent-storage"];
+     "persistent-storage", "storage-access"];
   if (RESIST_FINGERPRINTING_ENABLED) {
     // Canvas permission should be hidden unless privacy.resistFingerprinting
     // is true.
     expectedPermissions.push("canvas");
   }
   if (MIDI_ENABLED) {
     // Should remove this checking and add it as default after it is fully pref'd-on.
     expectedPermissions.push("midi");
@@ -114,17 +114,18 @@ add_task(async function testExactHostMat
     exactHostMatched.push("canvas");
   }
   if (MIDI_ENABLED) {
     // WebMIDI is only pref'd on in nightly.
     // Should remove this checking and add it as default after it is fully pref-on.
     exactHostMatched.push("midi");
     exactHostMatched.push("midi-sysex");
   }
-  let nonExactHostMatched = ["image", "cookie", "plugin:flash", "popup", "install", "shortcuts"];
+  let nonExactHostMatched = ["image", "cookie", "plugin:flash", "popup", "install", "shortcuts",
+                             "storage-access"];
 
   let permissions = SitePermissions.listPermissions();
   for (let permission of permissions) {
     SitePermissions.set(uri, permission, SitePermissions.ALLOW);
 
     if (exactHostMatched.includes(permission)) {
       // Check that the sub-origin does not inherit the permission from its parent.
       Assert.equal(SitePermissions.get(subUri, permission).state, SitePermissions.getDefault(permission),
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -27,17 +27,19 @@
 
 /* INDIVIDUAL NOTIFICATIONS */
 
 .focus-tab-by-prompt-icon {
   list-style-image: url(chrome://browser/skin/notification-icons/focus-tab-by-prompt.svg);
 }
 
 .popup-notification-icon[popupid="persistent-storage"],
-.persistent-storage-icon {
+.popup-notification-icon[popupid="storage-access"],
+.persistent-storage-icon,
+.storage-access-icon {
   list-style-image: url(chrome://browser/skin/notification-icons/persistent-storage.svg);
 }
 
 .persistent-storage-icon.blocked-permission-icon {
   list-style-image: url(chrome://browser/skin/notification-icons/persistent-storage-blocked.svg);
 }
 
 .popup-notification-icon[popupid="web-notifications"],
@@ -68,16 +70,34 @@
 .popup-notification-icon[popupid="autoplay-media"] {
   list-style-image: url(chrome://browser/skin/notification-icons/autoplay-media-detailed.svg);
 }
 
 .autoplay-media-icon.blocked-permission-icon {
   list-style-image: url(chrome://browser/skin/notification-icons/autoplay-media-blocked.svg);
 }
 
+.storage-access-notification-content {
+  color: var(--panel-disabled-color);
+  font-style: italic;
+  margin-top: 15px;
+}
+
+.storage-access-notification-content .text-link {
+  color: -moz-nativehyperlinktext;
+}
+
+.storage-access-notification-content .text-link:hover {
+  text-decoration: underline;
+}
+
+#storage-access-notification .popup-notification-body-container {
+  padding: 20px;
+}
+
 .popup-notification-icon[popupid="indexedDB-permissions-prompt"],
 .indexedDB-icon {
   list-style-image: url(chrome://browser/skin/notification-icons/indexedDB.svg);
 }
 
 .login-icon {
   list-style-image: url(chrome://browser/skin/notification-icons/login.svg);
 }
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -121,20 +121,22 @@ set_config('YASM_ASFLAGS', yasm_asflags)
 # nasm detection
 # ==============================================================
 nasm = check_prog('NASM', ['nasm'], allow_missing=True)
 
 
 @depends_if(nasm)
 @checking('nasm version')
 def nasm_version(nasm):
-    version = check_cmd_output(
-        nasm, '-v',
-        onerror=lambda: die('Failed to get nasm version.')
-    ).splitlines()[0].split()[2]
+    (retcode, stdout, _) = get_cmd_output(nasm, '-v')
+    if retcode:
+        # mac stub binary
+        return None
+
+    version = stdout.splitlines()[0].split()[2]
     return Version(version)
 
 
 @depends_if(nasm_version)
 def nasm_major_version(nasm_version):
     return str(nasm_version.major)
 
 
--- a/build/pgo/profileserver.py
+++ b/build/pgo/profileserver.py
@@ -21,18 +21,24 @@ PORT = 8888
 PATH_MAPPINGS = {
     '/js-input/webkit/PerformanceTests': 'third_party/webkit/PerformanceTests',
 }
 
 
 if __name__ == '__main__':
     cli = CLI()
     debug_args, interactive = cli.debugger_arguments()
+    runner_args = cli.runner_args()
 
     build = MozbuildObject.from_environment()
+
+    binary = runner_args.get('binary')
+    if not binary:
+        binary = build.get_binary_path(where="staged-package")
+
     path_mappings = {
         k: os.path.join(build.topsrcdir, v)
         for k, v in PATH_MAPPINGS.items()
     }
     httpd = MozHttpd(port=PORT,
                      docroot=os.path.join(build.topsrcdir, "build", "pgo"),
                      path_mappings=path_mappings)
     httpd.start(block=False)
@@ -81,29 +87,27 @@ if __name__ == '__main__':
 
                 vcdir = os.path.abspath(os.path.join(env[e], '../../VC/bin'))
                 if os.path.exists(vcdir):
                     env['PATH'] = '%s;%s' % (vcdir, env['PATH'])
                     break
 
         # Run Firefox a first time to initialize its profile
         runner = FirefoxRunner(profile=profile,
-                               binary=build.get_binary_path(
-                                   where="staged-package"),
+                               binary=binary,
                                cmdargs=['data:text/html,<script>Quitter.quit()</script>'],
                                env=env)
         runner.start()
         runner.wait()
 
         jarlog = os.getenv("JARLOG_FILE")
         if jarlog:
             env["MOZ_JAR_LOG_FILE"] = os.path.abspath(jarlog)
             print("jarlog: %s" % env["MOZ_JAR_LOG_FILE"])
 
         cmdargs = ["http://localhost:%d/index.html" % PORT]
         runner = FirefoxRunner(profile=profile,
-                               binary=build.get_binary_path(
-                                   where="staged-package"),
+                               binary=binary,
                                cmdargs=cmdargs,
                                env=env)
         runner.start(debug_args=debug_args, interactive=interactive)
         runner.wait()
         httpd.stop()
--- a/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
+++ b/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
@@ -142,24 +142,31 @@ function requestExtensions() {
   return async (dispatch, getState) => {
     dispatch({ type: REQUEST_EXTENSIONS_START });
 
     const runtime = getCurrentRuntime(getState().runtimes);
     const clientWrapper = getCurrentClient(getState().runtimes);
 
     try {
       const { addons } = await clientWrapper.listAddons();
-      const extensions = addons.filter(a => a.debuggable);
+      let extensions = addons.filter(a => a.debuggable);
+
+      // Filter out system addons unless the dedicated preference is set to true.
+      if (!getState().ui.showSystemAddons) {
+        extensions = extensions.filter(e => !e.isSystem);
+      }
+
       if (runtime.type !== RUNTIMES.THIS_FIREFOX) {
         // manifestURL can only be used when debugging local addons, remove this
         // information for the extension data.
         extensions.forEach(extension => {
           extension.manifestURL = null;
         });
       }
+
       const installedExtensions = extensions.filter(e => !e.temporarilyInstalled);
       const temporaryExtensions = extensions.filter(e => e.temporarilyInstalled);
 
       dispatch({
         type: REQUEST_EXTENSIONS_SUCCESS,
         installedExtensions,
         temporaryExtensions,
       });
--- a/devtools/client/aboutdebugging-new/src/constants.js
+++ b/devtools/client/aboutdebugging-new/src/constants.js
@@ -62,16 +62,18 @@ const DEBUG_TARGET_PANE = {
 const PAGES = {
   THIS_FIREFOX: "this-firefox",
   CONNECT: "connect",
 };
 
 const PREFERENCES = {
   // Temporary preference without any default value until network locations are enabled.
   NETWORK_ENABLED: "devtools.aboutdebugging.network",
+  // Preference that drives the display of system addons in about:debugging.
+  SHOW_SYSTEM_ADDONS: "devtools.aboutdebugging.showSystemAddons",
   // Temporary preference without any default value until wifi is enabled.
   WIFI_ENABLED: "devtools.aboutdebugging.wifi",
 };
 
 const RUNTIME_PREFERENCE = {
   CONNECTION_PROMPT: "devtools.debugger.prompt-connection",
 };
 
--- a/devtools/client/aboutdebugging-new/src/create-store.js
+++ b/devtools/client/aboutdebugging-new/src/create-store.js
@@ -42,12 +42,15 @@ function configureStore() {
   return createStore(rootReducer, initialState, middleware);
 }
 
 function getUiState() {
   const collapsibilities = getDebugTargetCollapsibilities();
   const locations = getNetworkLocations();
   const networkEnabled = Services.prefs.getBoolPref(PREFERENCES.NETWORK_ENABLED, false);
   const wifiEnabled = Services.prefs.getBoolPref(PREFERENCES.WIFI_ENABLED, false);
-  return new UiState(locations, collapsibilities, networkEnabled, wifiEnabled);
+  const showSystemAddons = Services.prefs.getBoolPref(PREFERENCES.SHOW_SYSTEM_ADDONS,
+    false);
+  return new UiState(locations, collapsibilities, networkEnabled, wifiEnabled,
+    showSystemAddons);
 }
 
 exports.configureStore = configureStore;
--- a/devtools/client/aboutdebugging-new/src/reducers/ui-state.js
+++ b/devtools/client/aboutdebugging-new/src/reducers/ui-state.js
@@ -9,24 +9,25 @@ const {
   DEBUG_TARGET_COLLAPSIBILITY_UPDATED,
   NETWORK_LOCATIONS_UPDATED,
   PAGE_SELECTED,
   USB_RUNTIMES_SCAN_START,
   USB_RUNTIMES_SCAN_SUCCESS,
 } = require("../constants");
 
 function UiState(locations = [], debugTargetCollapsibilities = {},
-                 networkEnabled = false, wifiEnabled = false) {
+                 networkEnabled = false, wifiEnabled = false, showSystemAddons = false) {
   return {
     adbAddonStatus: null,
     debugTargetCollapsibilities,
     isScanningUsb: false,
     networkEnabled,
     networkLocations: locations,
     selectedPage: null,
+    showSystemAddons,
     wifiEnabled,
   };
 }
 
 function uiReducer(state = UiState(), action) {
   switch (action.type) {
     case ADB_ADDON_STATUS_UPDATED: {
       const { adbAddonStatus } = action;
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -1,11 +1,15 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
+prefs =
+  # showSystemAddons has different values depending on the build flags,
+  # ensure consistent test behavior by always setting this to false.
+  devtools.aboutdebugging.showSystemAddons=false
 support-files =
   debug-target-pane_collapsibilities_head.js
   head-addons-script.js
   head.js
   mocks/*
   resources/test-adb-extension/*
   resources/test-temporary-extension/*
   test-tab-favicons.html
@@ -24,11 +28,12 @@ skip-if = (os == 'linux' && bits == 32) 
 [browser_aboutdebugging_debug-target-pane_usb_runtime.js]
 [browser_aboutdebugging_navigate.js]
 [browser_aboutdebugging_sidebar_network_runtimes.js]
 [browser_aboutdebugging_sidebar_usb_runtime.js]
 [browser_aboutdebugging_sidebar_usb_runtime_connect.js]
 [browser_aboutdebugging_sidebar_usb_runtime_refresh.js]
 [browser_aboutdebugging_sidebar_usb_status.js]
 skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug 1499638
+[browser_aboutdebugging_system_addons.js]
 [browser_aboutdebugging_tab_favicons.js]
 [browser_aboutdebugging_thisfirefox.js]
 [browser_aboutdebugging_thisfirefox_runtime_info.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_system_addons.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from mocks/head-client-wrapper-mock.js */
+Services.scriptloader.loadSubScript(
+  CHROME_URL_ROOT + "mocks/head-client-wrapper-mock.js", this);
+/* import-globals-from mocks/head-runtime-client-factory-mock.js */
+Services.scriptloader.loadSubScript(
+  CHROME_URL_ROOT + "mocks/head-runtime-client-factory-mock.js", this);
+
+// Test that system addons are only displayed when the showSystemAddons preference is
+// true.
+
+const SYSTEM_ADDON =
+  createAddonData({ id: "system", name: "System Addon", isSystem: true });
+const INSTALLED_ADDON =
+  createAddonData({ id: "installed", name: "Installed Addon", isSystem: false });
+
+add_task(async function testShowSystemAddonsFalse() {
+  const thisFirefoxClient = setupThisFirefoxMock();
+  thisFirefoxClient.listAddons = () => ({ addons: [SYSTEM_ADDON, INSTALLED_ADDON] });
+
+  info("Hide system addons in aboutdebugging via preference");
+  await pushPref("devtools.aboutdebugging.showSystemAddons", false);
+
+  const { document, tab } = await openAboutDebugging();
+
+  const hasSystemAddon = !!findDebugTargetByText("System Addon", document);
+  const hasInstalledAddon = !!findDebugTargetByText("Installed Addon", document);
+  ok(!hasSystemAddon, "System addon is hidden when system addon pref is false");
+  ok(hasInstalledAddon, "Installed addon is displayed when system addon pref is false");
+
+  await removeTab(tab);
+});
+
+add_task(async function testShowSystemAddonsTrue() {
+  const thisFirefoxClient = setupThisFirefoxMock();
+  thisFirefoxClient.listAddons = () => ({ addons: [SYSTEM_ADDON, INSTALLED_ADDON] });
+
+  info("Show system addons in aboutdebugging via preference");
+  await pushPref("devtools.aboutdebugging.showSystemAddons", true);
+
+  const { document, tab } = await openAboutDebugging();
+  const hasSystemAddon = !!findDebugTargetByText("System Addon", document);
+  const hasInstalledAddon = !!findDebugTargetByText("Installed Addon", document);
+  ok(hasSystemAddon, "System addon is displayed when system addon pref is true");
+  ok(hasInstalledAddon, "Installed addon is displayed when system addon pref is true");
+
+  await removeTab(tab);
+});
+
+// Create a basic mock for this-firefox client, and setup a runtime-client-factory mock
+// to return our mock client when needed.
+function setupThisFirefoxMock() {
+  const runtimeClientFactoryMock = createRuntimeClientFactoryMock();
+  const thisFirefoxClient = createThisFirefoxClientMock();
+  runtimeClientFactoryMock.createClientForRuntime = runtime => {
+    const { RUNTIMES } = require("devtools/client/aboutdebugging-new/src/constants");
+    if (runtime.id === RUNTIMES.THIS_FIREFOX) {
+      return { clientWrapper: thisFirefoxClient };
+    }
+    throw new Error("Unexpected runtime id " + runtime.id);
+  };
+
+  info("Enable mocks");
+  enableRuntimeClientFactoryMock(runtimeClientFactoryMock);
+  registerCleanupFunction(() => {
+    disableRuntimeClientFactoryMock();
+  });
+
+  return thisFirefoxClient;
+}
+
+// Create basic addon data as the DebuggerClient would return it (debuggable and non
+// temporary).
+function createAddonData({ id, name, isSystem }) {
+  return {
+    actor: `actorid-${id}`,
+    iconURL: `moz-extension://${id}/icon-url.png`,
+    id,
+    manifestURL: `moz-extension://${id}/manifest-url.json`,
+    name,
+    isSystem,
+    temporarilyInstalled: false,
+    debuggable: true,
+  };
+}
new file mode 100644
--- /dev/null
+++ b/dom/base/StorageAccessPermissionRequest.cpp
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "StorageAccessPermissionRequest.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(StorageAccessPermissionRequest,
+                                   ContentPermissionRequestBase)
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(StorageAccessPermissionRequest,
+                                               ContentPermissionRequestBase)
+
+StorageAccessPermissionRequest::StorageAccessPermissionRequest(
+    nsPIDOMWindowInner* aWindow,
+    nsIPrincipal* aNodePrincipal,
+    AllowCallback&& aAllowCallback,
+    AllowAnySiteCallback&& aAllowAnySiteCallback,
+    CancelCallback&& aCancelCallback)
+  : ContentPermissionRequestBase(aNodePrincipal, false, aWindow,
+                                 NS_LITERAL_CSTRING("dom.storage_access"),
+                                 NS_LITERAL_CSTRING("storage-access")),
+    mAllowCallback(std::move(aAllowCallback)),
+    mAllowAnySiteCallback(std::move(aAllowAnySiteCallback)),
+    mCancelCallback(std::move(aCancelCallback)),
+    mCallbackCalled(false)
+{
+  mPermissionRequests.AppendElement(PermissionRequest(mType, nsTArray<nsString>()));
+}
+
+StorageAccessPermissionRequest::~StorageAccessPermissionRequest()
+{
+  Cancel();
+}
+
+NS_IMETHODIMP
+StorageAccessPermissionRequest::Cancel()
+{
+  if (!mCallbackCalled) {
+    mCallbackCalled = true;
+    mCancelCallback();
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageAccessPermissionRequest::Allow(JS::HandleValue aChoices)
+{
+  nsTArray<PermissionChoice> choices;
+  nsresult rv = TranslateChoices(aChoices, mPermissionRequests, choices);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  if (!mCallbackCalled) {
+    mCallbackCalled = true;
+    if (choices.Length() == 1 &&
+        choices[0].choice().EqualsLiteral("allow-on-any-site")) {
+      mAllowAnySiteCallback();
+    } else {
+      mAllowCallback();
+    }
+  }
+  return NS_OK;
+}
+
+already_AddRefed<StorageAccessPermissionRequest>
+StorageAccessPermissionRequest::Create(nsPIDOMWindowInner* aWindow,
+                                       AllowCallback&& aAllowCallback,
+                                       AllowAnySiteCallback&& aAllowAnySiteCallback,
+                                       CancelCallback&& aCancelCallback)
+{
+  if (!aWindow) {
+    return nullptr;
+  }
+  nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(aWindow);
+  if (!win->GetPrincipal()) {
+    return nullptr;
+  }
+  RefPtr<StorageAccessPermissionRequest> request =
+    new StorageAccessPermissionRequest(aWindow,
+                                       win->GetPrincipal(),
+                                       std::move(aAllowCallback),
+                                       std::move(aAllowAnySiteCallback),
+                                       std::move(aCancelCallback));
+  return request.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/StorageAccessPermissionRequest.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef StorageAccessPermissionRequest_h_
+#define StorageAccessPermissionRequest_h_
+
+#include "nsContentPermissionHelper.h"
+
+#include <functional>
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class StorageAccessPermissionRequest final : public ContentPermissionRequestBase
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StorageAccessPermissionRequest,
+                                           ContentPermissionRequestBase)
+
+  // nsIContentPermissionRequest
+  NS_IMETHOD Cancel(void) override;
+  NS_IMETHOD Allow(JS::HandleValue choices) override;
+
+  typedef std::function<void()> AllowCallback;
+  typedef std::function<void()> AllowAnySiteCallback;
+  typedef std::function<void()> CancelCallback;
+
+  static already_AddRefed<StorageAccessPermissionRequest> Create(
+    nsPIDOMWindowInner* aWindow,
+    AllowCallback&& aAllowCallback,
+    AllowAnySiteCallback&& aAllowAnySiteCallback,
+    CancelCallback&& aCancelCallback);
+
+private:
+  StorageAccessPermissionRequest(nsPIDOMWindowInner* aWindow,
+                                 nsIPrincipal* aNodePrincipal,
+                                 AllowCallback&& aAllowCallback,
+                                 AllowAnySiteCallback&& aAllowAnySiteCallback,
+                                 CancelCallback&& aCancelCallback);
+  ~StorageAccessPermissionRequest();
+
+  AllowCallback mAllowCallback;
+  AllowAnySiteCallback mAllowAnySiteCallback;
+  CancelCallback mCancelCallback;
+  nsTArray<PermissionRequest> mPermissionRequests;
+  bool mCallbackCalled;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // StorageAccessPermissionRequest_h_
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -368,16 +368,17 @@ UNIFIED_SOURCES += [
     'ProcessMessageManager.cpp',
     'ResponsiveImageSelector.cpp',
     'SameProcessMessageQueue.cpp',
     'ScreenLuminance.cpp',
     'ScreenOrientation.cpp',
     'Selection.cpp',
     'SelectionChangeEventDispatcher.cpp',
     'ShadowRoot.cpp',
+    'StorageAccessPermissionRequest.cpp',
     'StructuredCloneBlob.cpp',
     'StructuredCloneHolder.cpp',
     'StructuredCloneTester.cpp',
     'StyleSheetList.cpp',
     'SubtleCrypto.cpp',
     'TabGroup.cpp',
     'Text.cpp',
     'TextInputProcessor.cpp',
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -144,16 +144,17 @@
 #include "nsIDOMChromeWindow.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsIDragService.h"
 #include "nsIFormControl.h"
 #include "nsIForm.h"
 #include "nsIFragmentContentSink.h"
 #include "nsContainerFrame.h"
 #include "nsIHTMLDocument.h"
+#include "nsIHttpChannelInternal.h"
 #include "nsIIdleService.h"
 #include "nsIImageLoadingContent.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIIOService.h"
 #include "nsILoadContext.h"
 #include "nsILoadGroup.h"
 #include "nsIMemoryReporter.h"
@@ -8966,29 +8967,57 @@ nsContentUtils::IsThirdPartyWindowOrChan
   if (!thirdPartyUtil) {
     return false;
   }
 
   // In the absence of a window or channel, we assume that we are first-party.
   bool thirdParty = false;
 
   if (aWindow) {
-    Unused << thirdPartyUtil->IsThirdPartyWindow(aWindow->GetOuterWindow(),
-                                                 aURI,
-                                                 &thirdParty);
+    nsresult rv = thirdPartyUtil->IsThirdPartyWindow(aWindow->GetOuterWindow(),
+                                                     aURI,
+                                                     &thirdParty);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      // Ideally we would do something similar to the channel code path here,
+      // but existing code depends on this behaviour.
+      return false;
+    }
   }
 
   if (aChannel) {
     // Note, we must call IsThirdPartyChannel() here and not just try to
     // use nsILoadInfo.isThirdPartyContext.  That nsILoadInfo property only
     // indicates if the parent loading window is third party or not.  We
     // want to check the channel URI against the loading principal as well.
-    Unused << thirdPartyUtil->IsThirdPartyChannel(aChannel,
-                                                  nullptr,
-                                                  &thirdParty);
+    nsresult rv = thirdPartyUtil->IsThirdPartyChannel(aChannel,
+                                                      nullptr,
+                                                      &thirdParty);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      // Assume third-party in case of failure
+      thirdParty = true;
+    }
+
+    // We check isThirdPartyWindow to expand the list of domains that are
+    // considered first party (e.g., if facebook.com includes an iframe from
+    // fatratgames.com, all subsources included in that iframe are considered
+    // third-party with isThirdPartyChannel, even if they are not third-party
+    // w.r.t.  facebook.com), and isThirdPartyChannel to prevent top-level
+    // navigations from being detected as third-party.
+    bool isThirdPartyWindow = true;
+    nsCOMPtr<nsIHttpChannelInternal> chan = do_QueryInterface(aChannel, &rv);
+    if (NS_SUCCEEDED(rv) && chan) {
+      nsCOMPtr<nsIURI> topWinURI;
+      rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI));
+      if (NS_SUCCEEDED(rv) && topWinURI) {
+        rv = thirdPartyUtil->IsThirdPartyURI(aURI, topWinURI, &isThirdPartyWindow);
+        if (NS_SUCCEEDED(rv)) {
+          thirdParty = thirdParty && isThirdPartyWindow;
+        }
+      }
+    }
   }
 
   return thirdParty;
 }
 
 // static public
 bool
 nsContentUtils::IsTrackingResourceWindow(nsPIDOMWindowInner* aWindow)
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -279,16 +279,17 @@
 #include "mozilla/DocumentStyleRootIterator.h"
 #include "mozilla/PendingFullscreenEvent.h"
 #include "mozilla/RestyleManager.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "nsHTMLTags.h"
 #include "NodeUbiReporting.h"
 #include "nsICookieService.h"
 #include "mozilla/net/RequestContextService.h"
+#include "StorageAccessPermissionRequest.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 typedef nsTArray<Link*> LinkArray;
 
 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
 static LazyLogModule gCspPRLog("CSP");
@@ -13901,17 +13902,17 @@ nsIDocument::RequestStorageAccess(mozill
   // Propagate user input event handling to the resolve handler
   RefPtr<Promise> promise = Promise::Create(global, aRv,
                                             Promise::ePropagateUserInteraction);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   // Step 1. If the document already has been granted access, resolve.
-  nsPIDOMWindowInner* inner = GetInnerWindow();
+  nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
   RefPtr<nsGlobalWindowOuter> outer;
   if (inner) {
     outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
     if (outer->HasStorageAccess()) {
       promise->MaybeResolveWithUndefined();
       return promise.forget();
     }
   }
@@ -13971,63 +13972,97 @@ nsIDocument::RequestStorageAccess(mozill
 
   if (nsContentUtils::IsInPrivateBrowsing(this)) {
     // If the document is in PB mode, it doesn't have access to its persistent
     // cookie jar, so reject the promise here.
     promise->MaybeRejectWithUndefined();
     return promise.forget();
   }
 
-  bool granted = true;
-  bool isTrackingWindow = false;
   if (StaticPrefs::network_cookie_cookieBehavior() ==
-        nsICookieService::BEHAVIOR_REJECT_TRACKER) {
+        nsICookieService::BEHAVIOR_REJECT_TRACKER &&
+      inner) {
     // Only do something special for third-party tracking content.
     if (nsContentUtils::StorageDisabledByAntiTracking(this, nullptr)) {
       // Note: If this has returned true, the top-level document is guaranteed
       // to not be on the Content Blocking allow list.
       DebugOnly<bool> isOnAllowList = false;
       // If we have a parent document, it has to be non-private since we verified
       // earlier that our own document is non-private and a private document can
       // never have a non-private document as its child.
       MOZ_ASSERT_IF(parent, !nsContentUtils::IsInPrivateBrowsing(parent));
       MOZ_ASSERT_IF(NS_SUCCEEDED(AntiTrackingCommon::IsOnContentBlockingAllowList(
                                    parent->GetDocumentURI(),
                                    false,
                                    AntiTrackingCommon::eStorageChecks,
                                    isOnAllowList)),
                     !isOnAllowList);
 
-      isTrackingWindow = true;
-      // TODO: prompt for permission
-    }
-  }
-
-  // Step 10. Grant the document access to cookies and store that fact for
-  //          the purposes of future calls to hasStorageAccess() and
-  //          requestStorageAccess().
-  if (granted && inner) {
-    if (isTrackingWindow) {
-      AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(NodePrincipal(),
-                                                               inner,
-                                                               AntiTrackingCommon::eStorageAccessAPI)
-        ->Then(GetCurrentThreadSerialEventTarget(), __func__,
-               [outer, promise] (bool) {
-                 outer->SetHasStorageAccess(true);
-                 promise->MaybeResolveWithUndefined();
-               },
-               [outer, promise] (bool) {
-                 outer->SetHasStorageAccess(false);
-                 promise->MaybeRejectWithUndefined();
-               });
-    } else {
-      outer->SetHasStorageAccess(true);
-      promise->MaybeResolveWithUndefined();
-    }
-  }
+      auto performFinalChecks = [inner] () -> RefPtr<AntiTrackingCommon::StorageAccessFinalCheckPromise> {
+          RefPtr<AntiTrackingCommon::StorageAccessFinalCheckPromise::Private> p =
+            new AntiTrackingCommon::StorageAccessFinalCheckPromise::Private(__func__);
+          RefPtr<StorageAccessPermissionRequest> sapr =
+            StorageAccessPermissionRequest::Create(inner,
+              // Allow
+              [p] { p->Resolve(false, __func__); },
+              // Allow on any site
+              [p] { p->Resolve(true, __func__); },
+              // Block
+              [p] { p->Reject(false, __func__); });
+
+          typedef ContentPermissionRequestBase::PromptResult PromptResult;
+          PromptResult pr = sapr->CheckPromptPrefs();
+          bool onAnySite = false;
+          if (pr == PromptResult::Pending) {
+            // Also check our custom pref for the "Allow on any site" case
+            if (Preferences::GetBool("dom.storage_access.prompt.testing", false) &&
+                Preferences::GetBool("dom.storage_access.prompt.testing.allowonanysite", false)) {
+              pr = PromptResult::Granted;
+              onAnySite = true;
+            }
+          }
+
+          if (pr != PromptResult::Pending) {
+            MOZ_ASSERT_IF(pr != PromptResult::Granted,
+                          pr == PromptResult::Denied);
+            if (pr == PromptResult::Granted) {
+              return AntiTrackingCommon::StorageAccessFinalCheckPromise::
+                CreateAndResolve(onAnySite, __func__);
+            }
+            return AntiTrackingCommon::StorageAccessFinalCheckPromise::
+              CreateAndReject(false, __func__);
+          }
+
+          sapr->RequestDelayedTask(inner->EventTargetFor(TaskCategory::Other),
+                                   ContentPermissionRequestBase::DelayedTaskType::Request);
+          return p.forget();
+        };
+      AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(
+          NodePrincipal(),
+          inner,
+          AntiTrackingCommon::eStorageAccessAPI,
+          performFinalChecks)->Then(GetCurrentThreadSerialEventTarget(), __func__,
+                   [outer, promise] {
+                     // Step 10. Grant the document access to cookies and store that fact for
+                     //          the purposes of future calls to hasStorageAccess() and
+                     //          requestStorageAccess().
+                     outer->SetHasStorageAccess(true);
+                     promise->MaybeResolveWithUndefined();
+                   },
+                   [outer, promise] {
+                     outer->SetHasStorageAccess(false);
+                     promise->MaybeRejectWithUndefined();
+                   });
+
+      return promise.forget();
+    }
+  }
+
+  outer->SetHasStorageAccess(true);
+  promise->MaybeResolveWithUndefined();
   return promise.forget();
 }
 
 void
 nsIDocument::RecordNavigationTiming(ReadyState aReadyState)
 {
   if (!XRE_IsContentProcess()) {
     return;
--- a/dom/html/test/forms/test_input_event.html
+++ b/dom/html/test/forms/test_input_event.html
@@ -52,17 +52,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     }
     is(aEvent.cancelable, false,
        `"input" event should be never cancelable ${aDescription}`);
     is(aEvent.bubbles, true,
        `"input" event should always bubble ${aDescription}`);
   }
 
   function checkIfInputIsEvent(aEvent, aDescription) {
-    ok(event instanceof Event && !(event instanceof UIEvent),
+    ok(aEvent instanceof Event && !(aEvent instanceof UIEvent),
        `"input" event should be dispatched with InputEvent interface ${aDescription}`);
     is(aEvent.cancelable, false,
        `"input" event should be never cancelable ${aDescription}`);
     is(aEvent.bubbles, true,
        `"input" event should always bubble ${aDescription}`);
   }
 
   var textareaInput = 0;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -6005,24 +6005,33 @@ ContentParent::RecvBHRThreadHang(const H
       new nsHangDetails(HangDetails(aDetails));
     obs->NotifyObservers(hangDetails, "bhr-thread-hang", nullptr);
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvFirstPartyStorageAccessGrantedForOrigin(const Principal& aParentPrincipal,
+                                                           const Principal& aTrackingPrincipal,
                                                            const nsCString& aTrackingOrigin,
                                                            const nsCString& aGrantedOrigin,
+                                                           const bool& aAnySite,
                                                            FirstPartyStorageAccessGrantedForOriginResolver&& aResolver)
 {
   AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(aParentPrincipal,
+                                                                                 aTrackingPrincipal,
                                                                                  aTrackingOrigin,
                                                                                  aGrantedOrigin,
-                                                                                 std::move(aResolver));
+                                                                                 aAnySite)
+    ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+           [aResolver = std::move(aResolver)]
+           (AntiTrackingCommon::FirstPartyStorageAccessGrantPromise::ResolveOrRejectValue&& aValue) {
+             bool success = aValue.IsResolve() && NS_SUCCEEDED(aValue.ResolveValue());
+             aResolver(success);
+           });
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvStoreUserInteractionAsPermission(const Principal& aPrincipal)
 {
   AntiTrackingCommon::StoreUserInteractionFor(aPrincipal);
   return IPC_OK();
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1243,18 +1243,20 @@ public:
   virtual mozilla::ipc::IPCResult RecvRecordDiscardedData(
     const DiscardedData& aDiscardedData) override;
 
   virtual mozilla::ipc::IPCResult RecvBHRThreadHang(
     const HangDetails& aHangDetails) override;
 
   virtual mozilla::ipc::IPCResult
   RecvFirstPartyStorageAccessGrantedForOrigin(const Principal& aParentPrincipal,
+                                              const Principal& aTrackingPrincipal,
                                               const nsCString& aTrackingOrigin,
                                               const nsCString& aGrantedOrigin,
+                                              const bool& aAnySite,
                                               FirstPartyStorageAccessGrantedForOriginResolver&& aResolver) override;
 
   virtual mozilla::ipc::IPCResult
   RecvStoreUserInteractionAsPermission(const Principal& aPrincipal) override;
 
   // Notify the ContentChild to enable the input event prioritization when
   // initializing.
   void MaybeEnableRemoteInputEventQueue();
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -1152,18 +1152,20 @@ parent:
 
     async AddPerformanceMetrics(nsID aID, PerformanceInfo[] aMetrics);
 
     /*
      * A 3rd party tracking origin (aTrackingOrigin) has received the permission
      * granted to have access to aGrantedOrigin when loaded by aParentPrincipal.
      */
     async FirstPartyStorageAccessGrantedForOrigin(Principal aParentPrincipal,
+                                                  Principal aTrackingPrincipal,
                                                   nsCString aTrackingOrigin,
-                                                  nsCString aGrantedOrigin)
+                                                  nsCString aGrantedOrigin,
+                                                  bool aAnySite)
           returns (bool unused);
 
     async StoreUserInteractionAsPermission(Principal aPrincipal);
 
     /**
      * Sync the BrowsingContext with id 'aContextId' and name 'aName'
      * to the parent, and attach it to the BrowsingContext with id
      * 'aParentContextId'. If 'aParentContextId' is '0' the
--- a/dom/media/MediaData.cpp
+++ b/dom/media/MediaData.cpp
@@ -414,16 +414,20 @@ MediaRawDataWriter::MediaRawDataWriter(M
 bool MediaRawDataWriter::SetSize(size_t aSize) {
   return mTarget->mBuffer.SetLength(aSize);
 }
 
 bool MediaRawDataWriter::Prepend(const uint8_t* aData, size_t aSize) {
   return mTarget->mBuffer.Prepend(aData, aSize);
 }
 
+bool MediaRawDataWriter::Append(const uint8_t* aData, size_t aSize) {
+  return mTarget->mBuffer.Append(aData, aSize);
+}
+
 bool MediaRawDataWriter::Replace(const uint8_t* aData, size_t aSize) {
   return mTarget->mBuffer.Replace(aData, aSize);
 }
 
 void MediaRawDataWriter::Clear() { mTarget->mBuffer.Clear(); }
 
 uint8_t* MediaRawDataWriter::Data() { return mTarget->mBuffer.Data(); }
 
--- a/dom/media/MediaData.h
+++ b/dom/media/MediaData.h
@@ -541,16 +541,17 @@ class MediaRawDataWriter {
 
   // Data manipulation methods. mData and mSize may be updated accordingly.
 
   // Set size of buffer, allocating memory as required.
   // If size is increased, new buffer area is filled with 0.
   MOZ_MUST_USE bool SetSize(size_t aSize);
   // Add aData at the beginning of buffer.
   MOZ_MUST_USE bool Prepend(const uint8_t* aData, size_t aSize);
+  MOZ_MUST_USE bool Append(const uint8_t* aData, size_t aSize);
   // Replace current content with aData.
   MOZ_MUST_USE bool Replace(const uint8_t* aData, size_t aSize);
   // Clear the memory buffer. Will set target mData and mSize to 0.
   void Clear();
   // Remove aSize bytes from the front of the sample.
   void PopFront(size_t aSize);
 
  private:
new file mode 100644
--- /dev/null
+++ b/dom/media/gtest/TestMediaDataEncoder.cpp
@@ -0,0 +1,271 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "gtest/gtest.h"
+
+#include "nsMimeTypes.h"
+#include "VideoUtils.h"
+#include "PEMFactory.h"
+#include "ImageContainer.h"
+#include "AnnexB.h"
+
+#include "mozilla/AbstractThread.h"
+#include "mozilla/media/MediaUtils.h" // For media::Await
+
+#include <algorithm>
+
+#include <fstream>
+
+#define SKIP_IF_NOT_SUPPORTED(mimeType) \
+do { \
+  RefPtr<PEMFactory> f(new PEMFactory()); \
+  if (!f->SupportsMimeType(NS_LITERAL_CSTRING(mimeType))) { \
+    return; \
+  } \
+} while (0)
+
+using namespace mozilla;
+
+static gfx::IntSize kImageSize(640, 480);
+
+class MediaDataEncoderTest : public testing::Test
+{
+protected:
+  void SetUp() override
+  {
+    InitData(kImageSize);
+  }
+
+  void TearDown() override
+  {
+    DeinitData();
+  }
+
+  layers::PlanarYCbCrData mData;
+  UniquePtr<uint8_t[]> mBackBuffer;
+
+private:
+  void InitData(const gfx::IntSize& aSize)
+  {
+    mData.mPicSize = aSize;
+    mData.mYStride = aSize.width;
+    mData.mYSize = aSize;
+    mData.mCbCrStride = aSize.width / 2;
+    mData.mCbCrSize = gfx::IntSize(aSize.width / 2, aSize.height / 2);
+    size_t bufferSize = mData.mYStride * mData.mYSize.height +
+                        mData.mCbCrStride * mData.mCbCrSize.height +
+                        mData.mCbCrStride * mData.mCbCrSize.height;
+    mBackBuffer = MakeUnique<uint8_t[]>(bufferSize);
+    std::fill_n(mBackBuffer.get(), bufferSize, 42);
+    mData.mYChannel = mBackBuffer.get();
+    mData.mCbChannel = mData.mYChannel + mData.mYStride * mData.mYSize.height;
+    mData.mCrChannel =
+      mData.mCbChannel + mData.mCbCrStride * mData.mCbCrSize.height;
+  }
+
+  void DeinitData()
+  {
+    mBackBuffer.reset();
+  }
+};
+
+static already_AddRefed<MediaDataEncoder>
+CreateH264Encoder(MediaDataEncoder::Usage aUsage,
+                  MediaDataEncoder::PixelFormat aPixelFormat)
+{
+  RefPtr<PEMFactory> f(new PEMFactory());
+
+  if (!f->SupportsMimeType(NS_LITERAL_CSTRING(VIDEO_MP4))) {
+    return nullptr;
+  }
+
+  VideoInfo videoInfo(1280, 720);
+  videoInfo.mMimeType = NS_LITERAL_CSTRING(VIDEO_MP4);
+  const RefPtr<TaskQueue> taskQueue(
+    new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK)));
+  CreateEncoderParams c(videoInfo /* track info */,
+                        aUsage,
+                        taskQueue,
+                        aPixelFormat,
+                        30 /* FPS */,
+                        10 * 1024 * 1024 /* bitrate */);
+  return f->CreateEncoder(c);
+}
+
+void
+WaitForShutdown(RefPtr<MediaDataEncoder> aEncoder)
+{
+  MOZ_ASSERT(aEncoder);
+
+  Maybe<bool> result;
+  // media::Await() supports exclusive promises only, but ShutdownPromise is not.
+  aEncoder->Shutdown()->Then(AbstractThread::MainThread(),
+                             __func__,
+                             [&result](bool rv) {
+                               EXPECT_TRUE(rv);
+                               result = Some(true);
+                             },
+                             [&result]() {
+                               FAIL() << "Shutdown should never be rejected";
+                               result = Some(false);
+                             });
+  SpinEventLoopUntil([&result]() { return result; });
+}
+
+TEST_F(MediaDataEncoderTest, H264Create)
+{
+  SKIP_IF_NOT_SUPPORTED(VIDEO_MP4);
+
+  RefPtr<MediaDataEncoder> e = CreateH264Encoder(
+    MediaDataEncoder::Usage::Realtime, MediaDataEncoder::PixelFormat::YUV420P);
+
+  EXPECT_TRUE(e);
+
+  WaitForShutdown(e);
+}
+
+static bool
+EnsureInit(RefPtr<MediaDataEncoder> aEncoder)
+{
+  if (!aEncoder) {
+    return false;
+  }
+
+  bool succeeded;
+  media::Await(GetMediaThreadPool(MediaThreadType::PLAYBACK),
+               aEncoder->Init(),
+               [&succeeded](TrackInfo::TrackType t)
+               {
+                 EXPECT_EQ(TrackInfo::TrackType::kVideoTrack, t);
+                 succeeded = true;
+               },
+               [&succeeded](MediaResult r) { succeeded = false; });
+  return succeeded;
+}
+
+TEST_F(MediaDataEncoderTest, H264Init)
+{
+  SKIP_IF_NOT_SUPPORTED(VIDEO_MP4);
+
+  RefPtr<MediaDataEncoder> e = CreateH264Encoder(
+    MediaDataEncoder::Usage::Realtime, MediaDataEncoder::PixelFormat::YUV420P);
+
+  EXPECT_TRUE(EnsureInit(e));
+
+  WaitForShutdown(e);
+}
+
+static MediaDataEncoder::EncodedData
+Encode(const RefPtr<MediaDataEncoder> aEncoder,
+       const size_t aNumFrames,
+       const layers::PlanarYCbCrData& aYCbCrData)
+{
+  MediaDataEncoder::EncodedData output;
+  bool succeeded;
+  for (size_t i = 0; i < aNumFrames; i++) {
+    RefPtr<layers::PlanarYCbCrImage> img =
+      new layers::RecyclingPlanarYCbCrImage(new layers::BufferRecycleBin());
+    img->AdoptData(aYCbCrData);
+    RefPtr<MediaData> frame =
+      VideoData::CreateFromImage(kImageSize,
+                                0,
+                                TimeUnit::FromMicroseconds(i * 30000),
+                                TimeUnit::FromMicroseconds(30000),
+                                img,
+                                (i & 0xF) == 0,
+                                TimeUnit::FromMicroseconds(i * 30000));
+    media::Await(GetMediaThreadPool(MediaThreadType::PLAYBACK),
+                 aEncoder->Encode(frame),
+                 [&output, &succeeded](MediaDataEncoder::EncodedData encoded)
+                 {
+                   output.AppendElements(std::move(encoded));
+                   succeeded = true;
+                 },
+                 [&succeeded](MediaResult r) { succeeded = false; });
+    EXPECT_TRUE(succeeded);
+    if (!succeeded) {
+      return output;
+    }
+  }
+
+  size_t pending = 0;
+  media::Await(GetMediaThreadPool(MediaThreadType::PLAYBACK),
+               aEncoder->Drain(),
+               [&pending, &output, &succeeded](MediaDataEncoder::EncodedData encoded)
+               {
+                 pending = encoded.Length();
+                 output.AppendElements(std::move(encoded));
+                 succeeded = true;
+               },
+               [&succeeded](MediaResult r) { succeeded = false; });
+  EXPECT_TRUE(succeeded);
+  if (!succeeded) {
+    return output;
+  }
+
+  if (pending > 0) {
+    media::Await(GetMediaThreadPool(MediaThreadType::PLAYBACK),
+                 aEncoder->Drain(),
+                 [&succeeded](MediaDataEncoder::EncodedData encoded)
+                 {
+                   EXPECT_EQ(encoded.Length(), 0UL);
+                   succeeded = true;
+                 },
+                 [&succeeded](MediaResult r) { succeeded = false; });
+    EXPECT_TRUE(succeeded);
+  }
+
+  return output;
+}
+
+TEST_F(MediaDataEncoderTest, H264EncodeOneFrameAsAnnexB)
+{
+  SKIP_IF_NOT_SUPPORTED(VIDEO_MP4);
+
+  RefPtr<MediaDataEncoder> e = CreateH264Encoder(
+    MediaDataEncoder::Usage::Realtime, MediaDataEncoder::PixelFormat::YUV420P);
+  EnsureInit(e);
+
+  MediaDataEncoder::EncodedData output = Encode(e, 1UL, mData);
+  EXPECT_EQ(output.Length(), 1UL);
+  EXPECT_TRUE(AnnexB::IsAnnexB(output[0]));
+
+  WaitForShutdown(e);
+}
+
+TEST_F(MediaDataEncoderTest, EncodeMultipleFramesAsAnnexB)
+{
+  SKIP_IF_NOT_SUPPORTED(VIDEO_MP4);
+
+  RefPtr<MediaDataEncoder> e = CreateH264Encoder(
+    MediaDataEncoder::Usage::Realtime, MediaDataEncoder::PixelFormat::YUV420P);
+  EnsureInit(e);
+
+  MediaDataEncoder::EncodedData output = Encode(e, 30UL, mData);
+  EXPECT_EQ(output.Length(), 30UL);
+  for (auto frame : output) {
+    EXPECT_TRUE(AnnexB::IsAnnexB(frame));
+  }
+
+  WaitForShutdown(e);
+}
+
+TEST_F(MediaDataEncoderTest, EncodeMultipleFramesAsAVCC)
+{
+  SKIP_IF_NOT_SUPPORTED(VIDEO_MP4);
+
+  RefPtr<MediaDataEncoder> e = CreateH264Encoder(
+    MediaDataEncoder::Usage::Record, MediaDataEncoder::PixelFormat::YUV420P);
+  EnsureInit(e);
+
+  MediaDataEncoder::EncodedData output = Encode(e, 30UL, mData);
+  EXPECT_EQ(output.Length(), 30UL);
+  AnnexB::IsAVCC(output[0]); // Only 1st frame has extra data.
+  for (auto frame : output) {
+    EXPECT_FALSE(AnnexB::IsAnnexB(frame));
+  }
+
+  WaitForShutdown(e);
+}
\ No newline at end of file
--- a/dom/media/gtest/moz.build
+++ b/dom/media/gtest/moz.build
@@ -25,16 +25,17 @@ UNIFIED_SOURCES += [
     'TestBlankVideoDataCreator.cpp',
     'TestCDMStorage.cpp',
     'TestDataMutex.cpp',
     'TestGMPCrossOrigin.cpp',
     'TestGMPRemoveAndDelete.cpp',
     'TestGMPUtils.cpp',
     'TestIntervalSet.cpp',
     'TestMediaDataDecoder.cpp',
+    'TestMediaDataEncoder.cpp',
     'TestMediaEventSource.cpp',
     'TestMediaMIMETypes.cpp',
     'TestMP3Demuxer.cpp',
     'TestMP4Demuxer.cpp',
     'TestOpusParser.cpp',
     'TestRust.cpp',
     'TestVideoSegment.cpp',
     'TestVideoUtils.cpp',
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/PEMFactory.cpp
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "PEMFactory.h"
+
+#ifdef MOZ_APPLEMEDIA
+#include "AppleEncoderModule.h"
+#endif
+
+namespace mozilla {
+
+PEMFactory::PEMFactory() {
+#ifdef MOZ_APPLEMEDIA
+  RefPtr<PlatformEncoderModule> m(new AppleEncoderModule());
+  mModules.AppendElement(m);
+#endif
+}
+
+bool PEMFactory::SupportsMimeType(const nsACString& aMimeType) const {
+  for (auto m : mModules) {
+    if (m->SupportsMimeType(aMimeType)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+already_AddRefed<MediaDataEncoder> PEMFactory::CreateEncoder(
+    const CreateEncoderParams& aParams) {
+  const TrackInfo& info = aParams.mConfig;
+  RefPtr<PlatformEncoderModule> m = FindPEM(info);
+  if (!m) {
+    return nullptr;
+  }
+
+  return info.IsVideo() ? m->CreateVideoEncoder(aParams) : nullptr;
+}
+
+already_AddRefed<PlatformEncoderModule> PEMFactory::FindPEM(
+    const TrackInfo& aTrackInfo) const {
+  RefPtr<PlatformEncoderModule> found;
+  for (auto m : mModules) {
+    if (m->SupportsMimeType(aTrackInfo.mMimeType)) {
+      found = m;
+      break;
+    }
+  }
+
+  return found.forget();
+}
+
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/PEMFactory.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#if !defined(PEMFactory_h_)
+#define PEMFactory_h_
+
+#include "PlatformEncoderModule.h"
+
+namespace mozilla {
+
+class PEMFactory final {
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PEMFactory)
+
+  PEMFactory();
+
+  // Factory method that creates the appropriate PlatformEncoderModule for
+  // the platform we're running on. Caller is responsible for deleting this
+  // instance. It's expected that there will be multiple
+  // PlatformEncoderModules alive at the same time.
+  already_AddRefed<MediaDataEncoder> CreateEncoder(
+      const CreateEncoderParams& aParams);
+
+  bool SupportsMimeType(const nsACString& aMimeType) const;
+
+ private:
+  virtual ~PEMFactory() {}
+  // Returns the first PEM in our list supporting the mimetype.
+  already_AddRefed<PlatformEncoderModule> FindPEM(
+      const TrackInfo& aTrackInfo) const;
+
+  nsTArray<RefPtr<PlatformEncoderModule>> mModules;
+};
+
+}  // namespace mozilla
+
+#endif /* PEMFactory_h_ */
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/PlatformEncoderModule.h
@@ -0,0 +1,270 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#if !defined(PlatformEncoderModule_h_)
+#define PlatformEncoderModule_h_
+
+#include "MediaData.h"
+#include "MediaInfo.h"
+#include "MediaResult.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/dom/ImageBitmapBinding.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+
+class MediaDataEncoder;
+struct CreateEncoderParams;
+
+static LazyLogModule sPEMLog("PlatformEncoderModule");
+
+class PlatformEncoderModule {
+ public:
+  NS_INLINE_DECL_REFCOUNTING(PlatformEncoderModule)
+
+  virtual already_AddRefed<MediaDataEncoder> CreateVideoEncoder(
+      const CreateEncoderParams& aParams) const {
+    return nullptr;
+  };
+
+  virtual already_AddRefed<MediaDataEncoder> CreateAudioEncoder(
+      const CreateEncoderParams& aParams) const {
+    return nullptr;
+  };
+
+  // Indicates if the PlatformDecoderModule supports encoding of aMimeType.
+  virtual bool SupportsMimeType(const nsACString& aMimeType) const = 0;
+
+ protected:
+  PlatformEncoderModule() = default;
+  virtual ~PlatformEncoderModule() {};
+};
+
+class MediaDataEncoder {
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDataEncoder)
+
+  enum class Usage {
+    Realtime,  // For WebRTC
+    Record     // For MediaRecoder
+  };
+
+  enum class CodecType {
+    _BeginVideo_,
+    H264,
+    VP8,
+    VP9,
+    _EndVideo_,
+    _BeginAudio_ = _EndVideo_,
+    Opus,
+    G722,
+    _EndAudio_,
+  };
+
+  struct H264Specific final {
+    enum class ProfileLevel { BaselineAutoLevel, MainAutoLevel };
+
+    const size_t mKeyframeInterval;
+    const ProfileLevel mProfileLevel;
+
+    H264Specific(const size_t aKeyframeInterval,
+                 const ProfileLevel aProfileLevel)
+        : mKeyframeInterval(aKeyframeInterval), mProfileLevel(aProfileLevel) {}
+  };
+
+  struct OpusSpecific final {
+    enum class Application { Voip, Audio, RestricedLowDelay };
+
+    const Application mApplication;
+    const uint8_t mComplexity;  // from 0-10
+
+    OpusSpecific(const Application aApplication, const uint8_t aComplexity)
+        : mApplication(aApplication), mComplexity(aComplexity) {
+      MOZ_ASSERT(mComplexity <= 10);
+    }
+  };
+
+  static bool IsVideo(const CodecType aCodec) {
+    return aCodec > CodecType::_BeginVideo_ && aCodec < CodecType::_EndVideo_;
+  }
+  static bool IsAudio(const CodecType aCodec) {
+    return aCodec > CodecType::_BeginAudio_ && aCodec < CodecType::_EndAudio_;
+  }
+
+  using PixelFormat = dom::ImageBitmapFormat;
+  // Sample rate for audio, framerate for video, and bitrate for both.
+  using Rate = uint32_t;
+
+  using InitPromise =
+      MozPromise<TrackInfo::TrackType, MediaResult, /* IsExclusive = */ true>;
+  using EncodedData = nsTArray<RefPtr<MediaRawData>>;
+  using EncodePromise =
+      MozPromise<EncodedData, MediaResult, /* IsExclusive = */ true>;
+
+  // Initialize the encoder. It should be ready to encode once the returned
+  // promise resolves. The encoder should do any initialization here, rather
+  // than in its constructor or PlatformEncoderModule::Create*Encoder(),
+  // so that if the client needs to shutdown during initialization,
+  // it can call Shutdown() to cancel this operation. Any initialization
+  // that requires blocking the calling thread in this function *must*
+  // be done here so that it can be canceled by calling Shutdown()!
+  virtual RefPtr<InitPromise> Init() = 0;
+
+  // Inserts a sample into the encoder's encode pipeline. The EncodePromise it
+  // returns will be resolved with already encoded MediaRawData at the moment,
+  // or empty when there is none available yet.
+  virtual RefPtr<EncodePromise> Encode(const MediaData* aSample) = 0;
+
+  // Causes all complete samples in the pipeline that can be encoded to be
+  // output. It indicates that there is no more input sample to insert.
+  // This function is asynchronous.
+  // The MediaDataEncoder shall resolve the pending EncodePromise with drained
+  // samples. Drain will be called multiple times until the resolved
+  // EncodePromise is empty which indicates that there are no more samples to
+  // drain.
+  virtual RefPtr<EncodePromise> Drain() = 0;
+
+  // Cancels all init/encode/drain operations, and shuts down the encoder. The
+  // platform encoder should clean up any resources it's using and release
+  // memory etc. The shutdown promise will be resolved once the encoder has
+  // completed shutdown. The client will delete the decoder once the promise is
+  // resolved.
+  // The ShutdownPromise must only ever be resolved.
+  virtual RefPtr<ShutdownPromise> Shutdown() = 0;
+
+  virtual RefPtr<GenericPromise> SetBitrate(Rate aBitsPerSec) {
+    return GenericPromise::CreateAndResolve(true, __func__);
+  }
+
+  // Decoder needs to decide whether or not hardware acceleration is supported
+  // after creating. It doesn't need to call Init() before calling this
+  // function.
+  virtual bool IsHardwareAccelerated(nsACString& aFailureReason) const {
+    return false;
+  }
+
+  // Return the name of the MediaDataEncoder, only used for encoding.
+  // May be accessed in a non thread-safe fashion.
+  virtual nsCString GetDescriptionName() const = 0;
+
+  friend class PlatformEncoderModule;
+
+ protected:
+  template <typename T>
+  struct BaseConfig {
+    const CodecType mCodecType;
+    const Usage mUsage;
+    const Rate mBitsPerSec;
+    Maybe<T> mCodecSpecific;
+
+    void SetCodecSpecific(const T& aCodecSpecific) {
+      mCodecSpecific.emplace(aCodecSpecific);
+    }
+
+   protected:
+    BaseConfig(const CodecType aCodecType, const Usage aUsage,
+               const Rate aBitsPerSec)
+        : mCodecType(aCodecType), mUsage(aUsage), mBitsPerSec(aBitsPerSec) {}
+
+    virtual ~BaseConfig() {}
+  };
+
+  template <typename T>
+  struct VideoConfig final : public BaseConfig<T> {
+    const gfx::IntSize mSize;
+    const PixelFormat mSourcePixelFormat;
+    const uint8_t mFramerate;
+    VideoConfig(const CodecType aCodecType, const Usage aUsage,
+                const gfx::IntSize& aSize, const PixelFormat aSourcePixelFormat,
+                const uint8_t aFramerate, const Rate aBitrate)
+        : BaseConfig<T>(aCodecType, aUsage, aBitrate),
+          mSize(aSize),
+          mSourcePixelFormat(aSourcePixelFormat),
+          mFramerate(aFramerate) {}
+  };
+
+  template <typename T>
+  struct AudioConfig final : public BaseConfig<T> {
+    const uint8_t mNumChannels;
+    const Rate mSampleRate;
+
+    AudioConfig(const CodecType aCodecType, const Usage aUsage,
+                const Rate aBitrate, const Rate aSampleRate,
+                const uint8_t aNumChannels)
+        : BaseConfig<T>(aCodecType, aUsage, aBitrate),
+          mNumChannels(aNumChannels),
+          mSampleRate(aSampleRate) {}
+  };
+
+  virtual ~MediaDataEncoder() {}
+};
+
+struct MOZ_STACK_CLASS CreateEncoderParams final {
+  union CodecSpecific {
+    MediaDataEncoder::H264Specific mH264;
+    MediaDataEncoder::OpusSpecific mOpus;
+
+    explicit CodecSpecific(const MediaDataEncoder::H264Specific&& aH264)
+        : mH264(aH264) {}
+    explicit CodecSpecific(const MediaDataEncoder::OpusSpecific&& aOpus)
+        : mOpus(aOpus) {}
+  };
+
+  CreateEncoderParams(const TrackInfo& aConfig,
+                      const MediaDataEncoder::Usage aUsage,
+                      const RefPtr<TaskQueue> aTaskQueue,
+                      const MediaDataEncoder::PixelFormat aPixelFormat,
+                      const uint8_t aFramerate,
+                      const MediaDataEncoder::Rate aBitrate)
+      : mConfig(aConfig),
+        mUsage(aUsage),
+        mTaskQueue(aTaskQueue),
+        mPixelFormat(aPixelFormat),
+        mFramerate(aFramerate),
+        mBitrate(aBitrate) {
+    MOZ_ASSERT(mTaskQueue);
+  }
+
+  template <typename... Ts>
+  CreateEncoderParams(const TrackInfo& aConfig,
+                      const MediaDataEncoder::Usage aUsage,
+                      const RefPtr<TaskQueue> aTaskQueue,
+                      const MediaDataEncoder::PixelFormat aPixelFormat,
+                      const uint8_t aFramerate,
+                      const MediaDataEncoder::Rate aBitrate,
+                      const Ts&&... aCodecSpecific)
+      : mConfig(aConfig),
+        mUsage(aUsage),
+        mTaskQueue(aTaskQueue),
+        mPixelFormat(aPixelFormat),
+        mFramerate(aFramerate),
+        mBitrate(aBitrate) {
+    MOZ_ASSERT(mTaskQueue);
+    Set(std::forward<Ts>(aCodecSpecific)...);
+  }
+
+  const TrackInfo& mConfig;
+  const MediaDataEncoder::Usage mUsage;
+  const RefPtr<TaskQueue> mTaskQueue;
+  const MediaDataEncoder::PixelFormat mPixelFormat;
+  const uint8_t mFramerate;
+  const MediaDataEncoder::Rate mBitrate;
+  Maybe<CodecSpecific> mCodecSpecific;
+
+ private:
+  template <typename T>
+  void Set(const T&& aCodecSpecific) {
+    mCodecSpecific.emplace(std::forward<T>(aCodecSpecific));
+  }
+};
+
+}  // namespace mozilla
+
+#endif /* PlatformEncoderModule_h_ */
--- a/dom/media/platforms/android/RemoteDataDecoder.cpp
+++ b/dom/media/platforms/android/RemoteDataDecoder.cpp
@@ -114,17 +114,17 @@ class RemoteVideoDecoder : public Remote
       bool isEOS = !!(flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM);
       if (!ok && !isEOS) {
         // Ignore output with no corresponding input.
         return;
       }
 
       if (ok && (size > 0 || presentationTimeUs >= 0)) {
         RefPtr<layers::Image> img = new SurfaceTextureImage(
-            mDecoder->mSurfaceHandle, inputInfo.mImageSize,
+            mDecoder->mImageHandle, inputInfo.mImageSize,
             false /* NOT continuous */, gl::OriginPos::BottomLeft);
 
         RefPtr<VideoData> v = VideoData::CreateFromImage(
             inputInfo.mDisplaySize, offset,
             TimeUnit::FromMicroseconds(presentationTimeUs),
             TimeUnit::FromMicroseconds(inputInfo.mDurationUs), img,
             !!(flags & MediaCodec::BUFFER_FLAG_SYNC_FRAME),
             TimeUnit::FromMicroseconds(presentationTimeUs));
@@ -163,17 +163,17 @@ class RemoteVideoDecoder : public Remote
   RefPtr<InitPromise> Init() override {
     mSurface = GeckoSurface::LocalRef(SurfaceAllocator::AcquireSurface(
         mConfig.mImage.width, mConfig.mImage.height, false));
     if (!mSurface) {
       return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                                           __func__);
     }
 
-    mSurfaceHandle = mSurface->GetHandle();
+    mImageHandle = mSurface->GetImageHandle();
 
     // Register native methods.
     JavaCallbacksSupport::Init();
 
     mJavaCallbacks = CodecProxy::NativeCallbacks::New();
     JavaCallbacksSupport::AttachNative(
         mJavaCallbacks, mozilla::MakeUnique<CallbacksSupport>(this));
 
@@ -251,17 +251,17 @@ class RemoteVideoDecoder : public Remote
 
   ConversionRequired NeedsConversion() const override {
     return ConversionRequired::kNeedAnnexB;
   }
 
  private:
   const VideoInfo mConfig;
   GeckoSurface::GlobalRef mSurface;
-  AndroidSurfaceTextureHandle mSurfaceHandle;
+  AndroidSurfaceTextureHandle mImageHandle;
   // Only accessed on reader's task queue.
   bool mIsCodecSupportAdaptivePlayback = false;
   // Can be accessed on any thread, but only written on during init.
   bool mIsHardwareAccelerated = false;
   // Accessed on mTaskQueue, reader's TaskQueue and Java callback tread.
   // SimpleMap however is thread-safe, so it's okay to do so.
   SimpleMap<InputInfo> mInputInfos;
   // Only accessed on the TaskQueue.
deleted file mode 100644
--- a/dom/media/platforms/apple/AppleCMFunctions.h
+++ /dev/null
@@ -1,12 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* 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/. */
-
-// Construct references to each of the CoreMedia symbols we use.
-
-LINK_FUNC(VideoFormatDescriptionCreate)
-LINK_FUNC(BlockBufferCreateWithMemoryBlock)
-LINK_FUNC(SampleBufferCreate)
-LINK_FUNC(TimeMake)
--- a/dom/media/platforms/apple/AppleDecoderModule.cpp
+++ b/dom/media/platforms/apple/AppleDecoderModule.cpp
@@ -1,64 +1,48 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "AppleATDecoder.h"
-#include "AppleCMLinker.h"
 #include "AppleDecoderModule.h"
 #include "AppleVTDecoder.h"
-#include "AppleVTLinker.h"
-#include "MacIOSurfaceImage.h"
+#include "mozilla/gfx/MacIOSurface.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Logging.h"
 #include "mozilla/gfx/gfxVars.h"
 
 namespace mozilla {
 
 bool AppleDecoderModule::sInitialized = false;
-bool AppleDecoderModule::sIsCoreMediaAvailable = false;
-bool AppleDecoderModule::sIsVTAvailable = false;
-bool AppleDecoderModule::sIsVTHWAvailable = false;
 bool AppleDecoderModule::sCanUseHardwareVideoDecoder = true;
 
 AppleDecoderModule::AppleDecoderModule() {}
 
 AppleDecoderModule::~AppleDecoderModule() {}
 
 /* static */
 void AppleDecoderModule::Init() {
   if (sInitialized) {
     return;
   }
 
   // Ensure IOSurface framework is loaded.
   MacIOSurfaceLib::LoadLibrary();
-  const bool loaded = MacIOSurfaceLib::isInit();
-
-  // dlopen CoreMedia.framework if it's available.
-  sIsCoreMediaAvailable = AppleCMLinker::Link();
-  // dlopen VideoToolbox.framework if it's available.
-  // We must link both CM and VideoToolbox framework to allow for proper
-  // paired Link/Unlink calls
-  bool haveVideoToolbox = loaded && AppleVTLinker::Link();
-  sIsVTAvailable = sIsCoreMediaAvailable && haveVideoToolbox;
-
-  sIsVTHWAvailable = AppleVTLinker::skPropEnableHWAccel != nullptr;
 
   sCanUseHardwareVideoDecoder =
-      loaded && gfx::gfxVars::CanUseHardwareVideoDecoding();
+      MacIOSurfaceLib::isInit() && gfx::gfxVars::CanUseHardwareVideoDecoding();
 
   sInitialized = true;
 }
 
 nsresult AppleDecoderModule::Startup() {
-  if (!sInitialized || !sIsVTAvailable) {
+  if (!sInitialized) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 already_AddRefed<MediaDataDecoder> AppleDecoderModule::CreateVideoDecoder(
     const CreateDecoderParams& aParams) {
   RefPtr<MediaDataDecoder> decoder =
@@ -71,16 +55,15 @@ already_AddRefed<MediaDataDecoder> Apple
     const CreateDecoderParams& aParams) {
   RefPtr<MediaDataDecoder> decoder =
       new AppleATDecoder(aParams.AudioConfig(), aParams.mTaskQueue);
   return decoder.forget();
 }
 
 bool AppleDecoderModule::SupportsMimeType(
     const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const {
-  return (sIsCoreMediaAvailable &&
-          (aMimeType.EqualsLiteral("audio/mpeg") ||
-           aMimeType.EqualsLiteral("audio/mp4a-latm"))) ||
-         (sIsVTAvailable && (aMimeType.EqualsLiteral("video/mp4") ||
-                             aMimeType.EqualsLiteral("video/avc")));
+  return aMimeType.EqualsLiteral("audio/mpeg") ||
+         aMimeType.EqualsLiteral("audio/mp4a-latm") ||
+         aMimeType.EqualsLiteral("video/mp4") ||
+         aMimeType.EqualsLiteral("video/avc");
 }
 
 }  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/apple/AppleDecoderModule.cpp.rej
@@ -0,0 +1,12 @@
+diff a/dom/media/platforms/apple/AppleDecoderModule.cpp b/dom/media/platforms/apple/AppleDecoderModule.cpp	(rejected hunks)
+@@ -30,8 +30,8 @@ void AppleDecoderModule::Init() {
+   // Ensure IOSurface framework is loaded.
+   MacIOSurfaceLib::LoadLibrary();
+ 
+-  sCanUseHardwareVideoDecoder = MacIOSurfaceLib::isInit() &&
+-      gfx::gfxVars::CanUseHardwareVideoDecoding();
++  sCanUseHardwareVideoDecoder =
++      MacIOSurfaceLib::isInit() && gfx::gfxVars::CanUseHardwareVideoDecoding();
+ 
+   sInitialized = true;
+ }
--- a/dom/media/platforms/apple/AppleDecoderModule.h
+++ b/dom/media/platforms/apple/AppleDecoderModule.h
@@ -30,16 +30,13 @@ class AppleDecoderModule : public Platfo
                         DecoderDoctorDiagnostics* aDiagnostics) const override;
 
   static void Init();
 
   static bool sCanUseHardwareVideoDecoder;
 
  private:
   static bool sInitialized;
-  static bool sIsCoreMediaAvailable;
-  static bool sIsVTAvailable;
-  static bool sIsVTHWAvailable;
 };
 
 }  // namespace mozilla
 
 #endif  // mozilla_AppleDecoderModule_h
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/apple/AppleEncoderModule.cpp
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "AppleEncoderModule.h"
+
+#include "nsMimeTypes.h"
+
+#include "AppleVTEncoder.h"
+
+namespace mozilla {
+
+bool AppleEncoderModule::SupportsMimeType(const nsACString& aMimeType) const {
+  return aMimeType.EqualsLiteral(VIDEO_MP4) ||
+         aMimeType.EqualsLiteral("video/avc");
+}
+
+already_AddRefed<MediaDataEncoder> AppleEncoderModule::CreateVideoEncoder(
+    const CreateEncoderParams& aParams) const {
+  const VideoInfo* info = aParams.mConfig.GetAsVideoInfo();
+  MOZ_ASSERT(info);
+
+  using Config = AppleVTEncoder::Config;
+  Config config =
+      Config(MediaDataEncoder::CodecType::H264, aParams.mUsage, info->mImage,
+             aParams.mPixelFormat, aParams.mFramerate, aParams.mBitrate);
+  if (aParams.mCodecSpecific) {
+    config.SetCodecSpecific(aParams.mCodecSpecific.ref().mH264);
+  }
+
+  RefPtr<MediaDataEncoder> encoder(
+      new AppleVTEncoder(std::forward<Config>(config), aParams.mTaskQueue));
+  return encoder.forget();
+}
+
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/apple/AppleEncoderModule.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef AppleEncoderModule_h_
+#define AppleEncoderModule_h_
+
+#include "PlatformEncoderModule.h"
+
+namespace mozilla {
+class AppleEncoderModule final : public PlatformEncoderModule {
+ public:
+  AppleEncoderModule() {}
+  virtual ~AppleEncoderModule() {}
+
+  bool SupportsMimeType(const nsACString& aMimeType) const override;
+
+  already_AddRefed<MediaDataEncoder> CreateVideoEncoder(
+      const CreateEncoderParams& aParams) const override;
+};
+
+}  // namespace mozilla
+
+#endif /* AppleEncoderModule_h_ */
--- a/dom/media/platforms/apple/AppleUtils.h
+++ b/dom/media/platforms/apple/AppleUtils.h
@@ -3,41 +3,50 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Utility functions to help with Apple API calls.
 
 #ifndef mozilla_AppleUtils_h
 #define mozilla_AppleUtils_h
 
 #include "mozilla/Attributes.h"
+#include <CoreFoundation/CFBase.h>  // For CFRelease()
+#include <CoreVideo/CVBuffer.h>     // For CVBufferRelease()
 
 namespace mozilla {
 
-// Wrapper class to call CFRelease on reference types
+// Wrapper class to call CFRelease/CVBufferRelease on reference types
 // when they go out of scope.
-template <class T>
-class AutoCFRelease {
+template <class T, class F, F relFunc>
+class AutoObjRefRelease {
  public:
-  MOZ_IMPLICIT AutoCFRelease(T aRef) : mRef(aRef) {}
-  ~AutoCFRelease() {
+  MOZ_IMPLICIT AutoObjRefRelease(T aRef) : mRef(aRef) {}
+  ~AutoObjRefRelease() {
     if (mRef) {
-      CFRelease(mRef);
+      relFunc(mRef);
     }
   }
   // Return the wrapped ref so it can be used as an in parameter.
   operator T() { return mRef; }
   // Return a pointer to the wrapped ref for use as an out parameter.
   T* receive() { return &mRef; }
 
  private:
   // Copy operator isn't supported and is not implemented.
-  AutoCFRelease<T>& operator=(const AutoCFRelease<T>&);
+  AutoObjRefRelease<T, F, relFunc>& operator=(
+      const AutoObjRefRelease<T, F, relFunc>&);
   T mRef;
 };
 
+template <typename T>
+using AutoCFRelease = AutoObjRefRelease<T, decltype(&CFRelease), &CFRelease>;
+template <typename T>
+using AutoCVBufferRelease =
+    AutoObjRefRelease<T, decltype(&CVBufferRelease), &CVBufferRelease>;
+
 // CFRefPtr: A CoreFoundation smart pointer.
 template <class T>
 class CFRefPtr {
  public:
   explicit CFRefPtr(T aRef) : mRef(aRef) {
     if (mRef) {
       CFRetain(mRef);
     }
--- a/dom/media/platforms/apple/AppleVTDecoder.cpp
+++ b/dom/media/platforms/apple/AppleVTDecoder.cpp
@@ -1,37 +1,37 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 <CoreFoundation/CFString.h>
+#include "AppleVTDecoder.h"
 
-#include "AppleVTDecoder.h"
-#include "AppleCMLinker.h"
 #include "AppleDecoderModule.h"
 #include "AppleUtils.h"
-#include "AppleVTLinker.h"
 #include "MacIOSurfaceImage.h"
 #include "MediaData.h"
 #include "mozilla/ArrayUtils.h"
 #include "H264.h"
 #include "nsAutoPtr.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Logging.h"
 #include "VideoUtils.h"
 #include "gfxPlatform.h"
+#include "MacIOSurfaceImage.h"
 
 #define LOG(...) DDMOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, __VA_ARGS__)
 #define LOGEX(_this, ...) \
   DDMOZ_LOGEX(_this, sPDMLog, mozilla::LogLevel::Debug, __VA_ARGS__)
 
 namespace mozilla {
 
+using namespace layers;
+
 AppleVTDecoder::AppleVTDecoder(const VideoInfo& aConfig, TaskQueue* aTaskQueue,
                                layers::ImageContainer* aImageContainer,
                                CreateDecoderParams::OptionSet aOptions)
     : mExtraData(aConfig.mExtraData),
       mPictureWidth(aConfig.mImage.width),
       mPictureHeight(aConfig.mImage.height),
       mDisplayWidth(aConfig.mDisplay.width),
       mDisplayHeight(aConfig.mDisplay.height),
@@ -454,66 +454,63 @@ MediaResult AppleVTDecoder::InitializeSe
                                    outputConfiguration,  // Output video format.
                                    &cb, &mSession);
 
   if (rv != noErr) {
     return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                        RESULT_DETAIL("Couldn't create decompression session!"));
   }
 
-  if (AppleVTLinker::skPropUsingHWAccel) {
-    CFBooleanRef isUsingHW = nullptr;
-    rv = VTSessionCopyProperty(mSession, AppleVTLinker::skPropUsingHWAccel,
-                               kCFAllocatorDefault, &isUsingHW);
-    if (rv != noErr) {
-      LOG("AppleVTDecoder: system doesn't support hardware acceleration");
-    }
-    mIsHardwareAccelerated = rv == noErr && isUsingHW == kCFBooleanTrue;
-    LOG("AppleVTDecoder: %s hardware accelerated decoding",
-        mIsHardwareAccelerated ? "using" : "not using");
-  } else {
-    LOG("AppleVTDecoder: couldn't determine hardware acceleration status.");
+  CFBooleanRef isUsingHW = nullptr;
+  rv = VTSessionCopyProperty(
+      mSession,
+      kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder,
+      kCFAllocatorDefault, &isUsingHW);
+  if (rv != noErr) {
+    LOG("AppleVTDecoder: system doesn't support hardware acceleration");
   }
+  mIsHardwareAccelerated = rv == noErr && isUsingHW == kCFBooleanTrue;
+  LOG("AppleVTDecoder: %s hardware accelerated decoding",
+      mIsHardwareAccelerated ? "using" : "not using");
+
   return NS_OK;
 }
 
 CFDictionaryRef AppleVTDecoder::CreateDecoderExtensions() {
   AutoCFRelease<CFDataRef> avc_data = CFDataCreate(
       kCFAllocatorDefault, mExtraData->Elements(), mExtraData->Length());
 
   const void* atomsKey[] = {CFSTR("avcC")};
   const void* atomsValue[] = {avc_data};
   static_assert(ArrayLength(atomsKey) == ArrayLength(atomsValue),
                 "Non matching keys/values array size");
 
   AutoCFRelease<CFDictionaryRef> atoms = CFDictionaryCreate(
       kCFAllocatorDefault, atomsKey, atomsValue, ArrayLength(atomsKey),
       &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 
-  const void* extensionKeys[] = {kCVImageBufferChromaLocationBottomFieldKey,
-                                 kCVImageBufferChromaLocationTopFieldKey,
-                                 AppleCMLinker::skPropExtensionAtoms};
+  const void* extensionKeys[] = {
+      kCVImageBufferChromaLocationBottomFieldKey,
+      kCVImageBufferChromaLocationTopFieldKey,
+      kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms};
 
   const void* extensionValues[] = {kCVImageBufferChromaLocation_Left,
                                    kCVImageBufferChromaLocation_Left, atoms};
   static_assert(ArrayLength(extensionKeys) == ArrayLength(extensionValues),
                 "Non matching keys/values array size");
 
   return CFDictionaryCreate(kCFAllocatorDefault, extensionKeys, extensionValues,
                             ArrayLength(extensionKeys),
                             &kCFTypeDictionaryKeyCallBacks,
                             &kCFTypeDictionaryValueCallBacks);
 }
 
 CFDictionaryRef AppleVTDecoder::CreateDecoderSpecification() {
-  if (!AppleVTLinker::skPropEnableHWAccel) {
-    return nullptr;
-  }
-
-  const void* specKeys[] = {AppleVTLinker::skPropEnableHWAccel};
+  const void* specKeys[] = {
+      kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder};
   const void* specValues[1];
   if (AppleDecoderModule::sCanUseHardwareVideoDecoder) {
     specValues[0] = kCFBooleanTrue;
   } else {
     // This GPU is blacklisted for hardware decoding.
     specValues[0] = kCFBooleanFalse;
   }
   static_assert(ArrayLength(specKeys) == ArrayLength(specValues),
--- a/dom/media/platforms/apple/AppleVTDecoder.h
+++ b/dom/media/platforms/apple/AppleVTDecoder.h
@@ -2,24 +2,26 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
 #ifndef mozilla_AppleVTDecoder_h
 #define mozilla_AppleVTDecoder_h
 
+#include <CoreFoundation/CFDictionary.h>  // For CFDictionaryRef
+#include <CoreMedia/CoreMedia.h>          // For CMVideoFormatDescriptionRef
+#include <VideoToolbox/VideoToolbox.h>    // For VTDecompressionSessionRef
+
 #include "PlatformDecoderModule.h"
 #include "mozilla/Atomics.h"
 #include "nsIThread.h"
 #include "ReorderQueue.h"
 #include "TimeUnits.h"
 
-#include "VideoToolbox/VideoToolbox.h"
-
 namespace mozilla {
 
 DDLoggedTypeDeclNameAndBase(AppleVTDecoder, MediaDataDecoder);
 
 class AppleVTDecoder : public MediaDataDecoder,
                        public DecoderDoctorLifeLogger<AppleVTDecoder> {
  public:
   AppleVTDecoder(const VideoInfo& aConfig, TaskQueue* aTaskQueue,
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/apple/AppleVTEncoder.cpp
@@ -0,0 +1,559 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "AppleVTEncoder.h"
+
+#include <CoreFoundation/CFArray.h>
+#include <CoreFoundation/CFByteOrder.h>
+#include <CoreFoundation/CFDictionary.h>
+
+#include "ImageContainer.h"
+#include "AnnexB.h"
+#include "H264.h"
+
+#include "libyuv.h"
+
+#include "AppleUtils.h"
+
+#define VTENC_LOGE(fmt, ...)                 \
+  MOZ_LOG(sPEMLog, mozilla::LogLevel::Error, \
+          ("[AppleVTEncoder] %s: " fmt, __func__, ##__VA_ARGS__))
+#define VTENC_LOGD(fmt, ...)                 \
+  MOZ_LOG(sPEMLog, mozilla::LogLevel::Debug, \
+          ("[AppleVTEncoder] %s: " fmt, __func__, ##__VA_ARGS__))
+
+namespace mozilla {
+
+static CFDictionaryRef BuildEncoderSpec() {
+  const void* keys[] = {
+      kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder};
+  const void* values[] = {kCFBooleanTrue};
+
+  static_assert(ArrayLength(keys) == ArrayLength(values),
+                "Non matching keys/values array size");
+  return CFDictionaryCreate(kCFAllocatorDefault, keys, values,
+                            ArrayLength(keys), &kCFTypeDictionaryKeyCallBacks,
+                            &kCFTypeDictionaryValueCallBacks);
+}
+
+static void FrameCallback(void* aEncoder, void* aFrameParams, OSStatus aStatus,
+                          VTEncodeInfoFlags aInfoFlags,
+                          CMSampleBufferRef aSampleBuffer) {
+  if (aStatus != noErr || !aSampleBuffer) {
+    VTENC_LOGE("VideoToolbox encoder returned no data");
+    aSampleBuffer = nullptr;
+  } else if (aInfoFlags & kVTEncodeInfo_FrameDropped) {
+    VTENC_LOGE("  ...frame tagged as dropped...");
+  }
+
+  static_cast<AppleVTEncoder*>(aEncoder)->OutputFrame(aSampleBuffer);
+}
+
+static bool SetProfileLevel(
+    VTCompressionSessionRef& aSession,
+    AppleVTEncoder::H264Specific::ProfileLevel aValue) {
+  CFStringRef profileLevel = nullptr;
+  switch (aValue) {
+    case AppleVTEncoder::H264Specific::ProfileLevel::BaselineAutoLevel:
+      profileLevel = kVTProfileLevel_H264_Baseline_AutoLevel;
+      break;
+    case AppleVTEncoder::H264Specific::ProfileLevel::MainAutoLevel:
+      profileLevel = kVTProfileLevel_H264_Main_AutoLevel;
+      break;
+  }
+
+  return profileLevel ? VTSessionSetProperty(
+                            aSession, kVTCompressionPropertyKey_ProfileLevel,
+                            profileLevel) == noErr
+                      : false;
+}
+
+RefPtr<MediaDataEncoder::InitPromise> AppleVTEncoder::Init() {
+  MOZ_ASSERT(!mInited, "Cannot initialize encoder again without shutting down");
+
+  AutoCFRelease<CFDictionaryRef> spec(BuildEncoderSpec());
+  AutoCFRelease<CFDictionaryRef> srcBufferAttr(
+      BuildSourceImageBufferAttributes());
+  if (!srcBufferAttr) {
+    return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR,
+                                        __func__);
+  }
+
+  OSStatus status = VTCompressionSessionCreate(
+      kCFAllocatorDefault, mConfig.mSize.width, mConfig.mSize.height,
+      kCMVideoCodecType_H264, spec, srcBufferAttr, kCFAllocatorDefault,
+      &FrameCallback, this, &mSession);
+  if (status != noErr) {
+    VTENC_LOGE("fail to create encoder session");
+    // TODO: new error codes for encoder
+    return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_ABORT_ERR, __func__);
+  }
+
+  const Maybe<H264Specific>& h264Config = mConfig.mCodecSpecific;
+  if (h264Config) {
+    if (!SetProfileLevel(mSession, h264Config.ref().mProfileLevel)) {
+      VTENC_LOGE("fail to configurate profile level");
+      return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_ABORT_ERR,
+                                          __func__);
+    }
+
+    int64_t interval = h264Config.ref().mKeyframeInterval;
+    AutoCFRelease<CFNumberRef> cf(
+        CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &interval));
+    if (VTSessionSetProperty(mSession,
+                             kVTCompressionPropertyKey_MaxKeyFrameInterval,
+                             cf) != noErr) {
+      VTENC_LOGE("fail to configurate keyframe interval");
+      return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_ABORT_ERR,
+                                          __func__);
+    }
+  }
+
+  CFBooleanRef isUsingHW = nullptr;
+  status = VTSessionCopyProperty(
+      mSession, kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder,
+      kCFAllocatorDefault, &isUsingHW);
+  mIsHardwareAccelerated = status == noErr && isUsingHW == kCFBooleanTrue;
+
+  mError = NS_OK;
+  return InitPromise::CreateAndResolve(TrackInfo::TrackType::kVideoTrack,
+                                       __func__);
+}
+
+static Maybe<OSType> MapPixelFormat(
+    MediaDataEncoder::PixelFormat aFormat) {
+  switch (aFormat) {
+    case MediaDataEncoder::PixelFormat::RGBA32:
+    case MediaDataEncoder::PixelFormat::BGRA32:
+      return Some(kCVPixelFormatType_32BGRA);
+    case MediaDataEncoder::PixelFormat::RGB24:
+      return Some(kCVPixelFormatType_24RGB);
+    case MediaDataEncoder::PixelFormat::BGR24:
+      return Some(kCVPixelFormatType_24BGR);
+    case MediaDataEncoder::PixelFormat::GRAY8:
+      return Some(kCVPixelFormatType_OneComponent8);
+    case MediaDataEncoder::PixelFormat::YUV444P:
+      return Some(kCVPixelFormatType_444YpCbCr8);
+    case MediaDataEncoder::PixelFormat::YUV420P:
+      return Some(kCVPixelFormatType_420YpCbCr8PlanarFullRange);
+    case MediaDataEncoder::PixelFormat::YUV420SP_NV12:
+      return Some(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange);
+    default:
+      return Nothing();
+  }
+}
+
+CFDictionaryRef AppleVTEncoder::BuildSourceImageBufferAttributes() {
+  Maybe<OSType> fmt = MapPixelFormat(mConfig.mSourcePixelFormat);
+  if (fmt.isNothing()) {
+    VTENC_LOGE("unsupported source pixel format");
+    return nullptr;
+  }
+
+  // Source image buffer attributes
+  const void* keys[] = {kCVPixelBufferOpenGLCompatibilityKey,  // TODO
+                        kCVPixelBufferIOSurfacePropertiesKey,  // TODO
+                        kCVPixelBufferPixelFormatTypeKey};
+
+  AutoCFRelease<CFDictionaryRef> ioSurfaceProps(CFDictionaryCreate(
+      kCFAllocatorDefault, nullptr, nullptr, 0, &kCFTypeDictionaryKeyCallBacks,
+      &kCFTypeDictionaryValueCallBacks));
+  AutoCFRelease<CFNumberRef> pixelFormat(
+      CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &fmt));
+  const void* values[] = {kCFBooleanTrue, ioSurfaceProps, pixelFormat};
+
+  MOZ_ASSERT(ArrayLength(keys) == ArrayLength(values),
+             "Non matching keys/values array size");
+
+  return CFDictionaryCreate(kCFAllocatorDefault, keys, values,
+                            ArrayLength(keys), &kCFTypeDictionaryKeyCallBacks,
+                            &kCFTypeDictionaryValueCallBacks);
+}
+
+static bool IsKeyframe(CMSampleBufferRef aSample) {
+  CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(aSample, 0);
+  if (attachments == nullptr || CFArrayGetCount(attachments) == 0) {
+    return false;
+  }
+
+  return !CFDictionaryContainsKey(
+      static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachments, 0)),
+      kCMSampleAttachmentKey_NotSync);
+}
+
+static size_t GetNumParamSets(CMFormatDescriptionRef aDescription) {
+  size_t numParamSets = 0;
+  OSStatus status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
+      aDescription, 0, nullptr, nullptr, &numParamSets, nullptr);
+  if (status != noErr) {
+    VTENC_LOGE("Cannot get number of parameter sets from format description");
+  }
+
+  return numParamSets;
+}
+
+static const uint8_t kNALUStart[4] = {0, 0, 0, 1};
+
+static size_t GetParamSet(CMFormatDescriptionRef aDescription,
+                                 size_t aIndex, const uint8_t** aDataPtr) {
+  size_t length = 0;
+  int headerSize = 0;
+  if (CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
+          aDescription, aIndex, aDataPtr, &length, nullptr, &headerSize) !=
+      noErr) {
+    VTENC_LOGE("fail to get parameter set from format description");
+    return 0;
+  }
+  MOZ_ASSERT(headerSize == sizeof(kNALUStart), "Only support 4 byte header");
+
+  return length;
+}
+
+static bool WriteSPSPPS(MediaRawData* aDst,
+                        CMFormatDescriptionRef aDescription) {
+  // Get SPS/PPS
+  const size_t numParamSets = GetNumParamSets(aDescription);
+  UniquePtr<MediaRawDataWriter> writer(aDst->CreateWriter());
+  for (size_t i = 0; i < numParamSets; i++) {
+    const uint8_t* data = nullptr;
+    size_t length = GetParamSet(aDescription, i, &data);
+    if (length == 0) {
+      return false;
+    }
+    if (!writer->Append(kNALUStart, sizeof(kNALUStart))) {
+      VTENC_LOGE("Cannot write NAL unit start code");
+      return false;
+    }
+    if (!writer->Append(data, length)) {
+      VTENC_LOGE("Cannot write parameter set");
+      return false;
+    }
+  }
+  return true;
+}
+
+static RefPtr<MediaByteBuffer> extractAvcc(
+    CMFormatDescriptionRef aDescription) {
+  CFPropertyListRef list = CMFormatDescriptionGetExtension(
+      aDescription,
+      kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms);
+  if (!list) {
+    VTENC_LOGE("fail to get atoms");
+    return nullptr;
+  }
+  CFDataRef avcC = static_cast<CFDataRef>(
+      CFDictionaryGetValue(static_cast<CFDictionaryRef>(list), CFSTR("avcC")));
+  if (!avcC) {
+    VTENC_LOGE("fail to extract avcC");
+    return nullptr;
+  }
+  CFIndex length = CFDataGetLength(avcC);
+  const UInt8* bytes = CFDataGetBytePtr(avcC);
+  if (length <= 0 || !bytes) {
+    VTENC_LOGE("empty avcC");
+    return nullptr;
+  }
+
+  RefPtr<MediaByteBuffer> config = new MediaByteBuffer(length);
+  config->AppendElements(bytes, length);
+  return config;
+}
+
+bool AppleVTEncoder::WriteExtraData(MediaRawData* aDst, CMSampleBufferRef aSrc,
+                                    const bool aAsAnnexB) {
+  if (!IsKeyframe(aSrc)) {
+    return true;
+  }
+
+  CMFormatDescriptionRef desc = CMSampleBufferGetFormatDescription(aSrc);
+  if (!desc) {
+    VTENC_LOGE("fail to get format description from sample");
+    return false;
+  }
+
+  if (aAsAnnexB) {
+    return WriteSPSPPS(aDst, desc);
+  }
+
+  RefPtr<MediaByteBuffer> avcc = extractAvcc(desc);
+  if (!avcc) {
+    return false;
+  }
+
+  if (!mAvcc || !H264::CompareExtraData(avcc, mAvcc)) {
+    mAvcc = avcc;
+    aDst->mExtraData = mAvcc;
+  }
+
+  return avcc != nullptr;
+}
+
+static bool WriteNALUs(MediaRawData* aDst, CMSampleBufferRef aSrc,
+                       bool aAsAnnexB = false) {
+  size_t srcRemaining = CMSampleBufferGetTotalSampleSize(aSrc);
+  CMBlockBufferRef block = CMSampleBufferGetDataBuffer(aSrc);
+  if (!block) {
+    VTENC_LOGE("Cannot get block buffer frome sample");
+    return false;
+  }
+  UniquePtr<MediaRawDataWriter> writer(aDst->CreateWriter());
+  size_t writtenLength = aDst->Size();
+  // Ensure capacity.
+  if (!writer->SetSize(writtenLength + srcRemaining)) {
+    VTENC_LOGE("Cannot allocate buffer");
+    return false;
+  }
+  size_t readLength = 0;
+  while (srcRemaining > 0) {
+    // Extract the size of next NAL unit
+    uint8_t unitSizeBytes[4];
+    MOZ_ASSERT(srcRemaining > sizeof(unitSizeBytes));
+    if (CMBlockBufferCopyDataBytes(block, readLength, sizeof(unitSizeBytes),
+                                   reinterpret_cast<uint32_t*>(
+                                       unitSizeBytes)) != kCMBlockBufferNoErr) {
+      VTENC_LOGE("Cannot copy unit size bytes");
+      return false;
+    }
+    size_t unitSize =
+        CFSwapInt32BigToHost(*reinterpret_cast<uint32_t*>(unitSizeBytes));
+
+    if (aAsAnnexB) {
+      // Replace unit size bytes with NALU start code.
+      PodCopy(writer->Data() + writtenLength, kNALUStart, sizeof(kNALUStart));
+      readLength += sizeof(unitSizeBytes);
+      srcRemaining -= sizeof(unitSizeBytes);
+      writtenLength += sizeof(kNALUStart);
+    } else {
+      // Copy unit size bytes + data.
+      unitSize += sizeof(unitSizeBytes);
+    }
+    MOZ_ASSERT(writtenLength + unitSize <= aDst->Size());
+    // Copy NAL unit data
+    if (CMBlockBufferCopyDataBytes(block, readLength, unitSize,
+                                   writer->Data() + writtenLength) !=
+        kCMBlockBufferNoErr) {
+      VTENC_LOGE("Cannot copy unit data");
+      return false;
+    }
+    readLength += unitSize;
+    srcRemaining -= unitSize;
+    writtenLength += unitSize;
+  }
+  MOZ_ASSERT(writtenLength == aDst->Size());
+  return true;
+}
+
+void AppleVTEncoder::OutputFrame(CMSampleBufferRef aBuffer) {
+  RefPtr<MediaRawData> output(new MediaRawData());
+
+  bool asAnnexB = mConfig.mUsage == Usage::Realtime;
+  bool succeeded = WriteExtraData(output, aBuffer, asAnnexB) &&
+                   WriteNALUs(output, aBuffer, asAnnexB);
+
+  ProcessOutput(succeeded ? std::move(output) : nullptr);
+}
+
+void AppleVTEncoder::ProcessOutput(RefPtr<MediaRawData>&& aOutput) {
+  if (!mTaskQueue->IsCurrentThreadIn()) {
+    nsresult rv = mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
+        "AppleVTEncoder::ProcessOutput", this, &AppleVTEncoder::ProcessOutput,
+        std::move(aOutput)));
+    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+    Unused << rv;
+    return;
+  }
+  AssertOnTaskQueue();
+
+  if (aOutput) {
+    mEncodedData.AppendElement(std::move(aOutput));
+  } else {
+    mError = NS_ERROR_DOM_MEDIA_FATAL_ERR;
+  }
+}
+
+RefPtr<MediaDataEncoder::EncodePromise> AppleVTEncoder::Encode(
+    const MediaData* aSample) {
+  MOZ_ASSERT(aSample != nullptr);
+  RefPtr<const VideoData> sample(aSample->As<const VideoData>());
+
+  return InvokeAsync<RefPtr<const VideoData>>(mTaskQueue, this, __func__,
+                                              &AppleVTEncoder::ProcessEncode,
+                                              std::move(sample));
+}
+
+RefPtr<MediaDataEncoder::EncodePromise> AppleVTEncoder::ProcessEncode(
+    RefPtr<const VideoData> aSample) {
+  AssertOnTaskQueue();
+  MOZ_ASSERT(mSession);
+
+  if (NS_FAILED(mError)) {
+    return EncodePromise::CreateAndReject(mError, __func__);
+  }
+
+  AutoCVBufferRelease<CVImageBufferRef> buffer(
+      CreateCVPixelBuffer(aSample->mImage));
+  if (!buffer) {
+    return EncodePromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
+  }
+
+  CFDictionaryRef frameProps = nullptr;
+  if (aSample->mKeyframe) {
+    CFTypeRef keys[] = {kVTEncodeFrameOptionKey_ForceKeyFrame};
+    CFTypeRef values[] = {kCFBooleanTrue};
+    MOZ_ASSERT(ArrayLength(keys) == ArrayLength(values));
+    frameProps = CFDictionaryCreate(
+        kCFAllocatorDefault, keys, values, ArrayLength(keys),
+        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+  };
+
+  VTEncodeInfoFlags info;
+  OSStatus status = VTCompressionSessionEncodeFrame(
+      mSession, buffer,
+      CMTimeMake(aSample->mTime.ToMicroseconds(), USECS_PER_S),
+      CMTimeMake(aSample->mDuration.ToMicroseconds(), USECS_PER_S), frameProps,
+      nullptr, &info);
+  if (status != noErr) {
+    return EncodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                          __func__);
+  }
+
+  return EncodePromise::CreateAndResolve(std::move(mEncodedData), __func__);
+}
+
+static size_t NumberOfPlanes(
+    MediaDataEncoder::PixelFormat aPixelFormat) {
+  switch (aPixelFormat) {
+    case MediaDataEncoder::PixelFormat::RGBA32:
+    case MediaDataEncoder::PixelFormat::BGRA32:
+    case MediaDataEncoder::PixelFormat::RGB24:
+    case MediaDataEncoder::PixelFormat::BGR24:
+    case MediaDataEncoder::PixelFormat::GRAY8:
+      return 1;
+    case MediaDataEncoder::PixelFormat::YUV444P:
+    case MediaDataEncoder::PixelFormat::YUV420P:
+      return 3;
+    case MediaDataEncoder::PixelFormat::YUV420SP_NV12:
+      return 2;
+    default:
+      VTENC_LOGE("Unsupported input pixel format");
+      return 0;
+  }
+}
+
+using namespace layers;
+
+CVPixelBufferRef AppleVTEncoder::CreateCVPixelBuffer(const Image* aSource) {
+  AssertOnTaskQueue();
+
+  // TODO: support types other than YUV
+  const PlanarYCbCrImage* image =
+      const_cast<Image*>(aSource)->AsPlanarYCbCrImage();
+  if (!image || !image->GetData()) {
+    return nullptr;
+  }
+
+  OSType format = MapPixelFormat(mConfig.mSourcePixelFormat).ref();
+  size_t numPlanes = NumberOfPlanes(mConfig.mSourcePixelFormat);
+  const PlanarYCbCrImage::Data* yuv = image->GetData();
+  void* addresses[3] = {};
+  size_t widths[3] = {};
+  size_t heights[3] = {};
+  size_t strides[3] = {};
+  switch (numPlanes) {
+    case 3:
+      addresses[2] = yuv->mCrChannel;
+      widths[2] = yuv->mCbCrSize.width;
+      heights[2] = yuv->mCbCrSize.height;
+      strides[2] = yuv->mCbCrStride;
+      MOZ_FALLTHROUGH;
+    case 2:
+      addresses[1] = yuv->mCrChannel;
+      widths[1] = yuv->mCbCrSize.width;
+      heights[1] = yuv->mCbCrSize.height;
+      strides[1] = yuv->mCbCrStride;
+      MOZ_FALLTHROUGH;
+    case 1:
+      addresses[0] = yuv->mYChannel;
+      widths[0] = yuv->mYSize.width;
+      heights[0] = yuv->mYSize.height;
+      strides[0] = yuv->mYStride;
+      break;
+    default:
+      return nullptr;
+  }
+
+  CVPixelBufferRef buffer = nullptr;
+  return CVPixelBufferCreateWithPlanarBytes(
+             kCFAllocatorDefault, yuv->mPicSize.width, yuv->mPicSize.height,
+             format, nullptr, 0, numPlanes, addresses, widths, heights, strides,
+             nullptr, nullptr, nullptr, &buffer) == kCVReturnSuccess
+             ? buffer
+             : nullptr;
+}
+
+RefPtr<MediaDataEncoder::EncodePromise> AppleVTEncoder::Drain() {
+  return InvokeAsync(mTaskQueue, this, __func__, &AppleVTEncoder::ProcessDrain);
+}
+
+RefPtr<MediaDataEncoder::EncodePromise> AppleVTEncoder::ProcessDrain() {
+  AssertOnTaskQueue();
+  MOZ_ASSERT(mSession);
+
+  if (mFramesCompleted) {
+    MOZ_DIAGNOSTIC_ASSERT(mEncodedData.IsEmpty());
+    return EncodePromise::CreateAndResolve(EncodedData(), __func__);
+  }
+
+  OSStatus status =
+      VTCompressionSessionCompleteFrames(mSession, kCMTimeIndefinite);
+  if (status != noErr) {
+    return EncodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                          __func__);
+  }
+  mFramesCompleted = true;
+  // VTCompressionSessionCompleteFrames() could have queued multiple tasks with
+  // the new drained frames. Dispatch a task after them to resolve the promise
+  // with those frames.
+  RefPtr<AppleVTEncoder> self = this;
+  return InvokeAsync(mTaskQueue, __func__, [self]() {
+    EncodedData pendingFrames(std::move(self->mEncodedData));
+    self->mEncodedData = EncodedData();
+    return EncodePromise::CreateAndResolve(std::move(pendingFrames), __func__);
+  });
+}
+
+RefPtr<ShutdownPromise> AppleVTEncoder::Shutdown() {
+  return InvokeAsync(mTaskQueue, this, __func__,
+                     &AppleVTEncoder::ProcessShutdown);
+}
+
+RefPtr<ShutdownPromise> AppleVTEncoder::ProcessShutdown() {
+  if (mSession) {
+    VTCompressionSessionInvalidate(mSession);
+    CFRelease(mSession);
+    mSession = nullptr;
+    mInited = false;
+  }
+  return ShutdownPromise::CreateAndResolve(true, __func__);
+}
+
+RefPtr<GenericPromise> AppleVTEncoder::SetBitrate(
+    MediaDataEncoder::Rate aBitsPerSec) {
+  RefPtr<AppleVTEncoder> self = this;
+  return InvokeAsync(mTaskQueue, __func__, [self, aBitsPerSec]() {
+    MOZ_ASSERT(self->mSession);
+    AutoCFRelease<CFNumberRef> bitrate(
+        CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &aBitsPerSec));
+    return VTSessionSetProperty(self->mSession,
+                                kVTCompressionPropertyKey_AverageBitRate,
+                                bitrate) == noErr
+               ? GenericPromise::CreateAndResolve(true, __func__)
+               : GenericPromise::CreateAndReject(
+                     NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, __func__);
+  });
+}
+
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/apple/AppleVTEncoder.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef mozilla_AppleVTEncoder_h_
+#define mozilla_AppleVTEncoder_h_
+
+#include <CoreMedia/CoreMedia.h>
+#include <VideoToolbox/VideoToolbox.h>
+
+#include "PlatformEncoderModule.h"
+#include "TimeUnits.h"
+
+namespace mozilla {
+
+namespace layers {
+class Image;
+}
+
+class AppleVTEncoder final : public MediaDataEncoder {
+ public:
+  using Config = VideoConfig<H264Specific>;
+
+  struct FrameParams {
+    using TimeUnit = media::TimeUnit;
+
+    const gfx::IntSize mSize;
+    const TimeUnit mDecodeTime;
+    const TimeUnit mTimestamp;
+    const bool mIsKey;
+
+    FrameParams(gfx::IntSize aSize, TimeUnit aDecodeTime, TimeUnit aTimestamp,
+                bool aIsKey)
+        : mSize(aSize),
+          mDecodeTime(aDecodeTime),
+          mTimestamp(aTimestamp),
+          mIsKey(aIsKey) {}
+  };
+
+  AppleVTEncoder(const Config& aConfig, RefPtr<TaskQueue> aTaskQueue)
+      : mConfig(aConfig),
+        mTaskQueue(aTaskQueue),
+        mFramesCompleted(false),
+        mError(NS_OK),
+        mSession(nullptr) {
+    MOZ_ASSERT(mTaskQueue);
+  }
+
+  RefPtr<InitPromise> Init() override;
+  RefPtr<EncodePromise> Encode(const MediaData* aSample) override;
+  RefPtr<EncodePromise> Drain() override;
+  RefPtr<ShutdownPromise> Shutdown() override;
+  RefPtr<GenericPromise> SetBitrate(Rate aBitsPerSec) override;
+
+  nsCString GetDescriptionName() const override {
+    MOZ_ASSERT(mSession);
+    return mIsHardwareAccelerated
+               ? NS_LITERAL_CSTRING("apple hardware VT encoder")
+               : NS_LITERAL_CSTRING("apple software VT encoder");
+  }
+
+  void OutputFrame(CMSampleBufferRef aBuffer);
+
+ private:
+  virtual ~AppleVTEncoder() { MOZ_ASSERT(!mSession); }
+  RefPtr<EncodePromise> ProcessEncode(RefPtr<const VideoData> aSample);
+  void ProcessOutput(RefPtr<MediaRawData>&& aOutput);
+  void ResolvePromise();
+  RefPtr<EncodePromise> ProcessDrain();
+  RefPtr<ShutdownPromise> ProcessShutdown();
+
+  CFDictionaryRef BuildSourceImageBufferAttributes();
+  CVPixelBufferRef CreateCVPixelBuffer(const layers::Image* aSource);
+  bool WriteExtraData(MediaRawData* aDst, CMSampleBufferRef aSrc,
+                      const bool aAsAnnexB);
+  void AssertOnTaskQueue() { MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); }
+
+  const Config mConfig;
+  const RefPtr<TaskQueue> mTaskQueue;
+  // Access only in mTaskQueue.
+  EncodedData mEncodedData;
+  bool mFramesCompleted;
+  RefPtr<MediaByteBuffer> mAvcc;  // Stores latest avcC data.
+  MediaResult mError;
+
+  // Written by Init() but used only in task queue.
+  VTCompressionSessionRef mSession;
+  // Can be accessed on any thread, but only written on during init.
+  Atomic<bool> mIsHardwareAccelerated;
+  // Written during init and shutdown.
+  Atomic<bool> mInited;
+};
+
+}  // namespace mozilla
+
+#endif  // mozilla_AppleVTEncoder_h_
deleted file mode 100644
--- a/dom/media/platforms/apple/AppleVTFunctions.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* 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/. */
-
-// Construct references to each of the VideoToolbox symbols we use.
-
-LINK_FUNC(VTDecompressionSessionCreate)
-LINK_FUNC(VTDecompressionSessionDecodeFrame)
-LINK_FUNC(VTDecompressionSessionInvalidate)
-LINK_FUNC(VTDecompressionSessionWaitForAsynchronousFrames)
-LINK_FUNC(VTSessionCopyProperty)
-LINK_FUNC(VTSessionCopySupportedPropertyDictionary)
--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -9,17 +9,19 @@ EXPORTS += [
     'agnostic/DummyMediaDataDecoder.h',
     'agnostic/OpusDecoder.h',
     'agnostic/TheoraDecoder.h',
     'agnostic/VorbisDecoder.h',
     'agnostic/VPXDecoder.h',
     'AllocationPolicy.h',
     'MediaTelemetryConstants.h',
     'PDMFactory.h',
+    'PEMFactory.h',
     'PlatformDecoderModule.h',
+    'PlatformEncoderModule.h',
     'ReorderQueue.h',
     'SimpleMap.h',
     'wrappers/MediaChangeMonitor.h',
     'wrappers/MediaDataDecoderProxy.h'
 ]
 
 UNIFIED_SOURCES += [
     'agnostic/AgnosticDecoderModule.cpp',
@@ -28,16 +30,17 @@ UNIFIED_SOURCES += [
     'agnostic/NullDecoderModule.cpp',
     'agnostic/OpusDecoder.cpp',
     'agnostic/TheoraDecoder.cpp',
     'agnostic/VorbisDecoder.cpp',
     'agnostic/VPXDecoder.cpp',
     'agnostic/WAVDecoder.cpp',
     'AllocationPolicy.cpp',
     'PDMFactory.cpp',
+    'PEMFactory.cpp',
     'wrappers/MediaChangeMonitor.cpp',
     'wrappers/MediaDataDecoderProxy.cpp'
 ]
 
 DIRS += [
     'agnostic/bytestreams',
     'agnostic/eme',
     'agnostic/gmp',
@@ -80,26 +83,32 @@ if CONFIG['MOZ_OMX']:
     ]
     UNIFIED_SOURCES += [
         'omx/OmxCoreLibLinker.cpp',
     ]
 
 if CONFIG['MOZ_APPLEMEDIA']:
   EXPORTS += [
       'apple/AppleDecoderModule.h',
+      'apple/AppleEncoderModule.h',
   ]
   UNIFIED_SOURCES += [
       'apple/AppleATDecoder.cpp',
-      'apple/AppleCMLinker.cpp',
       'apple/AppleDecoderModule.cpp',
+      'apple/AppleEncoderModule.cpp',
       'apple/AppleVTDecoder.cpp',
-      'apple/AppleVTLinker.cpp',
+      'apple/AppleVTEncoder.cpp',
+  ]
+  LOCAL_INCLUDES += [
+      '/media/libyuv/libyuv/include',
   ]
   OS_LIBS += [
       '-framework AudioToolbox',
+      '-framework CoreMedia',
+      '-framework VideoToolbox',
   ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     EXPORTS += [
         'android/AndroidDecoderModule.h',
         'android/JavaCallbacksSupport.h',
--- a/gfx/gl/AndroidSurfaceTexture.cpp
+++ b/gfx/gl/AndroidSurfaceTexture.cpp
@@ -3,16 +3,23 @@
 /* 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/. */
 
 #ifdef MOZ_WIDGET_ANDROID
 
 #include "AndroidSurfaceTexture.h"
 
+#include "GeneratedJNINatives.h"
+
+#include "AndroidNativeWindow.h"
+#include "GLContextEGL.h"
+#include "GLBlitHelper.h"
+#include "GLImages.h"
+
 using namespace mozilla;
 
 namespace mozilla {
 namespace gl {
 
 void
 AndroidSurfaceTexture::GetTransformMatrix(java::sdk::SurfaceTexture::Param surfaceTexture,
                                           gfx::Matrix4x4* outMatrix)
@@ -24,11 +31,189 @@ AndroidSurfaceTexture::GetTransformMatri
 
   jfloat* array = env->GetFloatArrayElements(jarray.Get(), nullptr);
 
   memcpy(&(outMatrix->_11), array, sizeof(float)*16);
 
   env->ReleaseFloatArrayElements(jarray.Get(), array, 0);
 }
 
+class SharedGL {
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedGL);
+
+  SharedGL(AndroidNativeWindow& window)
+  {
+    MutexAutoLock lock(sMutex);
+
+    if (!sContext) {
+      MOZ_ASSERT(sInstanceCount == 0);
+      sContext = CreateContext();
+      if (!sContext) {
+        return;
+      }
+    }
+
+    InitSurface(window);
+    ++sInstanceCount;
+  }
+
+  void Blit(const AndroidSurfaceTextureHandle& sourceTextureHandle,
+            const gfx::IntSize& imageSize)
+  {
+    MutexAutoLock lock(sMutex);
+    MOZ_ASSERT(sContext);
+
+    // Setting overide also makes conext and surface current.
+    sContext->SetEGLSurfaceOverride(mTargetSurface);
+    RefPtr<layers::SurfaceTextureImage> img =
+      new layers::SurfaceTextureImage(sourceTextureHandle,
+                                      imageSize,
+                                      false,
+                                      OriginPos::TopLeft);
+    sContext->BlitHelper()->BlitImage(img, imageSize, OriginPos::BottomLeft);
+    sContext->SwapBuffers();
+    // This method is called through binder IPC and could run on any thread in
+    // the pool. Release the context and surface from this thread after use so
+    // they can be bound to another thread later.
+    UnmakeCurrent(sContext);
+  }
+
+private:
+  ~SharedGL()
+  {
+    MutexAutoLock lock(sMutex);
+
+    if (mTargetSurface != EGL_NO_SURFACE) {
+      GLLibraryEGL::Get()->fDestroySurface(EGL_DISPLAY(), mTargetSurface);
+    }
+
+    // Destroy shared GL context when no one uses it.
+    if (--sInstanceCount == 0) {
+      sContext.reset();
+    }
+  }
+
+  static UniquePtr<GLContextEGL> CreateContext()
+  {
+    sMutex.AssertCurrentThreadOwns();
+    MOZ_ASSERT(!sContext);
+
+    auto* egl = gl::GLLibraryEGL::Get();
+    EGLDisplay eglDisplay = egl->fGetDisplay(EGL_DEFAULT_DISPLAY);
+    EGLConfig eglConfig;
+    CreateConfig(&eglConfig, /* bpp */ 24, /* depth buffer? */ false);
+    EGLint attributes[] = {
+      LOCAL_EGL_CONTEXT_CLIENT_VERSION, 2,
+      LOCAL_EGL_NONE
+    };
+    EGLContext eglContext =
+      egl->fCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attributes);
+    UniquePtr<GLContextEGL> gl = MakeUnique<GLContextEGL>(CreateContextFlags::NONE,
+                                                          SurfaceCaps::Any(),
+                                                          /* offscreen? */ false,
+                                                          eglConfig,
+                                                          EGL_NO_SURFACE,
+                                                          eglContext);
+    if (!gl->Init()) {
+      NS_WARNING("Fail to create GL context for native blitter.");
+      return nullptr;
+    }
+
+    // Yield the current state made in constructor.
+    UnmakeCurrent(gl);
+    return gl;
+  }
+
+  void InitSurface(AndroidNativeWindow& window)
+  {
+    sMutex.AssertCurrentThreadOwns();
+    MOZ_ASSERT(sContext);
+
+    mTargetSurface = gl::GLLibraryEGL::Get()->fCreateWindowSurface(sContext->GetEGLDisplay(),
+                                                                   sContext->mConfig,
+                                                                   window.NativeWindow(),
+                                                                   0);
+  }
+
+  static bool UnmakeCurrent(UniquePtr<GLContextEGL>& gl)
+  {
+    sMutex.AssertCurrentThreadOwns();
+    MOZ_ASSERT(gl);
+
+    if (!gl->IsCurrent()) {
+      return true;
+    }
+
+    return  gl::GLLibraryEGL::Get()->fMakeCurrent(EGL_DISPLAY(),
+                                                  EGL_NO_SURFACE,
+                                                  EGL_NO_SURFACE,
+                                                  EGL_NO_CONTEXT);
+  }
+
+  static Mutex sMutex;
+  static UniquePtr<GLContextEGL> sContext;
+  static size_t sInstanceCount;
+
+  EGLSurface mTargetSurface;
+};
+
+Mutex SharedGL::sMutex("SharedGLContext::sMutex");
+UniquePtr<GLContextEGL> SharedGL::sContext(nullptr);
+size_t SharedGL::sInstanceCount = 0;
+
+class GLBlitterSupport final
+  : public java::GeckoSurfaceTexture::NativeGLBlitHelper::Natives<GLBlitterSupport>
+{
+public:
+  using Base = java::GeckoSurfaceTexture::NativeGLBlitHelper::Natives<GLBlitterSupport>;
+  using Base::AttachNative;
+  using Base::GetNative;
+  using Base::DisposeNative;
+
+  static java::GeckoSurfaceTexture::NativeGLBlitHelper::LocalRef
+  Create(jint sourceTextureHandle,
+         jni::Object::Param targetSurface,
+         jint width,
+         jint height)
+  {
+    AndroidNativeWindow win(java::GeckoSurface::Ref::From(targetSurface));
+    auto helper = java::GeckoSurfaceTexture::NativeGLBlitHelper::New();
+    RefPtr<SharedGL> gl = new SharedGL(win);
+    GLBlitterSupport::AttachNative(
+      helper,
+      MakeUnique<GLBlitterSupport>(std::move(gl),
+                                   sourceTextureHandle,
+                                   width,
+                                   height));
+    return helper;
+  }
+
+  GLBlitterSupport(RefPtr<SharedGL>&& gl,
+                   jint sourceTextureHandle,
+                   jint width,
+                   jint height)
+    : mGl(gl)
+    , mSourceTextureHandle(sourceTextureHandle)
+    , mSize(width, height)
+  {
+  }
+
+  void Blit()
+  {
+    mGl->Blit(mSourceTextureHandle, mSize);
+  }
+
+private:
+  const RefPtr<SharedGL> mGl;
+  const AndroidSurfaceTextureHandle mSourceTextureHandle;
+  const gfx::IntSize mSize;
+};
+
+void
+AndroidSurfaceTexture::Init()
+{
+  GLBlitterSupport::Init();
+}
+
 } // gl
 } // mozilla
 #endif // MOZ_WIDGET_ANDROID
--- a/gfx/gl/AndroidSurfaceTexture.h
+++ b/gfx/gl/AndroidSurfaceTexture.h
@@ -13,16 +13,17 @@
 
 typedef uint32_t AndroidSurfaceTextureHandle;
 
 namespace mozilla {
 namespace gl {
 
 class AndroidSurfaceTexture {
 public:
+  static void Init();
   static void GetTransformMatrix(java::sdk::SurfaceTexture::Param surfaceTexture,
                                  mozilla::gfx::Matrix4x4* outMatrix);
 
 };
 
 } // gl
 } // mozilla
 
--- a/gfx/gl/SharedSurfaceEGL.cpp
+++ b/gfx/gl/SharedSurfaceEGL.cpp
@@ -275,17 +275,17 @@ bool
 SharedSurface_SurfaceTexture::IsBufferAvailable() const {
     return mSurface->GetAvailable();
 }
 
 bool
 SharedSurface_SurfaceTexture::ToSurfaceDescriptor(layers::SurfaceDescriptor* const out_descriptor)
 {
     *out_descriptor =
-        layers::SurfaceTextureDescriptor(mSurface->GetHandle(),
+        layers::SurfaceTextureDescriptor(mSurface->GetImageHandle(),
                                          mSize,
                                          gfx::SurfaceFormat::R8G8B8A8,
                                          false /* NOT continuous */,
                                          false /* Do not ignore transform */);
     return true;
 }
 
 ////////////////////////////////////////////////////////////////////////
--- a/gfx/layers/LayersLogging.h
+++ b/gfx/layers/LayersLogging.h
@@ -25,16 +25,17 @@ namespace mozilla {
 
 namespace gfx {
 template <class units, class F> struct RectTyped;
 } // namespace gfx
 
 enum class ImageFormat;
 
 namespace layers {
+struct ZoomConstraints;
 
 void
 AppendToString(std::stringstream& aStream, const void* p,
                const char* pfx="", const char* sfx="");
 
 void
 AppendToString(std::stringstream& aStream, ScrollableLayerGuid::ViewID n,
                const char* pfx="", const char* sfx="");
--- a/gfx/layers/apz/src/APZUtils.cpp
+++ b/gfx/layers/apz/src/APZUtils.cpp
@@ -35,11 +35,23 @@ InitializeGlobalState()
 /*static*/ const ScreenMargin
 CalculatePendingDisplayPort(const FrameMetrics& aFrameMetrics,
                             const ParentLayerPoint& aVelocity)
 {
   return AsyncPanZoomController::CalculatePendingDisplayPort(
       aFrameMetrics, aVelocity);
 }
 
+/*static*/ bool
+IsCloseToHorizontal(float aAngle, float aThreshold)
+{
+  return (aAngle < aThreshold || aAngle > (M_PI - aThreshold));
+}
+
+/*static*/ bool
+IsCloseToVertical(float aAngle, float aThreshold)
+{
+  return (fabs(aAngle - (M_PI / 2)) < aThreshold);
+}
+
 } // namespace apz
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/APZUtils.h
+++ b/gfx/layers/apz/src/APZUtils.h
@@ -126,14 +126,24 @@ void InitializeGlobalState();
 /**
  * See AsyncPanZoomController::CalculatePendingDisplayPort. This
  * function simply delegates to that one, so that non-layers code
  * never needs to include AsyncPanZoomController.h
  */
 const ScreenMargin CalculatePendingDisplayPort(const FrameMetrics& aFrameMetrics,
                                                const ParentLayerPoint& aVelocity);
 
+/**
+ * Is aAngle within the given threshold of the horizontal axis?
+ * @param aAngle an angle in radians in the range [0, pi]
+ * @param aThreshold an angle in radians in the range [0, pi/2]
+ */
+bool IsCloseToHorizontal(float aAngle, float aThreshold);
+
+// As above, but for the vertical axis.
+bool IsCloseToVertical(float aAngle, float aThreshold);
+
 } // namespace apz
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_APZUtils_h
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -519,32 +519,16 @@ static const double kDefaultEstimatedPai
  * extra memory for a larger displayport to reduce checkerboarding.
  */
 static bool gIsHighMemSystem = false;
 static bool IsHighMemSystem()
 {
   return gIsHighMemSystem;
 }
 
-/**
- * Is aAngle within the given threshold of the horizontal axis?
- * @param aAngle an angle in radians in the range [0, pi]
- * @param aThreshold an angle in radians in the range [0, pi/2]
- */
-static bool IsCloseToHorizontal(float aAngle, float aThreshold)
-{
-  return (aAngle < aThreshold || aAngle > (M_PI - aThreshold));
-}
-
-// As above, but for the vertical axis.
-static bool IsCloseToVertical(float aAngle, float aThreshold)
-{
-  return (fabs(aAngle - (M_PI / 2)) < aThreshold);
-}
-
 // Counter used to give each APZC a unique id
 static uint32_t sAsyncPanZoomControllerCount = 0;
 
 AsyncPanZoomAnimation*
 PlatformSpecificStateBase::CreateFlingAnimation(AsyncPanZoomController& aApzc,
                                                 const FlingHandoffState& aHandoffState,
                                                 float aPLPPI)
 {
@@ -966,43 +950,59 @@ AsyncPanZoomController::GetSecondTapTole
 }
 
 /* static */AsyncPanZoomController::PinchLockMode AsyncPanZoomController::GetPinchLockMode()
 {
   return static_cast<PinchLockMode>(gfxPrefs::APZPinchLockMode());
 }
 
 bool
-AsyncPanZoomController::ArePointerEventsConsumable(TouchBlockState* aBlock, uint32_t aTouchPoints) {
-  if (aTouchPoints == 0) {
+AsyncPanZoomController::ArePointerEventsConsumable(TouchBlockState* aBlock, const MultiTouchInput& aInput) {
+  uint32_t touchPoints = aInput.mTouches.Length();
+  if (touchPoints == 0) {
     // Cant' do anything with zero touch points
     return false;
   }
 
-  // This logic is simplified, erring on the side of returning true
-  // if we're not sure. It's safer to pretend that we can consume the
-  // event and then not be able to than vice-versa.
+  // This logic is simplified, erring on the side of returning true if we're
+  // not sure. It's safer to pretend that we can consume the event and then
+  // not be able to than vice-versa. But at the same time, we should try hard
+  // to return an accurate result, because returning true can trigger a
+  // pointercancel event to web content, which can break certain features
+  // that are using touch-action and handling the pointermove events.
+  //
   // We could probably enhance this logic to determine things like "we're
   // not pannable, so we can only zoom in, and the zoom is already maxed
   // out, so we're not zoomable either" but no need for that at this point.
 
-  bool pannable = aBlock->GetOverscrollHandoffChain()->CanBePanned(this);
+  bool pannableX = aBlock->TouchActionAllowsPanningX() &&
+      aBlock->GetOverscrollHandoffChain()->CanScrollInDirection(this, ScrollDirection::eHorizontal);
+  bool pannableY = aBlock->TouchActionAllowsPanningY() &&
+      aBlock->GetOverscrollHandoffChain()->CanScrollInDirection(this, ScrollDirection::eVertical);
+
+  bool pannable;
+
+  Maybe<ScrollDirection> panDirection = aBlock->GetBestGuessPanDirection(aInput);
+  if (panDirection == Some(ScrollDirection::eVertical)) {
+    pannable = pannableY;
+  } else if (panDirection == Some(ScrollDirection::eHorizontal)) {
+    pannable = pannableX;
+  } else {
+    // If we don't have a guessed pan direction, err on the side of returning true.
+    pannable = pannableX || pannableY;
+  }
+
+  if (touchPoints == 1) {
+    return pannable;
+  }
+
   bool zoomable = mZoomConstraints.mAllowZoom;
-
-  pannable &= (aBlock->TouchActionAllowsPanningX() || aBlock->TouchActionAllowsPanningY());
   zoomable &= (aBlock->TouchActionAllowsPinchZoom());
 
-  // XXX once we fix bug 1031443, consumable should be assigned
-  // pannable || zoomable if aTouchPoints > 1.
-  bool consumable = (aTouchPoints == 1 ? pannable : zoomable);
-  if (!consumable) {
-    return false;
-  }
-
-  return true;
+  return pannable || zoomable;
 }
 
 nsEventStatus AsyncPanZoomController::HandleDragEvent(const MouseInput& aEvent,
                                                       const AsyncDragMetrics& aDragMetrics,
                                                       CSSCoord aInitialThumbPos)
 {
   if (!gfxPrefs::APZDragEnabled()) {
     return nsEventStatus_eIgnore;
@@ -2760,44 +2760,44 @@ void AsyncPanZoomController::HandlePanni
   RefPtr<const OverscrollHandoffChain> overscrollHandoffChain =
     GetCurrentInputBlock()->GetOverscrollHandoffChain();
   bool canScrollHorizontal = !mX.IsAxisLocked() &&
     overscrollHandoffChain->CanScrollInDirection(this, ScrollDirection::eHorizontal);
   bool canScrollVertical = !mY.IsAxisLocked() &&
     overscrollHandoffChain->CanScrollInDirection(this, ScrollDirection::eVertical);
   if (GetCurrentTouchBlock()->TouchActionAllowsPanningXY()) {
     if (canScrollHorizontal && canScrollVertical) {
-      if (IsCloseToHorizontal(aAngle, gfxPrefs::APZAxisLockAngle())) {
+      if (apz::IsCloseToHorizontal(aAngle, gfxPrefs::APZAxisLockAngle())) {
         mY.SetAxisLocked(true);
         SetState(PANNING_LOCKED_X);
-      } else if (IsCloseToVertical(aAngle, gfxPrefs::APZAxisLockAngle())) {
+      } else if (apz::IsCloseToVertical(aAngle, gfxPrefs::APZAxisLockAngle())) {
         mX.SetAxisLocked(true);
         SetState(PANNING_LOCKED_Y);
       } else {
         SetState(PANNING);
       }
     } else if (canScrollHorizontal || canScrollVertical) {
       SetState(PANNING);
     } else {
       SetState(NOTHING);
     }
   } else if (GetCurrentTouchBlock()->TouchActionAllowsPanningX()) {
     // Using bigger angle for panning to keep behavior consistent
     // with IE.
-    if (IsCloseToHorizontal(aAngle, gfxPrefs::APZAllowedDirectPanAngle())) {
+    if (apz::IsCloseToHorizontal(aAngle, gfxPrefs::APZAllowedDirectPanAngle())) {
       mY.SetAxisLocked(true);
       SetState(PANNING_LOCKED_X);
       mPanDirRestricted = true;
     } else {
       // Don't treat these touches as pan/zoom movements since 'touch-action' value
       // requires it.
       SetState(NOTHING);
     }
   } else if (GetCurrentTouchBlock()->TouchActionAllowsPanningY()) {
-    if (IsCloseToVertical(aAngle, gfxPrefs::APZAllowedDirectPanAngle())) {
+    if (apz::IsCloseToVertical(aAngle, gfxPrefs::APZAllowedDirectPanAngle())) {
       mX.SetAxisLocked(true);
       SetState(PANNING_LOCKED_Y);
       mPanDirRestricted = true;
     } else {
       SetState(NOTHING);
     }
   } else {
     SetState(NOTHING);
@@ -2818,22 +2818,22 @@ void AsyncPanZoomController::HandlePanni
     GetCurrentInputBlock()->GetOverscrollHandoffChain();
   bool canScrollHorizontal = !mX.IsAxisLocked() &&
     overscrollHandoffChain->CanScrollInDirection(this, ScrollDirection::eHorizontal);
   bool canScrollVertical = !mY.IsAxisLocked() &&
     overscrollHandoffChain->CanScrollInDirection(this, ScrollDirection::eVertical);
 
   if (!canScrollHorizontal || !canScrollVertical) {
     SetState(PANNING);
-  } else if (IsCloseToHorizontal(aAngle, gfxPrefs::APZAxisLockAngle())) {
+  } else if (apz::IsCloseToHorizontal(aAngle, gfxPrefs::APZAxisLockAngle())) {
     mY.SetAxisLocked(true);
     if (canScrollHorizontal) {
       SetState(PANNING_LOCKED_X);
     }
-  } else if (IsCloseToVertical(aAngle, gfxPrefs::APZAxisLockAngle())) {
+  } else if (apz::IsCloseToVertical(aAngle, gfxPrefs::APZAxisLockAngle())) {
     mX.SetAxisLocked(true);
     if (canScrollVertical) {
       SetState(PANNING_LOCKED_Y);
     }
   } else {
     SetState(PANNING);
   }
 }
@@ -2844,22 +2844,22 @@ void AsyncPanZoomController::HandlePanni
 
     double angle = atan2(aPanDistance.y, aPanDistance.x); // range [-pi, pi]
     angle = fabs(angle); // range [0, pi]
 
     float breakThreshold = gfxPrefs::APZAxisBreakoutThreshold() * GetDPI();
 
     if (fabs(aPanDistance.x) > breakThreshold || fabs(aPanDistance.y) > breakThreshold) {
       if (mState == PANNING_LOCKED_X) {
-        if (!IsCloseToHorizontal(angle, gfxPrefs::APZAxisBreakoutAngle())) {
+        if (!apz::IsCloseToHorizontal(angle, gfxPrefs::APZAxisBreakoutAngle())) {
           mY.SetAxisLocked(false);
           SetState(PANNING);
         }
       } else if (mState == PANNING_LOCKED_Y) {
-        if (!IsCloseToVertical(angle, gfxPrefs::APZAxisBreakoutAngle())) {
+        if (!apz::IsCloseToVertical(angle, gfxPrefs::APZAxisBreakoutAngle())) {
           mX.SetAxisLocked(false);
           SetState(PANNING);
         }
       }
     }
   }
 }
 
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -1210,22 +1210,22 @@ private:
 public:
   /**
    * Flush a repaint request if one is needed, without throttling it with the
    * paint throttler.
    */
   void FlushRepaintForNewInputBlock();
 
   /**
-   * Given the number of touch points in an input event and touch block they
-   * belong to, check if the event can result in a panning/zooming behavior.
+   * Given an input event and the touch block it belongs to, check if the
+   * event can lead to a panning/zooming behavior.
    * This is primarily used to figure out when to dispatch the pointercancel
    * event for the pointer events spec.
    */
-  bool ArePointerEventsConsumable(TouchBlockState* aBlock, uint32_t aTouchPoints);
+  bool ArePointerEventsConsumable(TouchBlockState* aBlock, const MultiTouchInput& aInput);
 
   /**
    * Clear internal state relating to touch input handling.
    */
   void ResetTouchInputState();
 
   /**
    * Gets a ref to the input queue that is shared across the entire tree manager.
--- a/gfx/layers/apz/src/InputBlockState.cpp
+++ b/gfx/layers/apz/src/InputBlockState.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "InputBlockState.h"
 
+#include "APZUtils.h"
 #include "AsyncPanZoomController.h"         // for AsyncPanZoomController
 #include "ScrollAnimationPhysics.h"         // for kScrollSeriesTimeoutMs
 #include "gfxPrefs.h"                       // for gfxPrefs
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Telemetry.h"              // for Telemetry
 #include "mozilla/layers/IAPZCTreeManager.h" // for AllowedTouchBehavior
 #include "OverscrollHandoffState.h"
 #include "QueuedInput.h"
@@ -904,16 +905,39 @@ TouchBlockState::UpdateSlopState(const M
       // this block
       TBS_LOG("%p exiting slop\n", this);
       mInSlop = false;
     }
   }
   return mInSlop;
 }
 
+Maybe<ScrollDirection>
+TouchBlockState::GetBestGuessPanDirection(const MultiTouchInput& aInput)
+{
+  if (aInput.mType != MultiTouchInput::MULTITOUCH_MOVE ||
+      aInput.mTouches.Length() != 1) {
+    return Nothing();
+  }
+  ScreenPoint vector = aInput.mTouches[0].mScreenPoint - mSlopOrigin;
+  double angle = atan2(vector.y, vector.x); // range [-pi, pi]
+  angle = fabs(angle); // range [0, pi]
+
+  double angleThreshold = TouchActionAllowsPanningXY()
+      ? gfxPrefs::APZAxisLockAngle()
+      : gfxPrefs::APZAllowedDirectPanAngle();
+  if (apz::IsCloseToHorizontal(angle, angleThreshold)) {
+    return Some(ScrollDirection::eHorizontal);
+  }
+  if (apz::IsCloseToVertical(angle, angleThreshold)) {
+    return Some(ScrollDirection::eVertical);
+  }
+  return Nothing();
+}
+
 uint32_t
 TouchBlockState::GetActiveTouchCount() const
 {
   return mTouchCounter.GetActiveTouchCount();
 }
 
 KeyboardBlockState::KeyboardBlockState(const RefPtr<AsyncPanZoomController>& aTargetApzc)
   : InputBlockState(aTargetApzc, TargetConfirmationFlags{true})
--- a/gfx/layers/apz/src/InputBlockState.h
+++ b/gfx/layers/apz/src/InputBlockState.h
@@ -480,16 +480,23 @@ public:
    * the slop area is - if this is true the slop area is larger.
    * @return true iff the provided event is a touchmove in the slop area and
    *         so should not be sent to content.
    */
   bool UpdateSlopState(const MultiTouchInput& aInput,
                        bool aApzcCanConsumeEvents);
 
   /**
+   * Based on the slop origin and the given input event, return a best guess
+   * as to the pan direction of this touch block. Returns Nothing() if no guess
+   * can be made.
+   */
+  Maybe<ScrollDirection> GetBestGuessPanDirection(const MultiTouchInput& aInput);
+
+  /**
    * Returns the number of touch points currently active.
    */
   uint32_t GetActiveTouchCount() const;
 
   void DispatchEvent(const InputData& aEvent) const override;
   bool MustStayActive() override;
   const char* Type() override;
 
--- a/gfx/layers/apz/src/InputQueue.cpp
+++ b/gfx/layers/apz/src/InputQueue.cpp
@@ -146,17 +146,17 @@ InputQueue::ReceiveTouchInput(const RefP
   nsEventStatus result = nsEventStatus_eIgnore;
 
   // XXX calling ArePointerEventsConsumable on |target| may be wrong here if
   // the target isn't confirmed and the real target turns out to be something
   // else. For now assume this is rare enough that it's not an issue.
   if (block->IsDuringFastFling()) {
     INPQ_LOG("dropping event due to block %p being in fast motion\n", block);
     result = nsEventStatus_eConsumeNoDefault;
-  } else if (target && target->ArePointerEventsConsumable(block, aEvent.mTouches.Length())) {
+  } else if (target && target->ArePointerEventsConsumable(block, aEvent)) {
     if (block->UpdateSlopState(aEvent, true)) {
       INPQ_LOG("dropping event due to block %p being in slop\n", block);
       result = nsEventStatus_eConsumeNoDefault;
     } else {
       result = nsEventStatus_eConsumeDoDefault;
     }
   } else if (block->UpdateSlopState(aEvent, false)) {
     INPQ_LOG("dropping event due to block %p being in mini-slop\n", block);
--- a/gfx/layers/apz/test/gtest/TestPanning.cpp
+++ b/gfx/layers/apz/test/gtest/TestPanning.cpp
@@ -103,17 +103,17 @@ TEST_F(APZCPanningTester, PanWithTouchAc
   SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   SCOPED_GFX_PREF(APZVelocityBias, float, 0.0); // Velocity bias can cause extra repaint requests
   DoPanTest(false, false, 0);
 }
 
 TEST_F(APZCPanningTester, PanWithTouchActionPanX) {
   SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   SCOPED_GFX_PREF(APZVelocityBias, float, 0.0); // Velocity bias can cause extra repaint requests
-  DoPanTest(false, true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN);
+  DoPanTest(false, false, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN);
 }
 
 TEST_F(APZCPanningTester, PanWithTouchActionPanY) {
   SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   SCOPED_GFX_PREF(APZVelocityBias, float, 0.0); // Velocity bias can cause extra repaint requests
   DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
 }
 
--- a/gfx/layers/apz/test/gtest/TestPinching.cpp
+++ b/gfx/layers/apz/test/gtest/TestPinching.cpp
@@ -233,17 +233,17 @@ TEST_F(APZCPinchGestureDetectorTester, P
   behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
   behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
   DoPinchTest(true, &behaviors);
 }
 
 TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNotAllowZoom) {
   SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   nsTArray<uint32_t> behaviors;
-  behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
+  behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::NONE);
   behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
   DoPinchTest(false, &behaviors);
 }
 
 TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNone_NoAPZZoom) {
   SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   SCOPED_GFX_PREF(APZAllowZooming, bool, false);
 
--- a/gfx/layers/opengl/TextureClientOGL.cpp
+++ b/gfx/layers/opengl/TextureClientOGL.cpp
@@ -165,17 +165,17 @@ AndroidNativeWindowTextureData::FillInfo
   aInfo.supportsMoz2D = true;
   aInfo.canExposeMappedData = false;
   aInfo.canConcurrentlyReadLock = false;
 }
 
 bool
 AndroidNativeWindowTextureData::Serialize(SurfaceDescriptor& aOutDescriptor)
 {
-  aOutDescriptor = SurfaceTextureDescriptor(mSurface->GetHandle(),
+  aOutDescriptor = SurfaceTextureDescriptor(mSurface->GetImageHandle(),
                                             mSize,
                                             mFormat,
                                             false /* not continuous */,
                                             true /* ignore transform */);
   return true;
 }
 
 bool
new file mode 100644
--- /dev/null
+++ b/gfx/tests/crashtests/1508822.html
@@ -0,0 +1,5 @@
+<style>
+* { scale: 0.55749 10 1 }
+</style>
+<svg>
+<circle r="49%" mask="url()">
--- a/gfx/tests/crashtests/crashtests.list
+++ b/gfx/tests/crashtests/crashtests.list
@@ -170,8 +170,9 @@ load 1468020.html
 load 1470437.html
 load 1470440.html
 load 1478035.html
 load 1490704-1.html
 load 1501518.html
 load 1503986-1.html
 load 1505426-1.html
 load 1508811.html
+load 1508822.html
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-7051f18fdcfbe60ecdbaeaa8e53c4ba98f2037a1
+05bdcae134d73aca7bb48358e91de1f8aef27773
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -590,26 +590,23 @@ struct BuiltDisplayListDescriptor {
   // The second IPC time stamp: after serialization
   uint64_t builder_finish_time;
   // The third IPC time stamp: just before sending
   uint64_t send_start_time;
   // The amount of clipping nodes created while building this display list.
   uintptr_t total_clip_nodes;
   // The amount of spatial nodes created while building this display list.
   uintptr_t total_spatial_nodes;
-  // An estimate of the number of primitives that will be created by this display list.
-  uintptr_t prim_count_estimate;
 
   bool operator==(const BuiltDisplayListDescriptor& aOther) const {
     return builder_start_time == aOther.builder_start_time &&
            builder_finish_time == aOther.builder_finish_time &&
            send_start_time == aOther.send_start_time &&
            total_clip_nodes == aOther.total_clip_nodes &&
-           total_spatial_nodes == aOther.total_spatial_nodes &&
-           prim_count_estimate == aOther.prim_count_estimate;
+           total_spatial_nodes == aOther.total_spatial_nodes;
   }
 };
 
 struct WrVecU8 {
   uint8_t *data;
   uintptr_t length;
   uintptr_t capacity;
 
--- a/gfx/wr/.taskcluster.yml
+++ b/gfx/wr/.taskcluster.yml
@@ -63,19 +63,17 @@ tasks:
         - /bin/bash
         - '--login'
         - '-c'
         - >-
           curl https://sh.rustup.rs -sSf | sh -s -- -y &&
           git clone {{event.head.repo.url}} webrender && cd webrender &&
           git checkout {{event.head.sha}} &&
           servo-tidy &&
-          (cd wrench && python script/headless.py reftest) &&
-          (cd wrench && python script/headless.py rawtest) &&
-          (cd wrench && cargo build --release)
+          ci-scripts/linux-release-tests.sh
     routes:
       - "index.garbage.webrender.ci.{{event.head.user.login}}.{{event.head.repo.branch}}.linux-release"
   - metadata:
       name: Linux debug tests
       description: Runs debug-mode WebRender CI stuff on a Linux TC worker
       owner: '{{ event.head.user.email }}'
       source: '{{ event.head.repo.url }}'
     provisionerId: '{{ taskcluster.docker.provisionerId }}'
@@ -98,32 +96,25 @@ tasks:
         - /bin/bash
         - '--login'
         - '-c'
         - >-
           curl https://sh.rustup.rs -sSf | sh -s -- -y &&
           git clone {{event.head.repo.url}} webrender && cd webrender &&
           git checkout {{event.head.sha}} &&
           servo-tidy &&
-          (cd webrender_api && cargo test --verbose --features "ipc") &&
-          (cd webrender && cargo build --verbose --no-default-features) &&
-          (cd webrender && cargo build --verbose --no-default-features --features capture) &&
-          (cd webrender && cargo build --verbose --features capture,profiler) &&
-          (cd webrender && cargo build --verbose --features replay) &&
-          (cd wrench && cargo build --verbose --features env_logger) &&
-          (cd examples && cargo build --verbose) &&
-          (cargo test --all --verbose)
+          ci-scripts/linux-debug-tests.sh
     routes:
       - "index.garbage.webrender.ci.{{event.head.user.login}}.{{event.head.repo.branch}}.linux-debug"
   # For the OS X jobs we use a pool of machines that we are managing, because
   # Mozilla releng doesn't have any spare OS X machines for us at this time.
   # Talk to :kats or :jrmuizel if you need more details about this. The machines
   # are hooked up to taskcluster using taskcluster-worker; they use a worker-type
-  # of kats-webrender-ci-osx. They are set up with all the dependencies needed
-  # to build and test webrender, including Rust (currently 1.24), servo-tidy,
+  # of webrender-ci-osx. They are set up with all the dependencies needed
+  # to build and test webrender, including Rust (currently 1.30), servo-tidy,
   # mako, zlib, etc. Note that unlike the docker-worker used for Linux, these
   # machines WILL persist state from one run to the next, so any cleanup needs
   # to be handled explicitly.
   - metadata:
       name: OS X release tests
       description: Runs release-mode WebRender CI stuff on a OS X TC worker
       owner: '{{ event.head.user.email }}'
       source: '{{ event.head.repo.url }}'
@@ -154,19 +145,17 @@ tasks:
             RUST_LOG=sccache=trace SCCACHE_ERROR_LOG=$PWD/../artifacts/sccache.log sccache --start-server
             export RUST_BACKTRACE=full
             export RUSTFLAGS='--deny warnings'
             export PKG_CONFIG_PATH="/usr/local/opt/zlib/lib/pkgconfig:$PKG_CONFIG_PATH"
             export RUSTC_WRAPPER=sccache
             echo 'exec make -j1 "$@"' > $HOME/make # See #2638
             chmod +x $HOME/make
             export MAKE="$HOME/make"
-            (cd wrench && python script/headless.py reftest)
-            (cd wrench && cargo build --release)
-            (cd wrench && cargo run --release -- --precache reftest reftests/clip/fixed-position-clipping.yaml)
+            ci-scripts/macos-release-tests.sh
             sccache --stop-server || true
       artifacts:
         - name: public/sccache.log
           path: artifacts/sccache.log
           type: file
     routes:
       - "index.garbage.webrender.ci.{{event.head.user.login}}.{{event.head.repo.branch}}.osx-release"
   - metadata:
@@ -201,24 +190,16 @@ tasks:
             RUST_LOG=sccache=trace SCCACHE_ERROR_LOG=$PWD/../artifacts/sccache.log sccache --start-server
             export RUST_BACKTRACE=full
             export RUSTFLAGS='--deny warnings'
             export PKG_CONFIG_PATH="/usr/local/opt/zlib/lib/pkgconfig:$PKG_CONFIG_PATH"
             export RUSTC_WRAPPER=sccache
             echo 'exec make -j1 "$@"' > $HOME/make # See #2638
             chmod +x $HOME/make
             export MAKE="$HOME/make"
-            (cd webrender_api && cargo test --verbose --features "ipc")
-            (cd webrender && cargo check --verbose --no-default-features)
-            (cd webrender && cargo check --verbose --no-default-features --features capture)
-            (cd webrender && cargo check --verbose --features capture,profiler)
-            (cd webrender && cargo check --verbose --features replay)
-            (cd webrender && cargo check --verbose --no-default-features --features pathfinder)
-            (cd wrench && cargo check --verbose --features env_logger)
-            (cd examples && cargo check --verbose)
-            (cargo test --all --verbose)
+            ci-scripts/macos-debug-tests.sh
             sccache --stop-server || true
       artifacts:
         - name: public/sccache.log
           path: artifacts/sccache.log
           type: file
     routes:
       - "index.garbage.webrender.ci.{{event.head.user.login}}.{{event.head.repo.branch}}.osx-debug"
new file mode 100755
--- /dev/null
+++ b/gfx/wr/ci-scripts/linux-debug-tests.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+
+# 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/. */
+
+# This must be run from the root webrender directory!
+# Users may set the CARGOFLAGS environment variable to pass
+# additional flags to cargo if desired.
+
+set -o errexit
+set -o nounset
+set -o pipefail
+set -o xtrace
+
+CARGOFLAGS=${CARGOFLAGS:-"--verbose"}  # default to --verbose if not set
+
+pushd webrender_api
+cargo test ${CARGOFLAGS} --features "ipc"
+popd
+
+pushd webrender
+cargo build ${CARGOFLAGS} --no-default-features
+cargo build ${CARGOFLAGS} --no-default-features --features capture
+cargo build ${CARGOFLAGS} --features capture,profiler
+cargo build ${CARGOFLAGS} --features replay
+popd
+
+pushd wrench
+cargo build ${CARGOFLAGS} --features env_logger
+popd
+
+pushd examples
+cargo build ${CARGOFLAGS}
+popd
+
+cargo test ${CARGOFLAGS} --all
new file mode 100755
--- /dev/null
+++ b/gfx/wr/ci-scripts/linux-release-tests.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+# 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/. */
+
+# This must be run from the root webrender directory!
+# Users may set the CARGOFLAGS environment variable to pass
+# additional flags to cargo if desired.
+
+set -o errexit
+set -o nounset
+set -o pipefail
+set -o xtrace
+
+CARGOFLAGS=${CARGOFLAGS:-""}  # default to empty if not set
+
+pushd wrench
+python script/headless.py reftest
+python script/headless.py rawtest
+cargo build ${CARGOFLAGS} --release
+popd
new file mode 100755
--- /dev/null
+++ b/gfx/wr/ci-scripts/macos-debug-tests.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+
+# 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/. */
+
+# This must be run from the root webrender directory!
+# Users may set the CARGOFLAGS environment variable to pass
+# additional flags to cargo if desired.
+
+set -o errexit
+set -o nounset
+set -o pipefail
+set -o xtrace
+
+CARGOFLAGS=${CARGOFLAGS:-"--verbose"}  # default to --verbose if not set
+
+pushd webrender_api
+cargo test ${CARGOFLAGS} --features "ipc"
+popd
+
+pushd webrender
+cargo check ${CARGOFLAGS} --no-default-features
+cargo check ${CARGOFLAGS} --no-default-features --features capture
+cargo check ${CARGOFLAGS} --features capture,profiler
+cargo check ${CARGOFLAGS} --features replay
+cargo check ${CARGOFLAGS} --no-default-features --features pathfinder
+popd
+
+pushd wrench
+cargo check ${CARGOFLAGS} --features env_logger
+popd
+
+pushd examples
+cargo check ${CARGOFLAGS}
+popd
+
+cargo test ${CARGOFLAGS} --all
new file mode 100755
--- /dev/null
+++ b/gfx/wr/ci-scripts/macos-release-tests.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+
+# 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/. */
+
+# This must be run from the root webrender directory!
+# Users may set the CARGOFLAGS environment variable to pass
+# additional flags to cargo if desired.
+
+set -o errexit
+set -o nounset
+set -o pipefail
+set -o xtrace
+
+CARGOFLAGS=${CARGOFLAGS:-""}  # default to empty if not set
+
+pushd wrench
+python script/headless.py reftest
+cargo build ${CARGOFLAGS} --release
+cargo run ${CARGOFLAGS} --release -- --precache \
+    reftest reftests/clip/fixed-position-clipping.yaml
+popd
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -10,20 +10,20 @@ use clip_scroll_tree::{ClipScrollTree, R
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheAddress};
 use gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders, ZBufferId, ZBufferIdGenerator};
 use gpu_types::{ClipMaskInstance, SplitCompositeInstance};
 use gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, TextureSource};
 use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureSurface};
-use prim_store::{BrushKind, BrushPrimitive, DeferredResolve};
+use prim_store::{BrushKind, BrushPrimitive, DeferredResolve, PrimitiveTemplateKind};
 use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveInstanceKind, PrimitiveStore};
-use prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity};
-use prim_store::{BrushSegment, BorderSource, ClipMaskKind, ClipTaskIndex, PrimitiveDetails};
+use prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
+use prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, 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};
@@ -543,22 +543,25 @@ impl AlphaBatchBuilder {
         // Get the clip task address for the global primitive, if one was set.
         let clip_task_address = get_clip_task_address(
             &ctx.scratch.clip_mask_instances,
             prim_instance.clip_task_index,
             0,
             render_tasks,
         ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
-        match prim_instance.kind {
-            PrimitiveInstanceKind::Clear => {
-                let prim_data = &ctx
-                    .resources
-                    .prim_data_store[prim_instance.prim_data_handle];
+        let prim_data = &ctx
+            .resources
+            .prim_data_store[prim_instance.prim_data_handle];
 
+        match (&prim_instance.kind, &prim_data.kind) {
+            (
+                PrimitiveInstanceKind::Clear,
+                PrimitiveTemplateKind::Clear,
+            ) => {
                 let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
 
                 // TODO(gw): We can abstract some of the common code below into
                 //           helper methods, as we port more primitives to make
                 //           use of interning.
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_data.prim_rect,
@@ -592,25 +595,100 @@ impl AlphaBatchBuilder {
 
                 self.batch_list.push_single_instance(
                     batch_key,
                     bounding_rect,
                     z_id,
                     PrimitiveInstanceData::from(instance),
                 );
             }
-            PrimitiveInstanceKind::TextRun { run_index, .. } => {
-                let run = &ctx.prim_store.text_runs[run_index];
+            (
+                PrimitiveInstanceKind::NormalBorder { cache_handles, .. },
+                PrimitiveTemplateKind::NormalBorder { template, .. },
+            ) => {
+                let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
+                let cache_handles = &ctx.scratch.border_cache_handles[*cache_handles];
+                let specified_blend_mode = BlendMode::PremultipliedAlpha;
+                let mut segment_data: SmallVec<[SegmentInstanceData; 8]> = SmallVec::new();
+
+                // Collect the segment instance data from each render
+                // task for each valid edge / corner of the border.
+
+                for handle in cache_handles {
+                    let rt_cache_entry = ctx.resource_cache
+                        .get_cached_render_task(handle);
+                    let cache_item = ctx.resource_cache
+                        .get_texture_cache_item(&rt_cache_entry.handle);
+                    segment_data.push(
+                        SegmentInstanceData {
+                            textures: BatchTextures::color(cache_item.texture_id),
+                            user_data: cache_item.uv_rect_handle.as_int(gpu_cache),
+                        }
+                    );
+                }
+
+                let non_segmented_blend_mode = if !prim_data.opacity.is_opaque ||
+                    prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                    transform_kind == TransformedRectKind::Complex
+                {
+                    specified_blend_mode
+                } else {
+                    BlendMode::None
+                };
+
+                let prim_header = PrimitiveHeader {
+                    local_rect: prim_data.prim_rect,
+                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    task_address,
+                    specific_prim_address: prim_cache_address,
+                    clip_task_address,
+                    transform_id,
+                };
+
+                let batch_params = BrushBatchParameters::instanced(
+                    BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
+                    [
+                        ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
+                        RasterizationSpace::Local as i32,
+                        get_shader_opacity(1.0),
+                    ],
+                    segment_data,
+                );
+
+                let prim_header_index = prim_headers.push(
+                    &prim_header,
+                    z_id,
+                    batch_params.prim_user_data,
+                );
+
+                self.add_segmented_prim_to_batch(
+                    Some(template.brush_segments.as_slice()),
+                    prim_data.opacity,
+                    &batch_params,
+                    specified_blend_mode,
+                    non_segmented_blend_mode,
+                    prim_header_index,
+                    clip_task_address,
+                    bounding_rect,
+                    transform_kind,
+                    render_tasks,
+                    z_id,
+                    prim_instance.clip_task_index,
+                    ctx,
+                );
+            }
+            (
+                PrimitiveInstanceKind::TextRun { run_index, .. },
+                PrimitiveTemplateKind::TextRun { .. },
+            ) => {
+                let run = &ctx.prim_store.text_runs[*run_index];
                 let subpx_dir = run.used_font.get_subpx_dir();
 
                 // The GPU cache data is stored in the template and reused across
                 // frames and display lists.
-                let prim_data = &ctx
-                    .resources
-                    .prim_data_store[prim_instance.prim_data_handle];
 
                 let glyph_fetch_buffer = &mut self.glyph_fetch_buffer;
                 let alpha_batch_list = &mut self.batch_list.alpha_batch_list;
                 let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_data.prim_rect,
                     local_clip_rect: prim_instance.combined_local_clip_rect,
@@ -705,24 +783,23 @@ impl AlphaBatchBuilder {
                                 glyph.uv_rect_address.as_int(),
                                 (subpx_dir as u32 as i32) << 16 |
                                 (color_mode as u32 as i32),
                             ));
                         }
                     },
                 );
             }
-            PrimitiveInstanceKind::LineDecoration { ref cache_handle, .. } => {
+            (
+                PrimitiveInstanceKind::LineDecoration { ref cache_handle, .. },
+                PrimitiveTemplateKind::LineDecoration { .. },
+            ) => {
                 // The GPU cache data is stored in the template and reused across
                 // frames and display lists.
 
-                let prim_data = &ctx
-                    .resources
-                    .prim_data_store[prim_instance.prim_data_handle];
-
                 let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
 
                 let (batch_kind, textures, prim_user_data, segment_user_data) = match cache_handle {
                     Some(cache_handle) => {
                         let rt_cache_entry = ctx
                             .resource_cache
                             .get_cached_render_task(cache_handle);
                         let cache_item = ctx
@@ -794,17 +871,20 @@ impl AlphaBatchBuilder {
 
                 self.batch_list.push_single_instance(
                     batch_key,
                     bounding_rect,
                     z_id,
                     PrimitiveInstanceData::from(instance),
                 );
             }
-            PrimitiveInstanceKind::Picture { pic_index } => {
+            (
+                PrimitiveInstanceKind::Picture { pic_index },
+                PrimitiveTemplateKind::Unused,
+            ) => {
                 let picture = &ctx.prim_store.pictures[pic_index.0];
                 let non_segmented_blend_mode = BlendMode::PremultipliedAlpha;
                 let prim_cache_address = gpu_cache.get_address(&picture.gpu_location);
 
                 let prim_header = PrimitiveHeader {
                     local_rect: picture.local_rect,
                     local_clip_rect: prim_instance.combined_local_clip_rect,
                     task_address,
@@ -818,16 +898,19 @@ impl AlphaBatchBuilder {
                     Picture3DContext::In { root_data: Some(ref list), .. } => {
                         for child in list {
                             let prim_instance = &picture.prim_list.prim_instances[child.anchor];
                             let pic_index = match prim_instance.kind {
                                 PrimitiveInstanceKind::Picture { pic_index } => pic_index,
                                 PrimitiveInstanceKind::LineDecoration { .. } |
                                 PrimitiveInstanceKind::TextRun { .. } |
                                 PrimitiveInstanceKind::LegacyPrimitive { .. } |
+                                PrimitiveInstanceKind::NormalBorder { .. } |
+                                PrimitiveInstanceKind::ImageBorder { .. } |
+                                PrimitiveInstanceKind::Rectangle { .. } |
                                 PrimitiveInstanceKind::Clear => {
                                     unreachable!();
                                 }
                             };
                             let pic = &ctx.prim_store.pictures[pic_index.0];
 
                             // Get clip task, if set, for the picture primitive.
                             let clip_task_address = get_clip_task_address(
@@ -1309,17 +1392,20 @@ impl AlphaBatchBuilder {
                             prim_headers,
                             transforms,
                             root_spatial_node_index,
                             z_generator,
                         );
                     }
                 }
             }
-            PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
+            (
+                PrimitiveInstanceKind::LegacyPrimitive { prim_index },
+                PrimitiveTemplateKind::Unused,
+            ) => {
                 let prim = &ctx.prim_store.primitives[prim_index.0];
 
                 // 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(),
@@ -1438,18 +1524,19 @@ impl AlphaBatchBuilder {
                                     ctx.prim_store,
                                 ) {
                                     let prim_header_index = prim_headers.push(&prim_header, z_id, params.prim_user_data);
                                     if prim_instance.is_chased() {
                                         println!("\t{:?} {:?}, task relative bounds {:?}",
                                             params.batch_kind, prim_header_index, bounding_rect);
                                     }
 
-                                    self.add_brush_to_batch(
-                                        brush,
+                                    self.add_segmented_prim_to_batch(
+                                        brush.segment_desc.as_ref().map(|desc| desc.segments.as_slice()),
+                                        brush.opacity,
                                         &params,
                                         specified_blend_mode,
                                         non_segmented_blend_mode,
                                         prim_header_index,
                                         clip_task_address,
                                         bounding_rect,
                                         transform_kind,
                                         render_tasks,
@@ -1458,16 +1545,152 @@ impl AlphaBatchBuilder {
                                         ctx,
                                     );
                                 }
                             }
                         }
                     }
                 }
             }
+            (
+                PrimitiveInstanceKind::ImageBorder { .. },
+                PrimitiveTemplateKind::ImageBorder { request, brush_segments, .. }
+            ) => {
+                let cache_item = resolve_image(
+                    *request,
+                    ctx.resource_cache,
+                    gpu_cache,
+                    deferred_resolves,
+                );
+                if cache_item.texture_id == TextureSource::Invalid {
+                    return;
+                }
+
+                let textures = BatchTextures::color(cache_item.texture_id);
+                let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
+                let specified_blend_mode = BlendMode::PremultipliedAlpha;
+                let non_segmented_blend_mode = if !prim_data.opacity.is_opaque ||
+                    prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                    transform_kind == TransformedRectKind::Complex
+                {
+                    specified_blend_mode
+                } else {
+                    BlendMode::None
+                };
+
+                let prim_header = PrimitiveHeader {
+                    local_rect: prim_data.prim_rect,
+                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    task_address,
+                    specific_prim_address: prim_cache_address,
+                    clip_task_address,
+                    transform_id,
+                };
+
+                let batch_params = BrushBatchParameters::shared(
+                    BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
+                    textures,
+                    [
+                        ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
+                        RasterizationSpace::Local as i32,
+                        get_shader_opacity(1.0),
+                    ],
+                    cache_item.uv_rect_handle.as_int(gpu_cache),
+                );
+
+                let prim_header_index = prim_headers.push(
+                    &prim_header,
+                    z_id,
+                    batch_params.prim_user_data,
+                );
+
+                self.add_segmented_prim_to_batch(
+                    Some(brush_segments.as_slice()),
+                    prim_data.opacity,
+                    &batch_params,
+                    specified_blend_mode,
+                    non_segmented_blend_mode,
+                    prim_header_index,
+                    clip_task_address,
+                    bounding_rect,
+                    transform_kind,
+                    render_tasks,
+                    z_id,
+                    prim_instance.clip_task_index,
+                    ctx,
+                );
+            }
+            (
+                PrimitiveInstanceKind::Rectangle { segment_instance_index, opacity_binding_index, .. },
+                PrimitiveTemplateKind::Rectangle { .. }
+            ) => {
+                let specified_blend_mode = BlendMode::PremultipliedAlpha;
+                let opacity_binding = ctx.prim_store.get_opacity_binding(*opacity_binding_index);
+
+                let opacity = PrimitiveOpacity::from_alpha(opacity_binding);
+                let opacity = opacity.combine(prim_data.opacity);
+
+                let non_segmented_blend_mode = if !opacity.is_opaque ||
+                    prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                    transform_kind == TransformedRectKind::Complex
+                {
+                    specified_blend_mode
+                } else {
+                    BlendMode::None
+                };
+
+                let batch_params = BrushBatchParameters::shared(
+                    BrushBatchKind::Solid,
+                    BatchTextures::no_texture(),
+                    [get_shader_opacity(opacity_binding), 0, 0],
+                    0,
+                );
+
+                let (prim_cache_address, segments) = if *segment_instance_index == SegmentInstanceIndex::UNUSED {
+                    (gpu_cache.get_address(&prim_data.gpu_cache_handle), None)
+                } else {
+                    let segment_instance = &ctx.scratch.segment_instances[*segment_instance_index];
+                    let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
+                    (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
+                };
+
+                let prim_header = PrimitiveHeader {
+                    local_rect: prim_data.prim_rect,
+                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    task_address,
+                    specific_prim_address: prim_cache_address,
+                    clip_task_address,
+                    transform_id,
+                };
+
+                let prim_header_index = prim_headers.push(
+                    &prim_header,
+                    z_id,
+                    batch_params.prim_user_data,
+                );
+
+                self.add_segmented_prim_to_batch(
+                    segments,
+                    opacity,
+                    &batch_params,
+                    specified_blend_mode,
+                    non_segmented_blend_mode,
+                    prim_header_index,
+                    clip_task_address,
+                    bounding_rect,
+                    transform_kind,
+                    render_tasks,
+                    z_id,
+                    prim_instance.clip_task_index,
+                    ctx,
+                );
+            }
+            _ => {
+                unreachable!();
+            }
         }
     }
 
     fn add_image_tile_to_batch(
         &mut self,
         batch_kind: BrushBatchKind,
         blend_mode: BlendMode,
         textures: BatchTextures,
@@ -1556,92 +1779,93 @@ impl AlphaBatchBuilder {
             batch_key,
             bounding_rect,
             z_id,
             instance,
         );
     }
 
     /// Add any segment(s) from a brush to batches.
-    fn add_brush_to_batch(
+    fn add_segmented_prim_to_batch(
         &mut self,
-        brush: &BrushPrimitive,
+        brush_segments: Option<&[BrushSegment]>,
+        prim_opacity: PrimitiveOpacity,
         params: &BrushBatchParameters,
         alpha_blend_mode: BlendMode,
         non_segmented_blend_mode: BlendMode,
         prim_header_index: PrimitiveHeaderIndex,
         clip_task_address: RenderTaskAddress,
         bounding_rect: &PictureRect,
         transform_kind: TransformedRectKind,
         render_tasks: &RenderTaskTree,
         z_id: ZBufferId,
         clip_task_index: ClipTaskIndex,
         ctx: &RenderTargetContext,
     ) {
-        match (&brush.segment_desc, &params.segment_data) {
-            (Some(ref segment_desc), SegmentDataKind::Instanced(ref segment_data)) => {
+        match (brush_segments, &params.segment_data) {
+            (Some(ref brush_segments), 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
+                debug_assert_eq!(brush_segments.len(), segment_data.len());
+                for (segment_index, (segment, segment_data)) in brush_segments
                     .iter()
                     .zip(segment_data.iter())
                     .enumerate()
                 {
                     self.add_segment_to_batch(
                         segment,
                         segment_data,
                         segment_index as i32,
                         params.batch_kind,
                         prim_header_index,
                         alpha_blend_mode,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
                         z_id,
-                        brush.opacity,
+                        prim_opacity,
                         clip_task_index,
                         ctx,
                     );
                 }
             }
-            (Some(ref segment_desc), SegmentDataKind::Shared(ref segment_data)) => {
+            (Some(ref brush_segments), 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
+                for (segment_index, segment) in brush_segments
                     .iter()
                     .enumerate()
                 {
                     self.add_segment_to_batch(
                         segment,
                         segment_data,
                         segment_index as i32,
                         params.batch_kind,
                         prim_header_index,
                         alpha_blend_mode,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
                         z_id,
-                        brush.opacity,
+                        prim_opacity,
                         clip_task_index,
                         ctx,
                     );
                 }
             }
             (None, SegmentDataKind::Shared(ref segment_data)) => {
                 // No segments, and thus no per-segment instance data.
                 // Note: the blend mode already takes opacity into account
                 let batch_key = BatchKey {
                     blend_mode: non_segmented_blend_mode,
                     kind: BatchKind::Brush(params.batch_kind),
                     textures: segment_data.textures,
                 };
                 let instance = PrimitiveInstanceData::from(BrushInstance {
-                    segment_index: 0,
+                    segment_index: INVALID_SEGMENT_INDEX,
                     edge_flags: EdgeAaSegmentMask::all(),
                     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,
@@ -1836,83 +2060,16 @@ impl BrushPrimitive {
                             ShaderColorMode::Image as i32 | ((alpha_type as i32) << 16),
                             RasterizationSpace::Local as i32,
                             get_shader_opacity(opacity_binding),
                         ],
                         cache_item.uv_rect_handle.as_int(gpu_cache),
                     ))
                 }
             }
-            BrushKind::Border { ref source, .. } => {
-                match *source {
-                    BorderSource::Image(request) => {
-                        let cache_item = resolve_image(
-                            request,
-                            resource_cache,
-                            gpu_cache,
-                            deferred_resolves,
-                        );
-
-                        if cache_item.texture_id == TextureSource::Invalid {
-                            return None;
-                        }
-
-                        let textures = BatchTextures::color(cache_item.texture_id);
-
-                        Some(BrushBatchParameters::shared(
-                            BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
-                            textures,
-                            [
-                                ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
-                                RasterizationSpace::Local as i32,
-                                get_shader_opacity(1.0),
-                            ],
-                            cache_item.uv_rect_handle.as_int(gpu_cache),
-                        ))
-                    }
-                    BorderSource::Border { ref segments, .. } => {
-                        let mut segment_data = SmallVec::new();
-
-                        // Collect the segment instance data from each render
-                        // task for each valid edge / corner of the border.
-
-                        for segment in segments {
-                            let rt_cache_entry = resource_cache
-                                .get_cached_render_task(segment.handle.as_ref().unwrap());
-                            let cache_item = resource_cache
-                                .get_texture_cache_item(&rt_cache_entry.handle);
-                            segment_data.push(
-                                SegmentInstanceData {
-                                    textures: BatchTextures::color(cache_item.texture_id),
-                                    user_data: cache_item.uv_rect_handle.as_int(gpu_cache),
-                                }
-                            );
-                        }
-
-                        Some(BrushBatchParameters::instanced(
-                            BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
-                            [
-                                ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
-                                RasterizationSpace::Local as i32,
-                                get_shader_opacity(1.0),
-                            ],
-                            segment_data,
-                        ))
-                    }
-                }
-            }
-            BrushKind::Solid { opacity_binding_index, .. } => {
-                let opacity_binding = prim_store.get_opacity_binding(opacity_binding_index);
-                Some(BrushBatchParameters::shared(
-                    BrushBatchKind::Solid,
-                    BatchTextures::no_texture(),
-                    [get_shader_opacity(opacity_binding), 0, 0],
-                    0,
-                ))
-            }
             BrushKind::RadialGradient { ref stops_handle, .. } => {
                 Some(BrushBatchParameters::shared(
                     BrushBatchKind::RadialGradient,
                     BatchTextures::no_texture(),
                     [
                         stops_handle.as_int(gpu_cache),
                         0,
                         0,
@@ -2001,21 +2158,19 @@ impl PrimitiveInstance {
             PrimitiveDetails::Brush(ref brush) => {
                 match brush.kind {
                     BrushKind::Image { alpha_type, .. } => {
                         match alpha_type {
                             AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
                             AlphaType::Alpha => BlendMode::Alpha,
                         }
                     }
-                    BrushKind::Solid { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
-                    BrushKind::LinearGradient { .. } |
-                    BrushKind::Border { .. } => {
+                    BrushKind::LinearGradient { .. } => {
                         BlendMode::PremultipliedAlpha
                     }
                 }
             }
         }
     }
 
     pub fn is_cacheable(
--- a/gfx/wr/webrender/src/border.rs
+++ b/gfx/wr/webrender/src/border.rs
@@ -1,23 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, BorderSide, BorderStyle, ColorF, ColorU, DeviceRect, DeviceSize};
 use api::{LayoutSideOffsets, LayoutSizeAu, LayoutPrimitiveInfo, LayoutToDeviceScale};
 use api::{DeviceVector2D, DevicePoint, LayoutRect, LayoutSize, NormalBorder, DeviceIntSize};
-use api::{AuHelpers, NinePatchBorder, LayoutPoint, RepeatMode, TexelRect};
+use api::{AuHelpers, LayoutPoint, RepeatMode, TexelRect};
 use ellipse::Ellipse;
-use euclid::vec2;
+use euclid::{SideOffsets2D, vec2};
 use display_list_flattener::DisplayListFlattener;
 use gpu_types::{BorderInstance, BorderSegment, BrushFlags};
-use prim_store::{BorderSegmentInfo, BrushKind, BrushPrimitive, BrushSegment, BrushSegmentVec};
-use prim_store::{EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain, BrushSegmentDescriptor};
-use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind};
+use prim_store::{BorderSegmentInfo, BrushSegment};
+use prim_store::{EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
 use util::{lerp, RectHelpers};
 
 // Using 2048 as the maximum radius in device space before which we
 // start stretching is up for debate.
 // the value must be chosen so that the corners will not use an
 // unreasonable amount of memory but should allow crisp corners in the
 // common cases.
 
@@ -88,35 +87,74 @@ impl From<BorderSide> for BorderSideAu {
     fn from(side: BorderSide) -> Self {
         BorderSideAu {
             color: side.color.into(),
             style: side.style,
         }
     }
 }
 
+impl From<BorderSideAu> for BorderSide {
+    fn from(side: BorderSideAu) -> Self {
+        BorderSide {
+            color: side.color.into(),
+            style: side.style,
+        }
+    }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Hash, Eq, PartialEq)]
+pub struct NormalBorderAu {
+    pub left: BorderSideAu,
+    pub right: BorderSideAu,
+    pub top: BorderSideAu,
+    pub bottom: BorderSideAu,
+    pub radius: BorderRadiusAu,
+    /// Whether to apply anti-aliasing on the border corners.
+    ///
+    /// Note that for this to be `false` and work, this requires the borders to
+    /// be solid, and no border-radius.
+    pub do_aa: bool,
+}
+
+impl From<NormalBorder> for NormalBorderAu {
+    fn from(border: NormalBorder) -> Self {
+        NormalBorderAu {
+            left: border.left.into(),
+            right: border.right.into(),
+            top: border.top.into(),
+            bottom: border.bottom.into(),
+            radius: border.radius.into(),
+            do_aa: border.do_aa,
+        }
+    }
+}
+
+impl From<NormalBorderAu> for NormalBorder {
+    fn from(border: NormalBorderAu) -> Self {
+        NormalBorder {
+            left: border.left.into(),
+            right: border.right.into(),
+            top: border.top.into(),
+            bottom: border.bottom.into(),
+            radius: border.radius.into(),
+            do_aa: border.do_aa,
+        }
+    }
+}
+
 /// Cache key that uniquely identifies a border
-/// edge in the render task cache.
+/// segment in the render task cache.
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct BorderEdgeCacheKey {
-    pub side: BorderSideAu,
+pub struct BorderSegmentCacheKey {
     pub size: LayoutSizeAu,
-    pub do_aa: bool,
-    pub segment: BorderSegment,
-}
-
-/// Cache key that uniquely identifies a border
-/// corner in the render task cache.
-#[derive(Clone, Debug, Hash, PartialEq, Eq)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct BorderCornerCacheKey {
-    pub widths: LayoutSizeAu,
     pub radius: LayoutSizeAu,
     pub side0: BorderSideAu,
     pub side1: BorderSideAu,
     pub segment: BorderSegment,
     pub do_aa: bool,
 }
 
 pub fn ensure_no_corner_overlap(
@@ -170,27 +208,24 @@ impl<'a> DisplayListFlattener<'a> {
         info: &LayoutPrimitiveInfo,
         border: &NormalBorder,
         widths: LayoutSideOffsets,
         clip_and_scroll: ScrollNodeAndClipChain,
     ) {
         let mut border = *border;
         ensure_no_corner_overlap(&mut border.radius, &info.rect);
 
-        let prim = create_normal_border_prim(
-            &info.rect,
-            border,
-            widths,
-        );
-
         self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
-            PrimitiveContainer::Brush(prim),
+            PrimitiveContainer::NormalBorder {
+                border,
+                widths,
+            },
         );
     }
 }
 
 pub trait BorderSideHelpers {
     fn border_color(&self, is_inner_border: bool) -> ColorF;
 }
 
@@ -596,22 +631,22 @@ fn get_edge_info(
         _ => {
             EdgeInfo::new(0.0, avail_size, 8.0)
         }
     }
 }
 
 /// Create the set of border segments and render task
 /// cache keys for a given CSS border.
-fn create_border_segments(
+pub fn create_border_segments(
     rect: &LayoutRect,
     border: &NormalBorder,
     widths: &LayoutSideOffsets,
     border_segments: &mut Vec<BorderSegmentInfo>,
-    brush_segments: &mut BrushSegmentVec,
+    brush_segments: &mut Vec<BrushSegment>,
 ) {
     let local_size_tl = LayoutSize::new(
         border.radius.top_left.width.max(widths.left),
         border.radius.top_left.height.max(widths.top),
     );
     let local_size_tr = LayoutSize::new(
         border.radius.top_right.width.max(widths.right),
         border.radius.top_right.height.max(widths.top),
@@ -931,17 +966,17 @@ fn add_segment(
 fn add_corner_segment(
     image_rect: LayoutRect,
     side0: BorderSide,
     side1: BorderSide,
     widths: LayoutSize,
     radius: LayoutSize,
     segment: BorderSegment,
     edge_flags: EdgeAaSegmentMask,
-    brush_segments: &mut BrushSegmentVec,
+    brush_segments: &mut Vec<BrushSegment>,
     border_segments: &mut Vec<BorderSegmentInfo>,
     do_aa: bool,
 ) {
     if side0.color.a <= 0.0 && side1.color.a <= 0.0 {
         return;
     }
 
     if widths.width <= 0.0 && widths.height <= 0.0 {
@@ -962,44 +997,38 @@ fn add_corner_segment(
             /* may_need_clip_mask = */ true,
             edge_flags,
             [0.0; 4],
             BrushFlags::SEGMENT_RELATIVE,
         )
     );
 
     border_segments.push(BorderSegmentInfo {
-        handle: None,
         local_task_size: image_rect.size,
-        cache_key: RenderTaskCacheKey {
-            size: DeviceIntSize::zero(),
-            kind: RenderTaskCacheKeyKind::BorderCorner(
-                BorderCornerCacheKey {
-                    do_aa,
-                    side0: side0.into(),
-                    side1: side1.into(),
-                    segment,
-                    radius: radius.to_au(),
-                    widths: widths.to_au(),
-                }
-            ),
+        cache_key: BorderSegmentCacheKey {
+            do_aa,
+            side0: side0.into(),
+            side1: side1.into(),
+            segment,
+            radius: radius.to_au(),
+            size: widths.to_au(),
         },
     });
 }
 
 /// Add an edge segment (if valid) to the list of
 /// border segments for this primitive.
 fn add_edge_segment(
     image_rect: LayoutRect,
     edge_info: &EdgeInfo,
     side: BorderSide,
     width: f32,
     segment: BorderSegment,
     edge_flags: EdgeAaSegmentMask,
-    brush_segments: &mut BrushSegmentVec,
+    brush_segments: &mut Vec<BrushSegment>,
     border_segments: &mut Vec<BorderSegmentInfo>,
     do_aa: bool,
 ) {
     if side.color.a <= 0.0 {
         return;
     }
 
     if side.style.is_hidden() {
@@ -1028,54 +1057,39 @@ fn add_edge_segment(
             /* may_need_clip_mask = */ true,
             edge_flags,
             [0.0, 0.0, size.width, size.height],
             BrushFlags::SEGMENT_RELATIVE | brush_flags,
         )
     );
 
     border_segments.push(BorderSegmentInfo {
-        handle: None,
         local_task_size: size,
-        cache_key: RenderTaskCacheKey {
-            size: DeviceIntSize::zero(),
-            kind: RenderTaskCacheKeyKind::BorderEdge(
-                BorderEdgeCacheKey {
-                    do_aa,
-                    side: side.into(),
-                    size: size.to_au(),
-                    segment,
-                },
-            ),
+        cache_key: BorderSegmentCacheKey {
+            do_aa,
+            side0: side.into(),
+            side1: side.into(),
+            radius: LayoutSizeAu::zero(),
+            size: size.to_au(),
+            segment,
         },
     });
 }
 
 /// Build the set of border instances needed to draw a border
 /// segment into the render task cache.
 pub fn build_border_instances(
-    cache_key: &RenderTaskCacheKey,
+    cache_key: &BorderSegmentCacheKey,
+    cache_size: DeviceIntSize,
     border: &NormalBorder,
     scale: LayoutToDeviceScale,
 ) -> Vec<BorderInstance> {
     let mut instances = Vec::new();
 
-    let (segment, widths, radius) = match cache_key.kind {
-        RenderTaskCacheKeyKind::BorderEdge(ref key) => {
-            (key.segment, LayoutSize::from_au(key.size), LayoutSize::zero())
-        }
-        RenderTaskCacheKeyKind::BorderCorner(ref key) => {
-            (key.segment, LayoutSize::from_au(key.widths), LayoutSize::from_au(key.radius))
-        }
-        _ => {
-            unreachable!();
-        }
-    };
-
-    let (side0, side1, flip0, flip1) = match segment {
+    let (side0, side1, flip0, flip1) = match cache_key.segment {
         BorderSegment::Left => (&border.left, &border.left, false, false),
         BorderSegment::Top => (&border.top, &border.top, false, false),
         BorderSegment::Right => (&border.right, &border.right, true, true),
         BorderSegment::Bottom => (&border.bottom, &border.bottom, true, true),
         BorderSegment::TopLeft => (&border.left, &border.top, false, false),
         BorderSegment::TopRight => (&border.top, &border.right, false, true),
         BorderSegment::BottomRight => (&border.right, &border.bottom, true, true),
         BorderSegment::BottomLeft => (&border.bottom, &border.left, true, false),
@@ -1090,89 +1104,67 @@ pub fn build_border_instances(
         side0.style
     } else {
         side1.style
     };
 
     let color0 = side0.border_color(flip0);
     let color1 = side1.border_color(flip1);
 
-    let widths = (widths * scale).ceil();
-    let radius = (radius * scale).ceil();
+    let widths = (LayoutSize::from_au(cache_key.size) * scale).ceil();
+    let radius = (LayoutSize::from_au(cache_key.radius) * scale).ceil();
 
     add_segment(
-        DeviceRect::new(DevicePoint::zero(), cache_key.size.to_f32()),
+        DeviceRect::new(DevicePoint::zero(), cache_size.to_f32()),
         style0,
         style1,
         color0,
         color1,
-        segment,
+        cache_key.segment,
         &mut instances,
         widths,
         radius,
         border.do_aa,
     );
 
     instances
 }
 
-pub fn create_normal_border_prim(
-    local_rect: &LayoutRect,
-    border: NormalBorder,
-    widths: LayoutSideOffsets,
-) -> BrushPrimitive {
-    let mut brush_segments = BrushSegmentVec::new();
-    let mut border_segments = Vec::new();
-
-    create_border_segments(
-        local_rect,
-        &border,
-        &widths,
-        &mut border_segments,
-        &mut brush_segments,
-    );
-
-    BrushPrimitive::new(
-        BrushKind::new_border(
-            border,
-            widths,
-            border_segments,
-        ),
-        Some(BrushSegmentDescriptor {
-            segments: brush_segments,
-        }),
-    )
-}
-
 pub fn create_nine_patch_segments(
     rect: &LayoutRect,
     widths: &LayoutSideOffsets,
-    border: &NinePatchBorder,
-) -> BrushSegmentDescriptor {
+    width: i32,
+    height: i32,
+    slice: SideOffsets2D<i32>,
+    fill: bool,
+    repeat_horizontal: RepeatMode,
+    repeat_vertical: RepeatMode,
+    outset: SideOffsets2D<f32>,
+) -> Vec<BrushSegment> {
     // Calculate the modified rect as specific by border-image-outset
     let origin = LayoutPoint::new(
-        rect.origin.x - border.outset.left,
-        rect.origin.y - border.outset.top,
+        rect.origin.x - outset.left,
+        rect.origin.y - outset.top,
     );
     let size = LayoutSize::new(
-        rect.size.width + border.outset.left + border.outset.right,
-        rect.size.height + border.outset.top + border.outset.bottom,
+        rect.size.width + outset.left + outset.right,
+        rect.size.height + outset.top + outset.bottom,
     );
     let rect = LayoutRect::new(origin, size);
 
     // Calculate the local texel coords of the slices.
     let px0 = 0.0;
-    let px1 = border.slice.left as f32;
-    let px2 = border.width as f32 - border.slice.right as f32;
-    let px3 = border.width as f32;
+    let px1 = slice.left as f32;
+    let px2 = width as f32 - slice.right as f32;
+    let px3 = width as f32;
 
     let py0 = 0.0;
-    let py1 = border.slice.top as f32;
-    let py2 = border.height as f32 - border.slice.bottom as f32;
-    let py3 = border.height as f32;
+    let py1 = slice.top as f32;
+    let py2 = height as f32 - slice.bottom as f32;
+    let py3 = height as f32;
 
     let tl_outer = LayoutPoint::new(rect.origin.x, rect.origin.y);
     let tl_inner = tl_outer + vec2(widths.left, widths.top);
 
     let tr_outer = LayoutPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
     let tr_inner = tr_outer + vec2(-widths.right, widths.top);
 
     let bl_outer = LayoutPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
@@ -1180,17 +1172,17 @@ pub fn create_nine_patch_segments(
 
     let br_outer = LayoutPoint::new(
         rect.origin.x + rect.size.width,
         rect.origin.y + rect.size.height,
     );
     let br_inner = br_outer - vec2(widths.right, widths.bottom);
 
     fn add_segment(
-        segments: &mut BrushSegmentVec,
+        segments: &mut Vec<BrushSegment>,
         rect: LayoutRect,
         uv_rect: TexelRect,
         repeat_horizontal: RepeatMode,
         repeat_vertical: RepeatMode
     ) {
         if uv_rect.uv1.x > uv_rect.uv0.x &&
            uv_rect.uv1.y > uv_rect.uv0.y {
 
@@ -1221,17 +1213,17 @@ pub fn create_nine_patch_segments(
                 brush_flags,
             );
 
             segments.push(segment);
         }
     }
 
     // Build the list of image segments
-    let mut segments = BrushSegmentVec::new();
+    let mut segments = Vec::new();
 
     // Top left
     add_segment(
         &mut segments,
         LayoutRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
         TexelRect::new(px0, py0, px1, py1),
         RepeatMode::Stretch,
         RepeatMode::Stretch
@@ -1257,56 +1249,55 @@ pub fn create_nine_patch_segments(
         &mut segments,
         LayoutRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
         TexelRect::new(px0, py2, px1, py3),
         RepeatMode::Stretch,
         RepeatMode::Stretch
     );
 
     // Center
-    if border.fill {
+    if fill {
         add_segment(
             &mut segments,
             LayoutRect::from_floats(tl_inner.x, tl_inner.y, tr_inner.x, bl_inner.y),
             TexelRect::new(px1, py1, px2, py2),
-            border.repeat_horizontal,
-            border.repeat_vertical
+            repeat_horizontal,
+            repeat_vertical
         );
     }
 
     // Add edge segments.
 
     // Top
     add_segment(
         &mut segments,
         LayoutRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
         TexelRect::new(px1, py0, px2, py1),
-        border.repeat_horizontal,
+        repeat_horizontal,
         RepeatMode::Stretch,
     );
     // Bottom
     add_segment(
         &mut segments,
         LayoutRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
         TexelRect::new(px1, py2, px2, py3),
-        border.repeat_horizontal,
+        repeat_horizontal,
         RepeatMode::Stretch,
     );
     // Left
     add_segment(
         &mut segments,
         LayoutRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
         TexelRect::new(px0, py1, px1, py2),
         RepeatMode::Stretch,
-        border.repeat_vertical,
+        repeat_vertical,
     );
     // Right
     add_segment(
         &mut segments,
         LayoutRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
         TexelRect::new(px2, py1, px3, py2),
         RepeatMode::Stretch,
-        border.repeat_vertical,
+        repeat_vertical,
     );
-    BrushSegmentDescriptor {
-        segments,
-    }
+
+    segments
 }
--- a/gfx/wr/webrender/src/box_shadow.rs
+++ b/gfx/wr/webrender/src/box_shadow.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, DeviceIntSize, LayoutPrimitiveInfo};
 use api::{LayoutRect, LayoutSize, LayoutVector2D, MAX_BLUR_RADIUS};
 use clip::ClipItemKey;
 use display_list_flattener::DisplayListFlattener;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BoxShadowStretchMode;
-use prim_store::{BrushKind, BrushPrimitive, PrimitiveContainer};
+use prim_store::PrimitiveContainer;
 use prim_store::ScrollNodeAndClipChain;
 use render_task::RenderTaskCacheEntryHandle;
 use util::RectHelpers;
 
 #[derive(Debug, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BoxShadowClipSource {
@@ -144,17 +144,19 @@ impl<'a> DisplayListFlattener<'a> {
                 clip_radius,
                 ClipMode::Clip,
             ));
 
             self.add_primitive(
                 clip_and_scroll,
                 &LayoutPrimitiveInfo::with_clip_rect(final_prim_rect, prim_info.clip_rect),
                 clips,
-                PrimitiveContainer::Brush(BrushPrimitive::new(BrushKind::new_solid(*color), None)),
+                PrimitiveContainer::Rectangle {
+                    color: *color,
+                },
             );
         } else {
             // Normal path for box-shadows with a valid blur radius.
             let blur_offset = BLUR_SAMPLE_SCALE * blur_radius;
             let mut extra_clips = vec![];
 
             // Add a normal clip mask to clip out the contents
             // of the surrounding primitive.
@@ -165,17 +167,19 @@ impl<'a> DisplayListFlattener<'a> {
             ));
 
             // Get the local rect of where the shadow will be drawn,
             // expanded to include room for the blurred region.
             let dest_rect = shadow_rect.inflate(blur_offset, blur_offset);
 
             // Draw the box-shadow as a solid rect, using a box-shadow
             // clip mask item.
-            let prim = BrushPrimitive::new(BrushKind::new_solid(*color), None);
+            let prim = PrimitiveContainer::Rectangle {
+                color: *color,
+            };
 
             // Create the box-shadow clip item.
             let shadow_clip_source = ClipItemKey::box_shadow(
                 shadow_rect,
                 shadow_radius,
                 dest_rect,
                 blur_radius,
                 clip_mode,
@@ -216,17 +220,17 @@ impl<'a> DisplayListFlattener<'a> {
                     prim_info.clone()
                 }
             };
 
             self.add_primitive(
                 clip_and_scroll,
                 &prim_info,
                 extra_clips,
-                PrimitiveContainer::Brush(prim),
+                prim,
             );
         }
     }
 }
 
 fn adjust_border_radius_for_box_shadow(radius: BorderRadius, spread_amount: f32) -> BorderRadius {
     BorderRadius {
         top_left: adjust_corner_for_box_shadow(radius.top_left, spread_amount),
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -20,22 +20,23 @@ use frame_builder::{ChasePrimitive, Fram
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PrimitiveList};
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveInstance, PrimitiveDataInterner, PrimitiveKeyKind};
 use prim_store::{ImageSource, PrimitiveOpacity, PrimitiveKey, PrimitiveSceneData, PrimitiveInstanceKind};
-use prim_store::{BorderSource, PrimitiveContainer, PrimitiveDataHandle, PrimitiveStore, PrimitiveStoreStats};
+use prim_store::{PrimitiveContainer, PrimitiveDataHandle, PrimitiveStore, PrimitiveStoreStats, BrushSegmentDescriptor};
 use prim_store::{ScrollNodeAndClipChain, PictureIndex, register_prim_chase_id, OpacityBindingIndex};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::DocumentResources;
+use smallvec::SmallVec;
 use spatial_node::{StickyFrameInfo};
 use std::{f32, mem};
 use std::collections::vec_deque::VecDeque;
 use tiling::{CompositeOps};
 use util::{MaxRect};
 
 #[derive(Debug, Copy, Clone)]
 struct ClipNode {
@@ -147,19 +148,16 @@ pub struct DisplayListFlattener<'a> {
     /// The configuration to use for the FrameBuilder. We consult this in
     /// order to determine the default font.
     pub config: FrameBuilderConfig,
 
     /// Reference to the document resources, which contains
     /// shared (interned) data between display lists.
     resources: &'a mut DocumentResources,
 
-    /// The estimated count of primtives we expect to encounter during flattening.
-    prim_count_estimate: usize,
-
     /// The root picture index for this flattener. This is the picture
     /// to start the culling phase from.
     pub root_pic_index: PictureIndex,
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn create_frame_builder(
         scene: &Scene,
@@ -189,17 +187,16 @@ impl<'a> DisplayListFlattener<'a> {
             id_to_index_mapper: ClipIdToIndexMapper::default(),
             hit_testing_runs: Vec::new(),
             pending_shadow_items: VecDeque::new(),
             sc_stack: Vec::new(),
             pipeline_clip_chain_stack: vec![ClipChainId::NONE],
             prim_store: PrimitiveStore::new(&prim_store_stats),
             clip_store: ClipStore::new(),
             resources,
-            prim_count_estimate: 0,
             root_pic_index: PictureIndex(0),
         };
 
         flattener.push_root(
             root_pipeline_id,
             &root_pipeline.viewport_size,
             &root_pipeline.content_size,
         );
@@ -281,19 +278,16 @@ impl<'a> DisplayListFlattener<'a> {
                         reference_frame_info,
                         &info,
                         bg_color,
                     );
                 }
             }
         }
 
-        self.prim_count_estimate += pipeline.display_list.prim_count_estimate();
-        self.prim_store.primitives.reserve(self.prim_count_estimate);
-
         self.flatten_items(&mut pipeline.display_list.iter(), pipeline_id, LayoutVector2D::zero());
 
         self.pop_stacking_context();
     }
 
     fn flatten_items(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
@@ -841,17 +835,19 @@ impl<'a> DisplayListFlattener<'a> {
         clip_chain_id: ClipChainId,
         spatial_node_index: SpatialNodeIndex,
         container: PrimitiveContainer,
     ) -> PrimitiveInstance {
         // Build a primitive key, and optionally an old
         // style PrimitiveDetails structure from the
         // source primitive container.
         let mut info = info.clone();
-        let (prim_key_kind, prim_details) = container.build(&mut info);
+        let (prim_key_kind, prim_details) = container.build(
+            &mut info,
+        );
 
         let prim_key = PrimitiveKey::new(
             info.is_backface_visible,
             info.rect,
             info.clip_rect,
             prim_key_kind,
         );
 
@@ -1556,17 +1552,16 @@ impl<'a> DisplayListFlattener<'a> {
 
                             // Construct and add a primitive for the given shadow.
                             let shadow_prim_instance = self.create_primitive(
                                 &info,
                                 pending_primitive.clip_and_scroll.clip_chain_id,
                                 pending_primitive.clip_and_scroll.spatial_node_index,
                                 pending_primitive.container.create_shadow(
                                     &pending_shadow.shadow,
-                                    &info.rect,
                                 ),
                             );
 
                             // Add the new primitive to the shadow picture.
                             prims.push(shadow_prim_instance);
                         }
                     }
 
@@ -1681,26 +1676,23 @@ impl<'a> DisplayListFlattener<'a> {
     ) {
         if color.a == 0.0 {
             // Don't add transparent rectangles to the draw list, but do consider them for hit
             // testing. This allows specifying invisible hit testing areas.
             self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
             return;
         }
 
-        let prim = BrushPrimitive::new(
-            BrushKind::new_solid(color),
-            None,
-        );
-
         self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
-            PrimitiveContainer::Brush(prim),
+            PrimitiveContainer::Rectangle {
+                color,
+            },
         );
     }
 
     pub fn add_clear_rectangle(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
     ) {
@@ -1741,66 +1733,110 @@ impl<'a> DisplayListFlattener<'a> {
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         border_item: &BorderDisplayItem,
         gradient_stops: ItemRange<GradientStop>,
         pipeline_id: PipelineId,
     ) {
         match border_item.details {
             BorderDetails::NinePatch(ref border) => {
-                let descriptor = create_nine_patch_segments(
-                    &info.rect,
-                    &border_item.widths,
-                    border,
-                );
-
-                let brush_kind = match border.source {
+                let prim = match border.source {
                     NinePatchBorderSource::Image(image_key) => {
-                        BrushKind::Border {
-                            source: BorderSource::Image(ImageRequest {
+                        PrimitiveContainer::ImageBorder {
+                            request: ImageRequest {
                                 key: image_key,
                                 rendering: ImageRendering::Auto,
                                 tile: None,
-                            })
+                            },
+                            widths: border_item.widths,
+                            width: border.width,
+                            height: border.height,
+                            slice: border.slice,
+                            fill: border.fill,
+                            repeat_horizontal: border.repeat_horizontal,
+                            repeat_vertical: border.repeat_vertical,
+                            outset: border.outset,
                         }
                     }
                     NinePatchBorderSource::Gradient(gradient) => {
                         match self.create_brush_kind_for_gradient(
                             &info,
                             gradient.start_point,
                             gradient.end_point,
                             gradient_stops,
                             gradient.extend_mode,
                             LayoutSize::new(border.height as f32, border.width as f32),
                             LayoutSize::zero(),
                             pipeline_id,
                         ) {
-                            Some(brush_kind) => brush_kind,
+                            Some(brush_kind) => {
+                                let segments = create_nine_patch_segments(
+                                    &info.rect,
+                                    &border_item.widths,
+                                    border.width,
+                                    border.height,
+                                    border.slice,
+                                    border.fill,
+                                    border.repeat_horizontal,
+                                    border.repeat_vertical,
+                                    border.outset,
+                                );
+
+                                let descriptor = BrushSegmentDescriptor {
+                                    segments: SmallVec::from_vec(segments),
+                                };
+
+                                PrimitiveContainer::Brush(
+                                    BrushPrimitive::new(brush_kind, Some(descriptor))
+                                )
+                            }
                             None => return,
                         }
                     }
                     NinePatchBorderSource::RadialGradient(gradient) => {
-                        self.create_brush_kind_for_radial_gradient(
+                        let brush_kind = self.create_brush_kind_for_radial_gradient(
                             &info,
                             gradient.center,
                             gradient.start_offset * gradient.radius.width,
                             gradient.end_offset * gradient.radius.width,
                             gradient.radius.width / gradient.radius.height,
                             gradient_stops,
                             gradient.extend_mode,
                             LayoutSize::new(border.height as f32, border.width as f32),
                             LayoutSize::zero(),
+                        );
+
+                        let segments = create_nine_patch_segments(
+                            &info.rect,
+                            &border_item.widths,
+                            border.width,
+                            border.height,
+                            border.slice,
+                            border.fill,
+                            border.repeat_horizontal,
+                            border.repeat_vertical,
+                            border.outset,
+                        );
+
+                        let descriptor = BrushSegmentDescriptor {
+                            segments: SmallVec::from_vec(segments),
+                        };
+
+                        PrimitiveContainer::Brush(
+                            BrushPrimitive::new(brush_kind, Some(descriptor))
                         )
                     }
                 };
 
-                let prim = PrimitiveContainer::Brush(
-                    BrushPrimitive::new(brush_kind, Some(descriptor))
+                self.add_primitive(
+                    clip_and_scroll,
+                    info,
+                    Vec::new(),
+                    prim,
                 );
-                self.add_primitive(clip_and_scroll, info, Vec::new(), prim);
             }
             BorderDetails::Normal(ref border) => {
                 self.add_normal_border(
                     info,
                     border,
                     border_item.widths,
                     clip_and_scroll,
                 );
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -78,20 +78,18 @@ pub struct FrameBuildingContext<'a> {
 pub struct FrameBuildingState<'a> {
     pub render_tasks: &'a mut RenderTaskTree,
     pub profile_counters: &'a mut FrameProfileCounters,
     pub clip_store: &'a mut ClipStore,
     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,
     pub surfaces: &'a mut Vec<SurfaceInfo>,
-    pub scratch: &'a mut PrimitiveScratchBuffer,
 }
 
 /// 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,
@@ -249,20 +247,18 @@ impl FrameBuilder {
         let mut frame_state = FrameBuildingState {
             render_tasks,
             profile_counters,
             clip_store: &mut self.clip_store,
             resource_cache,
             gpu_cache,
             special_render_passes,
             transforms: transform_palette,
-            resources,
             segment_builder: SegmentBuilder::new(),
             surfaces: pic_update_state.surfaces,
-            scratch,
         };
 
         let (pic_context, mut pic_state, mut prim_list) = self
             .prim_store
             .pictures[self.root_pic_index.0]
             .take_context(
                 self.root_pic_index,
                 root_spatial_node_index,
@@ -275,16 +271,18 @@ impl FrameBuilder {
             .unwrap();
 
         self.prim_store.prepare_primitives(
             &mut prim_list,
             &pic_context,
             &mut pic_state,
             &frame_context,
             &mut frame_state,
+            resources,
+            scratch,
         );
 
         let pic = &mut self.prim_store.pictures[self.root_pic_index.0];
         pic.restore_context(
             prim_list,
             pic_context,
             pic_state,
             &mut frame_state,
--- a/gfx/wr/webrender/src/gpu_types.rs
+++ b/gfx/wr/webrender/src/gpu_types.rs
@@ -308,16 +308,18 @@ impl From<SplitCompositeInstance> for Pr
             ],
         }
     }
 }
 
 bitflags! {
     /// Flags that define how the common brush shader
     /// code should process this instance.
+    #[cfg_attr(feature = "capture", derive(Serialize))]
+    #[cfg_attr(feature = "replay", derive(Deserialize))]
     pub struct BrushFlags: u8 {
         /// Apply perspective interpolation to UVs
         const PERSPECTIVE_INTERPOLATION = 0x1;
         /// Do interpolation relative to segment rect,
         /// rather than primitive rect.
         const SEGMENT_RELATIVE = 0x2;
         /// Repeat UVs horizontally.
         const SEGMENT_REPEAT_X = 0x4;
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -409,48 +409,49 @@ impl TileCache {
 
                 match prim.details {
                     PrimitiveDetails::Brush(ref brush) => {
                         match brush.kind {
                             // Rectangles and images may depend on opacity bindings.
                             // TODO(gw): In future, we might be able to completely remove
                             //           opacity collapsing support. It's of limited use
                             //           once we have full picture caching.
-                            BrushKind::Solid { opacity_binding_index, .. } => {
-                                let opacity_binding = &opacity_binding_store[opacity_binding_index];
-                                for binding in &opacity_binding.bindings {
-                                    if let PropertyBinding::Binding(key, default) = binding {
-                                        opacity_bindings.push((key.id, *default));
-                                    }
-                                }
-                            }
                             BrushKind::Image { opacity_binding_index, ref request, .. } => {
                                 let opacity_binding = &opacity_binding_store[opacity_binding_index];
                                 for binding in &opacity_binding.bindings {
                                     if let PropertyBinding::Binding(key, default) = binding {
                                         opacity_bindings.push((key.id, *default));
                                     }
                                 }
 
                                 image_keys.push(request.key);
                             }
                             BrushKind::YuvImage { ref yuv_key, .. } => {
                                 image_keys.extend_from_slice(yuv_key);
                             }
                             BrushKind::RadialGradient { .. } |
-                            BrushKind::LinearGradient { .. } |
-                            BrushKind::Border { .. } => {
+                            BrushKind::LinearGradient { .. } => {
                             }
                         }
                     }
                 }
             }
+            PrimitiveInstanceKind::Rectangle { opacity_binding_index, .. } => {
+                let opacity_binding = &opacity_binding_store[opacity_binding_index];
+                for binding in &opacity_binding.bindings {
+                    if let PropertyBinding::Binding(key, default) = binding {
+                        opacity_bindings.push((key.id, *default));
+                    }
+                }
+            }
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } |
-            PrimitiveInstanceKind::Clear => {
+            PrimitiveInstanceKind::Clear |
+            PrimitiveInstanceKind::NormalBorder { .. } |
+            PrimitiveInstanceKind::ImageBorder { .. } => {
                 // These don't contribute dependencies
             }
         }
 
         for (key, current) in &mut opacity_bindings {
             if let Some(value) = scene_properties.get_float_value(*key) {
                 *current = value;
             }
--- a/gfx/wr/webrender/src/prim_store.rs
+++ b/gfx/wr/webrender/src/prim_store.rs
@@ -1,42 +1,45 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipMode, ColorF, PictureRect, ColorU, LayoutPrimitiveInfo};
-use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode, DeviceRect};
-use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, TileOffset};
+use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode, DeviceRect, LayoutSideOffsetsAu};
+use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, TileOffset, RepeatMode};
 use api::{RasterSpace, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize, LayoutToWorldTransform};
-use api::{LayoutVector2D, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, LayoutRectAu};
+use api::{LayoutVector2D, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat};
 use api::{DeviceIntSideOffsets, WorldPixel, BoxShadowClipMode, NormalBorder, WorldRect, LayoutToWorldScale};
 use api::{PicturePixel, RasterPixel, ColorDepth, LineStyle, LineOrientation, LayoutSizeAu, AuHelpers, LayoutVector2DAu};
 use app_units::Au;
-use border::{get_max_scale_for_border, build_border_instances, create_normal_border_prim};
-use clip::ClipStore;
+use border::{get_max_scale_for_border, build_border_instances, create_border_segments};
+use border::{create_nine_patch_segments, BorderSegmentCacheKey, NormalBorderAu};
+use clip::{ClipStore};
 use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
-use clip::{ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
-use euclid::{TypedTransform3D, TypedRect, TypedScale};
+use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
+use euclid::{SideOffsets2D, 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::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use gpu_types::BrushFlags;
 use image::{self, Repetition};
 use intern;
 use picture::{PictureCompositeMode, PicturePrimitive, PictureUpdateState};
 use picture::{ClusterRange, PrimitiveList, SurfaceIndex};
 #[cfg(debug_assertions)]
-use render_backend::FrameId;
+use render_backend::{FrameId};
+use render_backend::FrameResources;
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey, RenderTaskTree, 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, ops, u32, usize};
+use segment::SegmentBuilder;
+use std::{cmp, fmt, hash, mem, ops, u32, usize};
 #[cfg(debug_assertions)]
 use std::sync::atomic::{AtomicUsize, Ordering};
 use storage;
 use tiling::SpecialRenderPasses;
 use util::{ScaleOffset, MatrixHelpers, MaxRect, recycle_vec};
 use util::{pack_as_float, project_rect, raster_rect_to_device_pixels};
 use smallvec::SmallVec;
 
@@ -93,16 +96,22 @@ impl PrimitiveOpacity {
         PrimitiveOpacity { is_opaque: false }
     }
 
     pub fn from_alpha(alpha: f32) -> PrimitiveOpacity {
         PrimitiveOpacity {
             is_opaque: alpha >= 1.0,
         }
     }
+
+    pub fn combine(&self, other: PrimitiveOpacity) -> PrimitiveOpacity {
+        PrimitiveOpacity{
+            is_opaque: self.is_opaque && other.is_opaque
+        }
+    }
 }
 
 #[derive(Debug, Copy, Clone)]
 pub enum VisibleFace {
     Front,
     Back,
 }
 
@@ -355,39 +364,98 @@ pub enum PrimitiveKeyKind {
         // that relies on a render task (e.g. wavy). If the
         // cache key is None, it uses a fast path to draw the
         // line decoration as a solid rect.
         cache_key: Option<LineDecorationCacheKey>,
         color: ColorU,
     },
     /// Clear an existing rect, used for special effects on some platforms.
     Clear,
+    NormalBorder {
+        border: NormalBorderAu,
+        widths: LayoutSideOffsetsAu,
+    },
+    ImageBorder {
+        request: ImageRequest,
+        widths: LayoutSideOffsetsAu,
+        width: i32,
+        height: i32,
+        slice: SideOffsets2D<i32>,
+        fill: bool,
+        repeat_horizontal: RepeatMode,
+        repeat_vertical: RepeatMode,
+        outset: SideOffsets2D<Au>,
+    },
+    Rectangle {
+        color: ColorU,
+    }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, PartialEq)]
+pub struct RectangleKey {
+    x: f32,
+    y: f32,
+    w: f32,
+    h: f32,
+}
+
+impl Eq for RectangleKey {}
+
+impl hash::Hash for RectangleKey {
+    fn hash<H: hash::Hasher>(&self, state: &mut H) {
+        self.x.to_bits().hash(state);
+        self.y.to_bits().hash(state);
+        self.w.to_bits().hash(state);
+        self.h.to_bits().hash(state);
+    }
+}
+
+impl From<RectangleKey> for LayoutRect {
+    fn from(key: RectangleKey) -> LayoutRect {
+        LayoutRect {
+            origin: LayoutPoint::new(key.x, key.y),
+            size: LayoutSize::new(key.w, key.h),
+        }
+    }
+}
+
+impl From<LayoutRect> for RectangleKey {
+    fn from(rect: LayoutRect) -> RectangleKey {
+        RectangleKey {
+            x: rect.origin.x,
+            y: rect.origin.y,
+            w: rect.size.width,
+            h: rect.size.height,
+        }
+    }
 }
 
 #[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,
-    pub prim_rect: LayoutRectAu,
-    pub clip_rect: LayoutRectAu,
+    pub prim_rect: RectangleKey,
+    pub clip_rect: RectangleKey,
     pub kind: PrimitiveKeyKind,
 }
 
 impl PrimitiveKey {
     pub fn new(
         is_backface_visible: bool,
         prim_rect: LayoutRect,
         clip_rect: LayoutRect,
         kind: PrimitiveKeyKind,
     ) -> Self {
         PrimitiveKey {
             is_backface_visible,
-            prim_rect: prim_rect.to_au(),
-            clip_rect: clip_rect.to_au(),
+            prim_rect: prim_rect.into(),
+            clip_rect: clip_rect.into(),
             kind,
         }
     }
 
     /// Construct a primitive instance that matches the type
     /// of primitive key.
     pub fn to_instance_kind(
         &self,
@@ -408,60 +476,165 @@ impl PrimitiveKey {
 
                 PrimitiveInstanceKind::TextRun {
                     run_index
                 }
             }
             PrimitiveKeyKind::Clear => {
                 PrimitiveInstanceKind::Clear
             }
+            PrimitiveKeyKind::NormalBorder { .. } => {
+                PrimitiveInstanceKind::NormalBorder {
+                    cache_handles: storage::Range::empty(),
+                }
+            }
+            PrimitiveKeyKind::ImageBorder { .. } => {
+                PrimitiveInstanceKind::ImageBorder {
+                }
+            }
+            PrimitiveKeyKind::Rectangle { .. } => {
+                PrimitiveInstanceKind::Rectangle {
+                    opacity_binding_index: OpacityBindingIndex::INVALID,
+                    segment_instance_index: SegmentInstanceIndex::INVALID,
+                }
+            }
             PrimitiveKeyKind::Unused => {
                 // Should never be hit as this method should not be
                 // called for old style primitives.
                 unreachable!();
             }
         }
     }
 }
 
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct NormalBorderTemplate {
+    pub brush_segments: Vec<BrushSegment>,
+    pub border_segments: Vec<BorderSegmentInfo>,
+    pub border: NormalBorder,
+    pub widths: LayoutSideOffsets,
+}
+
 /// The shared information for a given primitive. This is interned and retained
 /// both across frames and display lists, by comparing the matching PrimitiveKey.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum PrimitiveTemplateKind {
     LineDecoration {
         cache_key: Option<LineDecorationCacheKey>,
         color: ColorF,
     },
     TextRun {
         font: FontInstance,
         offset: LayoutVector2DAu,
         glyphs: Vec<GlyphInstance>,
     },
+    NormalBorder {
+        template: Box<NormalBorderTemplate>,
+    },
+    ImageBorder {
+        request: ImageRequest,
+        brush_segments: Vec<BrushSegment>,
+    },
+    Rectangle {
+        color: ColorF,
+    },
     Clear,
     Unused,
 }
 
 /// Construct the primitive template data from a primitive key. This
 /// is invoked when a primitive key is created and the interner
 /// doesn't currently contain a primitive with this key.
-impl From<PrimitiveKeyKind> for PrimitiveTemplateKind {
-    fn from(item: PrimitiveKeyKind) -> Self {
-        match item {
+impl PrimitiveKeyKind {
+    fn into_template(
+        self,
+        rect: &LayoutRect,
+    ) -> PrimitiveTemplateKind {
+        match self {
             PrimitiveKeyKind::Unused => PrimitiveTemplateKind::Unused,
             PrimitiveKeyKind::TextRun { glyphs, font, offset, .. } => {
                 PrimitiveTemplateKind::TextRun {
                     font,
                     offset,
                     glyphs,
                 }
             }
             PrimitiveKeyKind::Clear => {
                 PrimitiveTemplateKind::Clear
             }
+            PrimitiveKeyKind::NormalBorder { widths, border, .. } => {
+                let mut border: NormalBorder = border.into();
+                let widths = LayoutSideOffsets::from_au(widths);
+
+                // FIXME(emilio): Is this the best place to do this?
+                border.normalize(&widths);
+
+                let mut brush_segments = Vec::new();
+                let mut border_segments = Vec::new();
+
+                create_border_segments(
+                    rect,
+                    &border,
+                    &widths,
+                    &mut border_segments,
+                    &mut brush_segments,
+                );
+
+                PrimitiveTemplateKind::NormalBorder {
+                    template: Box::new(NormalBorderTemplate {
+                        border,
+                        widths,
+                        border_segments,
+                        brush_segments,
+                    })
+                }
+            }
+            PrimitiveKeyKind::ImageBorder {
+                widths,
+                request,
+                width,
+                height,
+                slice,
+                fill,
+                repeat_horizontal,
+                repeat_vertical,
+                outset,
+                ..
+            } => {
+                let widths = LayoutSideOffsets::from_au(widths);
+
+                let brush_segments = create_nine_patch_segments(
+                    rect,
+                    &widths,
+                    width,
+                    height,
+                    slice,
+                    fill,
+                    repeat_horizontal,
+                    repeat_vertical,
+                    SideOffsets2D::new(
+                        outset.top.to_f32_px(),
+                        outset.right.to_f32_px(),
+                        outset.bottom.to_f32_px(),
+                        outset.left.to_f32_px(),
+                    ),
+                );
+
+                PrimitiveTemplateKind::ImageBorder {
+                    request,
+                    brush_segments,
+                }
+            }
+            PrimitiveKeyKind::Rectangle { color, .. } => {
+                PrimitiveTemplateKind::Rectangle {
+                    color: color.into(),
+                }
+            }
             PrimitiveKeyKind::LineDecoration { cache_key, color } => {
                 PrimitiveTemplateKind::LineDecoration {
                     cache_key,
                     color: color.into(),
                 }
             }
         }
     }
@@ -479,52 +652,128 @@ pub struct PrimitiveTemplate {
     /// is retained across display lists by interning, this GPU cache handle
     /// also remains valid, which reduces the number of updates to the GPU
     /// cache when a new display list is processed.
     pub gpu_cache_handle: GpuCacheHandle,
 }
 
 impl From<PrimitiveKey> for PrimitiveTemplate {
     fn from(item: PrimitiveKey) -> Self {
+        let prim_rect = item.prim_rect.into();
+        let clip_rect = item.clip_rect.into();
+        let kind = item.kind.into_template(&prim_rect);
+
         PrimitiveTemplate {
             is_backface_visible: item.is_backface_visible,
-            prim_rect: LayoutRect::from_au(item.prim_rect),
-            clip_rect: LayoutRect::from_au(item.clip_rect),
-            kind: item.kind.into(),
+            prim_rect,
+            clip_rect,
+            kind,
             gpu_cache_handle: GpuCacheHandle::new(),
             opacity: PrimitiveOpacity::translucent(),
         }
     }
 }
 
 impl PrimitiveTemplate {
     /// Update the GPU cache for a given primitive template. This may be called multiple
     /// times per frame, by each primitive reference that refers to this interned
     /// template. The initial request call to the GPU cache ensures that work is only
     /// done if the cache entry is invalid (due to first use or eviction).
     pub fn update(
         &mut self,
-        gpu_cache: &mut GpuCache,
+        frame_state: &mut FrameBuildingState,
     ) {
         self.opacity = match self.kind {
             PrimitiveTemplateKind::Clear => {
-                if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
+                if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
                     // Opaque black with operator dest out
                     request.push(PremultipliedColorF::BLACK);
 
                     request.write_segment(
                         self.prim_rect,
                         [0.0; 4],
                     );
                 }
 
                 PrimitiveOpacity::translucent()
             }
+            PrimitiveTemplateKind::Rectangle { ref color, .. } => {
+                if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
+                    request.push(color.premultiplied());
+                }
+
+                PrimitiveOpacity::from_alpha(color.a)
+            }
+            PrimitiveTemplateKind::NormalBorder { ref template, .. } => {
+                if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
+                    // Border primitives currently used for
+                    // image borders, and run through the
+                    // normal brush_image shader.
+                    request.push(PremultipliedColorF::WHITE);
+                    request.push(PremultipliedColorF::WHITE);
+                    request.push([
+                        self.prim_rect.size.width,
+                        self.prim_rect.size.height,
+                        0.0,
+                        0.0,
+                    ]);
+
+                    for segment in &template.brush_segments {
+                        // has to match VECS_PER_SEGMENT
+                        request.write_segment(
+                            segment.local_rect,
+                            segment.extra_data,
+                        );
+                    }
+                }
+
+                // Shouldn't matter, since the segment opacity is used instead
+                PrimitiveOpacity::translucent()
+            }
+            PrimitiveTemplateKind::ImageBorder { request, ref brush_segments, .. } => {
+                if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
+                    // Border primitives currently used for
+                    // image borders, and run through the
+                    // normal brush_image shader.
+                    request.push(PremultipliedColorF::WHITE);
+                    request.push(PremultipliedColorF::WHITE);
+                    request.push([
+                        self.prim_rect.size.width,
+                        self.prim_rect.size.height,
+                        0.0,
+                        0.0,
+                    ]);
+
+                    for segment in brush_segments {
+                        // has to match VECS_PER_SEGMENT
+                        request.write_segment(
+                            segment.local_rect,
+                            segment.extra_data,
+                        );
+                    }
+                }
+
+                let image_properties = frame_state
+                    .resource_cache
+                    .get_image_properties(request.key);
+
+                if let Some(image_properties) = image_properties {
+                    frame_state.resource_cache.request_image(
+                        request,
+                        frame_state.gpu_cache,
+                    );
+                    PrimitiveOpacity {
+                        is_opaque: image_properties.descriptor.is_opaque,
+                    }
+                } else {
+                    PrimitiveOpacity::opaque()
+                }
+            }
             PrimitiveTemplateKind::LineDecoration { ref cache_key, ref color } => {
-                if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
+                if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
                     // Work out the stretch parameters (for image repeat) based on the
                     // line decoration parameters.
 
                     match cache_key {
                         Some(cache_key) => {
                             request.push(color.premultiplied());
                             request.push(PremultipliedColorF::WHITE);
                             request.push([
@@ -546,17 +795,17 @@ impl PrimitiveTemplate {
                 }
 
                 match cache_key {
                     Some(..) => PrimitiveOpacity::translucent(),
                     None => PrimitiveOpacity::from_alpha(color.a),
                 }
             }
             PrimitiveTemplateKind::TextRun { ref glyphs, ref font, ref offset, .. } => {
-                if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
+                if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
                     request.push(ColorF::from(font.color).premultiplied());
                     // this is the only case where we need to provide plain color to GPU
                     let bg_color = ColorF::from(font.bg_color);
                     request.push([bg_color.r, bg_color.g, bg_color.b, 1.0]);
                     request.push([
                         offset.x.to_f32_px(),
                         offset.y.to_f32_px(),
                         0.0,
@@ -668,38 +917,25 @@ pub struct VisibleMaskImageTile {
 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.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug)]
 pub struct BorderSegmentInfo {
-    pub handle: Option<RenderTaskCacheEntryHandle>,
     pub local_task_size: LayoutSize,
-    pub cache_key: RenderTaskCacheKey,
-}
-
-#[derive(Debug)]
-pub enum BorderSource {
-    Image(ImageRequest),
-    Border {
-        segments: Vec<BorderSegmentInfo>,
-        border: NormalBorder,
-        widths: LayoutSideOffsets,
-    },
+    pub cache_key: BorderSegmentCacheKey,
 }
 
 pub enum BrushKind {
-    Solid {
-        color: ColorF,
-        opacity_binding_index: OpacityBindingIndex,
-    },
     Image {
         request: ImageRequest,
         alpha_type: AlphaType,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         color: ColorF,
         source: ImageSource,
         sub_rect: Option<DeviceIntRect>,
@@ -732,67 +968,35 @@ pub enum BrushKind {
         reverse_stops: bool,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         visible_tiles: Vec<VisibleGradientTile>,
         stops_opacity: PrimitiveOpacity,
     },
-    Border {
-        source: BorderSource,
-    },
 }
 
 impl BrushKind {
     fn supports_segments(&self, resource_cache: &ResourceCache) -> bool {
         match *self {
             BrushKind::Image { ref request, .. } => {
                 // tiled images don't support segmentation
                 resource_cache
                     .get_image_properties(request.key)
                     .and_then(|properties| properties.tiling)
                     .is_none()
             }
 
-            BrushKind::Solid { .. } |
             BrushKind::YuvImage { .. } |
             BrushKind::RadialGradient { .. } |
-            BrushKind::Border { .. } |
             BrushKind::LinearGradient { .. } => true,
         }
     }
 
-    // Construct a brush that is a solid color rectangle.
-    pub fn new_solid(color: ColorF) -> BrushKind {
-        BrushKind::Solid {
-            color,
-            opacity_binding_index: OpacityBindingIndex::INVALID,
-        }
-    }
-
-    // Construct a brush that is a border with `border` style and `widths`
-    // dimensions.
-    pub fn new_border(
-        mut border: NormalBorder,
-        widths: LayoutSideOffsets,
-        segments: Vec<BorderSegmentInfo>,
-    ) -> BrushKind {
-        // FIXME(emilio): Is this the best place to do this?
-        border.normalize(&widths);
-
-        BrushKind::Border {
-            source: BorderSource::Border {
-                border,
-                widths,
-                segments,
-            }
-        }
-    }
-
     // Construct a brush that is an image wisth `stretch_size` dimensions and
     // `color`.
     pub fn new_image(
         request: ImageRequest,
         stretch_size: LayoutSize,
         color: ColorF
     ) -> BrushKind {
         BrushKind::Image {
@@ -832,16 +1036,18 @@ pub enum ClipMaskKind {
     /// The segment has a clip mask, specified by the render task.
     Mask(RenderTaskId),
     /// The segment has no clip mask.
     None,
     /// The segment is made invisible / clipped completely.
     Clipped,
 }
 
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone)]
 pub struct BrushSegment {
     pub local_rect: LayoutRect,
     pub may_need_clip_mask: bool,
     pub edge_flags: EdgeAaSegmentMask,
     pub extra_data: [f32; 4],
     pub brush_flags: BrushFlags,
 }
@@ -861,24 +1067,25 @@ impl BrushSegment {
             extra_data,
             brush_flags,
         }
     }
 
     /// Write out to the clip mask instances array the correct clip mask
     /// config for this segment.
     pub fn update_clip_task(
-        &mut self,
+        &self,
         clip_chain: Option<&ClipChainInstance>,
         prim_bounding_rect: WorldRect,
         root_spatial_node_index: SpatialNodeIndex,
         surface_index: SurfaceIndex,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
+        clip_data_store: &mut ClipDataStore,
     ) -> ClipMaskKind {
         match clip_chain {
             Some(clip_chain) => {
                 if !clip_chain.needs_mask ||
                    (!self.may_need_clip_mask && !clip_chain.has_non_local_clips) {
                     return ClipMaskKind::None;
                 }
 
@@ -898,17 +1105,17 @@ impl BrushSegment {
                 let clip_task = RenderTask::new_mask(
                     device_rect.to_i32(),
                     clip_chain.clips_range,
                     root_spatial_node_index,
                     frame_state.clip_store,
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_state.render_tasks,
-                    &mut frame_state.resources.clip_data_store,
+                    clip_data_store,
                 );
 
                 let clip_task_id = frame_state.render_tasks.add(clip_task);
                 frame_state.surfaces[surface_index.0].tasks.push(clip_task_id);
                 ClipMaskKind::Mask(clip_task_id)
             }
             None => {
                 ClipMaskKind::Clipped
@@ -947,29 +1154,16 @@ impl BrushPrimitive {
     fn write_gpu_blocks_if_required(
         &mut self,
         local_rect: LayoutRect,
         gpu_cache: &mut GpuCache,
     ) {
         if let Some(mut request) = gpu_cache.request(&mut self.gpu_location) {
             // has to match VECS_PER_SPECIFIC_BRUSH
             match self.kind {
-                BrushKind::Border { .. } => {
-                    // Border primitives currently used for
-                    // image borders, and run through the
-                    // normal brush_image shader.
-                    request.push(PremultipliedColorF::WHITE);
-                    request.push(PremultipliedColorF::WHITE);
-                    request.push([
-                        local_rect.size.width,
-                        local_rect.size.height,
-                        0.0,
-                        0.0,
-                    ]);
-                }
                 BrushKind::YuvImage { color_depth, .. } => {
                     request.push([
                         color_depth.rescaling_factor(),
                         0.0,
                         0.0,
                         0.0
                     ]);
                 }
@@ -980,20 +1174,16 @@ impl BrushPrimitive {
                     request.push(PremultipliedColorF::WHITE);
                     request.push([
                         stretch_size.width + tile_spacing.width,
                         stretch_size.height + tile_spacing.height,
                         0.0,
                         0.0,
                     ]);
                 }
-                // Solid rects also support opacity collapsing.
-                BrushKind::Solid { ref color, .. } => {
-                    request.push(color.premultiplied());
-                }
                 BrushKind::LinearGradient { stretch_size, start_point, end_point, extend_mode, .. } => {
                     request.push([
                         start_point.x,
                         start_point.y,
                         end_point.x,
                         end_point.y,
                     ]);
                     request.push([
@@ -1573,16 +1763,34 @@ pub enum PrimitiveContainer {
     Clear,
     Brush(BrushPrimitive),
     LineDecoration {
         color: ColorF,
         style: LineStyle,
         orientation: LineOrientation,
         wavy_line_thickness: f32,
     },
+    NormalBorder {
+        border: NormalBorder,
+        widths: LayoutSideOffsets,
+    },
+    ImageBorder {
+        request: ImageRequest,
+        widths: LayoutSideOffsets,
+        width: i32,
+        height: i32,
+        slice: SideOffsets2D<i32>,
+        fill: bool,
+        repeat_horizontal: RepeatMode,
+        repeat_vertical: RepeatMode,
+        outset: SideOffsets2D<f32>,
+    },
+    Rectangle {
+        color: ColorF,
+    },
 }
 
 impl PrimitiveContainer {
     // Return true if the primary primitive is visible.
     // Used to trivially reject non-visible primitives.
     // TODO(gw): Currently, primitives other than those
     //           listed here are handled before the
     //           add_primitive() call. In the future
@@ -1590,31 +1798,30 @@ impl PrimitiveContainer {
     //           primitive types to use this.
     pub fn is_visible(&self) -> bool {
         match *self {
             PrimitiveContainer::TextRun { ref font, .. } => {
                 font.color.a > 0
             }
             PrimitiveContainer::Brush(ref brush) => {
                 match brush.kind {
-                    BrushKind::Solid { ref color, .. } => {
-                        color.a > 0.0
-                    }
                     BrushKind::Image { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
-                    BrushKind::Border { .. } |
                     BrushKind::LinearGradient { .. } => {
                         true
                     }
                 }
             }
+            PrimitiveContainer::NormalBorder { .. } |
+            PrimitiveContainer::ImageBorder { .. } |
             PrimitiveContainer::Clear => {
                 true
             }
+            PrimitiveContainer::Rectangle { ref color, .. } |
             PrimitiveContainer::LineDecoration { ref color, .. } => {
                 color.a > 0.0
             }
         }
     }
 
     /// Convert a source primitive container into a key, and optionally
     /// an old style PrimitiveDetails structure.
@@ -1631,16 +1838,62 @@ impl PrimitiveContainer {
                     shadow,
                 };
 
                 (key, None)
             }
             PrimitiveContainer::Clear => {
                 (PrimitiveKeyKind::Clear, None)
             }
+            PrimitiveContainer::Rectangle { color, .. } => {
+                let key = PrimitiveKeyKind::Rectangle {
+                    color: color.into(),
+                };
+
+                (key, None)
+            }
+            PrimitiveContainer::ImageBorder {
+                request,
+                widths,
+                width,
+                height,
+                slice,
+                fill,
+                repeat_vertical,
+                repeat_horizontal,
+                outset,
+                ..
+            } => {
+                let key = PrimitiveKeyKind::ImageBorder {
+                    request,
+                    widths: widths.to_au(),
+                    width,
+                    height,
+                    slice,
+                    fill,
+                    repeat_horizontal,
+                    repeat_vertical,
+                    outset: SideOffsets2D::new(
+                        Au::from_f32_px(outset.top),
+                        Au::from_f32_px(outset.right),
+                        Au::from_f32_px(outset.bottom),
+                        Au::from_f32_px(outset.left),
+                    ),
+                };
+
+                (key, None)
+            }
+            PrimitiveContainer::NormalBorder { border, widths, .. } => {
+                let key = PrimitiveKeyKind::NormalBorder {
+                    border: border.into(),
+                    widths: widths.to_au(),
+                };
+
+                (key, None)
+            }
             PrimitiveContainer::LineDecoration { color, style, orientation, wavy_line_thickness } => {
                 // For line decorations, we can construct the render task cache key
                 // here during scene building, since it doesn't depend on device
                 // pixel ratio or transform.
 
                 let size = get_line_decoration_sizes(
                     &info.rect.size,
                     orientation,
@@ -1702,17 +1955,16 @@ impl PrimitiveContainer {
     }
 
     // Create a clone of this PrimitiveContainer, applying whatever
     // changes are necessary to the primitive to support rendering
     // it as part of the supplied shadow.
     pub fn create_shadow(
         &self,
         shadow: &Shadow,
-        prim_rect: &LayoutRect,
     ) -> PrimitiveContainer {
         match *self {
             PrimitiveContainer::TextRun { ref font, offset, ref glyphs, .. } => {
                 let mut font = FontInstance {
                     color: shadow.color.into(),
                     ..font.clone()
                 };
                 if shadow.blur_radius > 0.0 {
@@ -1729,60 +1981,48 @@ impl PrimitiveContainer {
             PrimitiveContainer::LineDecoration { style, orientation, wavy_line_thickness, .. } => {
                 PrimitiveContainer::LineDecoration {
                     color: shadow.color,
                     style,
                     orientation,
                     wavy_line_thickness,
                 }
             }
+            PrimitiveContainer::Rectangle { .. } => {
+                PrimitiveContainer::Rectangle {
+                    color: shadow.color,
+                }
+            }
+            PrimitiveContainer::NormalBorder { border, widths, .. } => {
+                let border = border.with_color(shadow.color);
+                PrimitiveContainer::NormalBorder {
+                    border,
+                    widths,
+                }
+            }
             PrimitiveContainer::Brush(ref brush) => {
                 match brush.kind {
-                    BrushKind::Solid { .. } => {
-                        PrimitiveContainer::Brush(BrushPrimitive::new(
-                            BrushKind::new_solid(shadow.color),
-                            None,
-                        ))
-                    }
-                    BrushKind::Border { ref source } => {
-                        let prim = match *source {
-                            BorderSource::Image(request) => {
-                                BrushPrimitive::new(
-                                    BrushKind::Border {
-                                        source: BorderSource::Image(request)
-                                    },
-                                    None,
-                                )
-                            }
-                            BorderSource::Border { border, widths, .. } => {
-                                let border = border.with_color(shadow.color);
-                                create_normal_border_prim(
-                                    prim_rect,
-                                    border,
-                                    widths,
-                                )
-                            }
-                        };
-                        PrimitiveContainer::Brush(prim)
-                    }
                     BrushKind::Image { request, stretch_size, .. } => {
                         PrimitiveContainer::Brush(BrushPrimitive::new(
                             BrushKind::new_image(request.clone(),
                                                  stretch_size.clone(),
                                                  shadow.color),
                             None,
                         ))
                     }
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::LinearGradient { .. } => {
                         panic!("bug: other brush kinds not expected here yet");
                     }
                 }
             }
+            PrimitiveContainer::ImageBorder { .. } => {
+                panic!("bug: image borders are not supported in shadow contexts");
+            }
             PrimitiveContainer::Clear => {
                 panic!("bug: clear rects are not supported in shadow contexts");
             }
         }
     }
 }
 
 pub enum PrimitiveDetails {
@@ -1823,16 +2063,25 @@ pub enum PrimitiveInstanceKind {
         //           prepare_prims and read during the batching pass.
         //           Once we unify the prepare_prims and batching to
         //           occur at the same time, we can remove most of
         //           the things we store here in the instance, and
         //           use them directly. This will remove cache_handle,
         //           but also the opacity, clip_task_id etc below.
         cache_handle: Option<RenderTaskCacheEntryHandle>,
     },
+    NormalBorder {
+        cache_handles: storage::Range<RenderTaskCacheEntryHandle>,
+    },
+    ImageBorder {
+    },
+    Rectangle {
+        opacity_binding_index: OpacityBindingIndex,
+        segment_instance_index: SegmentInstanceIndex,
+    },
     /// Clear out a rect, used for special effects.
     Clear,
 }
 
 #[derive(Clone, Debug)]
 pub struct PrimitiveInstance {
     /// Identifies the kind of primitive this
     /// instance is, and references to where
@@ -1906,55 +2155,86 @@ impl PrimitiveInstance {
     }
 
     #[cfg(not(debug_assertions))]
     pub fn is_chased(&self) -> bool {
         false
     }
 }
 
+#[derive(Debug)]
+pub struct SegmentedInstance {
+    pub gpu_cache_handle: GpuCacheHandle,
+    pub segments_range: SegmentsRange,
+}
+
 pub type GlyphKeyStorage = storage::Storage<GlyphKey>;
 pub type TextRunIndex = storage::Index<TextRunPrimitive>;
 pub type TextRunStorage = storage::Storage<TextRunPrimitive>;
 pub type OpacityBindingIndex = storage::Index<OpacityBinding>;
 pub type OpacityBindingStorage = storage::Storage<OpacityBinding>;
+pub type BorderHandleStorage = storage::Storage<RenderTaskCacheEntryHandle>;
+pub type SegmentStorage = storage::Storage<BrushSegment>;
+pub type SegmentsRange = storage::Range<BrushSegment>;
+pub type SegmentInstanceStorage = storage::Storage<SegmentedInstance>;
+pub type SegmentInstanceIndex = storage::Index<SegmentedInstance>;
 
 /// Contains various vecs of data that is used only during frame building,
 /// where we want to recycle the memory each new display list, to avoid constantly
 /// re-allocating and moving memory around. Written during primitive preparation,
 /// and read during batching.
 pub struct PrimitiveScratchBuffer {
     /// Contains a list of clip mask instance parameters
     /// per segment generated.
     pub clip_mask_instances: Vec<ClipMaskKind>,
 
     /// List of glyphs keys that are allocated by each
     /// text run instance.
     pub glyph_keys: GlyphKeyStorage,
+
+    /// List of render task handles for border segment instances
+    /// that have been added this frame.
+    pub border_cache_handles: BorderHandleStorage,
+
+    /// A list of brush segments that have been built for this scene.
+    pub segments: SegmentStorage,
+
+    /// A list of segment ranges and GPU cache handles for prim instances
+    /// that have opted into segment building. In future, this should be
+    /// removed in favor of segment building during primitive interning.
+    pub segment_instances: SegmentInstanceStorage,
 }
 
 impl PrimitiveScratchBuffer {
     pub fn new() -> Self {
         PrimitiveScratchBuffer {
             clip_mask_instances: Vec::new(),
             glyph_keys: GlyphKeyStorage::new(0),
+            border_cache_handles: BorderHandleStorage::new(0),
+            segments: SegmentStorage::new(0),
+            segment_instances: SegmentInstanceStorage::new(0),
         }
     }
 
     pub fn recycle(&mut self) {
         recycle_vec(&mut self.clip_mask_instances);
         self.glyph_keys.recycle();
+        self.border_cache_handles.recycle();
+        self.segments.recycle();
+        self.segment_instances.recycle();
     }
 
     pub fn begin_frame(&mut self) {
         // Clear the clip mask tasks for the beginning of the frame. Append
         // a single kind representing no clip mask, at the ClipTaskIndex::INVALID
         // location.
         self.clip_mask_instances.clear();
-        self.clip_mask_instances.push(ClipMaskKind::None)
+        self.clip_mask_instances.push(ClipMaskKind::None);
+
+        self.border_cache_handles.clear();
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Clone, Debug)]
 pub struct PrimitiveStoreStats {
     primitive_count: usize,
@@ -2088,34 +2368,39 @@ impl PrimitiveStore {
         }
     }
 
     // Internal method that retrieves the primitive index of a primitive
     // that can be the target for collapsing parent opacity filters into.
     fn get_opacity_collapse_prim(
         &self,
         pic_index: PictureIndex,
-    ) -> Option<PrimitiveIndex> {
+    ) -> Option<PictureIndex> {
         let pic = &self.pictures[pic_index.0];
 
         // We can only collapse opacity if there is a single primitive, otherwise
         // the opacity needs to be applied to the primitives as a group.
         if pic.prim_list.prim_instances.len() != 1 {
             return None;
         }
 
         let prim_instance = &pic.prim_list.prim_instances[0];
 
         // For now, we only support opacity collapse on solid rects and images.
         // This covers the most common types of opacity filters that can be
         // handled by this optimization. In the future, we can easily extend
         // this to other primitives, such as text runs and gradients.
         match prim_instance.kind {
+            PrimitiveInstanceKind::Rectangle { .. } => {
+                return Some(pic_index);
+            }
             PrimitiveInstanceKind::Clear |
             PrimitiveInstanceKind::TextRun { .. } |
+            PrimitiveInstanceKind::NormalBorder { .. } |
+            PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 // TODO: Once rectangles and/or images are ported
                 //       to use interned primitives, we will need
                 //       to handle opacity collapse here.
             }
             PrimitiveInstanceKind::Picture { pic_index } => {
                 let pic = &self.pictures[pic_index.0];
 
@@ -2128,21 +2413,19 @@ impl PrimitiveStore {
             }
             PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
                 let prim = &self.primitives[prim_index.0];
                 match prim.details {
                     PrimitiveDetails::Brush(ref brush) => {
                         match brush.kind {
                             // If we find a single rect or image, we can use that
                             // as the primitive to collapse the opacity into.
-                            BrushKind::Solid { .. } |
                             BrushKind::Image { .. } => {
-                                return Some(prim_index)
+                                return Some(pic_index)
                             }
-                            BrushKind::Border { .. } |
                             BrushKind::YuvImage { .. } |
                             BrushKind::LinearGradient { .. } |
                             BrushKind::RadialGradient { .. } => {}
                         }
                     }
                 }
             }
         }
@@ -2166,39 +2449,53 @@ impl PrimitiveStore {
             _ => {
                 return;
             }
         };
 
         // See if this picture contains a single primitive that supports
         // opacity collapse.
         match self.get_opacity_collapse_prim(pic_index) {
-            Some(prim_index) => {
-                let prim = &mut self.primitives[prim_index.0];
-                match prim.details {
-                    PrimitiveDetails::Brush(ref mut brush) => {
-                        // By this point, we know we should only have found a primitive
-                        // that supports opacity collapse.
-                        match brush.kind {
-                            BrushKind::Solid { ref mut opacity_binding_index, .. } |
-                            BrushKind::Image { ref mut opacity_binding_index, .. } => {
-                                if *opacity_binding_index == OpacityBindingIndex::INVALID {
-                                    *opacity_binding_index = self.opacity_bindings.push(OpacityBinding::new());
+            Some(pic_index) => {
+                let pic = &mut self.pictures[pic_index.0];
+                let prim_instance = &mut pic.prim_list.prim_instances[0];
+                match prim_instance.kind {
+                    PrimitiveInstanceKind::Rectangle { ref mut opacity_binding_index, .. } => {
+                        if *opacity_binding_index == OpacityBindingIndex::INVALID {
+                            *opacity_binding_index = self.opacity_bindings.push(OpacityBinding::new());
+                        }
+                        let opacity_binding = &mut self.opacity_bindings[*opacity_binding_index];
+                        opacity_binding.push(binding);
+                    }
+                    PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
+                        let prim = &mut self.primitives[prim_index.0];
+                        match prim.details {
+                            PrimitiveDetails::Brush(ref mut brush) => {
+                                // By this point, we know we should only have found a primitive
+                                // that supports opacity collapse.
+                                match brush.kind {
+                                    BrushKind::Image { ref mut opacity_binding_index, .. } => {
+                                        if *opacity_binding_index == OpacityBindingIndex::INVALID {
+                                            *opacity_binding_index = self.opacity_bindings.push(OpacityBinding::new());
+                                        }
+                                        let opacity_binding = &mut self.opacity_bindings[*opacity_binding_index];
+                                        opacity_binding.push(binding);
+                                    }
+                                    BrushKind::YuvImage { .. } |
+                                    BrushKind::LinearGradient { .. } |
+                                    BrushKind::RadialGradient { .. } => {
+                                        unreachable!("bug: invalid prim type for opacity collapse");
+                                    }
                                 }
-                                let opacity_binding = &mut self.opacity_bindings[*opacity_binding_index];
-                                opacity_binding.push(binding);
-                            }
-                            BrushKind::YuvImage { .. } |
-                            BrushKind::Border { .. } |
-                            BrushKind::LinearGradient { .. } |
-                            BrushKind::RadialGradient { .. } => {
-                                unreachable!("bug: invalid prim type for opacity collapse");
                             }
                         }
                     }
+                    _ => {
+                        unreachable!();
+                    }
                 }
             }
             None => {
                 return;
             }
         }
 
         // The opacity filter has been collapsed, so mark this picture
@@ -2218,16 +2515,18 @@ impl PrimitiveStore {
         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,
+        resources: &mut FrameResources,
+        scratch: &mut PrimitiveScratchBuffer,
     ) -> 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 prim_instance.kind {
@@ -2249,18 +2548,21 @@ impl PrimitiveStore {
                                 println!("\tculled for carrying an invisible composite filter");
                             }
 
                             return false;
                         }
                     }
                 }
                 PrimitiveInstanceKind::TextRun { .. } |
+                PrimitiveInstanceKind::Rectangle { .. } |
                 PrimitiveInstanceKind::LineDecoration { .. } |
                 PrimitiveInstanceKind::LegacyPrimitive { .. } |
+                PrimitiveInstanceKind::NormalBorder { .. } |
+                PrimitiveInstanceKind::ImageBorder { .. } |
                 PrimitiveInstanceKind::Clear => {
                     None
                 }
             }
         };
 
         let (is_passthrough, clip_node_collector) = match pic_info {
             Some((pic_context_for_children, mut pic_state_for_children, mut prim_list)) => {
@@ -2268,16 +2570,18 @@ impl PrimitiveStore {
                 let is_passthrough = pic_context_for_children.is_passthrough;
 
                 self.prepare_primitives(
                     &mut prim_list,
                     &pic_context_for_children,
                     &mut pic_state_for_children,
                     frame_context,
                     frame_state,
+                    resources,
+                    scratch,
                 );
 
                 if !pic_state_for_children.is_cacheable {
                     pic_state.is_cacheable = false;
                 }
 
                 // Restore the dependencies (borrow check dance)
                 let clip_node_collector = self
@@ -2298,19 +2602,21 @@ impl PrimitiveStore {
 
         let (prim_local_rect, prim_local_clip_rect) = match prim_instance.kind {
             PrimitiveInstanceKind::Picture { pic_index } => {
                 let pic = &self.pictures[pic_index.0];
                 (pic.local_rect, LayoutRect::max_rect())
             }
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::Clear |
+            PrimitiveInstanceKind::NormalBorder { .. } |
+            PrimitiveInstanceKind::ImageBorder { .. } |
+            PrimitiveInstanceKind::Rectangle { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
-                let prim_data = &frame_state
-                    .resources
+                let prim_data = &resources
                     .prim_data_store[prim_instance.prim_data_handle];
                 (prim_data.prim_rect, prim_data.clip_rect)
             }
             PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
                 let prim = &self.primitives[prim_index.0];
                 (prim.local_rect, prim.local_clip_rect)
             }
         };
@@ -2372,17 +2678,17 @@ impl PrimitiveStore {
                     &pic_state.map_local_to_pic,
                     &pic_state.map_pic_to_world,
                     &frame_context.clip_scroll_tree,
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_context.device_pixel_scale,
                     &pic_context.dirty_world_rect,
                     &clip_node_collector,
-                    &mut frame_state.resources.clip_data_store,
+                    &mut resources.clip_data_store,
                 );
 
             let clip_chain = match clip_chain {
                 Some(clip_chain) => clip_chain,
                 None => {
                     if prim_instance.is_chased() {
                         println!("\tunable to build the clip chain, skipping");
                     }
@@ -2433,16 +2739,18 @@ impl PrimitiveStore {
                 pic_context.raster_spatial_node_index,
                 &clip_chain,
                 pic_context,
                 pic_state,
                 frame_context,
                 frame_state,
                 &clip_node_collector,
                 &mut self.primitives,
+                resources,
+                scratch,
             );
 
             if prim_instance.is_chased() {
                 println!("\tconsidered visible and ready with local rect {:?}", local_rect);
             }
         }
 
         #[cfg(debug_assertions)]
@@ -2488,23 +2796,28 @@ impl PrimitiveStore {
                     request.write_segment(
                         pic.local_rect,
                         [0.0; 4],
                     );
                 }
             }
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::Clear |
+            PrimitiveInstanceKind::Rectangle { .. } |
+            PrimitiveInstanceKind::NormalBorder { .. } |
+            PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 self.prepare_interned_prim_for_render(
                     prim_instance,
                     prim_context,
                     pic_context,
                     frame_context,
                     frame_state,
+                    resources,
+                    scratch,
                 );
             }
             PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
                 let prim_details = &mut self.primitives[prim_index.0].details;
 
                 prim_instance.prepare_prim_for_render_inner(
                     prim_local_rect,
                     prim_details,
@@ -2524,16 +2837,18 @@ impl PrimitiveStore {
 
     pub fn prepare_primitives(
         &mut self,
         prim_list: &mut PrimitiveList,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
+        resources: &mut FrameResources,
+        scratch: &mut PrimitiveScratchBuffer,
     ) {
         let display_list = &frame_context
             .pipelines
             .get(&pic_context.pipeline_id)
             .expect("No display list?")
             .display_list;
 
         for (plane_split_anchor, prim_instance) in prim_list.prim_instances.iter_mut().enumerate() {
@@ -2603,41 +2918,44 @@ impl PrimitiveStore {
                 prim_instance,
                 &prim_context,
                 pic_context,
                 pic_state,
                 frame_context,
                 frame_state,
                 display_list,
                 plane_split_anchor,
+                resources,
+                scratch,
             ) {
                 frame_state.profile_counters.visible_primitives.inc();
             }
         }
     }
 
     /// Prepare an interned primitive for rendering, by requesting
     /// resources, render tasks etc. This is equivalent to the
     /// prepare_prim_for_render_inner call for old style primitives.
     fn prepare_interned_prim_for_render(
         &mut self,
         prim_instance: &mut PrimitiveInstance,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
+        resources: &mut FrameResources,
+        scratch: &mut PrimitiveScratchBuffer,
     ) {
-        let prim_data = &mut frame_state
-            .resources
+        let prim_data = &mut resources
             .prim_data_store[prim_instance.prim_data_handle];
 
         // Update the template this instane references, which may refresh the GPU
         // cache with any shared template data.
         prim_data.update(
-            frame_state.gpu_cache,
+            frame_state,
         );
 
         let is_chased = prim_instance.is_chased();
 
         match (&mut prim_instance.kind, &mut prim_data.kind) {
             (
                 PrimitiveInstanceKind::LineDecoration { ref mut cache_handle, .. },
                 PrimitiveTemplateKind::LineDecoration { ref cache_key, .. }
@@ -2702,26 +3020,114 @@ impl PrimitiveStore {
                     glyphs,
                     frame_context.device_pixel_scale,
                     &transform,
                     pic_context,
                     frame_state.resource_cache,
                     frame_state.gpu_cache,
                     frame_state.render_tasks,
                     frame_state.special_render_passes,
-                    frame_state.scratch,
+                    scratch,
                 );
             }
             (
                 PrimitiveInstanceKind::Clear,
                 PrimitiveTemplateKind::Clear
             ) => {
                 // Nothing specific to prepare for clear rects, since the
                 // GPU cache is updated by the template earlier.
             }
+            (
+                PrimitiveInstanceKind::NormalBorder { ref mut cache_handles, .. },
+                PrimitiveTemplateKind::NormalBorder { template, .. }
+            ) => {
+                // TODO(gw): When drawing in screen raster mode, we should also incorporate a
+                //           scale factor from the world transform to get an appropriately
+                //           sized border task.
+                let world_scale = LayoutToWorldScale::new(1.0);
+                let mut scale = world_scale * frame_context.device_pixel_scale;
+                let max_scale = get_max_scale_for_border(&template.border.radius, &template.widths);
+                scale.0 = scale.0.min(max_scale.0);
+
+                // For each edge and corner, request the render task by content key
+                // from the render task cache. This ensures that the render task for
+                // this segment will be available for batching later in the frame.
+                let mut handles: SmallVec<[RenderTaskCacheEntryHandle; 8]> = SmallVec::new();
+                let surfaces = &mut frame_state.surfaces;
+
+                for segment in &template.border_segments {
+                    // Update the cache key device size based on requested scale.
+                    let cache_size = to_cache_size(segment.local_task_size * scale);
+                    let cache_key = RenderTaskCacheKey {
+                        kind: RenderTaskCacheKeyKind::BorderSegment(segment.cache_key.clone()),
+                        size: cache_size,
+                    };
+
+                    handles.push(frame_state.resource_cache.request_render_task(
+                        cache_key,
+                        frame_state.gpu_cache,
+                        frame_state.render_tasks,
+                        None,
+                        false,          // TODO(gw): We don't calculate opacity for borders yet!
+                        |render_tasks| {
+                            let task = RenderTask::new_border_segment(
+                                cache_size,
+                                build_border_instances(
+                                    &segment.cache_key,
+                                    cache_size,
+                                    &template.border,
+                                    scale,
+                                ),
+                            );
+
+                            let task_id = render_tasks.add(task);
+
+                            surfaces[pic_context.surface_index.0].tasks.push(task_id);
+
+                            task_id
+                        }
+                    ));
+                }
+
+                *cache_handles = scratch
+                    .border_cache_handles
+                    .extend(handles);
+            }
+            (
+                PrimitiveInstanceKind::ImageBorder { .. },
+                PrimitiveTemplateKind::ImageBorder { .. }
+            ) => {
+            }
+            (
+                PrimitiveInstanceKind::Rectangle { segment_instance_index, opacity_binding_index, .. },
+                PrimitiveTemplateKind::Rectangle { ref color, .. }
+            ) => {
+                if *segment_instance_index != SegmentInstanceIndex::UNUSED {
+                    let segment_instance = &mut scratch.segment_instances[*segment_instance_index];
+
+                    if let Some(mut request) = frame_state.gpu_cache.request(&mut segment_instance.gpu_cache_handle) {
+                        let segments = &scratch.segments[segment_instance.segments_range];
+
+                        request.push(color.premultiplied());
+
+                        for segment in segments {
+                            request.write_segment(
+                                segment.local_rect,
+                                [0.0; 4],
+                            );
+                        }
+                    }
+                }
+
+                update_opacity_binding(
+                    &mut self.opacity_bindings,
+                    *opacity_binding_index,
+                    frame_context.scene_properties,
+                );
+            }
             _ => {
                 unreachable!();
             }
         }
     }
 }
 
 fn build_gradient_stops_request(
@@ -2836,64 +3242,48 @@ impl<'a> GpuDataRequest<'a> {
         extra_data: [f32; 4],
     ) {
         let _ = VECS_PER_SEGMENT;
         self.push(local_rect);
         self.push(extra_data);
     }
 }
 
-impl BrushPrimitive {
     fn write_brush_segment_description(
-        &mut self,
         prim_local_rect: LayoutRect,
         prim_local_clip_rect: LayoutRect,
         clip_chain: &ClipChainInstance,
-        frame_state: &mut FrameBuildingState,
-    ) {
-        match self.segment_desc {
-            Some(..) => {
-                // If we already have a segment descriptor, skip segment build.
-                return;
-            }
-            None => {
-                // If no segment descriptor built yet, see if it is a brush
-                // type that wants to be segmented.
-                if !self.kind.supports_segments(frame_state.resource_cache) {
-                    return;
-                }
-            }
-        }
-
+        segment_builder: &mut SegmentBuilder,
+        clip_store: &ClipStore,
+        resources: &FrameResources,
+    ) -> bool {
         // If the brush is small, we generally want to skip building segments
         // and just draw it as a single primitive with clip mask. However,
         // if the clips are purely rectangles that have no per-fragment
         // clip masks, we will segment anyway. This allows us to completely
         // skip allocating a clip mask in these cases.
         let is_large = prim_local_rect.size.area() > MIN_BRUSH_SPLIT_AREA;
 
         // TODO(gw): We should probably detect and store this on each
         //           ClipSources instance, to avoid having to iterate
         //           the clip sources here.
         let mut rect_clips_only = true;
 
-        let segment_builder = &mut frame_state.segment_builder;
         segment_builder.initialize(
             prim_local_rect,
             None,
             prim_local_clip_rect
         );
 
         // Segment the primitive on all the local-space clip sources that we can.
         let mut local_clip_count = 0;
         for i in 0 .. clip_chain.clips_range.count {
-            let clip_instance = frame_state
-                .clip_store
+            let clip_instance = clip_store
                 .get_instance_from_range(&clip_chain.clips_range, i);
-            let clip_node = &frame_state.resources.clip_data_store[clip_instance.handle];
+            let clip_node = &resources.clip_data_store[clip_instance.handle];
 
             // If this clip item is positioned by another positioning node, its relative position
             // could change during scrolling. This means that we would need to resegment. Instead
             // of doing that, only segment with clips that have the same positioning node.
             // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only
             // when necessary while scrolling.
             if !clip_instance.flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) {
                 continue;
@@ -2973,117 +3363,236 @@ impl BrushPrimitive {
                             ),
                         );
 
                         segment_builder.push_mask_region(rect, LayoutRect::zero(), None);
                     }
                 }
             }
 
-            match self.segment_desc {
-                Some(..) => panic!("bug: should not already have descriptor"),
-                None => {
-                    // TODO(gw): We can probably make the allocation
-                    //           patterns of this and the segment
-                    //           builder significantly better, by
-                    //           retaining it across primitives.
-                    let mut segments = BrushSegmentVec::new();
-
-                    segment_builder.build(|segment| {
-                        segments.push(
-                            BrushSegment::new(
-                                segment.rect,
-                                segment.has_mask,
-                                segment.edge_flags,
-                                [0.0; 4],
-                                BrushFlags::empty(),
-                            ),
-                        );
-                    });
-
-                    self.segment_desc = Some(BrushSegmentDescriptor {
-                        segments,
-                    });
+            return true
+        }
+
+        false
+    }
+
+impl PrimitiveInstance {
+    fn build_segments_if_needed(
+        &mut self,
+        prim_local_rect: LayoutRect,
+        prim_local_clip_rect: LayoutRect,
+        prim_clip_chain: &ClipChainInstance,
+        frame_state: &mut FrameBuildingState,
+        primitives: &mut [Primitive],
+        resources: &FrameResources,
+        scratch: &mut PrimitiveScratchBuffer,
+    ) {
+        match self.kind {
+            PrimitiveInstanceKind::Rectangle { ref mut segment_instance_index, .. } => {
+                if *segment_instance_index == SegmentInstanceIndex::INVALID {
+                    let mut segments: SmallVec<[BrushSegment; 8]> = SmallVec::new();
+
+                    if write_brush_segment_description(
+                        prim_local_rect,
+                        prim_local_clip_rect,
+                        prim_clip_chain,
+                        &mut frame_state.segment_builder,
+                        frame_state.clip_store,
+                        resources,
+                    ) {
+                        frame_state.segment_builder.build(|segment| {
+                            segments.push(
+                                BrushSegment::new(
+                                    segment.rect,
+                                    segment.has_mask,
+                                    segment.edge_flags,
+                                    [0.0; 4],
+                                    BrushFlags::empty(),
+                                ),
+                            );
+                        });
+                    }
+
+                    if segments.is_empty() {
+                        *segment_instance_index = SegmentInstanceIndex::UNUSED;
+                    } else {
+                        let segments_range = scratch
+                            .segments
+                            .extend(segments);
+
+                        let instance = SegmentedInstance {
+                            segments_range,
+                            gpu_cache_handle: GpuCacheHandle::new(),
+                        };
+
+                        *segment_instance_index = scratch
+                            .segment_instances
+                            .push(instance);
+                    };
                 }
             }
+            PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
+                let prim = &mut primitives[prim_index.0];
+                match prim.details {
+                    PrimitiveDetails::Brush(ref mut brush) => {
+                        match brush.segment_desc {
+                            Some(..) => {
+                                // If we already have a segment descriptor, skip segment build.
+                                return;
+                            }
+                            None => {
+                                // If no segment descriptor built yet, see if it is a brush
+                                // type that wants to be segmented.
+                                if brush.kind.supports_segments(frame_state.resource_cache) {
+                                    let mut segments = BrushSegmentVec::new();
+
+                                    if write_brush_segment_description(
+                                        prim_local_rect,
+                                        prim_local_clip_rect,
+                                        prim_clip_chain,
+                                        &mut frame_state.segment_builder,
+                                        frame_state.clip_store,
+                                        resources,
+                                    ) {
+                                        frame_state.segment_builder.build(|segment| {
+                                            segments.push(
+                                                BrushSegment::new(
+                                                    segment.rect,
+                                                    segment.has_mask,
+                                                    segment.edge_flags,
+                                                    [0.0; 4],
+                                                    BrushFlags::empty(),
+                                                ),
+                                            );
+                                        });
+                                    }
+
+                                    if !segments.is_empty() {
+                                        brush.segment_desc = Some(BrushSegmentDescriptor {
+                                            segments,
+                                        });
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            _ => {}
         }
     }
-}
-
-impl PrimitiveInstance {
+
     fn update_clip_task_for_brush(
         &mut self,
-        prim_local_rect: LayoutRect,
         prim_local_clip_rect: LayoutRect,
         root_spatial_node_index: SpatialNodeIndex,
         prim_bounding_rect: WorldRect,
         prim_context: &PrimitiveContext,
         prim_clip_chain: &ClipChainInstance,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         clip_node_collector: &Option<ClipNodeCollector>,
-        primitives: &mut [Primitive],
+        primitives: &[Primitive],
+        resources: &mut FrameResources,
+        scratch: &mut PrimitiveScratchBuffer,
     ) -> bool {
-        let brush = match self.kind {
+        let segments = match self.kind {
             PrimitiveInstanceKind::Picture { .. } |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::Clear |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 return false;
             }
-            PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
-                let prim = &mut primitives[prim_index.0];
-                match prim.details {
-                    PrimitiveDetails::Brush(ref mut brush) => brush,
+            PrimitiveInstanceKind::Rectangle { segment_instance_index, .. } => {
+                debug_assert!(segment_instance_index != SegmentInstanceIndex::INVALID);
+
+                if segment_instance_index == SegmentInstanceIndex::UNUSED {
+                    return false;
+                }
+
+                let segment_instance = &scratch.segment_instances[segment_instance_index];
+
+                &mut scratch.segments[segment_instance.segments_range]
+            }
+            PrimitiveInstanceKind::ImageBorder { .. } => {
+                let prim_data = &resources.prim_data_store[self.prim_data_handle];
+
+                // TODO: This is quite messy - once we remove legacy primitives we
+                //       can change this to be a tuple match on (instance, template)
+                match prim_data.kind {
+                    PrimitiveTemplateKind::ImageBorder { ref brush_segments, .. } => {
+                        brush_segments.as_slice()
+                    }
+                    _ => {
+                        unreachable!();
+                    }
                 }
             }
-        };
-
-        brush.write_brush_segment_description(
-            prim_local_rect,
-            prim_local_clip_rect,
-            prim_clip_chain,
-            frame_state,
-        );
-
-        let segment_desc = match brush.segment_desc {
-            Some(ref mut description) => description,
-            None => return false,
+            PrimitiveInstanceKind::NormalBorder { .. } => {
+                let prim_data = &resources.prim_data_store[self.prim_data_handle];
+
+                // TODO: This is quite messy - once we remove legacy primitives we
+                //       can change this to be a tuple match on (instance, template)
+                match prim_data.kind {
+                    PrimitiveTemplateKind::NormalBorder { ref template, .. } => {
+                        template.brush_segments.as_slice()
+                    }
+                    _ => {
+                        unreachable!();
+                    }
+                }
+            }
+            PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
+                let prim = &primitives[prim_index.0];
+                match prim.details {
+                    PrimitiveDetails::Brush(ref brush) => {
+                        match brush.segment_desc {
+                            Some(ref description) => {
+                                &description.segments
+                            }
+                            None => {
+                                return false;
+                            }
+                        }
+                    }
+                }
+            }
         };
 
         // If there are no segments, early out to avoid setting a valid
         // clip task instance location below.
-        if segment_desc.segments.is_empty() {
+        if segments.is_empty() {
             return true;
         }
 
         // Set where in the clip mask instances array the clip mask info
         // can be found for this primitive. Each segment will push the
         // clip mask information for itself in update_clip_task below.
-        self.clip_task_index = ClipTaskIndex(frame_state.scratch.clip_mask_instances.len() as _);
+        self.clip_task_index = ClipTaskIndex(scratch.clip_mask_instances.len() as _);
 
         // If we only built 1 segment, there is no point in re-running
         // the clip chain builder. Instead, just use the clip chain
         // instance that was built for the main primitive. This is a
         // significant optimization for the common case.
-        if segment_desc.segments.len() == 1 {
-            let clip_mask_kind = segment_desc.segments[0].update_clip_task(
+        if segments.len() == 1 {
+            let clip_mask_kind = segments[0].update_clip_task(
                 Some(prim_clip_chain),
                 prim_bounding_rect,
                 root_spatial_node_index,
                 pic_context.surface_index,
                 pic_state,
                 frame_context,
                 frame_state,
+                &mut resources.clip_data_store,
             );
-            frame_state.scratch.clip_mask_instances.push(clip_mask_kind);
+            scratch.clip_mask_instances.push(clip_mask_kind);
         } else {
-            for segment in &mut segment_desc.segments {
+            for segment in segments {
                 // Build a clip chain for the smaller segment rect. This will
                 // often manage to eliminate most/all clips, and sometimes
                 // clip the segment completely.
                 let segment_clip_chain = frame_state
                     .clip_store
                     .build_clip_chain_instance(
                         self.clip_chain_id,
                         segment.local_rect,
@@ -3092,29 +3601,30 @@ impl PrimitiveInstance {
                         &pic_state.map_local_to_pic,
                         &pic_state.map_pic_to_world,
                         &frame_context.clip_scroll_tree,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
                         frame_context.device_pixel_scale,
                         &pic_context.dirty_world_rect,
                         clip_node_collector,
-                        &mut frame_state.resources.clip_data_store,
+                        &mut resources.clip_data_store,
                     );
 
                 let clip_mask_kind = segment.update_clip_task(
                     segment_clip_chain.as_ref(),
                     prim_bounding_rect,
                     root_spatial_node_index,
                     pic_context.surface_index,
                     pic_state,
                     frame_context,
                     frame_state,
+                    &mut resources.clip_data_store,
                 );
-                frame_state.scratch.clip_mask_instances.push(clip_mask_kind);
+                scratch.clip_mask_instances.push(clip_mask_kind);
             }
         }
 
         true
     }
 
     fn prepare_prim_for_render_inner(
         &mut self,
@@ -3364,83 +3874,16 @@ impl PrimitiveInstance {
                                     tile: None,
                                 },
                                 frame_state.gpu_cache,
                             );
                         }
 
                         PrimitiveOpacity::opaque()
                     }
-                    BrushKind::Border { ref mut source, .. } => {
-                        match *source {
-                            BorderSource::Image(request) => {
-                                let image_properties = frame_state
-                                    .resource_cache
-                                    .get_image_properties(request.key);
-
-                                if let Some(image_properties) = image_properties {
-                                    frame_state.resource_cache.request_image(
-                                        request,
-                                        frame_state.gpu_cache,
-                                    );
-                                    PrimitiveOpacity {
-                                        is_opaque: image_properties.descriptor.is_opaque,
-                                    }
-                                } else {
-                                    PrimitiveOpacity::opaque()
-                                }
-                            }
-                            BorderSource::Border { ref border, ref widths, ref mut segments, .. } => {
-                                // TODO(gw): When drawing in screen raster mode, we should also incorporate a
-                                //           scale factor from the world transform to get an appropriately
-                                //           sized border task.
-                                let world_scale = LayoutToWorldScale::new(1.0);
-                                let mut scale = world_scale * frame_context.device_pixel_scale;
-                                let max_scale = get_max_scale_for_border(&border.radius, widths);
-                                scale.0 = scale.0.min(max_scale.0);
-
-                                // For each edge and corner, request the render task by content key
-                                // from the render task cache. This ensures that the render task for
-                                // this segment will be available for batching later in the frame.
-                                for segment in segments {
-                                    // Update the cache key device size based on requested scale.
-                                    segment.cache_key.size = to_cache_size(segment.local_task_size * scale);
-
-                                    let surfaces = &mut frame_state.surfaces;
-
-                                    segment.handle = Some(frame_state.resource_cache.request_render_task(
-                                        segment.cache_key.clone(),
-                                        frame_state.gpu_cache,
-                                        frame_state.render_tasks,
-                                        None,
-                                        false,          // TODO(gw): We don't calculate opacity for borders yet!
-                                        |render_tasks| {
-                                            let task = RenderTask::new_border_segment(
-                                                segment.cache_key.size,
-                                                build_border_instances(
-                                                    &segment.cache_key,
-                                                    border,
-                                                    scale,
-                                                ),
-                                            );
-
-                                            let task_id = render_tasks.add(task);
-
-                                            surfaces[pic_context.surface_index.0].tasks.push(task_id);
-
-                                            task_id
-                                        }
-                                    ));
-                                }
-
-                                // Shouldn't matter, since the segment opacity is used instead
-                                PrimitiveOpacity::translucent()
-                            }
-                        }
-                    }
                     BrushKind::RadialGradient {
                         stops_range,
                         center,
                         start_radius,
                         end_radius,
                         ratio_xy,
                         extend_mode,
                         stretch_size,
@@ -3549,24 +3992,16 @@ impl PrimitiveInstance {
                         let stride = stretch_size + tile_spacing;
                         if stride.width >= prim_local_rect.size.width &&
                            stride.height >= prim_local_rect.size.height {
                             stops_opacity
                         } else {
                             PrimitiveOpacity::translucent()
                         }
                     }
-                    BrushKind::Solid { ref color, opacity_binding_index, .. } => {
-                        let current_opacity = update_opacity_binding(
-                            opacity_bindings,
-                            opacity_binding_index,
-                            frame_context.scene_properties,
-                        );
-                        PrimitiveOpacity::from_alpha(current_opacity * color.a)
-                    }
                 };
             }
         }
 
         if is_tiled {
             // we already requested each tile's gpu data.
             return;
         }
@@ -3591,38 +4026,51 @@ impl PrimitiveInstance {
         root_spatial_node_index: SpatialNodeIndex,
         clip_chain: &ClipChainInstance,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         clip_node_collector: &Option<ClipNodeCollector>,
         primitives: &mut [Primitive],
+        resources: &mut FrameResources,
+        scratch: &mut PrimitiveScratchBuffer,
     ) {
         if self.is_chased() {
             println!("\tupdating clip task with pic rect {:?}", clip_chain.pic_clip_rect);
         }
 
         // Reset clips from previous frames since we may clip differently each frame.
         self.clip_task_index = ClipTaskIndex::INVALID;
 
+        self.build_segments_if_needed(
+            prim_local_rect,
+            prim_local_clip_rect,
+            clip_chain,
+            frame_state,
+            primitives,
+            resources,
+            scratch,
+        );
+
         // First try to  render this primitive's mask using optimized brush rendering.
         if self.update_clip_task_for_brush(
-            prim_local_rect,
             prim_local_clip_rect,
             root_spatial_node_index,
             prim_bounding_rect,
             prim_context,
             &clip_chain,
             pic_context,
             pic_state,
             frame_context,
             frame_state,
             clip_node_collector,
             primitives,
+            resources,
+            scratch,
         ) {
             if self.is_chased() {
                 println!("\tsegment tasks have been created for clipping");
             }
             return;
         }
 
         if clip_chain.needs_mask {
@@ -3636,27 +4084,27 @@ impl PrimitiveInstance {
                 let clip_task = RenderTask::new_mask(
                     device_rect,
                     clip_chain.clips_range,
                     root_spatial_node_index,
                     frame_state.clip_store,
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_state.render_tasks,
-                    &mut frame_state.resources.clip_data_store,
+                    &mut resources.clip_data_store,
                 );
 
                 let clip_task_id = frame_state.render_tasks.add(clip_task);
                 if self.is_chased() {
                     println!("\tcreated task {:?} with device rect {:?}",
                         clip_task_id, device_rect);
                 }
                 // Set the global clip mask instance for this primitive.
-                let clip_task_index = ClipTaskIndex(frame_state.scratch.clip_mask_instances.len() as _);
-                frame_state.scratch.clip_mask_instances.push(ClipMaskKind::Mask(clip_task_id));
+                let clip_task_index = ClipTaskIndex(scratch.clip_mask_instances.len() as _);
+                scratch.clip_mask_instances.push(ClipMaskKind::Mask(clip_task_id));
                 self.clip_task_index = clip_task_index;
                 frame_state.surfaces[pic_context.surface_index.0].tasks.push(clip_task_id);
             }
         }
     }
 }
 
 pub fn get_raster_rects(
@@ -3752,17 +4200,17 @@ fn update_opacity_binding(
 #[cfg(target_os = "linux")]
 fn test_struct_sizes() {
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
-    assert_eq!(mem::size_of::<PrimitiveContainer>(), 256, "PrimitiveContainer size changed");
+    assert_eq!(mem::size_of::<PrimitiveContainer>(), 216, "PrimitiveContainer size changed");
     assert_eq!(mem::size_of::<PrimitiveInstance>(), 120, "PrimitiveInstance size changed");
     assert_eq!(mem::size_of::<PrimitiveInstanceKind>(), 16, "PrimitiveInstanceKind size changed");
     assert_eq!(mem::size_of::<PrimitiveTemplate>(), 176, "PrimitiveTemplate size changed");
     assert_eq!(mem::size_of::<PrimitiveTemplateKind>(), 112, "PrimitiveTemplateKind size changed");
     assert_eq!(mem::size_of::<PrimitiveKey>(), 152, "PrimitiveKey size changed");
     assert_eq!(mem::size_of::<PrimitiveKeyKind>(), 112, "PrimitiveKeyKind size changed");
-    assert_eq!(mem::size_of::<Primitive>(), 280, "Primitive size changed");
+    assert_eq!(mem::size_of::<Primitive>(), 240, "Primitive size changed");
 }
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -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/. */
 
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceSize, DeviceIntSideOffsets};
 use api::{DevicePixelScale, ImageDescriptor, ImageFormat};
 use api::{LineStyle, LineOrientation, LayoutSize, ColorF, DirtyRect};
 #[cfg(feature = "pathfinder")]
 use api::FontRenderMode;
-use border::{BorderCornerCacheKey, BorderEdgeCacheKey};
+use border::BorderSegmentCacheKey;
 use box_shadow::{BoxShadowCacheKey};
 use clip::{ClipDataStore, ClipItem, ClipStore, ClipNodeRange};
 use clip_scroll_tree::SpatialNodeIndex;
 use device::TextureFilter;
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use glyph_rasterizer::GpuGlyphCacheKey;
@@ -1113,18 +1113,17 @@ impl RenderTask {
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskCacheKeyKind {
     BoxShadow(BoxShadowCacheKey),
     Image(ImageCacheKey),
     #[allow(dead_code)]
     Glyph(GpuGlyphCacheKey),
     Picture(SurfaceCacheKey),
-    BorderEdge(BorderEdgeCacheKey),
-    BorderCorner(BorderCornerCacheKey),
+    BorderSegment(BorderSegmentCacheKey),
     LineDecoration(LineDecorationCacheKey),
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskCacheKey {
     pub size: DeviceIntSize,
--- a/gfx/wr/webrender/src/resource_cache.rs
+++ b/gfx/wr/webrender/src/resource_cache.rs
@@ -789,32 +789,32 @@ impl ResourceCache {
 
         // Each cache entry stores its own copy of the image's dirty rect. This allows them to be
         // updated independently.
         match self.cached_images.try_get_mut(&image_key) {
             Some(&mut ImageResult::UntiledAuto(ref mut entry)) => {
                 entry.dirty_rect = entry.dirty_rect.union(dirty_rect);
             }
             Some(&mut ImageResult::Multi(ref mut entries)) => {
-                let tile_size = tiling.unwrap();
                 for (key, entry) in entries.iter_mut() {
                     // We want the dirty rect relative to the tile and not the whole image.
-                    let local_dirty_rect = match key.tile {
-                        Some(tile) => {
+                    let local_dirty_rect = match (tiling, key.tile) {
+                        (Some(tile_size), Some(tile)) => {
                             dirty_rect.map(|mut rect|{
                                 let tile_offset = DeviceIntPoint::new(
                                     tile.x as i32,
                                     tile.y as i32,
                                 ) * tile_size as i32;
                                 rect.origin -= tile_offset.to_vector();
 
                                 rect
                             })
                         }
-                        None => *dirty_rect,
+                        (None, Some(..)) => DirtyRect::All,
+                        _ => *dirty_rect,
                     };
                     entry.dirty_rect = entry.dirty_rect.union(&local_dirty_rect);
                 }
             }
             _ => {}
         }
 
         *image = ImageResource {
--- a/gfx/wr/webrender/src/storage.rs
+++ b/gfx/wr/webrender/src/storage.rs
@@ -26,16 +26,17 @@ impl<T> PartialEq for Index<T> {
 
 impl<T> Index<T> {
     fn new(idx: usize) -> Self {
         debug_assert!(idx < u32::max_value() as usize);
         Index(idx as u32, PhantomData)
     }
 
     pub const INVALID: Index<T> = Index(u32::MAX, PhantomData);
+    pub const UNUSED: Index<T> = Index(u32::MAX-1, PhantomData);
 }
 
 #[derive(Debug)]
 pub struct Range<T> {
     pub start: Index<T>,
     pub end: Index<T>,
 }
 
@@ -73,16 +74,20 @@ impl<T> Storage<T> {
             data: Vec::with_capacity(initial_capacity),
         }
     }
 
     pub fn len(&self) -> usize {
         self.data.len()
     }
 
+    pub fn clear(&mut self) {
+        self.data.clear();
+    }
+
     pub fn push(&mut self, t: T) -> Index<T> {
         let index = self.data.len();
         self.data.push(t);
         Index(index as u32, PhantomData)
     }
 
     pub fn recycle(&mut self) {
         recycle_vec(&mut self.data);
--- a/gfx/wr/webrender/src/surface.rs
+++ b/gfx/wr/webrender/src/surface.rs
@@ -237,16 +237,19 @@ impl SurfaceDescriptor {
             // descriptor.
             match prim_instance.kind {
                 PrimitiveInstanceKind::Picture { .. } |
                 PrimitiveInstanceKind::LegacyPrimitive { .. } => {
                     return None;
                 }
                 PrimitiveInstanceKind::LineDecoration { .. } |
                 PrimitiveInstanceKind::TextRun { .. } |
+                PrimitiveInstanceKind::NormalBorder { .. } |
+                PrimitiveInstanceKind::Rectangle { .. } |
+                PrimitiveInstanceKind::ImageBorder { .. } |
                 PrimitiveInstanceKind::Clear => {}
             }
 
             // Record the unique identifier for the content represented
             // by this primitive.
             primitive_ids.push(prim_instance.prim_data_handle.uid());
         }
 
--- a/gfx/wr/webrender_api/src/display_item.rs
+++ b/gfx/wr/webrender_api/src/display_item.rs
@@ -323,17 +323,17 @@ impl NormalBorder {
         normalize_side(&mut self.left, widths.left);
         normalize_side(&mut self.right, widths.right);
         normalize_side(&mut self.top, widths.top);
         normalize_side(&mut self.bottom, widths.bottom);
     }
 }
 
 #[repr(u32)]
-#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize, Eq, Hash)]
 pub enum RepeatMode {
     Stretch,
     Repeat,
     Round,
     Space,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
--- a/gfx/wr/webrender_api/src/display_list.rs
+++ b/gfx/wr/webrender_api/src/display_list.rs
@@ -84,18 +84,16 @@ pub struct BuiltDisplayListDescriptor {
     /// The second IPC time stamp: after serialization
     builder_finish_time: u64,
     /// The third IPC time stamp: just before sending
     send_start_time: u64,
     /// The amount of clipping nodes created while building this display list.
     total_clip_nodes: usize,
     /// The amount of spatial nodes created while building this display list.
     total_spatial_nodes: usize,
-    /// An estimate of the number of primitives that will be created by this display list.
-    prim_count_estimate: usize,
 }
 
 pub struct BuiltDisplayListIter<'a> {
     list: &'a BuiltDisplayList,
     data: &'a [u8],
     cur_item: DisplayItem,
     cur_stops: ItemRange<GradientStop>,
     cur_glyphs: ItemRange<GlyphInstance>,
@@ -143,20 +141,16 @@ impl BuiltDisplayList {
     pub fn item_slice(&self) -> &[u8] {
         &self.data[..]
     }
 
     pub fn descriptor(&self) -> &BuiltDisplayListDescriptor {
         &self.descriptor
     }
 
-    pub fn prim_count_estimate(&self) -> usize {
-        self.descriptor.prim_count_estimate
-    }
-
     pub fn times(&self) -> (u64, u64, u64) {
         (
             self.descriptor.builder_start_time,
             self.descriptor.builder_finish_time,
             self.descriptor.send_start_time,
         )
     }
 
@@ -605,17 +599,16 @@ impl<'de> Deserialize<'de> for BuiltDisp
         Ok(BuiltDisplayList {
             data,
             descriptor: BuiltDisplayListDescriptor {
                 builder_start_time: 0,
                 builder_finish_time: 1,
                 send_start_time: 0,
                 total_clip_nodes,
                 total_spatial_nodes,
-                prim_count_estimate: 0,
             },
         })
     }
 }
 
 // This is a replacement for bincode::serialize_into(&vec)
 // The default implementation Write for Vec will basically
 // call extend_from_slice(). Serde ends up calling that for every
@@ -842,17 +835,16 @@ pub struct SaveState {
 
 #[derive(Clone)]
 pub struct DisplayListBuilder {
     pub data: Vec<u8>,
     pub pipeline_id: PipelineId,
     clip_stack: Vec<ClipAndScrollInfo>,
     next_clip_index: usize,
     next_spatial_index: usize,
-    prim_count_estimate: usize,
     next_clip_chain_id: u64,
     builder_start_time: u64,
 
     /// The size of the content of this display list. This is used to allow scrolling
     /// outside the bounds of the display list items themselves.
     content_size: LayoutSize,
     save_state: Option<SaveState>,
 }
@@ -872,17 +864,16 @@ impl DisplayListBuilder {
         DisplayListBuilder {
             data: Vec::with_capacity(capacity),
             pipeline_id,
             clip_stack: vec![
                 ClipAndScrollInfo::simple(ClipId::root_scroll_node(pipeline_id)),
             ],
             next_clip_index: FIRST_CLIP_NODE_INDEX,
             next_spatial_index: FIRST_SPATIAL_NODE_INDEX,
-            prim_count_estimate: 0,
             next_clip_chain_id: 0,
             builder_start_time: start_time,
             content_size,
             save_state: None,
         }
     }
 
     /// Return the content size for this display list
@@ -969,34 +960,32 @@ impl DisplayListBuilder {
     }
 
     /// Add an item to the display list.
     ///
     /// NOTE: It is usually preferable to use the specialized methods to push
     /// display items. Pushing unexpected or invalid items here may
     /// result in WebRender panicking or behaving in unexpected ways.
     pub fn push_item(&mut self, item: SpecificDisplayItem, info: &LayoutPrimitiveInfo) {
-        self.prim_count_estimate += 1;
         serialize_fast(
             &mut self.data,
             &DisplayItem {
                 item,
                 clip_and_scroll: *self.clip_stack.last().unwrap(),
                 info: *info,
             },
         )
     }
 
     fn push_item_with_clip_scroll_info(
         &mut self,
         item: SpecificDisplayItem,
         info: &LayoutPrimitiveInfo,
         scrollinfo: ClipAndScrollInfo
     ) {
-        self.prim_count_estimate += 1;
         serialize_fast(
             &mut self.data,
             &DisplayItem {
                 item,
                 clip_and_scroll: scrollinfo,
                 info: *info,
             },
         )
@@ -1543,15 +1532,14 @@ impl DisplayListBuilder {
             self.content_size,
             BuiltDisplayList {
                 descriptor: BuiltDisplayListDescriptor {
                     builder_start_time: self.builder_start_time,
                     builder_finish_time: end_time,
                     send_start_time: 0,
                     total_clip_nodes: self.next_clip_index,
                     total_spatial_nodes: self.next_spatial_index,
-                    prim_count_estimate: self.prim_count_estimate,
                 },
                 data: self.data,
             },
         )
     }
 }
--- a/gfx/wr/webrender_api/src/units.rs
+++ b/gfx/wr/webrender_api/src/units.rs
@@ -118,16 +118,17 @@ pub type RasterToLayoutTransform = Typed
 pub type PictureToRasterTransform = TypedTransform3D<f32, PicturePixel, RasterPixel>;
 pub type RasterToPictureTransform = TypedTransform3D<f32, RasterPixel, PicturePixel>;
 
 // Fixed position coordinates, to avoid float precision errors.
 pub type LayoutPointAu = TypedPoint2D<Au, LayoutPixel>;
 pub type LayoutRectAu = TypedRect<Au, LayoutPixel>;
 pub type LayoutSizeAu = TypedSize2D<Au, LayoutPixel>;
 pub type LayoutVector2DAu = TypedVector2D<Au, LayoutPixel>;
+pub type LayoutSideOffsetsAu = TypedSideOffsets2D<Au, LayoutPixel>;
 
 pub type ImageDirtyRect = DirtyRect<i32, DevicePixel>;
 pub type BlobDirtyRect = DirtyRect<i32, LayoutPixel>;
 
 pub type BlobToDeviceTranslation = TypedTranslation2D<i32, LayoutPixel, DevicePixel>;
 
 /// Stores two coordinates in texel space. The coordinates
 /// are stored in texel coordinates because the texture atlas
@@ -227,8 +228,28 @@ impl AuHelpers<LayoutRectAu> for LayoutR
 
     fn to_au(&self) -> LayoutRectAu {
         LayoutRectAu::new(
             self.origin.to_au(),
             self.size.to_au(),
         )
     }
 }
+
+impl AuHelpers<LayoutSideOffsetsAu> for LayoutSideOffsets {
+    fn from_au(offsets: LayoutSideOffsetsAu) -> Self {
+        LayoutSideOffsets::new(
+            offsets.top.to_f32_px(),
+            offsets.right.to_f32_px(),
+            offsets.bottom.to_f32_px(),
+            offsets.left.to_f32_px(),
+        )
+    }
+
+    fn to_au(&self) -> LayoutSideOffsetsAu {
+        LayoutSideOffsetsAu::new(
+            Au::from_f32_px(self.top),
+            Au::from_f32_px(self.right),
+            Au::from_f32_px(self.bottom),
+            Au::from_f32_px(self.left),
+        )
+    }
+}
index 5f861f2b42fd3ea6d0e22eda1870340f1f3a1d77..e670f1d7a103dfe6fb3595468df8d8535926f58f
GIT binary patch
literal 48936
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
index f413da144e920ef21536abb4dc2f484665072af3..6b5299f386e4e6941e920d45a8629978a5fed6f6
GIT binary patch
literal 1630
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
NC{{oyqh&{2-oN|I5%2&2
index a9721cf19cb75d22dd3c8d0e4e04c99a86af26f3..da879c77a6ec87a276898144a79e18b78548ba13
GIT binary patch
literal 27629
zc%1Bf`8$<c{Pu%Wicley45bjEGQ~EPl5v|;gfx)M<2KVlB2yuWZO9a2CuH1Pna8cn
z<KCI)d7j_3biUX1zW>5|eShdUx}NnsYkh|MbKjq}R)HGmyL2>XX%GaVQ&POGg&<Vm
z4`t1f!{Be!(-OxJ#3Enm_Dvm^xY>b2{kpn@{Y9LlWGmf;pht<_{%kioAJHjdl3z=P
z4q%_~v@#5QKHZJIfBMM3yqxDLTd$7o?;D!6;PuRoiAmXYN+hhW7YLaNumw!>N|DXx
zcMW29WEsJSkVkmU6!??#QV<jTnR5FrCHUbJ$*To_4L^AY&JfZ1q(}jN$SE^jhQBHX
zUN{7QUjE;~|2z192mk*uSnfMb`5l{YfwDV&<$IT`ic|Bgu`A=UDzZDAR=NK9&e=L8
zEBm{P9Nv*)eNFomh@55;c!@?UVV|^LTM?FYyg5Ngz$lI>`I5>zEfKR&o$p^(s#&x)
zms&sF(Td`$=fLTz=$Gj9+TERuwMm&RRNvD#u)*%ER(M;N&ZdkurYc9hqg+du-7G(j
zd`*J$Wh&Hodhd(k+&RxZ*QL$f^$2^WpkFgRN`YGwTp9WWDHr`wYJ;^w%n`TXJjq_$
z-m>>ThwnJOXjj~mOw#e(OwHflt7rQ%fQio3Q{O9lMHf+epZZ*4uvY53a{B+UkN2>J
zXF$e^x~!6R+C@EHjU?V|{Y%Ypm#s=NRt7Ly8xH?g?956?v7g>^8x(9Gi*>J@>RMNS
zk?q)TSI?GhTRVmGj=G0KcOpLIJHXb7N50tK)Sv!z9W(b3zq8c1<*l8bIh=2vd1~&I
zf9>K<txEqs)0c%JW8Me4S<1B>Lb^Fu*qlGKC*HL0y3$`wiR@4U7x~;;h?u{aZBR%Q
zcf4ouk+zK=SM~`*a;+7QHJzHqmEY@0Ras5kmm)9MdkvnD+ux6Hy|OS85m9@n@`+EM
zb?i()sTmSJ4vlE(x6n7iO~_mBF-tI?>CTNQRZsBVD>iNVFetmFg4vR6>@p>M`nFE0
z{}Hrs`cW9W(px@l?lVs*5Z53W70QE!M2+~drkLa|BX_k39`(+=7Ma+~8iP6QxTB*P
zc-&3GCnok?+CzL&V_n60j!oC<lYLP0s!5dL(eV2zE5Gk@Jfmq}ZjntWelKV^6`Gq-
zG3;4BZfm^0+1sYKQt^7#m;T*apStA6pAkwQsawE`kEHzuPvn_4w}Q3ic<pXIo0IWh
zPulw|wfpdyF_kBF#zNazBFWjxJb*2QD>VG0$lJBv##khL8-8&oRMet#B7no|*N>M}
z!iQeezq_Pg=90T#_o}$FYm^|i7^Ph?_+xM;QzK2k5KVIm8Mz9RrTl!qKOZmeuQqjA
ztSbYbD!K1;?52HlT^WyVryjAVX`kbjuW#kB7aK~s`J>Wk<2C~#HwRN-frGF)7TdJ&
zS}B6tyuZjU=I#c~8~#^wt4?2Wam+Nj8%sk~jH&U`sR|)wN0DNGVAUVx{uN9$AHvQE
zD-;em8<_f36_UNCWwl%$UK6R(Q!yTNAkqfxet!`atj6%9or3eO28dMG`C1O|reI+X
zDVMKXE0Yltx5fLT_-JCcB*|R@ge7Gc?@Sez40-B|djd9yTt6^{PuY46tIhM3iQjG`
zZ|{n@uURsuUVS=r+t|~?YL<WgoZDx|UURLsv^d#AoG$<r$dPC&xhiffqvnd_ab-(E
zn4nrg!rb~Qi?M5#tHb0<+phR|+`b_4`ZS2qa7=}b=l)h#xnm`r_|v)D{_CHD`g{o`
zo4rkXq|AG2FS>}{TsxS@U50oHWHAvKY6)FLE9y9*5pqsKi~ru21@t1#Xhr~z+T-U`
zmwJN^uWm<{I~DSc1HN3mLWdA->?N_xOLl0H9>>0O2*z!Db}ajQqozKO*p;6WC_{W^
z6hN$u_yc!8AF2v0*_z4{H6zB|;;@MC6eDbHNd9E;;y#vmW3~M$XSGRyD|j~}eD`w2
zu={$yLs(512aZ8z;|Ho|P|SFLp&&zOCm_2Jt&obD{TPg<_PHVtU!SioyQ`xvvzbDa
z-QQ_))$Cd7BbgF<H_pyjFdA>rxzDCHnGmuKcmlR5<=(?(+P8uck(beUx7AkR7OCW(
zJTv~jb5qks#@ibe92?Wd5$oxDYvMvk;s^vFiOazv^L>Rlc8`Ud+4=<ysGjsP9Az=<
z#?T^RRfFVRTfrG*8<2UE3OKbM#+yAc_>8c~)4JmW9?Q)!rCL2Re8zgT8SNQdPE(N&
z`>c2n-lNc&=*_v(`Mq^J!d0wLtbeJ-pmn>PF|+6SR4RJKIkbPN&S(QLNe}}xiSSr+
zZ2Ac<g^A3poYwNaRiXK(<qKZzctXXtR=3<BJ_CgBaM(H}5=0FjX9S6ynfs)6s(~NZ
zK2-e%&lvWT(7V;!#3H6K+ED+rd~P5@$s5tAf<YK(yFFWIF;e%|ZG8ZP54!78HqSw*
zh|%Kx2Q?cRC*48tv_Hj+Fr5HN$oWEcXTdk#dvCMJUDHI%I2gUKnuxRVObS2eeE+&2
zUWqZo(Hz)e5Z13xtFwh<Q}#*J=19cqU;u}yvTm>k!ySeV$r=`#2aWGr&C`|cvW*@>
znAqW~_1n_aw9CDA9mla4H|D}^&&@_pt1(*tFA8)ShNUBK%ePiRpt6R6^n9&#+DgW}
z^`fE6p#KB+KXYJVdU_yfl8(%Nk8RJ6ZU3IFll@T~c~l7<`1`2Y9XeJqRnM)J-y`)A
zc*gSbjgl6Yb(Nr>zwh$QtBYEU9YQQm!#PNr6jjlQ_lIv@)3Y4SO29FCS`E?O`J#Fz
z!(96rDM51NI9Qw{be>Md8O>-z=NN9)58-D--l|xyIlaWGc_w|Z%nNBx@@-FjlV3?s
zLVQ+XjNTsoNzKvj)Mc1{i1w6A6iRHlqgC%^N;7zTHZQC_IiNi=N+<V|IPz!xV8S0H
zoia->^EsfR+$DSO<1zM)HwkmA1?57K;f!cSFp<rtLdc)OgNKX${7dV&zq@WRP~v1X
z8sAEwxrm+O?@zjQG(+QOA+-TF=JQ%#3oEifhR)`dT5b}a>Mzb4Ye|^bwWg8h7F>41
z;>h{cr%Wu9@;AqptR!r9`b=Y$WcMZ*ctxD18}@%GVI`INtoe}*323Bge}_+!juVM@
z*<EiJ@AP%15h|#jQF-WIp54}twS0GudpliL$!Y2$M!7;aFU`XMovEXwpRdTLho+ei
z_T0{9I&9k%6R~5KrXIbwV`kob)`aVvqlP^u)72rv@_`NF;~ltWqgcF+I*D-d96NVI
z!?4tOHNAh8hb6&Y@HU-AN9!}xOycf~uJ`n)E5(c_6`b@kPVS|oNco?HXnw6fhUSiW
zqd+R)IUM875$^>e2%Fsf4YX=HZu8RSomUD)y9&H_8}~yLvy~!7b~A=Umnr24$sl6C
z_4XG#cycAW63>-wC+y9j6jrA?+UNz<dE#vUEmx35cRc){td~7SP8C;TAMMi^)GTin
zi!f~hbf!g`N9_ZrfU%(GMpC|8!T!aRlQdSbVp{8St?ceDRC0t*2k$TFGRn%zDQQTs
zuRSFw8pW^IDOfu5Nn2f7mzv!r@jKbeJxahpO#6%2(L<SE{mFBc#^U`UZj05wG$J00
zng)NRa3(z<sPPrp2w4?Gi6bMd&><H7*Tw;O%=F|%(Tb5h9M)s@)1QP%-!m+6{GC%e
zms#R8ovD+dwU=CNIoe=bk}Kkn=1BHz9S(D6%wV&-y;Nj8T(h(vPC~+kV2FHSCI(ND
z%gbd)UqzrLFFUY#uKbRnEO_8Dlau0)(lOa0N2n`{_j7tKycB<{Kizqd$Ly1bd_3ep
ztUpZ+WaP>8++HKt4WW2W(8^z28^9QQdwQl2rPm)?MKy=L^jwV+ar`Xu$Y*6Tog~LM
z=xwks9P2tFB(+7&nGR$6fmzSDXD|A`XLGB49MLVyyqBFRz1A&{A$u0S=*R>_lrKY(
z<jrTv;dzlv6R8la$W3v9ZQmxxdf;6W)1Db3XMJF8A{B#Fq7mM0Q@f1s-j~lRg2fk?
zW!jBEMNq5kUhMY!J;@;RL3K;sZI3Loyw{$Q?nc4c1uOZ3QNW=ZkA3)X{aKLS;!#W-
zz3h(d?yQaXa@Vi)EXN!7#Z66aZxz{TCCIeK+PG0=L<;bWTD&ilWu{*{6fS&VMEl9q
z=&7_=>!tLK$jjOapa!;oHL27o_wtMqGA5Nu3^fX+{`9S!tZQX4cA?7f^Nm6|N0vCg
zo$6dY?9+W<g<%ja+Kul8M7{Ssa6=9am(!ne)9tO|yqk~3)G5sq_*ZB7L)Znf?+?<Y
zd?R~lg|RQmPH6elkdWj22XWyz@$061WqVPb-?gzMDJ+2P7u{czmMWfvU7A<7+iQO%
zF_{7qfKh#gQZD#l)>oZp?mJDjD_L~qtn#sY?=2_j;@zC27poY1?P}QyM&BE_iK|~R
z77RIiX?2<hzaB3hu3R0Ym343Mhln}Z6Hmd(4bd@hdutlkQQ~AFy}z?;R6Y7jo}t4$
z9h+u5rc2t|Ssoe4SfKAHa|O=tvq>mJF_V&dO<8S_*A$?5X$;MkEfbqe3UYe@Vmgh*
zm}Q!^#2ekTDSNBBPOH7w`DO92mc+)NrzOT8mh{qs#gV7NV6}<2J(ucvV(i8TYrFj@
zyHk`HtpIg18uSj~NGOwCoyjxeEJ#xWr9D)lh8F38+?iK%5Us;|G52WL1(IzgU1lTi
zv~l6nKoJj@rM>p{3@|$&0j&DC=hZ=30iS`%mRRM7`E=|2(Rsz{6p&MHpmYjXUaI(d
zbSHGOEp=}<)ub}z&YQBAI?g?JraA%1LT74LvLfFO!IfR8a3r9ylF!mstGW9FX)vuz
zFK&@*KzakhWV^@IYSDewwO<IbJ$@L5moCJyFcU+9noR=O()5g6s}sU|OPpH$j@HZ3
z?6Iefqmm>KsMBD8JC&Rg4Edmeoz3Olkk$8YvjtscL5(2d+QrIB|D`LmO%i#kc>TI(
z%`qF!sE?wRlO6SSD{iZ06p0t%r>f^bsJ5m?8%?R(9UZkvEZ-0^EXmjFe$L_b;aKwN
zYqz$>SiwN)b=6mz`Q}`@IWu%fHB_a%_q5Yg>w{7IGkGm#L3cyWii+5eD6%=VYZ@hK
zNU!PuOI9+tzibbcm2vgXw0k0iFtI>0-UrC4!XUNaBU?QR+}#G8HKxvcGV7j_$%3Mb
z_lu>c;B)q)Bl8*;=hfM4eZ&zaQ8=z!^`pq;Wv}1go;8*Q)dFQYhFh%71QNt3NwFEr
z&&;Igdsr#YjP`OzjsU1ZPIFf}GPsaC0dT6P`1Nvhw%2Aw?S^jShECESlNpb5*g0A~
zHd~lV@Jzk-q(W8-IGygSqO_Z$fWG2rBe1IpEDS++_V;!>q{~io#3YBwNG@CJ6ujst
zyOm2@jVhBJEsg0$(&OHh$##5x%Yx8A4y!R@+7dr&6I{5Uc9{I^wPMWmXF3`hiKfjT
zUHwlovLAAaX;$5cy<W|#n5uOt!)f|y1)1ayzXl0vP=AM>akfQHaZLi8B|Fd)vgi5z
zl)|W}xy>}16Xrp>4~N$GXzz{d7rbz#`cZQ*svVFUVN5cRg2TE}-+N41L)fMD1J2pM
z)~n+i<Q%^`V6)?5KC2JxD$k&nwk>MKXUBc^>ra^X7di?o^vJhQPXO;zA0~rj*z@}$
z>TC(Tx7-4}S5dUGu-9(9H+0A+|KR!Mg-Ql~DfU9XxD!loJ=c0_jg!MV=utC&n7{RQ
z8LLzV1ekNT@+1IY)4>TASn)eebI+D!i6dWxpc54VF&2N(mKGB4Hgy>{)LM$M@lGa@
zGww>=3Z!|)7n!M%6OQK9Oj7mNVs2pek%dhXroipFQgt(|(|ZsxevIgauAxfZ+{brt
z$FU0^j9vO$Fig1=6;HedV~-5x$|B`&pvN7B&4^9G$M(T&0kQR`VHN@M2J5j@Z**SY
z1i5qi_KCw<em|KMd5$F>@#U?M3%dG&@&^uP63sw0i}4o&>*DGIAIt#(AbXz2@5H2u
zj@xPHlM3lv*sB-t@&Qkux}C<-|6zevxwQ)D_UC7f@AZpplk^rp8x-bL_yq0(_QgLE
zY5UsdMw9rtDOURRJCcLPjlCnzJAAge(a$1x7{<%z-U5qg*IHgNpyIQ0iiT*sZxKPH
zyL!JEx-As0pR7>7t(_h>bO?d=`{hSviNef%ahmRQWex*XPUA^Uu;y%U0sa=+pjc~<
z!+<8tmazLBfb-^()|*qHGHCf8CLv2K@Kd~}1oTtxW$RCZ73x@4y!@GjTnKB57oa<P
zk&gXJa|~CCPI~B$P)K`ja{hob<<g0RLDuEF&ai^)F-3!_{JbvwJfFe$FM4&T2p*oc
z-I~y-3#c2C{O4@tt`7(Ws|e{4S>^U%Sc>(wXJ`u#6Vufb=5(b15BUUTOc5YmG_JFQ
z0RWIMNXbd^i&)2%$vW*U8X=o6f#<rPgVYS2BWzoH2dUh+x)JX_x5)E@u&H3(S?^a~
zN0e)%iQ-qW>b=Ogx7OdrDrXI|RICA%?P(mg_xa*bwNdpGASCXc#t>=&0XwwQ6nti|
zx_IPRravwIr49feVmytRM#q;)I!<v|ZCNdwKZ;Iu=OzQyIj{S0Z?naF>R(#H1)e|R
zWlPE+5&Vv7J&oYas~8LbngQ#UsgoHg?$ZyuA(mgSrkrQW=sssxbs|P{)&Nj5y&oJ4
zvivdY=|&!!-H3nzs3D@3yqwVxYgwO?%3NRk{^U<`8Kwg`bXYatdZ0JKXEp!%TM^3-
z?d9c6JsMG8G?Eo=WE+4Q()*0X23b5<U>y)AiH$<#{U9i5+I=XfbO7XjJ)eeFd!>1d
zLr~zs5P*h0>-wuf->p(Jb@3EcFbx!E@$00<{*aG=qv$Oj30ndvtqj<Eirx<em$e>H
ze2$PAnrLdzMM%wc<!7+S#ldK9cUzm8#GT*0^Ym$jZQ#|Xl)Ow9or=RXm#8jZ@Te-N
zin`1N-ghkJ#>OKX#10ByE!UmJ+KwDUlz=gzKg-4M+PF!->uGm-rcBr6l%Kw-H+@I?
z@?p)l<nm7v#iCRIUeIyU1H$mb%>blw+HJt{o)}eMI(OP*t;cMeQR6nrT?nK=JMSin
z4h-C$iv=M2S1bK(E_^8@cy3SSFPug{4F!XnQV5x4{h>pDO25;D5fnD)0>0P|1%lM!
z!HygX$HQf<E`hNB?7BSmpV|dXQ`BYQ5%w(op+iA$=1Hc+ge7qf5dI)}{AubpwLvY|
zo*k#-+=jlJ9)*e;@RD(LyUF&9h{OIEJ)d`fY~-H*`7y$lDOA34SS!OoZ>sf@Yru;>
zD?G(h29U6S81S28v4EQxE3End`My_S(e&Y(<V2!x8KrXgK3l^9uepa?JFIndpFW^A
zs4`NP{Os^I$c7dIBtfG8`!8iR_qo&Oh7CnVz8+<Kx1JvFT0@(1iv2eosE+PmSmf#<
zXnA%0<2~4w`{b@Sqi2EMVz*8C?)72f^sBqRK9=7Av3cwVf@|gXU0_O+=c+)R6j~2H
zq_ROS`GP3fP*d4CWX$UQb4k^YEIr?r9_Hr9((%%AbuzuxhobdYQmdY^D<G=EgC)*U
zy@k==dmC8g77sq#CcWODT)w|M55NIP2BpeZblUz`H)-pfh44%t%SG%{SQKaLxInJ!
z7p_tA%0Q3fw%W-848ug(pqbyvLCpN7%I$%JSI%;tn(kC$kOg3{y3`2fnX0T5!U__O
z&CXBU=hI!_@|{dtp)!p^l3SK6jFg>68;_Y@Yr0ekNlme^Z!U`qhim=MFSKEM6n9?3
zwFwE~SKfhQIf|Ryxnp=62(9fVpFq#`fi8Z|$Af6wa<6)gpi~ur3>C+N<_02?m~=B;
zsCYf#OohG}6cWizvzc#C5O8+8t6kII8U>emIPXgBIxZEF<r!Q7y+H}O_T!Wog?uVt
zmwD$FuZD@1u_e9C#-FCa(mu<)5Is5iqjwoL&{<n+YV>QM1R&(G=G3dz!sljanNGqW
z@%F#FssG(FBbv*j2cMY8=|Ex*&m-(y-|-a$(CoSBAKv=CPZ=xe&scMb7%hJuqU)aC
zX~7e(RIAd=+tSVgp3o3^d6+qTnB*y5j1qOr&wuPM9^T-!eF>AkqsC=-n^FvRuftE&
z#0A)mo6NYX0%`Tp_1xlzC!vcpbzEUGAA#Mg7y(s|P)CyzBnSTy@>zjd6AX~mV4dWS
zJFvM&mmy}8F+AYCP_#mb@|*^KpYT6Apq?$$b8YeP*FD(jm0oTVelbu|@Loa8)HT}B
zSrec!sN(L2MsgqIrM#p{Jujc)x(vy3_syx+J5#@IK7K^k3Hj4f|5QiTZh<_tG}EBV
zXrFbd8UXWnS&BNBU8f9n4s*a{n$JQeLw_{gEr3Pn5~;X~0~kauo}ZUjWBh70-*q}x
zvG&iJ$vVjjbU0+EqR0-wK#TPFAuWr}E3V#C6UlhQiw%CNJKESQ1JuNc<~m~9W!=ok
z!*pj;u4c0Ru(@A$|E$czDYEfL$qvBO^hGnQ3^EMF%?k(0D0WUBjD<}deq32=!cc3c
zasM$^v8DuMC&|1mg@S6t+x)K-nCgm#DN_((^U|G45x@wiIv$t-;=yg_uV$d`h`Oyj
zXY<+^G@`g~BUBa+IHkAuSBkS@s_g5-=o@<>AcS3~^4rkb<g3>uuCo|AzO^Q7ntD_I
zj~>|8#u29IQnWH79j7vsk|bxY+HfX-s>aD6y)}{if=s~Q`*MF!S8A=_p{1#4*==>o
z_0S_<f%}|vY#*hT#2wN=lyn+2mcQl>+nLRe5DyOn?h7}2FJv6=T00TQ6TiSGIQZj_
zaEnx10>Tu;AQQf|8leyxN!YAmReXlSr_2qcc4ntGMB!h0fQslNwfx7I0m?Q22*Y8r
zMqCQP3tfC3xs*&L>GAK}*59+cR?()687J(}W~~&KyT0_e2}fPG===iGa-Hl=&T@(5
z*F(#{I;J^X6N0DIxOgx7T}|-YiqeP(9(SsYjjqcD&(E(;oQKjNd>YR7u3+iR^#PaR
z5b#Yw!;<H6F40tSzhi~(^6@I>^yK+Re(yt1wO=ltYCmBQdC*c7+`O>$nJoh$7<3n~
zCt)gX`ah^LxkaGrM&@*iR0`}<L0tPDt{=#-jn4FNwK-@HaH>1bbYEL`c?h{v*F%R8
zSqk10gZK<;j>veAR)ET+SeoJ1{VhGAa1}T)>Y5PNbRhf8fujK{+237n=?}@&P7g&Q
z6Q4D{tM@XgAV*#XSQo{2DPp(6uN=R=cGw<*zkUBu^_$&oj0M0~2czoLg!1Rpo!PM<
z4QLWP-^WQa&shiLeGVsvqPWe}ohHKL?)ZO(UauVhVYyW+yEmwACovkS4Ows0_y8s6
zcG*^DdwQ6N$-nu|*}&%}0ZQpuZb)WI^2LvH0Tdef3XtSk4OSg1*^%X<lc}Rr8Ni^I
ztPoO08D3Dq83beit&j^)1PA}CdDr!2H#A8Aq=qGc>h4~KfkMsh@*@FxipQ0|e?=Fu
zwa!4s1JRygWC-*yP1v<A+&o>?zaXe3rXUB+x|f~*{PzXiQ0{Oj%Ji`6Ofzkd+(Vho
zi<h;NbOelM0H>MGQHr5S&D0mQ;p~Geq|P=dL}O8y&WYd2_ydOcPDOB$2hnuf+Z?fz
z^_KsK^^cIZ68pLZsh{?sKQNNG;H6o*VAx!D&TS$oW=SvWo&XNH&3`QMw5-e-#l>C=
zSQU{wZD~MV@0Yxu0iwg_T;8Lf|2A=X!p#;vD2)nj$fN{h#BX!Cvb?+X$IJQ&#}r0F
z29rW6X7Xgb``Y1eN1@-E1Pc2<y%8dG1D=*fdy0u^gEe;#eZ{B$B>m!SpWazfK8b#o
zNk5E&`$~AZ%Bh-<Zd5%!&>kbc3^H|0?_z|_+?c}v1j4rbmWqowA~*Mcsev0OF95RK
zaM>s^1sWqD&UGxNWPfk_uklfHMU`NVWl{X8zmp?SI`d`2bSWq4FWdB7FP1OZ-y_K>
z0k$R=>N1s)-kzq$>IuXch)aO!x~DPhUjKyOHEnr+nC=}7O-iwYf8<pATS?4&NvC%&
zx=sbby&wA@nYvl+NdTL77AgT-gPn{NWF(`Iq&$b-WaZndZemp&{XrE`s@)u`0yi<L
z;hvhrqdz}hPW2YRvR%f=ObVfySEQ(O7-YSPcUMuDodv+$e}Ae+TXOD(kj6QXILC`6
z9L7H1d2{Ncl<POZTiZ5iXlQoSbi7%Zk$+QFM(1-Bcej){ye0tfr3ST^6tv?@^T*>!
za+-jY$c^L_kDWF2T<r)_yK&WJ;#bn%KghmG%`x*I$R^8C^TAu7dbq8oEJWVA5sfod
zvq|KIar_uiCg-t(UMh10;0xv}6Tc-R8Mi5R;%&dWoQ~f6CEr@&1R^DXBR*Kgru8<T
zZUZZ?Dxe9k@2}4dbd-C0dj~aMihlTs)u!xs0^BI|_1Io{dmsY_ImvD4h_hl$G;mMn
z0&GJR*pQ9*D`T#l=y9LNGuL3}fiN<n_~E9X&+FPQ1fbco1Rzkz+4^L!!MEjx&Wne|
zPQkJHmObB^Vl?5l*Fjt+*wvf?3uOH>95!mO*t#&-`tA~#W$C;iP~Jd)>t;{US@ME3
zxIOWbnq32Dgx|R=2xP!gmrSK@hSnt<@`y`n8|wXyMNs{pges8|Ui7xpS{{L8#$Y=%
z_*lY0JCxU$P*xF1&_+vC^CP_f{M7{W>iET1V5TwGwen0sIiltOSqm|}&hI9jIN2jl
zwmQ|I(0Yo6_=>`(Ld+&!#Krao_Tko!18{@Y_qUP<z0dDt66<{qXv{hb9RB?o4B^pF
zO5BiUI+`uyjIaXTS&;=AB&g1PUdeM?Ir*+iV>BoQGo?7VgYqpEs)7Y~7&vdQrrLCW
zH35Yn3rEn`GjxiQrI5Skva>j{n^DWYOqqdoc?_1S#N+ys49k_GA4(E}3DDs9pjry*
zbK#rCHn4jLMm@;AAmPo|pG1Sn3`ZXbf<b!SB5nVDIPi!Epo4q6U!sg$1`pweu0Q&L
zI2~;nppsKMGOtQ^HnL29bu=2bxUrYL8HF4=GDqolpdh&YClodJ%XJ(JWQ2KtQSPf_
zXFl13OgV&HnrIe}nDjDfAWwCWez<M7C8Q*W(rEj>tKC(OUgL)KWK?Ezgi+L@Q?)Hk
zZJCc9ZX&!s!Q}|Fm-`{)!Q%=)uu8M|MapTbHv%bqx+%`HDPmSBps1L70TC(p9tDbt
z6QEOD+7F3R%c=t)@<8Z3Fs(niiIUd3@osQ94F7WV6#KkIyW$JdL!eV^6%)mmAuapX
z4UY-9iof_Pu+y>5twnajAnDxQz^A9$)L{qoD&1-puI%ON$vL#B`2|CdTpgSdNoERg
zcb_ZFY0I#^{G>K?^?*^l5iF@oKsYId@R3q;ENMF?yB#>80mleP3f_9d)uz6pXUn8#
zlibjL2)ppGrv=u&0&*bN8oTmKzFsaU+Ia2o-Y?TwC8z0EX1^lV(<<s#;4uZCD<a-|
z9tD;?S~&dL$G`Y()z=BWcZ-(g)%;WcJl2#m!euXI9Y@}_7IZ07b6l|^6pJrW!Il0}
z6CX%X5yH0v2yY-K-t+~v@G=e{1cztjvE(E2m){Gn9`@3k?QI>v1ULwl)iHp$t-G$B
zrmDF-*1Xzdmf*@9c2@Mob3j62*B#+BgL+|4$7P10T)=~BwrDsdD@R!&+2&iIxC1Nv
zWYZb5xFizZeOvn>U=|?$K&6)peNezBQ6~;~d8YyChYijA8p-}me#bqd5it-*Pv0s<
za6cmn0o@P-1fNymz>a9mjaeX2K<tKoMCqNCx_?$gachXy=1;i}P7v!&XivUNWJrKq
z`ThN3`_$e-8(bhGj~jDz>0F75_x?6{_b9UT12wM*a_mWsBL6)Xs&b8V^hU_}@!C(?
z03%garn_3mKbA2I7aW?d)jqF!eFDk|JwU<OJw*Vu4&*8Ou}>4R{ZTBcVwhC$!j%hl
zSU72$;-yi-kA|=dhQ!x!PsEp+VQoHokB$*p(sW^RZ~we7aCLHb9se@;s>e_ee~JT9
zNoJfAuJUD8PKk2YE8!a#80nAEL-I!Y@&J}!l+N<FJ6DU<x4`zVNldSJX3O&IVZTbb
z+rS2&3P}BPYnMQY3s8X71fNOvt)vJ>d;sx-EvvBT&M%7!?6jCqB~B4JrGh-v>YxN9
z`Z>+^c95%9FxR*odY(m3ZcXFXd5|C8XT$};+KR`^>Yi-R#qM}M|MlYyZ2$P2Tv@1O
zFk=rhU?%bg))DqXuJwoXngp<wL9JT-CUvk9W<`cJ{%t^oVMbQYJGa$$4ewVK=AaLb
zJ}7ZCA8Y!Y9`DwWRpdwIt1i3i=uLT`A>LgVwfwNM+C{1p-kwi5Hr(xQBV-Tv_m%5_
za!t=8N;W18*$)NsY+1ZC$-JNYNe}T^U8|t2e_~BYPSE4iqbI+N(9d7EfPw4!r|H8r
zF5$VIN(R{#K6Yk4ow~@+K&;pEBXTb+x|A8YFH&7L^6X%BebgwBYTBH#Js;f=y{=$=
zFw3HePj6OQy!YDvc0(DxmZ=fTxI?rl=jGqModj%!tJdGwwGvx&by74~p@jnE0<S4#
zO;FJK)3N|IQ{puJVx~K{7gSha87LwYNgQV-wBXLd$Y&bnm3`}SwOGg5u{#H!yhcmK
ztq^>A@Sr1Yd;*<8jW7{-5>!bEZX+jz>?;es>7U8#7Zt1w5@NGFN{uAH{*LOkZ&cn(
zhOIKWYasPNNtyigw@2zrFR5tKzxln-dYTzYRQ&SrGI>iKylUIZrfM`m_EJ~*_efHo
z`j(>^*|x7ucJ4xR7Cxq-b3i^K?ktfTJp+(PL-qgZn?jKYNc~Yn<Jef!(eef5Lx<|e
zudLw&QU&%e;hhpi^5WmL84(`FpV|;?Zy(s<*-_=ysEm6O_fQVR%wzjLtHMdxUraBG
zbDPRI(C%a1H-nJyRS+b$7{2~g8cfzVIbL+^{-07+LA8XLv2XcVD@l!Q{k`?1O#7rq
ziu*Q<`}O-t@7ik3r!1$cLIKID1_VHs7>r7g@hs6>{0w-JLg=}SCt=c^z|Db2=GFYy
z(<|I{s%J_CcDya^oXF<(CLalJyjR%LAD|QyY=i{9Ov)C;E2H!zStN>m4&E5&K>R&`
zA<qIWu|1QQyyHqGM?A{7yAzV#<X3)m68CziC}w4MtiBXIKbO$|j0O`YlOg0;oW0T$
zx)&k#>lB(ly^v8^_rQi#@t$iB3akh8Ro-7&<YPz8m*rIyElaO;SAzN7J7R|R&r7d3
zqceCV^VV_puI=T*eR0kq`TcJk4Q)I6N-u!~_?mBPjrnf(7?zU@`KHZhoo2c<hik*u
z59-Nj@+75C*}_y7;2@0LiWXgNPj>xtM`CwBNPS#ioNLWD^o(e%HYYmInWK~%`Ch$S
zn4Xug3ci|Q!!rKB%!0=m2JS5rMR_3Dm}0rYq^=8RdS+#RU!p<63u=M^D{>n=B;;l2
zqBEbamO2`hvc8U<lk^{AVsG32lp}&WZ9~sC>GzTmh029dcp^sJCSE`Pd6GEEr4mS5
zK3!$1!^i5RLXRM?f53f`S4z9v+iR^NHW$OD56z2&o3=aH^=f~dDpgMs@n4TuZlEVN
z(gSvlluy25wf;34Z9E4O;?N6#xu9mhZfMVlxxU&WOHPd%HH`P@GB*;yWNp<Cd)!$p
zGSa7(SeMwA_&X7odHnkIVR%H!0XKAleUw$&EkE?e&=DMh<W5e<js4T35$7dX#--cl
zO3s)&h^0G!R@&6{5*AL{{?`CTjwiHWuQJaY@))oU_Rr4D9z5nhvslZaSL!^Q@3CnQ
z-u+*1Ndn=N(Yw)w>f!5un>pzBsTQS$_!!^+zuLT`@$PfKADCfB)e)cWvPr*EO=HQP
z8J%%@Gt^%{+#a+eEZjG?43%EH56`6OX6h``Aje~g=$8@Adn0~#w`)+$Sh!c781DD;
zB*^d5dBU`=RKt6Ltn>1(K7-O|M1edO{y91E#rfO7AtD`d>~AV7!W|YvZ_hQE*gXSD
zr>T^izK1s%5l%6cI5DCy`aA#p!^su<?~Vsic57iQ*2Z*B!E<vo8jv24xE&v^J#qYX
zlN#`~*Ui#IGWN8N?VP>j--Pi`bQmY{@$za|Rv6|38VmRalDpY%KIbt~^~imknJ4ZN
zGxtU{G##sXh2K%Q=Wq{4+%s$Qwl3dYYYax=3<`4qly^Uz=%1?kl<H8B_a=fCO#ADJ
z_g5Z#60_=^h%x)N-?OsM#C>dS;&&zdfNQ<^sK;nkVlbl@VHf)lQty!CM5C>3^0158
z?l@Xz^C=yhqz-_6K%tLEIHC2-QW)O=o?OJpey52Un7dmp!D}nyqo_q%nCxCR(EeZU
zYXa@xb(AOW4>K@LlAOPi=QW9{Gvn>HVonGB39(;?SRN+!S(i5dEeivsg|filjO?Sd
z^JnohRTqP$=Lg&E?cwIP*bhLa4&XBjHw32SJ@d7aK8Au3KuQ=~tl!{Cle*igQ>bXk
zB*c1dga*|8lzi&}NvFxyJIM+mOMHUj{kFb>_!TWs9;>*6B^c&U`{$15b~j1<EdJS3
zS$_#ELeU&#20S?;TRj>sPyc)3cXC0qSf2}YR1Xo4R^>wt()Zp$=RgHaL(3xAB-OgU
z)MzIi%YKYq>OPyK;{p#NXBqF@dqV|#AA}_J4c|B{_MtqmC)2^6(5E*;+d29l1n+BT
zBwl~k0N-5yBHed6oH1fSrhS(904)gQ=MT57iR7^B{!anxFse=lO7g6PeM+dz)^nI}
zM=_2iH*(D<g6%8nmLI}K!>Z$2kJ&WIeb`M*`o?&i13Uz`1q0Fndx~g)R#lcrLS89(
z7Q<r;(}R=g3HM^ftnwWvwSYP*_u6eLS{`ujYX%r<nd4A#Mm5)b`j~_L8DRFkA5$G!
z!t!U?-6qce-SeI=Ti!VevIv8{I=4g1!!3&aO&mlE0+jf{aa!cL##sO7ZV}7ws}7yI
zKp=urU6mHl))J_8+Q};~0(ROKP-8@F7HpVi7hZ4|!|P<IQUE!c3cV;bm5;f1<62MK
znemyC5lP^~ozUr=Qna4YnZdkCt3JSl`@O{25c>{$p0Xz^evGlUj}~IkGN9uBuyFy4
zzK%&IYz)!jhH?SlHDjMd?JWE^z!$B~BUX#zifj1qap2p4t<^URJu7bnTbsql_wpu@
z@HeIsCO@jS(W(s+@^Lk20(=tZvhw?G2f@=l`~Hcpyh-=j0zK3IO*Crs?+8H$Ysbfc
zSF-;ZVT2P7c-Htn!?4uZZ76UfUafqdu-%@eCj^6T%!%jRBUDRZy)*IjIKq<vXw@It
zmAL@}vpBm~AmTQL!@7VJ(<!$1l8U8Aiqn!A*~8v6>k>QA1Q2jPTADtJsx-%ncWuw#
zU9)s6wm*CQ>FcxN#00Z83eIT1w}aR?QMBsW8-vz5Y&>N6cC)`t%_n{>X_(1R6jV#o
zJ&s5n^BZg@(E$znj{|WYFn+w<fe;lQCXT~XeNuqtprBv|Le{t?jY=-3sBtrZF;+V+
z3GK?m0r2t<S<-ptgR7LF7!=1jgsm48$dGXJ(P3h`<h6{EW0<oNn=sVGtov^q90vkS
znU5Xl@4Ejwm&l?7xp*M3`3!LvEdxTQ&ixIG(7ox-?A3mU6Ed+RTmqnbV+X+g#}AWy
zO5$42?=#agvm?SmOg?3pE<?;D$Np>JI&JjtUrF-11P?D6V6kfk(|+=ncTu^J^9_xl
z;KKm#@<DuOXr}`bsT{@kj2OH%ZM;7PF==r3lQY_q-s#q6*681-ok_0alR#VpUIobX
zeEGf?be!qSwpQ1UK(!C8EKew|&;X!to%Uxj(#X1(4fwUOJ98Ktre71N(hidQ_&SJ+
z<5>oAZ?N5dlm8w<n^VK*U(EIvO!XH#m<}0QT9-_68+zDzlv7Y4DGYHMil)G7bHR>#
zL!1D3!=qo(v8Juf_5mG7FTL_hb+gZHIvWq;N0p~0cM!Jd&h2o(+2%d^NqL}831>li
z$OmK^qT<!4eNIbH<~tGW9)HVXtic9wEJ7i?L~Ir(e^C((w6U~!b4ozu`7h2WLBn2<
z@~ZbFX0dnUy8Td^gP}}bffo*O;yf09Qpebh3!5o`I9r{9WH?lEiWh><nLmyWj~PjU
zH1i7nn3M$-xVX`Nhn5bwiO1o+^E=DMFJXPQkUR(7*JP)EBLe%uZ_>j=+4ywm%Y6l~
zKyk?8ATAqwZKeX*>C|N?3L>QgXG}hTAiErAPz3Ir8~W1)f^EdVBTLVWK9`Php0K-S
zFki)W?(|NW_kyJ)QZ-@4)BpP@CiSsa<C)oWQ~y1$kq8?T0!C$^4DE~xdDoY*>SpVU
z9zf^(9sl*@#}0uG$-ZPbm*rD}4?nop!UN_pzAjHrq%AWuF+F|t1VM%vxmTQQ?@3<k
z=>vg{aa`yFX0#{$`RVHfH}UdXWS05Zfiup_y8wWB%tQ>LF;no*OMblU)7JKL9!9mf
zvB#pnHGZ{IH701%S|?k-Km_j8xbecQ3cht_a<nS#t#E(F$iczgk^IHs+UfTO*1!8~
z%4c<@KzfRk>R>b_$DmJNU3|$Hu|(7uXQK8F20k)_r#62V!QK2Uml^yz=`70mi*x^5
zhy@BJ8kNl;zaIsA(RVW3p^-yx4F+%=+|;OfRld@yLJF*fha2OiUE$PMGTT}o{HaO_
zWjw4pGyiB8Aje3Gt+kFz?Pr5RYfyhQlNHX&cogyJW{w=B&G9}H@b;vidXo3Of&V2Y
z0ddl<d_}if!)oKg9I!OnXfg3CNph6wAR0sLBvBUF59j`XdRV~rMHWPE96aTPb?!9>
zl`TroZi0j_-CCQSh7sd2n{RJq3juX*h?5OIyS%$j&QgmN`zT`muJg--VeE_^pw~@4
zfxGMD0M1_jW+S?yEzhFl<Lv2M0Z!IQw8(6K$tj(agg11Iss}$><ou4(Gnzbj{g8-5
zn#8q?nPA&cOgKBV&C!54nVp5om<$ZRUd{@74YlxtO4PLJbVrV1AB;z$)12dDhcpMA
zFCD1k;o9$ojB}QWD0M1o`V~HUK*%+s1q}~s;y8A}y8ST^_iEhLM~~I1SziG^rF$&Z
z^GN>jq59-1o@qB+6C>~27&Q#%-F%iub&}Tqq8MysowK|F*Lso^QZgRzp8vG|B<`C*
z6rb*OGX*wpse^cVCCL`oeQqw;`GsV+Ej0U+wV9rhy{&2Cu!iabvOQ12@4|U(qE*I4
zdBiHQV)SD4V2Q*JVpbnZoM+EgpD#;QiSDfB@csg5>+=XTu|;}>lUrOeS|a!8s7<(b
z!_OLM!82h}ORpi9<M7(}nKk3Q+MzdHK`W$_p|zuM7|D$p62ZtgJl3i_Gb-ZvbrI%;
z0&vOoCske92IIPGy{_Zf)t%)Q3m|=3(_*cs*Z|BBZQTxlnLL(g3nR=gOlFJMA<^oF
zJYU}cKsbOmQT%#qkX|aG4a{N~BL=Wn4Q*Md{e6KM-DG!HB20;K8I0;unt7(pOX#&1
zzY<^0l}rn=+1H(iMEzT<`MA_n)tKP;JUz`p)K9dfhwbgHmrsL)TIF+xn@p0+O~M6$
z>_G0VB%2P(+LO4qXkBXm0QiwX>}Mmq8|>QampW%-zG?4ntvZ2{@Qj<55*fPQca(~o
zWcOIB=FE(UlSUC(_a|}pHA_SL`b!WZ^?J<nn#I}(+jFs-5|E6OD3GcVzHxmXF*YnE
zwSO4=1OVSwBlp>;&1(i$#iQ>_CO*A!1m>khjz<^9ogzNu;qJTfB+i$ONe0Bj1OTIn
zZ;gU&{~n?(Ye0i|Xa$-#85Bo*L<qT^_J)x@H$?5m)eSf5JJ3omzyeGvUro;BwGgLn
zG7gkDHAdorr;i}V!vf+KT>ANVxVI&N(pjSqIt(n-ULj-x&&17vLWldNeM5*(m8ooF
z=uJWODFYX3<hXAtnswZqM~p!-U4j>0T+oaKiWK(z;N=~Mcpb6K2YIGx6Qbs=RUlQa
z%sCM4A3J;!ZpxTQoB&R~ZMC&BDLEW0yaJ-fB?`Mna<{>+^;+=3)1ipZ=_Xqs-x;tU
zbZl=0O4i@}4XE$y-jVtUr*4yg$?f^_bv~3oCb=Tnvm;Y?8AOKfn>cSt7)0xK8t`Dj
zC$Xn>#|`bOnLna1IeI<mpjJr&`g?u+As{3R-`W};OjKPw_WsB)aoyqX0Oq+{A#A97
zR(;=B`A|_p#zr6tKmVs>uRWg*wteh?btv{70)_Z^!E%eNF@wxTB19eCH3<L`@T^Zq
zwm}zI?042tKZoQ_iE9#dM={CYoiwE2`wIZ6hS6Ly`rZI|%zL|!49U1w3c^YGChk2^
z<$!i!&oRLQ{@inY00WP5vAfS)+XeKGR4*87LnsF9>N5%NKCSM(6@fvJo*A)mN*=Lv
zNnh+YdU&BnF5k5IW2!@ov>qc5dIi|1mcCL#FIa}!XJ#!I)O>Z!W37&(^A2p&i7ue9
z+_vcX_AKV^xRK|o?z3T`XC{xy01h)iq=PJ|Gxf&I**$jn?ojAE1oZ0h?919|pDns_
z+<@c<E5Ma4DUZd0^{%;fn$KPVtRhdiv#0VVY?Hn2WHI(gN>huS0ua5<w>^_v%N}!+
z@h*oa^sZ%o@8n1@210r0A~pw#%H!D!ipGHEiQ15)aNpE{SA}J3u^3Vhc)u8p{MlP?
zgPPx>Pj?WaeBT{BULV0@mNul8ATy9P)ArRwes!|#?&?$rh=p(~f+GkX@wg9>_J?l_
z+TD&k2Q3P>x?i-W1OlZvs=L->RXDH$kkMi~yKoJT$-(4++CroAp*`T;9ryvT08N-C
z!8cnJc;a0ij~&YZV_>^W%(Vs7eOb)olkB6cv*!jApF&d}@V>_P0tGIMk8$4`0k~xX
zCyG1Vw}onl3aR5xM|sBmm81VC!}^kM<TiN;vK@FYQ?F-yBtpFta;1Z`43}od^hz`W
zhV<WQx6pS7K3E8aCyfhi2XEoNsT6TBzf$!};F<@bbM46J<;c$(Y0#=NjW?YWp%++f
z%95OCd!Nm+WekU+&@g1DI<u!it!DMB-K==xnJYm&GpJ)1&IUsiB6Ai<+k>Wmj~U#B
ziBOlx0F;dcmaRnVd#S=F@appm+|RJ*(TvnR5Lom96(;n4x5_eaOLL9#JS*}%@g)`Y
z3lP5RG>ArP#pmU#OHyK3>`VG@v~bNN_j&1p=f5vl)F$ZRj0}a!ckIUuGt|U-s!)i}
zyOZ<-bOH0P<XxU<^_>|$H^Es7v4X#hwIknLTP@~#b=C|c`()|vT3<(z-4l?Rmog%R
zy93pV-Z*Fb&d5Upoe?(u?7lu1Q!3N*qW!aG%(wM)*<IL-RysZJccGv+u-*%O8e`OT
za}Ct9*Y`F?`QtiPHimmp6##y49n5+U2LT;;_qED=R%*H`4c9hs(;1>q=TP;V)iCcJ
z`N{wWfIjewSP5Vex;YOez&xr2;_xdN9x)!2JeGzYUN@A>@60wx7bf12mk*%t2$S8L
zz|NdxA4kDs6OdyI8I!<@heQToEIB{c`h8|D?hmK?K~|17M8R!$r^(c4w<v7I#PU%_
z?x(~^N`whV8`lSdh|1R;Rc8%hw-or*aX3W$#bj%$(X)iv?|tY)hYlTV3?bFym(rt_
zqZlLDAno9w?g@dFK&WitU%;3?G4tPZiSmR!EqDN$4duK79Cof#!Z1z~ZE0;Q=oG#S
zgW{Az0%p2XP1uaM!nYkMXsC}xVH{#gAOW&FJ(%zG*x|KsZ%@t5HJJABOs*5$yZQa;
zPK@0|kBPRi!3K?N!ePm&&%+(*>gu!k+RQi#<c@g~@S}TAASh8qzP>jOD#BcUvE=Sb
z8z>nUamual354Z>bey5b>>$6#+B$P#k6Ps`s~oPdm6M1@%HHI|k*c&9?LU!r$EiVj
ze-<z%NI9U}d>Lf!H*x*ipPA+{?%SeC%^=|L9vyG<lX01Z3L#u9uc}P>p4;S?yt01~
zrWgk7)j5w>j1e!0isR3z;a%0&BzVHY0*Ai`Z>nT|-l^|0B{XdHc6AX6Yb(rN{d43l
zT;F5b3{5WcrVk^+NaFLj(f;wOv2){SYIs=nHK{6)5x`eg69wMijM3P8Z55BB^5|GD
zh{j2KbS&wTySB2l@AB~;4_l{@JK@@EK77ULu@)IB35Vk>u(ZQE7_*)tv$ydCCsqUO
zu+FU9nnS3td66XLU%T$Srdy-8amj(vcrsE@V8A1<JuiLU%0Jcs@wt`PFgYPLQpJ7l
zv`4(}2DGY>RBP8~&1BUi<@NaXQ-puBJ!VHOSd0hl@Uxz?f3jZTDo!-CDe)++llZMF
zbqo+*9p5;>@GV2_D@rv*&s80;AF|hWHf)eZN4l@>t`CN`+StX`KlENJNWi%f_R{4#
z)(Y^rPomCW+KY0%PYtG1Al=XZr@@4x!oH^;x(!kjpWwBn1TRa%=ANiUr_1>G_mx#4
z<Nf7$?V@3(hPF<E^J+l`ZYZ~&yFqoXG&awbBJtP5>4&3mK~haO%V0dR1hs?umY!o+
zI@Kt|;yzAojN0sN*5g%4lCm1-pV3`d-S;T&i1gX@;QC@?Y(9@S91Ox$Qplm*YC$z7
z*gpPNAlxhQ_ht4K-<~zL7cE-3%@)|K4u`qB@RSYb<8j%Z&%(4)Q;hH_{J6-5sO7`R
zBi~iqEs5OX?&A8J@P@Vhtq&evqfv0byTCA|>}@r@w9Zsp?Rum0G9~i$Cc9HFia+kd
z%&4E;I7;F*E99I<%HOTd3mMwJ%)ij+tuCOgAi4g~`|i@?XEmRwkk$ud`3~`a)y*iU
z7bNk6=?N+8q=??{QKn4`3h!y>6{Qz^92b5XA&A!GY}{bg7>;P0{>^s-D#YLg7vnJY
z)(_WOF=SV+H&FuoSZ^<Tvmi_=@@Ti&{v&E0b?x%_F{iyTPI1VwBT4wwyq5QX#J{;x
zxZ2q&y{@SC@#;?oq_gW8F$8T{BDu>pLJx(<(#~@0WV(v5ncLe@G4jKyZq1U>QyKQp
zY!Jk&YZ^eQ4Eevl6?HKz2<cZ0-ZNz^4o9uFZT}m@occykS>e<2YYt?GK#z8j5`>~A
zi*4i-e7D0qzV%tOQr11!6EEfImDm?)16C;!uYnus5qUL%N>n_-C<aVwxB)ack<~_#
zCyrCVc<#<W6<l6}<J&FX%BxeZRLBDD|Fo^#H3xTjN7xUvVTWF;uAY+bVI{Kne8>@<
zOeT7!_e6<WTI7dN|L9I-pxTFD;#HUDIoF{%;uf-cD;ap^*#sU=gecB@^>u6()f%p;
zPr@UJ`Ny`V&8k498~l86XPAkhx8NnfI;FOB6~mH?VVl({np~5F?UdQVbxI_0ISzFI
z&-0k@QaI~QI&sB9a=hx&8$g^D=~$J!40+gWeGpC@{!hL=!TIsG2&k<`IWf?SUohmT
z66YwHjUR7b`>m%ErL;>%PG;y_wn60B^`{-3D>hYfi|dQKZ^CN{bq|cav5{}j%cpR7
zEhyZ+^>V6Ht>h1-IXoSXxSD=wBLy~_5TBb_P#1?Bv;_h^ZOR1-toWN_B90}!t`9|V
zQwi%AynE5UAk-b}!iX&WpiEC)&V;Q~TNHIM6ea1|20TCoh&=LPIl*UeuVlx5vVwM(
zRt&^*d{IwvRiGNJS-5uZ&AEf7hU^}12)n@Q%;oHdlOacRv$&lmII&)C_R%zmeGfI-
z!7Pn2Vv5K+&PxsNVsMTN=tq|Ei+U8T3}8sFf1y!I1Eneg2FiVwTvjC+;uM_JtM=@G
zcIP;h`tHE~7fVg>TToB+OWNPO_OE5KDZX?@fdVNWQ%cWX4q=Qi#M+@fsW)K|3N<AN
z89Hj(>+zWySYH->9^P+48?a6(mvev8(U#o~rrBPA@6IjQbK+K0o||a;IC`h5Au57i
z>T1jwOGA67fjc8|LHx($Sk;&i+C@C`<9|<|x}umy4mo!jh)(efEOV+(Qy1ABf_2hE
z^Q%u$`tUZ}0g6<J{Sawa5lL8ZxVr~MGrxA)#kP5pTjQH^cD?3GH@_H3pNXbNa*KP5
zYhVR*dF{Ic05V+f9`3z$(an<GwUHmBhnl6|9T%(XKdfAR{kTmHe-t_KVa9Jcy23Ey
zL%Y2KDnPPM9k$radPOG7mQ&Ib(8QRAOLy5W8M<dXcAFR>a{nCU!{_dYQ~iH9y<vQ%
z{`5^<Zi#+>W$^;FzvjHOo1**d=pp23#?>StDfYsKz<L?teKTt6%{S17%~*azJAaYK
zek+v>q`I4^*^5q-@1*8xpUNUV+;Pmz#9$h;yYTuzf7?xX0;tYl9v>Yn@4f3JWPjhK
zN$6P0ja8k$_x4W}ktNy!)ws-xwCn$SmS0a9@2=8ioNz3u&PhQ3A|q|3yAGo9&Q#Lm
z=>|1gwE<Nb7s2KOn~wgJ%xMFw(>$K*SaJJ@r{9utu<hBgI^_g$B=MhzL;W*VX{Bvl
z6qK;@d5V6_bL`AylW=N_ta|6>qb|dU>&(i!1usa5&*P>4r=NvTfG{S}R&sNrS1^h~
zZz0UGZ#EjgtKI!54*$u6n<$>LCrofBA&P!M7NqkdlOS@4xN_yLySq!jP(tM8h-06Q
zP+g{%Zf8X_Zr9wvVhhl!^o|uR`0rE)=dpJBK8kALj9PysZ7#niw{&D)a+=|Fn!7Sv
zlnmy6T)<u*C3GYA%!D81k)3mJS7mm4K9wh4@s+x2!d!?{!8)nlgpjST6vYQ{j^^h<
zKkMTSU#va-f&*~=M$7{L5Lej6d6iLz7@D)9@7&fR(K0=>NcRzK>=;pmhg&!91ksLu
zApvf$aGVlUB}UV9y}LxJdotPxQVm|=CzDx`uM~TO{RdUn14p%U7EU~9A+-Gti&Ejh
z*dd*C^wJ#okJfB2rO)bG93v@2f2JxW9_Y#WNu>PMg+oaB1*Q!1wiE+vCH7T=;?~$-
zFR8iPc0Y<n92C!;P*F6GSsY`mK^!gHpF+6*fGJx>+nY^oj^lsqzi7QE;XPF}T!#3N
zuU1V?RE-@mi_~tT@S;7~XR75;(KEU%y>|c;atcv=TGO5I`g^?8CgmU5CXD_K+(G-O
z(9VYpx6@=7DjDF}b!>)&!@GK~4cXNxCBstX**+Cf3pXlcMq&Iqk61P&IhUz7q=6Oh
ztK$eCgYPiN4wbWcxhxeKyUgI2jJ*-pSx-=a5=N_1UIE6?O0jnY%%`(0Q@6EET?I36
z3BUV&Wwwx1<$aWT^VKb<k+()jor*M;8rvN=-#>*+!^<z1L(WZ-EnA{bU0I+k&r}E*
z(JWu6BMX+gP|1;8D$J*^U><)fg@zpZ`#kF({CY~GP&BvN5@osh`apyUlU1*xZk8LB
z9NJ(oAHxp#cA>*}s)PHEmOcv~$PO(^3{sr<#aH=m1>%dbp%UD#{Wx=NAy7@c&<s3c
zwKur1ne<-?2m8G7o?>T0*c+I<jC;N*y``bvV)yte4%mQD+|{`qHXb1CDAO~j4h~p`
ztTc<aGV3XNZYcuoLbsD+RTw4d94U!;4^LA9>J&wEI%rt{gfj==@`%gdAuUZULLuxu
zfN4|W3isYG!nQKJ@oJ#V%nufjPqMEV_*{%u{q6+xxi1^109XUly9aqGd%F=CUd!V=
zhiJWL4*toU2o8#>j5osnr@bo=hq8U!kLDLq^!8G+Oz9OPEmXEKXv$00p`lHpw1}3$
z*w^V-vP8BJW>AQ!5Jh&H5PFRoBavtr*_V_^(Rbg^RLA!_j_>>b`{Vo1;kch??zx}q
zzOM7UpX)rQDWY|d6y@^@rKi?S#?kRRDlHbiQ`H_iu&_)wXF>ZUQa`XM_sS!{>}miU
zsj0@G&81Ss)9Kv9j;4o?D~YdvFaF@k#Nw=lua9b#;o9DuWwGy)bT86#8L(DcQEB2v
zu<PUDC5A>o>F39_r79}1cP-YCSJ*7FJZsel)hxbYVi}WOPI>_&e>>l@c_pMJ1!5u6
z+Ltmqn4GXzDgkHyBFRs3Y`YZGZOK+AOpNHA!qcD{csTvpVgm)ILUQow8eM2j&9<<-
z6$|ElMTIpGa%2xwf_IyM!dE#pDUKZF{WX*UR&^kdS7dPF6zo8_^-qdov|RG4tQ7+s
z!>6o;w`Zi8=MOWF6I|}+R|#d-SB*dRSf{&F^d~uA_T~uMBSop%A}+f*di|!wF~=xV
ztFb1<gy)NPB{R#25i2mt#=1qKS?p&`)J%1>h3o3F+2O3_a+@}Tt+22a^_nHdr)nHH
ztI)7!KM2(#nV%<GqG6NQp7}i6RD}wPiK1esIAN<y8?)PP@x-&qkM447f@Yc=tNnWc
zMXT{(DF&(Gi?+Dw>Jm@F_u}j3rwy5odtCS?E+gdMs_#qN4>J435u<+eEX$H%o!#<l
zyr|9MhCplo;Z16>-R{qormRArFCkI|F^se9T$05-U0zVZDfvuw*aZ0EfnB1-+*td5
z|FP|;!aMtHYy4cUgx*UzM698Y5g>qN-ZYjmY~a-NDX_JUwRa5Mlx<sC3RtU0NB&ks
zjwge&7(Lcse_9&s{JwWWVrc(l&wn&6GRJFcVPo)}P}|%8)<&|6yG0FUm34~BJMeu%
zkx&Qq*P6zhu({lHU)IWO@0n&O6QsXiPMbaS-qYKOnk{KuLK#Gipd5A0(NYUpP1Tg?
zxutIcp$14Lq9NbL2gH%uG4tHePXIEJ*+g8Ft4O36ytT&pBn`Jr4c~_vAvM~uEfNHi
z;}s@Yf*GpUF0AM+XGK{0W}Gj>NI@sKzsF}g!E;x(h!waS@MHPzwKi6XEjK?3(|*hr
zNKP6DP1a}M1!SDS5{iV<BM;*G<PBmS)HXj<o^73X!=QU5B9n^`V!+0ce#8i>hVV`w
zom}Rp`HM^|1E|0_Wa;`=%P+(gnm+N1p3}YAVmRdh-IG0j`XVaOVybCp`(4w;Z?H4T
zF=PS+OKlA_)G1)tR##}#^d9wyDvi41wB~f3b&2jUwr=?ILVwy92%Mk5U$IsTI=O_O
zXI8X+*zhJ_>V0un_#LZ_BOQmUFWZ_WlY|e@y;A`K>O}xn*!HT&uPwq9@BFa6UMLb4
zl$pAJFXzN=YHs?RvKw4b+G{uZa+6F-O*;u(z#U|Zqe+d|qmrou<0>X_`ZD(k#UpP@
zyAMA5l4NeyevdrLU<(-DBnSBqxc^1xC~77~3J3Kdw|_Si#&$dYfWo&YwSGeUa1zRX
z4(oR65Zz(xRJvU`S(X46ZFJ%Zm`F=<a;mmfZ-(p3Iyqu}BLU0MBw2j%0U5JG!WvS}
z*r5;q#G&$u_%;*g%86_?BZtI@ZzEnv(#dByqp7TtKs1H&y<N&yU!;|S65K}sx56;x
z-H0<ww(FA}3h3H2lb#9sK>df)&UrRZh0I{QPOs!uGpCnqePhH%Lg((XzS;2fRTbV^
z`hYYY!?<PZaSe~tLB$=&lKHJ+ZI5hjq2sQR)gxCUg7c4=zpKP0EYU7_U8bf<rlqqn
z64voh+Sez=ri(dSh3?K{9kn%a&Sk{^Y{64iNUItKNejQT>#5Ax_yD2=4IkzWK^4}U
zoO@}==`Mjn2yBwxWM_5+R1nerEM<#^y}98HfqM;C$V?W@`e83iA<{a}!T0IP>~+Ei
zuI_{05Qx&btL?8NS~tNnz88Xlu`PFuIMY1xk0s7y7jWl}`{onfrtd0DXU76CDrZ+R
zlGs~G7q00{U=`#9I_gnwwnZqp#J33SuuGLnXYO4*#<bI<APxL=LryrYR{FBsD9mXL
zc-kkj^ychm%KFOO9TZBkVn`e2&cFU=WoCHLFkt?>>9=gD#=83n#8QD`Jynx4{}GzI
zj1>l8m^VLaze&?dBoQo8|0g5<wjl!?q{jrTLK>V%=-H(}m+AMXPcJ+;3g%tnYKkQ&
zE4KT*c`8TT<liESQBKm`$()L!dVA3>K*6|F#av(V!k0u)D3jFYgvnjb8=3o8GY@WM
zIvG(AIhVxtr+#lDS|`}$60sKrehc-IAU(wxj4me=c(B1g!RpJhH!CoV0@8&W9>(|%
ztioG36(iS%Nd9-GTi7VWr|Pn;s;vn-Lrj1kiDti#dalr?sUpRwu76w2{61L?6~8do
zNqdlwE4*^pj0B%N*Um4(57P_03Ot<J*)pR){ZbB5zNYn`tmu1F#2M`X-&AD7EsMGH
zz4ofdUdz=@`92pNU^^3bc6G~4$ba~G$(q&rH^J8W+z!@V*%D;>8wB>s_u8I53xaov
ze55N1j@xOTY^G?83VMS00zuE+=-MNbJ0^<4v}qYB!;gL7oBS6b5Px0bzWb~RM%ctK
z%xtB!KZeP@kMo*<&PY(x&#yj1(%J1AFPZj@v0EP1j3Gw{00wxzxFPt6-4nK)$zZr5
zU=Xs}NoI*N!ZU{`xW-*GhXL{F5-NtW&{!|l-kRIi)t}>~O}5MP<-|JT_WNJ1`ji4V
ziG{lpxmTwDd#jL>!1AcDCLYzfB^xYq<xI}p&xfLmOYgE7K3{}q8gocdS7br5+9}aU
zToU*|J>Q6Xroyt0R5FL^*FJq2<2BdFw)vyt{KuX2Bz7Q9`7Qor-QO6IaM(NdN2qhU
z>4v#9b+CoJ$BUW=f^*YHmwOx%!8ICQDD-yH(^&r%X8UksojRYMGCr|SyYI6wSyW_R
z>wJ>@aKOvGy+o3zkEbpkeT^UG&N{wDVASJT^PT)tA|R0B{CDse-rGq!Kja5uy*h6?
zSn3bdY|8F`@*N_-NP@mY#K)7KY%eEC<<Ft}*Sx?JFTmIx*8g&3W{Eox6L6VJ(%k~`
zXuV`L(bK>kWUc8_7ObNOc>m<Nc}t=4Lj~M6$Ml{Q5VE}iZE_|#P7QpvZJA-oR822Y
zgT=^m+pnrsP5&mZa0qqK6P>aUm%mkIs7ZlFM83}V%#-=pdlyST3K!e?;#8~T*7n9l
zHj_x}ZxkhC>R~`Onf|XnLg0ye*04#pe5E0kY>@Z^$_5p3|0n;T(}^cZ0XVHNd=?`g
zt8f<1g?+5}Z-H2NVg3|Y$$JNLN-ySs7<lJ1Cv4WU3}A#*$uw)^tuApKwSL?gRH1Q#
zrs0|Q_*@*wUpp#IezG4|UyPEno~#cO3||t)=U^4QQSV1T)F()NaP^4hg<Ggv=JvcL
zT*Wr}Ev_5LUwGmU)W(rh;l(jrtrR+%co;+%3K31kI-pBXgL0I3NL_^G`u5;T4BBF;
zP`KS*MO-F6$|7UuGqB)iEt#mP3BZQLxK#etx$8+F|F_DFaeZj;1t3yq@ZEZ}(@GXk
zwyS`#-lm@;5dW$kth&7K2}jgsK^c9+n=yctUxi+pZF;k|Aoc#X0qQAqKc!}!w?KHu
zcq2b?)hFv7n7F{G6N-FqB!bM?>;D2w#vae~qk_yhQirL&9If?MRC21<+lkz`#Vr+2
zEI==j`@l{V6U*LvB^ye7T|1#r5rpq3ca0XSgRM!rpsNEIj5OT;^O&hB#n}<XXc@(P
z`w<YjpwPJFn*KEUVOn^D8jdGfxZ+RI4dn%w9I=wbDNk8hrt(4F9lQzhu5ol#o&VVA
zDzsy;=Bbd!T4)C=m9#y<INGc?BlVZ1&%yg(Fck6zcRmAv;!>hyLJ%Ntfg&PJ2cfC2
zR-JQ(5?XaXw80AC{+vI~bQ3<9Z`WHF^0m=rKyR@A?h|SShQAA%pUUw{IzjS;nw5fC
z5VoIRg4^G<%;DrzU;g1$OT8<ZCQ0X!j6|Bwz`Kj>1>g{Xn<YQBSUdF3g%^%hH1y0S
zjAWszW5`IrDs-a$x|<zCwu^7vF*~IeG!p@K(d;PsZ7KQ5B~C%5zhcbs@t%Ho;Ly`4
zG$VvM->x&WRk68k8|o42`P;2$rgbaH-1)X-LIq@YwHQ9Exrotby${m88Dc|Lo2sEl
zPlzggPoh;fo`=nPV?SokL%u={Fma6-Mn&1e1Wb2A6KouHT0hqY>@(JhNPF$>k?{vq
zCyz`3fIGZtP$iCJuHSY<L3{%}%3^g^dmQ`qF>|!qJ{$gQ|G3Fzuxc1iS6)mZ)(Z6~
z&T<Q<)CO~!;+lCiCOG;2x7C&03NMc`%8v4P_`V2BN#}5z*FrN;woCg{-GvMywLSvu
zbx@n1FOu*L*X7kaU|%F*C$m4w?FN+=2pt_WuRgF(S#@Xl?O`Y(QAnH_1nd%UXmMFz
zMW-Ph9{_$|ls^|jy>n=PY6X-+7DN58VoS#_7tq{cy`jh6>%eV?qNrcoXt0eFQ82ei
zr$?D@Bpk9UX#pjQhKY9{5r}9Nmz}4RNYJfJMA&f{LEAHlNq+M@5D2;HGs~EozZ`T<
zm#b&r&XP=1q^sWx+A7j?7)C_#OYOeR35x(T3Iwy|9=p2Ks`iu&rS~@N`vgkP7{JrC
zh-sa_V%y-loi0k*og;(^9C8YO>~<UEClUdk&R}&-G|&4hUhjEtKUuF#_gc%2RvkpX
z!g@^nmxG@YpcbruxxCW_OY&?rK*_qZwoKb~fr~$C+Ivz4>o@DQXt&@wLNl<!(!!%Q
zFhFYUzs?dq*R{(p<lafc#JqI&71D(rZc8Hw%w>NtjIL*&e3remm^0E2(6=i$w!0*u
z!YjeMt3j?|Tr@ZTQBOqkBwNSXSp<RiJZH&JogKpgLM;%Ezr94Bn<S@ckxN{ARfM+!
z!gC5G4$cJRPq;3B&Gk@0Jss#LM5=6+5AvKTb9{8xL8_>R?E;XM7c0Bwo&@b*@XqoN
z&qiwm2kwgPpY4P^;AUK^mZdOR-XmN)vQ8TI_Z3Jc6thnZ*^1QtfVnBw@Iz~Y@M<Wf
zKHEA2_V`&j=BfQms<TTNw(3B5foB2Lsl8c-kIbWH;P~OixkUUwK3o+SKl|O{#Vmtw
zo5Q}Ax+)!fFdQBI`EEPwD<`74l)ZjPL8ML%Zr!GnR(hLeC%5Z=xg~8faaA%qT|Xy*
zG~=Y`=`DuR=u=hKg{4H$n_v2c3*VJFv^9&ZRoy&f6-u!EHZc@^Q&|Vg=l~>!3KHN1
z7Wo081TlTSf9088hN?;|fCwhcOUL_;j&AcB-F&e!|Md%UZpo)8$utAfYLU7^=u8yr
z+#A^LxOvXBF{kjxn6l|1o#9MSSOJwOeV`ruyqXl{tj6H4qN?f$OgCtHnkV;pEOB>#
ze7CJxEH^!szQ=34$l-gW73@k^#3`?3Sl=OeBHy9N5%Ie)LUyTN!F1d+l2umI+a`AK
zo{8WzCSaTwol*X>yaS%k&+iqfgKe7#612b+)Yi`(hXGC{Ol=cD!;2+}pNrc$Ut1zf
z*KgbTx~o%Y8nvW;A5#6@1MK{!II!~-Z<uVD3wZ1$7wzdpt)`}6-p9bzt_u-0QkA-A
z{otXHe+JLE<L$<N_lJ^=49Wm9e+q`u^nLig3mzOg`hH%$r6=eHcg(=$(Z=d<!^@KF
z%4H9O(U0;8-6VB#z0rEJHP>w3uf96Nb$_Uld0*Q7+~_Lr799k(dm_k#WOZeIP~#i#
z4y=~{TI@NM!r5@?_R)fSlD$K^jY0x@l@ryWn80UyYk0&yw})!Mh8;>ZiXE1^BL`gC
zGpgdMmnM4#5fkWqhmgO`m@_kp0wpM~(>|o!b0nH~f4f?7`wp0~D>1NB6G7gb_z!fq
znH4~`#Fbt2jyKCcSSki;`V?XU4R`@ZIu&CIb^Cg}<$sFzCQa%e(^V&AT)r?I<>9J>
zaNtv*?JEi_*KoD<2pe6TtB#v0ohQlmn7SG$mm4M;nc=U{lO$-cArlZN*VhmD@0d@o
z<oJE6iYsKrMy#YHUaEL=eVo%MGl`(hl8E(YhY<agN$udO$7iFpXDrw6a_0<?YbVLk
zg;jA_SKTbeGb|zpWi?17;Lsb`8UB-awVBp}43jy=7VPZ5rMq~44V@LKgAI~^Qa$l4
z(r~&;VSi4zOPVz{7zs$_o*^Ep!j<HLhuTR+F37_bQPFD8H5Z(RTwmv>*cm$Gxp1ZV
zICGm@A@wfi!~l#8Vo|_P3rAZv;fu`3<=xrXJ)iFI<)^L+Ngk9JcorjO^*_BEBM&=#
zG%mQO-<e;O%N-U~Gc(3Jhqu2GvetnQHS=#2ko_gTUZIsf5^eAK&5wikQBr(mPLy`0
zVKS#7%_AO~pEf)r@d^7=J{j2>e6w3TSxndez;{=|DLMv=grX0=xW&e5%O})>F>b3w
zz`=^SZ7x$=9Ha`jGS15*q1Vwd?0W|}Z;AlFOuz*wdaq43zfxh6t4DqiGdD2>=&{%D
zfv@R!^4`53t?Bt?$EhWh)yK2?xosmK5Y#6LiJk2YCV&F)&w7_ZGfKU3PpIqnhjJ%z
zOgiPs+AFB|K!nIcBjcO}{Z`c;O_zA-qOQ4WzCP2?VeZbkgC4Au1JUvb?1V&+JH;1{
z?&>OZ+~d+BMX7hP32D|WyTa|>^HsgMaK)_W1~)_sjoEOHbA~BKq#|uq(z<Qa#-%G`
z<`@BCo@2`$q!?I+`UxkB2=NvA$n&bd*nv%cRTQ3FG@Zp6Y?>X})R8)6^s4MtdA*P|
zv^E3`<mz6jllBXr+5+P8y_ak&eprkQJ6Ed&hJgsPlc3#!{!R#fV65<`u-D4qqp{X#
z&^2x~C9VpmS?@B1sQ9DnLH&)7ij7sQ-YrUw1yx9g-Y>U#f72V0(ub38I?CHwJH5nx
zQ{-^^de&k>MQ!rqPo8RG`@NjAKS_%aQD1f%eVwp%6r>?*g>7z@<x86cStXDH|GCck
zxOzwe3_`zMm3l}G>hY-`MN&KPqsm%$>dWcP3&!0LzC4Q&7>OhNM1Y6-YB+fZ03K@C
zQHAU1{xP^5o6sh=Md0HhrH0PSJV99r0AKpBkvoAeK9c?@d$D)c=~<}