merge mozilla-inbound to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Fri, 01 Sep 2017 10:38:51 +0200
changeset 378188 a3585c77e2b1bc5f5fea907e97762f7b47a12033
parent 378125 583e73fb8e3c734dbf3a5e13913df7617f5c492c (current diff)
parent 378187 2cbfa0e50247c97457644f5d731534cbbfe27162 (diff)
child 378189 10bb5830a5b218241b9831826baea0e38dfcc7de
child 378301 d56571d7f1be2e57d09cdf87d084d7eb731c8813
child 378340 cc85091ab81c7eb865d2cfb31539702c1bb7a18d
push id50200
push userarchaeopteryx@coole-files.de
push dateFri, 01 Sep 2017 08:44:00 +0000
treeherderautoland@10bb5830a5b2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone57.0a1
first release with
nightly mac
a3585c77e2b1 / 57.0a1 / 20170901100309 / files
nightly win32
a3585c77e2b1 / 57.0a1 / 20170901100309 / files
nightly win64
a3585c77e2b1 / 57.0a1 / 20170901100309 / files
nightly linux32
nightly linux64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central. r=merge a=merge MozReview-Commit-ID: ES9rKhiQo10
browser/base/content/browser.js
browser/base/content/tabbrowser.xml
browser/extensions/formautofill/FormAutofillParent.jsm
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
gfx/webrender/res/cs_clip_rectangle.fs.glsl
gfx/webrender/res/cs_clip_rectangle.vs.glsl
gfx/webrender/res/ps_border_corner.fs.glsl
gfx/webrender/res/ps_border_corner.vs.glsl
gfx/webrender/res/ps_border_edge.fs.glsl
gfx/webrender/res/ps_border_edge.vs.glsl
gfx/webrender/src/device.rs
memory/build/mozmemory_wrap.c
modules/libpref/init/all.js
mozglue/build/mozglue.def.in
testing/web-platform/meta/css/css-text-3/i18n/css3-text-line-break-baspglwj-022.html.ini
toolkit/components/telemetry/Histograms.json
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -843,18 +843,20 @@ Accessible::XULElmName(DocAccessible* aD
 nsresult
 Accessible::HandleAccEvent(AccEvent* aEvent)
 {
   NS_ENSURE_ARG_POINTER(aEvent);
 
   if (profiler_is_active()) {
     nsAutoCString strEventType;
     GetAccService()->GetStringEventType(aEvent->GetEventType(), strEventType);
-
-    profiler_tracing("A11y Event", strEventType.get());
+    nsAutoCString strMarker;
+    strMarker.AppendLiteral("A11y Event - ");
+    strMarker.Append(strEventType);
+    profiler_add_marker(strMarker.get());
   }
 
   if (IPCAccessibilityActive() && Document()) {
     DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
     MOZ_ASSERT(ipcDoc);
     if (ipcDoc) {
       uint64_t id = aEvent->GetAccessible()->IsDoc() ? 0 :
         reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -322,16 +322,17 @@ toolbarpaletteitem:-moz-any([place="pale
   display: -moz-box;
 }
 
 toolbarpaletteitem > toolbaritem[sdkstylewidget="true"][cui-areatype="toolbar"] > .toolbarbutton-text {
   display: -moz-box;
 }
 
 .webextension-browser-action > .toolbarbutton-badge-stack > .toolbarbutton-icon {
+  height: 16px;
   width: 16px;
 }
 
 @media not all and (min-resolution: 1.1dppx) {
   .webextension-browser-action {
     list-style-image: var(--webextension-toolbar-image, inherit);
   }
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1370,16 +1370,35 @@ var gBrowserInit = {
         document.documentElement.setAttribute("darkwindowframe", "true");
       }
     }
 
     ToolbarIconColor.init();
 
     gRemoteControl.updateVisualCue(Marionette.running);
 
+    // If we are given a tab to swap in, take care of it before first paint to
+    // avoid an about:blank flash.
+    let tabToOpen = window.arguments && window.arguments[0];
+    if (tabToOpen instanceof XULElement) {
+      // Clear the reference to the tab from the arguments array.
+      window.arguments[0] = null;
+
+      // Stop the about:blank load
+      gBrowser.stop();
+      // make sure it has a docshell
+      gBrowser.docShell;
+
+      try {
+        gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToOpen);
+      } catch (e) {
+        Cu.reportError(e);
+      }
+    }
+
     // Wait until chrome is painted before executing code not critical to making the window visible
     this._boundDelayedStartup = this._delayedStartup.bind(this);
     window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
 
     this._loadHandled = true;
   },
 
   _cancelDelayedStartup() {
@@ -1596,16 +1615,18 @@ var gBrowserInit = {
       firstBrowserPaintDeferred.resolve();
     });
 
     this._uriToLoadPromise.then(uriToLoad => {
       if (!uriToLoad || uriToLoad == "about:blank") {
         return;
       }
 
+      // We don't check if uriToLoad is a XULElement because this case has
+      // already been handled before first paint, and the argument cleared.
       if (uriToLoad instanceof Ci.nsIArray) {
         let count = uriToLoad.length;
         let specs = [];
         for (let i = 0; i < count; i++) {
           let urisstring = uriToLoad.queryElementAt(i, Ci.nsISupportsString);
           specs.push(urisstring.data);
         }
 
@@ -1613,49 +1634,16 @@ var gBrowserInit = {
         // so that we don't disrupt startup
         try {
           gBrowser.loadTabs(specs, {
             inBackground: false,
             replace: true,
             triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
           });
         } catch (e) {}
-      } else if (uriToLoad instanceof XULElement) {
-        // swap the given tab with the default about:blank tab and then close
-        // the original tab in the other window.
-        let tabToOpen = uriToLoad;
-
-        // If this tab was passed as a window argument, clear the
-        // reference to it from the arguments array.
-        if (window.arguments[0] == tabToOpen) {
-          window.arguments[0] = null;
-        }
-
-        // Stop the about:blank load
-        gBrowser.stop();
-        // make sure it has a docshell
-        gBrowser.docShell;
-
-        // We must set usercontextid before updateBrowserRemoteness()
-        // so that the newly created remote tab child has correct usercontextid
-        if (tabToOpen.hasAttribute("usercontextid")) {
-          let usercontextid = tabToOpen.getAttribute("usercontextid");
-          gBrowser.selectedBrowser.setAttribute("usercontextid", usercontextid);
-        }
-
-        try {
-          // Make sure selectedBrowser has the same remote settings as the one
-          // we are swapping in.
-          gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser,
-                                           tabToOpen.linkedBrowser.isRemoteBrowser,
-                                           { remoteType: tabToOpen.linkedBrowser.remoteType });
-          gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToOpen);
-        } catch (e) {
-          Cu.reportError(e);
-        }
       } else if (window.arguments.length >= 3) {
         // window.arguments[2]: referrer (nsIURI | string)
         //                 [3]: postData (nsIInputStream)
         //                 [4]: allowThirdPartyFixup (bool)
         //                 [5]: referrerPolicy (int)
         //                 [6]: userContextId (int)
         //                 [7]: originPrincipal (nsIPrincipal)
         //                 [8]: triggeringPrincipal (nsIPrincipal)
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3004,16 +3004,26 @@
                 // This call actually closes the window, unless the user
                 // cancels the operation.  We are finished here in both cases.
                 this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow);
                 return false;
               }
 
               newTab = true;
             }
+            aTab._endRemoveArgs = [closeWindow, newTab];
+
+            // swapBrowsersAndCloseOther will take care of closing the window without animation.
+            if (closeWindow && aAdoptedByTab) {
+              // Remove the tab's filter to avoid leaking.
+              if (aTab.linkedPanel) {
+                this._tabFilters.delete(aTab);
+              }
+              return true;
+            }
 
             if (!aTab._fullyOpen) {
               // If the opening tab animation hasn't finished before we start closing the
               // tab, decrement the animation count since _handleNewTab will not get called.
               this.tabAnimationsInProgress--;
             }
 
             this.tabAnimationsInProgress++;
@@ -3074,17 +3084,16 @@
 
             // Remove this tab as the owner of any other tabs, since it's going away.
             for (let tab of this.tabs) {
               if ("owner" in tab && tab.owner == aTab)
                 // |tab| is a child of the tab we're removing, make it an orphan
                 tab.owner = null;
             }
 
-            aTab._endRemoveArgs = [closeWindow, newTab];
             return true;
           ]]>
         </body>
       </method>
 
       <method name="_endRemoveTab">
         <parameter name="aTab"/>
         <body>
@@ -3292,16 +3301,35 @@
             }
 
             // First, start teardown of the other browser.  Make sure to not
             // fire the beforeunload event in the process.  Close the other
             // window if this was its last tab.
             if (!remoteBrowser._beginRemoveTab(aOtherTab, aOurTab, true))
               return;
 
+            // If this is the last tab of the window, hide the window
+            // immediately without animation before the docshell swap, to avoid
+            // about:blank being painted.
+            let [closeWindow] = aOtherTab._endRemoveArgs;
+            if (closeWindow) {
+              let win = aOtherTab.ownerGlobal;
+              let dwu = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                           .getInterface(Ci.nsIDOMWindowUtils);
+              dwu.suppressAnimation(true);
+              // Only suppressing window animations isn't enough to avoid
+              // an empty content area being painted.
+              let baseWin = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                               .getInterface(Ci.nsIDocShell)
+                               .QueryInterface(Ci.nsIDocShellTreeItem)
+                               .treeOwner
+                               .QueryInterface(Ci.nsIBaseWindow);
+              baseWin.visibility = false;
+            }
+
             let modifiedAttrs = [];
             if (aOtherTab.hasAttribute("muted")) {
               aOurTab.setAttribute("muted", "true");
               aOurTab.muteReason = aOtherTab.muteReason;
               ourBrowser.mute();
               modifiedAttrs.push("muted");
             }
             if (aOtherTab.hasAttribute("soundplaying")) {
@@ -3358,17 +3386,21 @@
                 otherFindBar.findMode == otherFindBar.FIND_NORMAL) {
               let ourFindBar = this.getFindBar(aOurTab);
               ourFindBar._findField.value = otherFindBar._findField.value;
               if (!otherFindBar.hidden)
                 ourFindBar.onFindCommand();
             }
 
             // Finish tearing down the tab that's going away.
-            remoteBrowser._endRemoveTab(aOtherTab);
+            if (closeWindow) {
+              aOtherTab.ownerGlobal.close();
+            } else {
+              remoteBrowser._endRemoveTab(aOtherTab);
+            }
 
             this.setTabTitle(aOurTab);
 
             // If the tab was already selected (this happpens in the scenario
             // of replaceTabWithWindow), notify onLocationChange, etc.
             if (aOurTab.selected)
               this.updateCurrentBrowser(true);
 
@@ -3724,16 +3756,24 @@
           <![CDATA[
             if (this.tabs.length == 1)
               return null;
 
             var options = "chrome,dialog=no,all";
             for (var name in aOptions)
               options += "," + name + "=" + aOptions[name];
 
+            // Play the tab closing animation to give immediate feedback while
+            // waiting for the new window to appear.
+            // content area when the docshells are swapped.
+            if (this.animationsEnabled) {
+              aTab.style.maxWidth = ""; // ensure that fade-out transition happens
+              aTab.removeAttribute("fadein");
+            }
+
             // tell a new window to take the "dropped" tab
             return window.openDialog(getBrowserURL(), "_blank", options, aTab);
           ]]>
         </body>
       </method>
 
       <!-- Opens a given tab to a non-remote window. -->
       <method name="openNonRemoteWindow">
@@ -3846,17 +3886,18 @@
           // Swap the dropped tab with a new one we create and then close
           // it in the other window (making it seem to have moved between
           // windows). We also ensure that the tab we create to swap into has
           // the same remote type and process as the one we're swapping in.
           // This makes sure we don't get a short-lived process for the new tab.
           let linkedBrowser = aTab.linkedBrowser;
           let params = { eventDetail: { adoptedTab: aTab },
                          preferredRemoteType: linkedBrowser.remoteType,
-                         sameProcessAsFrameLoader: linkedBrowser.frameLoader };
+                         sameProcessAsFrameLoader: linkedBrowser.frameLoader,
+                         skipAnimation: true };
           if (aTab.hasAttribute("usercontextid")) {
             // new tab must have the same usercontextid as the old one
             params.userContextId = aTab.getAttribute("usercontextid");
           }
           let newTab = this.addTab("about:blank", params);
           let newBrowser = this.getBrowserForTab(newTab);
 
           // Stop the about:blank load.
@@ -7394,17 +7435,17 @@
         if (this.tabbrowser.tabs.length == 1) {
           // resize _before_ move to ensure the window fits the new screen.  if
           // the window is too large for its screen, the window manager may do
           // automatic repositioning.
           window.resizeTo(winWidth, winHeight);
           window.moveTo(left, top);
           window.focus();
         } else {
-          let props = { screenX: left, screenY: top };
+          let props = { screenX: left, screenY: top, suppressanimation: 1 };
           if (AppConstants.platform != "win") {
             props.outerWidth = winWidth;
             props.outerHeight = winHeight;
           }
           this.tabbrowser.replaceTabWithWindow(draggedTab, props);
         }
         event.stopPropagation();
       ]]></handler>
--- a/browser/base/content/test/performance/browser_urlbar_keyed_search_reflows.js
+++ b/browser/base/content/test/performance/browser_urlbar_keyed_search_reflows.js
@@ -62,17 +62,17 @@ const EXPECTED_REFLOWS_FIRST_OPEN = [
     stack: [
       "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
       "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
       "_reuseAcItem@chrome://global/content/bindings/autocomplete.xml",
       "_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
       "_invalidate@chrome://global/content/bindings/autocomplete.xml",
       "invalidate@chrome://global/content/bindings/autocomplete.xml"
     ],
-    times: 1584, // This number should only ever go down - never up.
+    times: 1344, // This number should only ever go down - never up.
   },
 
   {
     stack: [
       "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
       "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
       "_onChanged@chrome://global/content/bindings/autocomplete.xml",
       "_appendCurrentResult/<@chrome://global/content/bindings/autocomplete.xml",
--- a/browser/base/content/test/performance/browser_urlbar_search_reflows.js
+++ b/browser/base/content/test/performance/browser_urlbar_search_reflows.js
@@ -62,17 +62,17 @@ const EXPECTED_REFLOWS_FIRST_OPEN = [
     stack: [
       "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
       "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
       "_reuseAcItem@chrome://global/content/bindings/autocomplete.xml",
       "_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
       "_invalidate@chrome://global/content/bindings/autocomplete.xml",
       "invalidate@chrome://global/content/bindings/autocomplete.xml"
     ],
-    times: 390, // This number should only ever go down - never up.
+    times: 330, // This number should only ever go down - never up.
   },
 
   {
     stack: [
       "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openPopup@chrome://global/content/bindings/autocomplete.xml",
       "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
@@ -114,17 +114,17 @@ const EXPECTED_REFLOWS_SECOND_OPEN = [
     stack: [
       "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
       "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
       "_reuseAcItem@chrome://global/content/bindings/autocomplete.xml",
       "_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
       "_invalidate@chrome://global/content/bindings/autocomplete.xml",
       "invalidate@chrome://global/content/bindings/autocomplete.xml"
     ],
-    times: 444, // This number should only ever go down - never up.
+    times: 384, // This number should only ever go down - never up.
   },
 
   // Bug 1384256
   {
     stack: [
       "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openPopup@chrome://global/content/bindings/autocomplete.xml",
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -313,25 +313,25 @@ const PanelUI = {
    * by the user.
    *
    * @param aCustomizing (optional) set to true if this was called while entering
    *        customization mode. If that's the case, we trust that customization
    *        mode will handle calling beginBatchUpdate and endBatchUpdate.
    *
    * @return a Promise that resolves once the panel is ready to roll.
    */
-  ensureReady() {
-    if (this._readyPromise) {
-      return this._readyPromise;
+  async ensureReady() {
+    if (this._isReady) {
+      return;
     }
+
+    await window.delayedStartupPromise;
     this._ensureEventListenersAdded();
     this.panel.hidden = false;
-    this._readyPromise = Promise.resolve();
     this._isReady = true;
-    return this._readyPromise;
   },
 
   /**
    * Switch the panel to the main view if it's not already
    * in that view.
    */
   showMainView() {
     this._ensureEventListenersAdded();
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -36,16 +36,17 @@ support-files =
 [browser_ext_browserAction_context.js]
 [browser_ext_browserAction_contextMenu.js]
 # bug 1369197
 skip-if = os == 'linux'
 [browser_ext_browserAction_disabled.js]
 [browser_ext_browserAction_pageAction_icon.js]
 [browser_ext_browserAction_pageAction_icon_permissions.js]
 [browser_ext_browserAction_popup.js]
+skip-if = debug && (os == 'linux' && bits == 32) # Bug 1313372
 [browser_ext_browserAction_popup_preload.js]
 skip-if = (os == 'win' && !debug) # bug 1352668
 [browser_ext_browserAction_popup_resize.js]
 [browser_ext_browserAction_simple.js]
 [browser_ext_browserAction_telemetry.js]
 [browser_ext_browserAction_theme_icons.js]
 [browser_ext_browsingData_formData.js]
 [browser_ext_browsingData_history.js]
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -367,22 +367,20 @@ var FormAutofillContent = {
       Services.cpmm.initialProcessData.autofillSavedFieldNames;
   },
 
   /**
    * Send the profile to parent for doorhanger and storage saving/updating.
    *
    * @param {Object} profile Submitted form's address/creditcard guid and record.
    * @param {Object} domWin Current content window.
-   * @param {int} timeStartedFillingMS Time of form filling started.
    */
-  _onFormSubmit(profile, domWin, timeStartedFillingMS) {
+  _onFormSubmit(profile, domWin) {
     let mm = this._messageManagerFromWindow(domWin);
-    mm.sendAsyncMessage("FormAutofill:OnFormSubmit",
-                        {profile, timeStartedFillingMS});
+    mm.sendAsyncMessage("FormAutofill:OnFormSubmit", profile);
   },
 
   /**
    * Handle earlyformsubmit event and early return when:
    * 1. In private browsing mode.
    * 2. Could not map any autofill handler by form element.
    * 3. Number of filled fields is less than autofill threshold
    *
@@ -404,17 +402,17 @@ var FormAutofillContent = {
       return true;
     }
 
     let records = handler.createRecords();
     if (!Object.keys(records).length) {
       return true;
     }
 
-    this._onFormSubmit(records, domWin, handler.timeStartedFillingMS);
+    this._onFormSubmit(records, domWin);
     return true;
   },
 
   receiveMessage({name, data}) {
     switch (name) {
       case "FormAutofill:enabledStatus": {
         if (data) {
           ProfileAutocomplete.ensureRegistered();
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -110,34 +110,28 @@ FormAutofillHandler.prototype = {
     // When the number of form controls is the same with last collection, it
     // can be recognized as there is no element changed. However, we should
     // improve the function to detect the element changes. e.g. a tel field
     // is changed from type="hidden" to type="tel".
     return this._formFieldCount != this.form.elements.length;
   },
 
   /**
-   * Time in milliseconds since epoch when a user started filling in the form.
-   */
-  timeStartedFillingMS: null,
-
-  /**
    * Set fieldDetails from the form about fields that can be autofilled.
    *
    * @param {boolean} allowDuplicates
    *        true to remain any duplicated field details otherwise to remove the
    *        duplicated ones.
    * @returns {Array} The valid address and credit card details.
    */
   collectFormFields(allowDuplicates = false) {
     this._cacheValue.allFieldNames = null;
     this._formFieldCount = this.form.elements.length;
     let fieldDetails = FormAutofillHeuristics.getFormInfo(this.form, allowDuplicates);
     this.fieldDetails = fieldDetails ? fieldDetails : [];
-    this.form.rootElement.addEventListener("input", this);
     log.debug("Collected details on", this.fieldDetails.length, "fields");
 
     this.address.fieldDetails = this.fieldDetails.filter(
       detail => FormAutofillUtils.isAddressField(detail.fieldName)
     );
     this.creditCard.fieldDetails = this.fieldDetails.filter(
       detail => FormAutofillUtils.isCreditCardField(detail.fieldName)
     );
@@ -583,48 +577,9 @@ FormAutofillHandler.prototype = {
       Services.cpmm.addMessageListener("FormAutofill:DecryptedString", function getResult(result) {
         Services.cpmm.removeMessageListener("FormAutofill:DecryptedString", getResult);
         resolve(result.data);
       });
 
       Services.cpmm.sendAsyncMessage("FormAutofill:GetDecryptedString", {cipherText, reauth});
     });
   },
-
-  /**
-   * Find the fieldDetail by HTML element (assume all details were collected in collectFormFields).
-   *
-   * @param {HTMLElement} element
-   *
-   * @returns {Object|null}
-   *          Return fieldDetail if fieldDetail's element ref could match the target.
-   *          (or return null if the element could not match any fieldDetail).
-   */
-  getFieldDetailsForElement(element) {
-    for (let detail of this.fieldDetails) {
-      if (detail.elementWeakRef.get() == element) {
-        return detail;
-      }
-    }
-    return null;
-  },
-
-  handleEvent(event) {
-    switch (event.type) {
-      case "input":
-        if (!event.isTrusted) {
-          return;
-        }
-
-        if (!FormAutofillUtils.isFieldEligibleForAutofill(event.target)) {
-          return;
-        }
-
-        if (!this.getFieldDetailsForElement(event.target)) {
-          return;
-        }
-
-        this.form.rootElement.removeEventListener("input", this);
-        this.timeStartedFillingMS = Date.now();
-        break;
-    }
-  },
 };
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -344,29 +344,27 @@ FormAutofillParent.prototype = {
       Services.ppmm.initialProcessData.autofillSavedFieldNames.delete(fieldName);
     });
 
     Services.ppmm.broadcastAsyncMessage("FormAutofill:savedFieldNames",
                                         Services.ppmm.initialProcessData.autofillSavedFieldNames);
     this._updateStatus();
   },
 
-  _onAddressSubmit(address, target, timeStartedFillingMS) {
+  _onAddressSubmit(address, target) {
     if (address.guid) {
       // Avoid updating the fields that users don't modify.
       let originalAddress = this.profileStorage.addresses.get(address.guid);
       for (let field in address.record) {
         if (address.untouchedFields.includes(field) && originalAddress[field]) {
           address.record[field] = originalAddress[field];
         }
       }
 
       if (!this.profileStorage.addresses.mergeIfPossible(address.guid, address.record)) {
-        this._recordFormFillingTime("address", "autofill-update", timeStartedFillingMS);
-
         FormAutofillDoorhanger.show(target, "update").then((state) => {
           let changedGUIDs = this.profileStorage.addresses.mergeToStorage(address.record);
           switch (state) {
             case "create":
               if (!changedGUIDs.length) {
                 changedGUIDs.push(this.profileStorage.addresses.add(address.record));
               }
               break;
@@ -380,27 +378,25 @@ FormAutofillParent.prototype = {
               break;
           }
           changedGUIDs.forEach(guid => this.profileStorage.addresses.notifyUsed(guid));
         });
         // Address should be updated
         Services.telemetry.scalarAdd("formautofill.addresses.fill_type_autofill_update", 1);
         return;
       }
-      this._recordFormFillingTime("address", "autofill", timeStartedFillingMS);
       this.profileStorage.addresses.notifyUsed(address.guid);
       // Address is merged successfully
       Services.telemetry.scalarAdd("formautofill.addresses.fill_type_autofill", 1);
     } else {
       let changedGUIDs = this.profileStorage.addresses.mergeToStorage(address.record);
       if (!changedGUIDs.length) {
         changedGUIDs.push(this.profileStorage.addresses.add(address.record));
       }
       changedGUIDs.forEach(guid => this.profileStorage.addresses.notifyUsed(guid));
-      this._recordFormFillingTime("address", "manual", timeStartedFillingMS);
 
       // Show first time use doorhanger
       if (Services.prefs.getBoolPref("extensions.formautofill.firstTimeUse")) {
         Services.prefs.setBoolPref("extensions.formautofill.firstTimeUse", false);
         FormAutofillDoorhanger.show(target, "firstTimeUse").then((state) => {
           if (state !== "open-pref") {
             return;
           }
@@ -434,33 +430,18 @@ FormAutofillParent.prototype = {
       return;
     }
 
     await this.profileStorage.creditCards.normalizeCCNumberFields(creditCard.record);
     this.profileStorage.creditCards.add(creditCard.record);
   },
 
   _onFormSubmit(data, target) {
-    let {profile: {address, creditCard}, timeStartedFillingMS} = data;
+    let {address, creditCard} = data;
 
     if (address) {
-      this._onAddressSubmit(address, target, timeStartedFillingMS);
+      this._onAddressSubmit(address, target);
     }
     if (creditCard) {
       this._onCreditCardSubmit(creditCard, target);
     }
   },
-  /**
-   * Set the probes for the filling time with specific filling type and form type.
-   *
-   * @private
-   * @param  {string} formType
-   *         3 type of form (address/creditcard/address-creditcard).
-   * @param  {string} fillingType
-   *         3 filling type (manual/autofill/autofill-update).
-   * @param  {int} startedFillingMS
-   *         Time that form started to filling in ms.
-   */
-  _recordFormFillingTime(formType, fillingType, startedFillingMS) {
-    let histogram = Services.telemetry.getKeyedHistogramById("FORM_FILLING_REQUIRED_TIME_MS");
-    histogram.add(`${formType}-${fillingType}`, Date.now() - startedFillingMS);
-  },
 };
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,5 +1,5 @@
 This is the PDF.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.9.489
+Current extension version is: 1.9.512
 
-Taken from upstream commit: b7fcaff0
+Taken from upstream commit: 066fea9c
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -1018,16 +1018,22 @@ function wrapReason(reason) {
     case 'MissingPDFException':
       return new MissingPDFException(reason.message);
     case 'UnexpectedResponseException':
       return new UnexpectedResponseException(reason.message, reason.status);
     default:
       return new UnknownErrorException(reason.message, reason.details);
   }
 }
+function makeReasonSerializable(reason) {
+  if (!(reason instanceof Error) || reason instanceof AbortException || reason instanceof MissingPDFException || reason instanceof UnexpectedResponseException || reason instanceof UnknownErrorException) {
+    return reason;
+  }
+  return new UnknownErrorException(reason.message, reason.toString());
+}
 function resolveOrReject(capability, success, reason) {
   if (success) {
     capability.resolve();
   } else {
     capability.reject(reason);
   }
 }
 function finalize(promise) {
@@ -1075,25 +1081,22 @@ function MessageHandler(sourceName, targ
           comObj.postMessage({
             sourceName,
             targetName,
             isReply: true,
             callbackId: data.callbackId,
             data: result
           });
         }, reason => {
-          if (reason instanceof Error) {
-            reason = reason + '';
-          }
           comObj.postMessage({
             sourceName,
             targetName,
             isReply: true,
             callbackId: data.callbackId,
-            error: reason
+            error: makeReasonSerializable(reason)
           });
         });
       } else if (data.streamId) {
         this._createStreamSink(data);
       } else {
         action[0].call(action[1], data.data);
       }
     } else {
@@ -1734,27 +1737,26 @@ Object.defineProperty(exports, "__esModu
   value: true
 });
 exports.AnnotationLayer = undefined;
 
 var _dom_utils = __w_pdfjs_require__(1);
 
 var _util = __w_pdfjs_require__(0);
 
-function AnnotationElementFactory() {}
-AnnotationElementFactory.prototype = {
-  create: function AnnotationElementFactory_create(parameters) {
-    var subtype = parameters.data.annotationType;
+class AnnotationElementFactory {
+  static create(parameters) {
+    let subtype = parameters.data.annotationType;
     switch (subtype) {
       case _util.AnnotationType.LINK:
         return new LinkAnnotationElement(parameters);
       case _util.AnnotationType.TEXT:
         return new TextAnnotationElement(parameters);
       case _util.AnnotationType.WIDGET:
-        var fieldType = parameters.data.fieldType;
+        let fieldType = parameters.data.fieldType;
         switch (fieldType) {
           case 'Tx':
             return new TextWidgetAnnotationElement(parameters);
           case 'Btn':
             if (parameters.data.radioButton) {
               return new RadioButtonWidgetAnnotationElement(parameters);
             } else if (parameters.data.checkBox) {
               return new CheckboxWidgetAnnotationElement(parameters);
@@ -1778,604 +1780,550 @@ AnnotationElementFactory.prototype = {
       case _util.AnnotationType.STRIKEOUT:
         return new StrikeOutAnnotationElement(parameters);
       case _util.AnnotationType.FILEATTACHMENT:
         return new FileAttachmentAnnotationElement(parameters);
       default:
         return new AnnotationElement(parameters);
     }
   }
-};
-var AnnotationElement = function AnnotationElementClosure() {
-  function AnnotationElement(parameters, isRenderable, ignoreBorder) {
-    this.isRenderable = isRenderable || false;
+}
+class AnnotationElement {
+  constructor(parameters, isRenderable = false, ignoreBorder = false) {
+    this.isRenderable = isRenderable;
     this.data = parameters.data;
     this.layer = parameters.layer;
     this.page = parameters.page;
     this.viewport = parameters.viewport;
     this.linkService = parameters.linkService;
     this.downloadManager = parameters.downloadManager;
     this.imageResourcesPath = parameters.imageResourcesPath;
     this.renderInteractiveForms = parameters.renderInteractiveForms;
     if (isRenderable) {
       this.container = this._createContainer(ignoreBorder);
     }
   }
-  AnnotationElement.prototype = {
-    _createContainer: function AnnotationElement_createContainer(ignoreBorder) {
-      var data = this.data,
-          page = this.page,
-          viewport = this.viewport;
-      var container = document.createElement('section');
-      var width = data.rect[2] - data.rect[0];
-      var height = data.rect[3] - data.rect[1];
-      container.setAttribute('data-annotation-id', data.id);
-      var rect = _util.Util.normalizeRect([data.rect[0], page.view[3] - data.rect[1] + page.view[1], data.rect[2], page.view[3] - data.rect[3] + page.view[1]]);
-      _dom_utils.CustomStyle.setProp('transform', container, 'matrix(' + viewport.transform.join(',') + ')');
-      _dom_utils.CustomStyle.setProp('transformOrigin', container, -rect[0] + 'px ' + -rect[1] + 'px');
-      if (!ignoreBorder && data.borderStyle.width > 0) {
-        container.style.borderWidth = data.borderStyle.width + 'px';
-        if (data.borderStyle.style !== _util.AnnotationBorderStyleType.UNDERLINE) {
-          width = width - 2 * data.borderStyle.width;
-          height = height - 2 * data.borderStyle.width;
-        }
-        var horizontalRadius = data.borderStyle.horizontalCornerRadius;
-        var verticalRadius = data.borderStyle.verticalCornerRadius;
-        if (horizontalRadius > 0 || verticalRadius > 0) {
-          var radius = horizontalRadius + 'px / ' + verticalRadius + 'px';
-          _dom_utils.CustomStyle.setProp('borderRadius', container, radius);
-        }
-        switch (data.borderStyle.style) {
-          case _util.AnnotationBorderStyleType.SOLID:
-            container.style.borderStyle = 'solid';
-            break;
-          case _util.AnnotationBorderStyleType.DASHED:
-            container.style.borderStyle = 'dashed';
-            break;
-          case _util.AnnotationBorderStyleType.BEVELED:
-            (0, _util.warn)('Unimplemented border style: beveled');
-            break;
-          case _util.AnnotationBorderStyleType.INSET:
-            (0, _util.warn)('Unimplemented border style: inset');
-            break;
-          case _util.AnnotationBorderStyleType.UNDERLINE:
-            container.style.borderBottomStyle = 'solid';
-            break;
-          default:
-            break;
-        }
-        if (data.color) {
-          container.style.borderColor = _util.Util.makeCssRgb(data.color[0] | 0, data.color[1] | 0, data.color[2] | 0);
-        } else {
-          container.style.borderWidth = 0;
-        }
-      }
-      container.style.left = rect[0] + 'px';
-      container.style.top = rect[1] + 'px';
-      container.style.width = width + 'px';
-      container.style.height = height + 'px';
-      return container;
-    },
-    _createPopup: function AnnotationElement_createPopup(container, trigger, data) {
-      if (!trigger) {
-        trigger = document.createElement('div');
-        trigger.style.height = container.style.height;
-        trigger.style.width = container.style.width;
-        container.appendChild(trigger);
-      }
-      var popupElement = new PopupElement({
-        container,
-        trigger,
-        color: data.color,
-        title: data.title,
-        contents: data.contents,
-        hideWrapper: true
-      });
-      var popup = popupElement.render();
-      popup.style.left = container.style.width;
-      container.appendChild(popup);
-    },
-    render: function AnnotationElement_render() {
-      throw new Error('Abstract method AnnotationElement.render called');
-    }
-  };
-  return AnnotationElement;
-}();
-var LinkAnnotationElement = function LinkAnnotationElementClosure() {
-  function LinkAnnotationElement(parameters) {
-    AnnotationElement.call(this, parameters, true);
-  }
-  _util.Util.inherit(LinkAnnotationElement, AnnotationElement, {
-    render: function LinkAnnotationElement_render() {
-      this.container.className = 'linkAnnotation';
-      var link = document.createElement('a');
-      (0, _dom_utils.addLinkAttributes)(link, {
-        url: this.data.url,
-        target: this.data.newWindow ? _dom_utils.LinkTarget.BLANK : undefined
-      });
-      if (!this.data.url) {
-        if (this.data.action) {
-          this._bindNamedAction(link, this.data.action);
-        } else {
-          this._bindLink(link, this.data.dest);
-        }
-      }
-      this.container.appendChild(link);
-      return this.container;
-    },
-    _bindLink(link, destination) {
-      link.href = this.linkService.getDestinationHash(destination);
-      link.onclick = () => {
-        if (destination) {
-          this.linkService.navigateTo(destination);
-        }
-        return false;
-      };
+  _createContainer(ignoreBorder = false) {
+    let data = this.data,
+        page = this.page,
+        viewport = this.viewport;
+    let container = document.createElement('section');
+    let width = data.rect[2] - data.rect[0];
+    let height = data.rect[3] - data.rect[1];
+    container.setAttribute('data-annotation-id', data.id);
+    let rect = _util.Util.normalizeRect([data.rect[0], page.view[3] - data.rect[1] + page.view[1], data.rect[2], page.view[3] - data.rect[3] + page.view[1]]);
+    _dom_utils.CustomStyle.setProp('transform', container, 'matrix(' + viewport.transform.join(',') + ')');
+    _dom_utils.CustomStyle.setProp('transformOrigin', container, -rect[0] + 'px ' + -rect[1] + 'px');
+    if (!ignoreBorder && data.borderStyle.width > 0) {
+      container.style.borderWidth = data.borderStyle.width + 'px';
+      if (data.borderStyle.style !== _util.AnnotationBorderStyleType.UNDERLINE) {
+        width = width - 2 * data.borderStyle.width;
+        height = height - 2 * data.borderStyle.width;
+      }
+      let horizontalRadius = data.borderStyle.horizontalCornerRadius;
+      let verticalRadius = data.borderStyle.verticalCornerRadius;
+      if (horizontalRadius > 0 || verticalRadius > 0) {
+        let radius = horizontalRadius + 'px / ' + verticalRadius + 'px';
+        _dom_utils.CustomStyle.setProp('borderRadius', container, radius);
+      }
+      switch (data.borderStyle.style) {
+        case _util.AnnotationBorderStyleType.SOLID:
+          container.style.borderStyle = 'solid';
+          break;
+        case _util.AnnotationBorderStyleType.DASHED:
+          container.style.borderStyle = 'dashed';
+          break;
+        case _util.AnnotationBorderStyleType.BEVELED:
+          (0, _util.warn)('Unimplemented border style: beveled');
+          break;
+        case _util.AnnotationBorderStyleType.INSET:
+          (0, _util.warn)('Unimplemented border style: inset');
+          break;
+        case _util.AnnotationBorderStyleType.UNDERLINE:
+          container.style.borderBottomStyle = 'solid';
+          break;
+        default:
+          break;
+      }
+      if (data.color) {
+        container.style.borderColor = _util.Util.makeCssRgb(data.color[0] | 0, data.color[1] | 0, data.color[2] | 0);
+      } else {
+        container.style.borderWidth = 0;
+      }
+    }
+    container.style.left = rect[0] + 'px';
+    container.style.top = rect[1] + 'px';
+    container.style.width = width + 'px';
+    container.style.height = height + 'px';
+    return container;
+  }
+  _createPopup(container, trigger, data) {
+    if (!trigger) {
+      trigger = document.createElement('div');
+      trigger.style.height = container.style.height;
+      trigger.style.width = container.style.width;
+      container.appendChild(trigger);
+    }
+    let popupElement = new PopupElement({
+      container,
+      trigger,
+      color: data.color,
+      title: data.title,
+      contents: data.contents,
+      hideWrapper: true
+    });
+    let popup = popupElement.render();
+    popup.style.left = container.style.width;
+    container.appendChild(popup);
+  }
+  render() {
+    throw new Error('Abstract method `AnnotationElement.render` called');
+  }
+}
+class LinkAnnotationElement extends AnnotationElement {
+  constructor(parameters) {
+    super(parameters, true);
+  }
+  render() {
+    this.container.className = 'linkAnnotation';
+    let link = document.createElement('a');
+    (0, _dom_utils.addLinkAttributes)(link, {
+      url: this.data.url,
+      target: this.data.newWindow ? _dom_utils.LinkTarget.BLANK : undefined
+    });
+    if (!this.data.url) {
+      if (this.data.action) {
+        this._bindNamedAction(link, this.data.action);
+      } else {
+        this._bindLink(link, this.data.dest);
+      }
+    }
+    this.container.appendChild(link);
+    return this.container;
+  }
+  _bindLink(link, destination) {
+    link.href = this.linkService.getDestinationHash(destination);
+    link.onclick = () => {
       if (destination) {
-        link.className = 'internalLink';
-      }
-    },
-    _bindNamedAction(link, action) {
-      link.href = this.linkService.getAnchorUrl('');
-      link.onclick = () => {
-        this.linkService.executeNamedAction(action);
-        return false;
-      };
+        this.linkService.navigateTo(destination);
+      }
+      return false;
+    };
+    if (destination) {
       link.className = 'internalLink';
     }
-  });
-  return LinkAnnotationElement;
-}();
-var TextAnnotationElement = function TextAnnotationElementClosure() {
-  function TextAnnotationElement(parameters) {
-    var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
-    AnnotationElement.call(this, parameters, isRenderable);
-  }
-  _util.Util.inherit(TextAnnotationElement, AnnotationElement, {
-    render: function TextAnnotationElement_render() {
-      this.container.className = 'textAnnotation';
-      var image = document.createElement('img');
-      image.style.height = this.container.style.height;
-      image.style.width = this.container.style.width;
-      image.src = this.imageResourcesPath + 'annotation-' + this.data.name.toLowerCase() + '.svg';
-      image.alt = '[{{type}} Annotation]';
-      image.dataset.l10nId = 'text_annotation_type';
-      image.dataset.l10nArgs = JSON.stringify({ type: this.data.name });
-      if (!this.data.hasPopup) {
-        this._createPopup(this.container, image, this.data);
-      }
-      this.container.appendChild(image);
-      return this.container;
-    }
-  });
-  return TextAnnotationElement;
-}();
-var WidgetAnnotationElement = function WidgetAnnotationElementClosure() {
-  function WidgetAnnotationElement(parameters, isRenderable) {
-    AnnotationElement.call(this, parameters, isRenderable);
-  }
-  _util.Util.inherit(WidgetAnnotationElement, AnnotationElement, {
-    render: function WidgetAnnotationElement_render() {
+  }
+  _bindNamedAction(link, action) {
+    link.href = this.linkService.getAnchorUrl('');
+    link.onclick = () => {
+      this.linkService.executeNamedAction(action);
+      return false;
+    };
+    link.className = 'internalLink';
+  }
+}
+class TextAnnotationElement extends AnnotationElement {
+  constructor(parameters) {
+    let isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    super(parameters, isRenderable);
+  }
+  render() {
+    this.container.className = 'textAnnotation';
+    let image = document.createElement('img');
+    image.style.height = this.container.style.height;
+    image.style.width = this.container.style.width;
+    image.src = this.imageResourcesPath + 'annotation-' + this.data.name.toLowerCase() + '.svg';
+    image.alt = '[{{type}} Annotation]';
+    image.dataset.l10nId = 'text_annotation_type';
+    image.dataset.l10nArgs = JSON.stringify({ type: this.data.name });
+    if (!this.data.hasPopup) {
+      this._createPopup(this.container, image, this.data);
+    }
+    this.container.appendChild(image);
+    return this.container;
+  }
+}
+class WidgetAnnotationElement extends AnnotationElement {
+  render() {
+    return this.container;
+  }
+}
+class TextWidgetAnnotationElement extends WidgetAnnotationElement {
+  constructor(parameters) {
+    let isRenderable = parameters.renderInteractiveForms || !parameters.data.hasAppearance && !!parameters.data.fieldValue;
+    super(parameters, isRenderable);
+  }
+  render() {
+    const TEXT_ALIGNMENT = ['left', 'center', 'right'];
+    this.container.className = 'textWidgetAnnotation';
+    let element = null;
+    if (this.renderInteractiveForms) {
+      if (this.data.multiLine) {
+        element = document.createElement('textarea');
+        element.textContent = this.data.fieldValue;
+      } else {
+        element = document.createElement('input');
+        element.type = 'text';
+        element.setAttribute('value', this.data.fieldValue);
+      }
+      element.disabled = this.data.readOnly;
+      if (this.data.maxLen !== null) {
+        element.maxLength = this.data.maxLen;
+      }
+      if (this.data.comb) {
+        let fieldWidth = this.data.rect[2] - this.data.rect[0];
+        let combWidth = fieldWidth / this.data.maxLen;
+        element.classList.add('comb');
+        element.style.letterSpacing = 'calc(' + combWidth + 'px - 1ch)';
+      }
+    } else {
+      element = document.createElement('div');
+      element.textContent = this.data.fieldValue;
+      element.style.verticalAlign = 'middle';
+      element.style.display = 'table-cell';
+      let font = null;
+      if (this.data.fontRefName) {
+        font = this.page.commonObjs.getData(this.data.fontRefName);
+      }
+      this._setTextStyle(element, font);
+    }
+    if (this.data.textAlignment !== null) {
+      element.style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment];
+    }
+    this.container.appendChild(element);
+    return this.container;
+  }
+  _setTextStyle(element, font) {
+    let style = element.style;
+    style.fontSize = this.data.fontSize + 'px';
+    style.direction = this.data.fontDirection < 0 ? 'rtl' : 'ltr';
+    if (!font) {
+      return;
+    }
+    style.fontWeight = font.black ? font.bold ? '900' : 'bold' : font.bold ? 'bold' : 'normal';
+    style.fontStyle = font.italic ? 'italic' : 'normal';
+    let fontFamily = font.loadedName ? '"' + font.loadedName + '", ' : '';
+    let fallbackName = font.fallbackName || 'Helvetica, sans-serif';
+    style.fontFamily = fontFamily + fallbackName;
+  }
+}
+class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
+  constructor(parameters) {
+    super(parameters, parameters.renderInteractiveForms);
+  }
+  render() {
+    this.container.className = 'buttonWidgetAnnotation checkBox';
+    let element = document.createElement('input');
+    element.disabled = this.data.readOnly;
+    element.type = 'checkbox';
+    if (this.data.fieldValue && this.data.fieldValue !== 'Off') {
+      element.setAttribute('checked', true);
+    }
+    this.container.appendChild(element);
+    return this.container;
+  }
+}
+class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
+  constructor(parameters) {
+    super(parameters, parameters.renderInteractiveForms);
+  }
+  render() {
+    this.container.className = 'buttonWidgetAnnotation radioButton';
+    let element = document.createElement('input');
+    element.disabled = this.data.readOnly;
+    element.type = 'radio';
+    element.name = this.data.fieldName;
+    if (this.data.fieldValue === this.data.buttonValue) {
+      element.setAttribute('checked', true);
+    }
+    this.container.appendChild(element);
+    return this.container;
+  }
+}
+class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
+  constructor(parameters) {
+    super(parameters, parameters.renderInteractiveForms);
+  }
+  render() {
+    this.container.className = 'choiceWidgetAnnotation';
+    let selectElement = document.createElement('select');
+    selectElement.disabled = this.data.readOnly;
+    if (!this.data.combo) {
+      selectElement.size = this.data.options.length;
+      if (this.data.multiSelect) {
+        selectElement.multiple = true;
+      }
+    }
+    for (let i = 0, ii = this.data.options.length; i < ii; i++) {
+      let option = this.data.options[i];
+      let optionElement = document.createElement('option');
+      optionElement.textContent = option.displayValue;
+      optionElement.value = option.exportValue;
+      if (this.data.fieldValue.indexOf(option.displayValue) >= 0) {
+        optionElement.setAttribute('selected', true);
+      }
+      selectElement.appendChild(optionElement);
+    }
+    this.container.appendChild(selectElement);
+    return this.container;
+  }
+}
+class PopupAnnotationElement extends AnnotationElement {
+  constructor(parameters) {
+    let isRenderable = !!(parameters.data.title || parameters.data.contents);
+    super(parameters, isRenderable);
+  }
+  render() {
+    const IGNORE_TYPES = ['Line'];
+    this.container.className = 'popupAnnotation';
+    if (IGNORE_TYPES.indexOf(this.data.parentType) >= 0) {
       return this.container;
     }
-  });
-  return WidgetAnnotationElement;
-}();
-var TextWidgetAnnotationElement = function TextWidgetAnnotationElementClosure() {
-  var TEXT_ALIGNMENT = ['left', 'center', 'right'];
-  function TextWidgetAnnotationElement(parameters) {
-    var isRenderable = parameters.renderInteractiveForms || !parameters.data.hasAppearance && !!parameters.data.fieldValue;
-    WidgetAnnotationElement.call(this, parameters, isRenderable);
-  }
-  _util.Util.inherit(TextWidgetAnnotationElement, WidgetAnnotationElement, {
-    render: function TextWidgetAnnotationElement_render() {
-      this.container.className = 'textWidgetAnnotation';
-      var element = null;
-      if (this.renderInteractiveForms) {
-        if (this.data.multiLine) {
-          element = document.createElement('textarea');
-          element.textContent = this.data.fieldValue;
-        } else {
-          element = document.createElement('input');
-          element.type = 'text';
-          element.setAttribute('value', this.data.fieldValue);
-        }
-        element.disabled = this.data.readOnly;
-        if (this.data.maxLen !== null) {
-          element.maxLength = this.data.maxLen;
-        }
-        if (this.data.comb) {
-          var fieldWidth = this.data.rect[2] - this.data.rect[0];
-          var combWidth = fieldWidth / this.data.maxLen;
-          element.classList.add('comb');
-          element.style.letterSpacing = 'calc(' + combWidth + 'px - 1ch)';
-        }
-      } else {
-        element = document.createElement('div');
-        element.textContent = this.data.fieldValue;
-        element.style.verticalAlign = 'middle';
-        element.style.display = 'table-cell';
-        var font = null;
-        if (this.data.fontRefName) {
-          font = this.page.commonObjs.getData(this.data.fontRefName);
-        }
-        this._setTextStyle(element, font);
-      }
-      if (this.data.textAlignment !== null) {
-        element.style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment];
-      }
-      this.container.appendChild(element);
-      return this.container;
-    },
-    _setTextStyle: function TextWidgetAnnotationElement_setTextStyle(element, font) {
-      var style = element.style;
-      style.fontSize = this.data.fontSize + 'px';
-      style.direction = this.data.fontDirection < 0 ? 'rtl' : 'ltr';
-      if (!font) {
-        return;
-      }
-      style.fontWeight = font.black ? font.bold ? '900' : 'bold' : font.bold ? 'bold' : 'normal';
-      style.fontStyle = font.italic ? 'italic' : 'normal';
-      var fontFamily = font.loadedName ? '"' + font.loadedName + '", ' : '';
-      var fallbackName = font.fallbackName || 'Helvetica, sans-serif';
-      style.fontFamily = fontFamily + fallbackName;
-    }
-  });
-  return TextWidgetAnnotationElement;
-}();
-var CheckboxWidgetAnnotationElement = function CheckboxWidgetAnnotationElementClosure() {
-  function CheckboxWidgetAnnotationElement(parameters) {
-    WidgetAnnotationElement.call(this, parameters, parameters.renderInteractiveForms);
-  }
-  _util.Util.inherit(CheckboxWidgetAnnotationElement, WidgetAnnotationElement, {
-    render: function CheckboxWidgetAnnotationElement_render() {
-      this.container.className = 'buttonWidgetAnnotation checkBox';
-      var element = document.createElement('input');
-      element.disabled = this.data.readOnly;
-      element.type = 'checkbox';
-      if (this.data.fieldValue && this.data.fieldValue !== 'Off') {
-        element.setAttribute('checked', true);
-      }
-      this.container.appendChild(element);
+    let selector = '[data-annotation-id="' + this.data.parentId + '"]';
+    let parentElement = this.layer.querySelector(selector);
+    if (!parentElement) {
       return this.container;
     }
-  });
-  return CheckboxWidgetAnnotationElement;
-}();
-var RadioButtonWidgetAnnotationElement = function RadioButtonWidgetAnnotationElementClosure() {
-  function RadioButtonWidgetAnnotationElement(parameters) {
-    WidgetAnnotationElement.call(this, parameters, parameters.renderInteractiveForms);
-  }
-  _util.Util.inherit(RadioButtonWidgetAnnotationElement, WidgetAnnotationElement, {
-    render: function RadioButtonWidgetAnnotationElement_render() {
-      this.container.className = 'buttonWidgetAnnotation radioButton';
-      var element = document.createElement('input');
-      element.disabled = this.data.readOnly;
-      element.type = 'radio';
-      element.name = this.data.fieldName;
-      if (this.data.fieldValue === this.data.buttonValue) {
-        element.setAttribute('checked', true);
-      }
-      this.container.appendChild(element);
-      return this.container;
-    }
-  });
-  return RadioButtonWidgetAnnotationElement;
-}();
-var ChoiceWidgetAnnotationElement = function ChoiceWidgetAnnotationElementClosure() {
-  function ChoiceWidgetAnnotationElement(parameters) {
-    WidgetAnnotationElement.call(this, parameters, parameters.renderInteractiveForms);
-  }
-  _util.Util.inherit(ChoiceWidgetAnnotationElement, WidgetAnnotationElement, {
-    render: function ChoiceWidgetAnnotationElement_render() {
-      this.container.className = 'choiceWidgetAnnotation';
-      var selectElement = document.createElement('select');
-      selectElement.disabled = this.data.readOnly;
-      if (!this.data.combo) {
-        selectElement.size = this.data.options.length;
-        if (this.data.multiSelect) {
-          selectElement.multiple = true;
-        }
-      }
-      for (var i = 0, ii = this.data.options.length; i < ii; i++) {
-        var option = this.data.options[i];
-        var optionElement = document.createElement('option');
-        optionElement.textContent = option.displayValue;
-        optionElement.value = option.exportValue;
-        if (this.data.fieldValue.indexOf(option.displayValue) >= 0) {
-          optionElement.setAttribute('selected', true);
-        }
-        selectElement.appendChild(optionElement);
-      }
-      this.container.appendChild(selectElement);
-      return this.container;
-    }
-  });
-  return ChoiceWidgetAnnotationElement;
-}();
-var PopupAnnotationElement = function PopupAnnotationElementClosure() {
-  var IGNORE_TYPES = ['Line'];
-  function PopupAnnotationElement(parameters) {
-    var isRenderable = !!(parameters.data.title || parameters.data.contents);
-    AnnotationElement.call(this, parameters, isRenderable);
-  }
-  _util.Util.inherit(PopupAnnotationElement, AnnotationElement, {
-    render: function PopupAnnotationElement_render() {
-      this.container.className = 'popupAnnotation';
-      if (IGNORE_TYPES.indexOf(this.data.parentType) >= 0) {
-        return this.container;
-      }
-      var selector = '[data-annotation-id="' + this.data.parentId + '"]';
-      var parentElement = this.layer.querySelector(selector);
-      if (!parentElement) {
-        return this.container;
-      }
-      var popup = new PopupElement({
-        container: this.container,
-        trigger: parentElement,
-        color: this.data.color,
-        title: this.data.title,
-        contents: this.data.contents
-      });
-      var parentLeft = parseFloat(parentElement.style.left);
-      var parentWidth = parseFloat(parentElement.style.width);
-      _dom_utils.CustomStyle.setProp('transformOrigin', this.container, -(parentLeft + parentWidth) + 'px -' + parentElement.style.top);
-      this.container.style.left = parentLeft + parentWidth + 'px';
-      this.container.appendChild(popup.render());
-      return this.container;
-    }
-  });
-  return PopupAnnotationElement;
-}();
-var PopupElement = function PopupElementClosure() {
-  var BACKGROUND_ENLIGHT = 0.7;
-  function PopupElement(parameters) {
+    let popup = new PopupElement({
+      container: this.container,
+      trigger: parentElement,
+      color: this.data.color,
+      title: this.data.title,
+      contents: this.data.contents
+    });
+    let parentLeft = parseFloat(parentElement.style.left);
+    let parentWidth = parseFloat(parentElement.style.width);
+    _dom_utils.CustomStyle.setProp('transformOrigin', this.container, -(parentLeft + parentWidth) + 'px -' + parentElement.style.top);
+    this.container.style.left = parentLeft + parentWidth + 'px';
+    this.container.appendChild(popup.render());
+    return this.container;
+  }
+}
+class PopupElement {
+  constructor(parameters) {
     this.container = parameters.container;
     this.trigger = parameters.trigger;
     this.color = parameters.color;
     this.title = parameters.title;
     this.contents = parameters.contents;
     this.hideWrapper = parameters.hideWrapper || false;
     this.pinned = false;
   }
-  PopupElement.prototype = {
-    render: function PopupElement_render() {
-      var wrapper = document.createElement('div');
-      wrapper.className = 'popupWrapper';
-      this.hideElement = this.hideWrapper ? wrapper : this.container;
+  render() {
+    const BACKGROUND_ENLIGHT = 0.7;
+    let wrapper = document.createElement('div');
+    wrapper.className = 'popupWrapper';
+    this.hideElement = this.hideWrapper ? wrapper : this.container;
+    this.hideElement.setAttribute('hidden', true);
+    let popup = document.createElement('div');
+    popup.className = 'popup';
+    let color = this.color;
+    if (color) {
+      let r = BACKGROUND_ENLIGHT * (255 - color[0]) + color[0];
+      let g = BACKGROUND_ENLIGHT * (255 - color[1]) + color[1];
+      let b = BACKGROUND_ENLIGHT * (255 - color[2]) + color[2];
+      popup.style.backgroundColor = _util.Util.makeCssRgb(r | 0, g | 0, b | 0);
+    }
+    let contents = this._formatContents(this.contents);
+    let title = document.createElement('h1');
+    title.textContent = this.title;
+    this.trigger.addEventListener('click', this._toggle.bind(this));
+    this.trigger.addEventListener('mouseover', this._show.bind(this, false));
+    this.trigger.addEventListener('mouseout', this._hide.bind(this, false));
+    popup.addEventListener('click', this._hide.bind(this, true));
+    popup.appendChild(title);
+    popup.appendChild(contents);
+    wrapper.appendChild(popup);
+    return wrapper;
+  }
+  _formatContents(contents) {
+    let p = document.createElement('p');
+    let lines = contents.split(/(?:\r\n?|\n)/);
+    for (let i = 0, ii = lines.length; i < ii; ++i) {
+      let line = lines[i];
+      p.appendChild(document.createTextNode(line));
+      if (i < ii - 1) {
+        p.appendChild(document.createElement('br'));
+      }
+    }
+    return p;
+  }
+  _toggle() {
+    if (this.pinned) {
+      this._hide(true);
+    } else {
+      this._show(true);
+    }
+  }
+  _show(pin = false) {
+    if (pin) {
+      this.pinned = true;
+    }
+    if (this.hideElement.hasAttribute('hidden')) {
+      this.hideElement.removeAttribute('hidden');
+      this.container.style.zIndex += 1;
+    }
+  }
+  _hide(unpin = true) {
+    if (unpin) {
+      this.pinned = false;
+    }
+    if (!this.hideElement.hasAttribute('hidden') && !this.pinned) {
       this.hideElement.setAttribute('hidden', true);
-      var popup = document.createElement('div');
-      popup.className = 'popup';
-      var color = this.color;
-      if (color) {
-        var r = BACKGROUND_ENLIGHT * (255 - color[0]) + color[0];
-        var g = BACKGROUND_ENLIGHT * (255 - color[1]) + color[1];
-        var b = BACKGROUND_ENLIGHT * (255 - color[2]) + color[2];
-        popup.style.backgroundColor = _util.Util.makeCssRgb(r | 0, g | 0, b | 0);
-      }
-      var contents = this._formatContents(this.contents);
-      var title = document.createElement('h1');
-      title.textContent = this.title;
-      this.trigger.addEventListener('click', this._toggle.bind(this));
-      this.trigger.addEventListener('mouseover', this._show.bind(this, false));
-      this.trigger.addEventListener('mouseout', this._hide.bind(this, false));
-      popup.addEventListener('click', this._hide.bind(this, true));
-      popup.appendChild(title);
-      popup.appendChild(contents);
-      wrapper.appendChild(popup);
-      return wrapper;
-    },
-    _formatContents: function PopupElement_formatContents(contents) {
-      var p = document.createElement('p');
-      var lines = contents.split(/(?:\r\n?|\n)/);
-      for (var i = 0, ii = lines.length; i < ii; ++i) {
-        var line = lines[i];
-        p.appendChild(document.createTextNode(line));
-        if (i < ii - 1) {
-          p.appendChild(document.createElement('br'));
-        }
-      }
-      return p;
-    },
-    _toggle: function PopupElement_toggle() {
-      if (this.pinned) {
-        this._hide(true);
-      } else {
-        this._show(true);
-      }
-    },
-    _show: function PopupElement_show(pin) {
-      if (pin) {
-        this.pinned = true;
-      }
-      if (this.hideElement.hasAttribute('hidden')) {
-        this.hideElement.removeAttribute('hidden');
-        this.container.style.zIndex += 1;
-      }
-    },
-    _hide: function PopupElement_hide(unpin) {
-      if (unpin) {
-        this.pinned = false;
-      }
-      if (!this.hideElement.hasAttribute('hidden') && !this.pinned) {
-        this.hideElement.setAttribute('hidden', true);
-        this.container.style.zIndex -= 1;
-      }
-    }
-  };
-  return PopupElement;
-}();
-var LineAnnotationElement = function LineAnnotationElementClosure() {
-  var SVG_NS = 'http://www.w3.org/2000/svg';
-  function LineAnnotationElement(parameters) {
-    var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
-    AnnotationElement.call(this, parameters, isRenderable, true);
-  }
-  _util.Util.inherit(LineAnnotationElement, AnnotationElement, {
-    render: function LineAnnotationElement_render() {
-      this.container.className = 'lineAnnotation';
-      var data = this.data;
-      var width = data.rect[2] - data.rect[0];
-      var height = data.rect[3] - data.rect[1];
-      var svg = document.createElementNS(SVG_NS, 'svg:svg');
-      svg.setAttributeNS(null, 'version', '1.1');
-      svg.setAttributeNS(null, 'width', width + 'px');
-      svg.setAttributeNS(null, 'height', height + 'px');
-      svg.setAttributeNS(null, 'preserveAspectRatio', 'none');
-      svg.setAttributeNS(null, 'viewBox', '0 0 ' + width + ' ' + height);
-      var line = document.createElementNS(SVG_NS, 'svg:line');
-      line.setAttributeNS(null, 'x1', data.rect[2] - data.lineCoordinates[0]);
-      line.setAttributeNS(null, 'y1', data.rect[3] - data.lineCoordinates[1]);
-      line.setAttributeNS(null, 'x2', data.rect[2] - data.lineCoordinates[2]);
-      line.setAttributeNS(null, 'y2', data.rect[3] - data.lineCoordinates[3]);
-      line.setAttributeNS(null, 'stroke-width', data.borderStyle.width);
-      line.setAttributeNS(null, 'stroke', 'transparent');
-      svg.appendChild(line);
-      this.container.append(svg);
-      this._createPopup(this.container, line, this.data);
-      return this.container;
-    }
-  });
-  return LineAnnotationElement;
-}();
-var HighlightAnnotationElement = function HighlightAnnotationElementClosure() {
-  function HighlightAnnotationElement(parameters) {
-    var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
-    AnnotationElement.call(this, parameters, isRenderable, true);
-  }
-  _util.Util.inherit(HighlightAnnotationElement, AnnotationElement, {
-    render: function HighlightAnnotationElement_render() {
-      this.container.className = 'highlightAnnotation';
-      if (!this.data.hasPopup) {
-        this._createPopup(this.container, null, this.data);
-      }
-      return this.container;
-    }
-  });
-  return HighlightAnnotationElement;
-}();
-var UnderlineAnnotationElement = function UnderlineAnnotationElementClosure() {
-  function UnderlineAnnotationElement(parameters) {
-    var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
-    AnnotationElement.call(this, parameters, isRenderable, true);
-  }
-  _util.Util.inherit(UnderlineAnnotationElement, AnnotationElement, {
-    render: function UnderlineAnnotationElement_render() {
-      this.container.className = 'underlineAnnotation';
-      if (!this.data.hasPopup) {
-        this._createPopup(this.container, null, this.data);
-      }
-      return this.container;
-    }
-  });
-  return UnderlineAnnotationElement;
-}();
-var SquigglyAnnotationElement = function SquigglyAnnotationElementClosure() {
-  function SquigglyAnnotationElement(parameters) {
-    var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
-    AnnotationElement.call(this, parameters, isRenderable, true);
-  }
-  _util.Util.inherit(SquigglyAnnotationElement, AnnotationElement, {
-    render: function SquigglyAnnotationElement_render() {
-      this.container.className = 'squigglyAnnotation';
-      if (!this.data.hasPopup) {
-        this._createPopup(this.container, null, this.data);
-      }
-      return this.container;
-    }
-  });
-  return SquigglyAnnotationElement;
-}();
-var StrikeOutAnnotationElement = function StrikeOutAnnotationElementClosure() {
-  function StrikeOutAnnotationElement(parameters) {
-    var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
-    AnnotationElement.call(this, parameters, isRenderable, true);
-  }
-  _util.Util.inherit(StrikeOutAnnotationElement, AnnotationElement, {
-    render: function StrikeOutAnnotationElement_render() {
-      this.container.className = 'strikeoutAnnotation';
-      if (!this.data.hasPopup) {
-        this._createPopup(this.container, null, this.data);
-      }
-      return this.container;
-    }
-  });
-  return StrikeOutAnnotationElement;
-}();
-var FileAttachmentAnnotationElement = function FileAttachmentAnnotationElementClosure() {
-  function FileAttachmentAnnotationElement(parameters) {
-    AnnotationElement.call(this, parameters, true);
-    var file = this.data.file;
+      this.container.style.zIndex -= 1;
+    }
+  }
+}
+class LineAnnotationElement extends AnnotationElement {
+  constructor(parameters) {
+    let isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    super(parameters, isRenderable, true);
+  }
+  render() {
+    const SVG_NS = 'http://www.w3.org/2000/svg';
+    this.container.className = 'lineAnnotation';
+    let data = this.data;
+    let width = data.rect[2] - data.rect[0];
+    let height = data.rect[3] - data.rect[1];
+    let svg = document.createElementNS(SVG_NS, 'svg:svg');
+    svg.setAttributeNS(null, 'version', '1.1');
+    svg.setAttributeNS(null, 'width', width + 'px');
+    svg.setAttributeNS(null, 'height', height + 'px');
+    svg.setAttributeNS(null, 'preserveAspectRatio', 'none');
+    svg.setAttributeNS(null, 'viewBox', '0 0 ' + width + ' ' + height);
+    let line = document.createElementNS(SVG_NS, 'svg:line');
+    line.setAttributeNS(null, 'x1', data.rect[2] - data.lineCoordinates[0]);
+    line.setAttributeNS(null, 'y1', data.rect[3] - data.lineCoordinates[1]);
+    line.setAttributeNS(null, 'x2', data.rect[2] - data.lineCoordinates[2]);
+    line.setAttributeNS(null, 'y2', data.rect[3] - data.lineCoordinates[3]);
+    line.setAttributeNS(null, 'stroke-width', data.borderStyle.width);
+    line.setAttributeNS(null, 'stroke', 'transparent');
+    svg.appendChild(line);
+    this.container.append(svg);
+    this._createPopup(this.container, line, this.data);
+    return this.container;
+  }
+}
+class HighlightAnnotationElement extends AnnotationElement {
+  constructor(parameters) {
+    let isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    super(parameters, isRenderable, true);
+  }
+  render() {
+    this.container.className = 'highlightAnnotation';
+    if (!this.data.hasPopup) {
+      this._createPopup(this.container, null, this.data);
+    }
+    return this.container;
+  }
+}
+class UnderlineAnnotationElement extends AnnotationElement {
+  constructor(parameters) {
+    let isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    super(parameters, isRenderable, true);
+  }
+  render() {
+    this.container.className = 'underlineAnnotation';
+    if (!this.data.hasPopup) {
+      this._createPopup(this.container, null, this.data);
+    }
+    return this.container;
+  }
+}
+class SquigglyAnnotationElement extends AnnotationElement {
+  constructor(parameters) {
+    let isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    super(parameters, isRenderable, true);
+  }
+  render() {
+    this.container.className = 'squigglyAnnotation';
+    if (!this.data.hasPopup) {
+      this._createPopup(this.container, null, this.data);
+    }
+    return this.container;
+  }
+}
+class StrikeOutAnnotationElement extends AnnotationElement {
+  constructor(parameters) {
+    let isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    super(parameters, isRenderable, true);
+  }
+  render() {
+    this.container.className = 'strikeoutAnnotation';
+    if (!this.data.hasPopup) {
+      this._createPopup(this.container, null, this.data);
+    }
+    return this.container;
+  }
+}
+class FileAttachmentAnnotationElement extends AnnotationElement {
+  constructor(parameters) {
+    super(parameters, true);
+    let file = this.data.file;
     this.filename = (0, _dom_utils.getFilenameFromUrl)(file.filename);
     this.content = file.content;
     this.linkService.onFileAttachmentAnnotation({
       id: (0, _util.stringToPDFString)(file.filename),
       filename: file.filename,
       content: file.content
     });
   }
-  _util.Util.inherit(FileAttachmentAnnotationElement, AnnotationElement, {
-    render: function FileAttachmentAnnotationElement_render() {
-      this.container.className = 'fileAttachmentAnnotation';
-      var trigger = document.createElement('div');
-      trigger.style.height = this.container.style.height;
-      trigger.style.width = this.container.style.width;
-      trigger.addEventListener('dblclick', this._download.bind(this));
-      if (!this.data.hasPopup && (this.data.title || this.data.contents)) {
-        this._createPopup(this.container, trigger, this.data);
-      }
-      this.container.appendChild(trigger);
-      return this.container;
-    },
-    _download: function FileAttachmentAnnotationElement_download() {
-      if (!this.downloadManager) {
-        (0, _util.warn)('Download cannot be started due to unavailable download manager');
-        return;
-      }
-      this.downloadManager.downloadData(this.content, this.filename, '');
-    }
-  });
-  return FileAttachmentAnnotationElement;
-}();
-var AnnotationLayer = function AnnotationLayerClosure() {
-  return {
-    render: function AnnotationLayer_render(parameters) {
-      var annotationElementFactory = new AnnotationElementFactory();
-      for (var i = 0, ii = parameters.annotations.length; i < ii; i++) {
-        var data = parameters.annotations[i];
-        if (!data) {
-          continue;
-        }
-        var element = annotationElementFactory.create({
-          data,
-          layer: parameters.div,
-          page: parameters.page,
-          viewport: parameters.viewport,
-          linkService: parameters.linkService,
-          downloadManager: parameters.downloadManager,
-          imageResourcesPath: parameters.imageResourcesPath || (0, _dom_utils.getDefaultSetting)('imageResourcesPath'),
-          renderInteractiveForms: parameters.renderInteractiveForms || false
-        });
-        if (element.isRenderable) {
-          parameters.div.appendChild(element.render());
-        }
-      }
-    },
-    update: function AnnotationLayer_update(parameters) {
-      for (var i = 0, ii = parameters.annotations.length; i < ii; i++) {
-        var data = parameters.annotations[i];
-        var element = parameters.div.querySelector('[data-annotation-id="' + data.id + '"]');
-        if (element) {
-          _dom_utils.CustomStyle.setProp('transform', element, 'matrix(' + parameters.viewport.transform.join(',') + ')');
-        }
-      }
-      parameters.div.removeAttribute('hidden');
-    }
-  };
-}();
+  render() {
+    this.container.className = 'fileAttachmentAnnotation';
+    let trigger = document.createElement('div');
+    trigger.style.height = this.container.style.height;
+    trigger.style.width = this.container.style.width;
+    trigger.addEventListener('dblclick', this._download.bind(this));
+    if (!this.data.hasPopup && (this.data.title || this.data.contents)) {
+      this._createPopup(this.container, trigger, this.data);
+    }
+    this.container.appendChild(trigger);
+    return this.container;
+  }
+  _download() {
+    if (!this.downloadManager) {
+      (0, _util.warn)('Download cannot be started due to unavailable download manager');
+      return;
+    }
+    this.downloadManager.downloadData(this.content, this.filename, '');
+  }
+}
+class AnnotationLayer {
+  static render(parameters) {
+    for (let i = 0, ii = parameters.annotations.length; i < ii; i++) {
+      let data = parameters.annotations[i];
+      if (!data) {
+        continue;
+      }
+      let element = AnnotationElementFactory.create({
+        data,
+        layer: parameters.div,
+        page: parameters.page,
+        viewport: parameters.viewport,
+        linkService: parameters.linkService,
+        downloadManager: parameters.downloadManager,
+        imageResourcesPath: parameters.imageResourcesPath || (0, _dom_utils.getDefaultSetting)('imageResourcesPath'),
+        renderInteractiveForms: parameters.renderInteractiveForms || false
+      });
+      if (element.isRenderable) {
+        parameters.div.appendChild(element.render());
+      }
+    }
+  }
+  static update(parameters) {
+    for (let i = 0, ii = parameters.annotations.length; i < ii; i++) {
+      let data = parameters.annotations[i];
+      let element = parameters.div.querySelector('[data-annotation-id="' + data.id + '"]');
+      if (element) {
+        _dom_utils.CustomStyle.setProp('transform', element, 'matrix(' + parameters.viewport.transform.join(',') + ')');
+      }
+    }
+    parameters.div.removeAttribute('hidden');
+  }
+}
 exports.AnnotationLayer = AnnotationLayer;
 
 /***/ }),
 /* 4 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
@@ -3841,18 +3789,18 @@ var _UnsupportedManager = function Unsup
       for (var i = 0, ii = listeners.length; i < ii; i++) {
         listeners[i](featureId);
       }
     }
   };
 }();
 var version, build;
 {
-  exports.version = version = '1.9.489';
-  exports.build = build = 'b7fcaff0';
+  exports.version = version = '1.9.512';
+  exports.build = build = '066fea9c';
 }
 exports.getDocument = getDocument;
 exports.LoopbackPort = LoopbackPort;
 exports.PDFDataRangeTransport = PDFDataRangeTransport;
 exports.PDFWorker = PDFWorker;
 exports.PDFDocumentProxy = PDFDocumentProxy;
 exports.PDFPageProxy = PDFPageProxy;
 exports.setPDFNetworkStreamClass = setPDFNetworkStreamClass;
@@ -4898,18 +4846,18 @@ var _svg = __w_pdfjs_require__(5);
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 var isWorker = typeof window === 'undefined';
 if (!_global_scope2.default.PDFJS) {
   _global_scope2.default.PDFJS = {};
 }
 var PDFJS = _global_scope2.default.PDFJS;
 {
-  PDFJS.version = '1.9.489';
-  PDFJS.build = 'b7fcaff0';
+  PDFJS.version = '1.9.512';
+  PDFJS.build = '066fea9c';
 }
 PDFJS.pdfBug = false;
 if (PDFJS.verbosity !== undefined) {
   (0, _util.setVerbosityLevel)(PDFJS.verbosity);
 }
 delete PDFJS.verbosity;
 Object.defineProperty(PDFJS, 'verbosity', {
   get() {
@@ -10465,18 +10413,18 @@ exports.PDFDataTransportStream = PDFData
 
 /***/ }),
 /* 15 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '1.9.489';
-var pdfjsBuild = 'b7fcaff0';
+var pdfjsVersion = '1.9.512';
+var pdfjsBuild = '066fea9c';
 var pdfjsSharedUtil = __w_pdfjs_require__(0);
 var pdfjsDisplayGlobal = __w_pdfjs_require__(9);
 var pdfjsDisplayAPI = __w_pdfjs_require__(4);
 var pdfjsDisplayTextLayer = __w_pdfjs_require__(6);
 var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(3);
 var pdfjsDisplayDOMUtils = __w_pdfjs_require__(1);
 var pdfjsDisplaySVG = __w_pdfjs_require__(5);
 ;
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -1018,16 +1018,22 @@ function wrapReason(reason) {
     case 'MissingPDFException':
       return new MissingPDFException(reason.message);
     case 'UnexpectedResponseException':
       return new UnexpectedResponseException(reason.message, reason.status);
     default:
       return new UnknownErrorException(reason.message, reason.details);
   }
 }
+function makeReasonSerializable(reason) {
+  if (!(reason instanceof Error) || reason instanceof AbortException || reason instanceof MissingPDFException || reason instanceof UnexpectedResponseException || reason instanceof UnknownErrorException) {
+    return reason;
+  }
+  return new UnknownErrorException(reason.message, reason.toString());
+}
 function resolveOrReject(capability, success, reason) {
   if (success) {
     capability.resolve();
   } else {
     capability.reject(reason);
   }
 }
 function finalize(promise) {
@@ -1075,25 +1081,22 @@ function MessageHandler(sourceName, targ
           comObj.postMessage({
             sourceName,
             targetName,
             isReply: true,
             callbackId: data.callbackId,
             data: result
           });
         }, reason => {
-          if (reason instanceof Error) {
-            reason = reason + '';
-          }
           comObj.postMessage({
             sourceName,
             targetName,
             isReply: true,
             callbackId: data.callbackId,
-            error: reason
+            error: makeReasonSerializable(reason)
           });
         });
       } else if (data.streamId) {
         this._createStreamSink(data);
       } else {
         action[0].call(action[1], data.data);
       }
     } else {
@@ -22646,23 +22649,29 @@ var XRef = function XRefClosure() {
       if (xrefEntry.gen !== gen) {
         throw new _util.FormatError('inconsistent generation in XRef');
       }
       var stream = this.stream.makeSubStream(xrefEntry.offset + this.stream.start);
       var parser = new _parser.Parser(new _parser.Lexer(stream), true, this);
       var obj1 = parser.getObj();
       var obj2 = parser.getObj();
       var obj3 = parser.getObj();
-      if (!(0, _util.isInt)(obj1) || parseInt(obj1, 10) !== num || !(0, _util.isInt)(obj2) || parseInt(obj2, 10) !== gen || !(0, _primitives.isCmd)(obj3)) {
+      if (!Number.isInteger(obj1)) {
+        obj1 = parseInt(obj1, 10);
+      }
+      if (!Number.isInteger(obj2)) {
+        obj2 = parseInt(obj2, 10);
+      }
+      if (obj1 !== num || obj2 !== gen || !(0, _primitives.isCmd)(obj3)) {
         throw new _util.FormatError('bad XRef entry');
       }
-      if (!(0, _primitives.isCmd)(obj3, 'obj')) {
+      if (obj3.cmd !== 'obj') {
         if (obj3.cmd.indexOf('obj') === 0) {
           num = parseInt(obj3.cmd.substring(3), 10);
-          if (!isNaN(num)) {
+          if (!Number.isNaN(num)) {
             return num;
           }
         }
         throw new _util.FormatError('bad XRef entry');
       }
       if (this.encrypt && !suppressEncryption) {
         xrefEntry = parser.getObj(this.encrypt.createCipherTransform(num, gen));
       } else {
@@ -27309,41 +27318,40 @@ var _obj = __w_pdfjs_require__(15);
 var _primitives = __w_pdfjs_require__(1);
 
 var _colorspace = __w_pdfjs_require__(3);
 
 var _evaluator = __w_pdfjs_require__(13);
 
 var _stream = __w_pdfjs_require__(2);
 
-function AnnotationFactory() {}
-AnnotationFactory.prototype = {
-  create: function AnnotationFactory_create(xref, ref, pdfManager, idFactory) {
-    var dict = xref.fetchIfRef(ref);
+class AnnotationFactory {
+  static create(xref, ref, pdfManager, idFactory) {
+    let dict = xref.fetchIfRef(ref);
     if (!(0, _primitives.isDict)(dict)) {
       return;
     }
-    var id = (0, _primitives.isRef)(ref) ? ref.toString() : 'annot_' + idFactory.createObjId();
-    var subtype = dict.get('Subtype');
+    let id = (0, _primitives.isRef)(ref) ? ref.toString() : 'annot_' + idFactory.createObjId();
+    let subtype = dict.get('Subtype');
     subtype = (0, _primitives.isName)(subtype) ? subtype.name : null;
-    var parameters = {
+    let parameters = {
       xref,
       dict,
       ref: (0, _primitives.isRef)(ref) ? ref : null,
       subtype,
       id,
       pdfManager
     };
     switch (subtype) {
       case 'Link':
         return new LinkAnnotation(parameters);
       case 'Text':
         return new TextAnnotation(parameters);
       case 'Widget':
-        var fieldType = _util.Util.getInheritableProperty(dict, 'FT');
+        let fieldType = _util.Util.getInheritableProperty(dict, 'FT');
         fieldType = (0, _primitives.isName)(fieldType) ? fieldType.name : null;
         switch (fieldType) {
           case 'Tx':
             return new TextWidgetAnnotation(parameters);
           case 'Btn':
             return new ButtonWidgetAnnotation(parameters);
           case 'Ch':
             return new ChoiceWidgetAnnotation(parameters);
@@ -27368,576 +27376,526 @@ AnnotationFactory.prototype = {
         if (!subtype) {
           (0, _util.warn)('Annotation is missing the required /Subtype.');
         } else {
           (0, _util.warn)('Unimplemented annotation type "' + subtype + '", ' + 'falling back to base annotation.');
         }
         return new Annotation(parameters);
     }
   }
-};
-var Annotation = function AnnotationClosure() {
-  function getTransformMatrix(rect, bbox, matrix) {
-    var bounds = _util.Util.getAxialAlignedBoundingBox(bbox, matrix);
-    var minX = bounds[0];
-    var minY = bounds[1];
-    var maxX = bounds[2];
-    var maxY = bounds[3];
-    if (minX === maxX || minY === maxY) {
-      return [1, 0, 0, 1, rect[0], rect[1]];
-    }
-    var xRatio = (rect[2] - rect[0]) / (maxX - minX);
-    var yRatio = (rect[3] - rect[1]) / (maxY - minY);
-    return [xRatio, 0, 0, yRatio, rect[0] - minX * xRatio, rect[1] - minY * yRatio];
-  }
-  function Annotation(params) {
-    var dict = params.dict;
+}
+function getTransformMatrix(rect, bbox, matrix) {
+  let bounds = _util.Util.getAxialAlignedBoundingBox(bbox, matrix);
+  let minX = bounds[0];
+  let minY = bounds[1];
+  let maxX = bounds[2];
+  let maxY = bounds[3];
+  if (minX === maxX || minY === maxY) {
+    return [1, 0, 0, 1, rect[0], rect[1]];
+  }
+  let xRatio = (rect[2] - rect[0]) / (maxX - minX);
+  let yRatio = (rect[3] - rect[1]) / (maxY - minY);
+  return [xRatio, 0, 0, yRatio, rect[0] - minX * xRatio, rect[1] - minY * yRatio];
+}
+class Annotation {
+  constructor(params) {
+    let dict = params.dict;
     this.setFlags(dict.get('F'));
     this.setRectangle(dict.getArray('Rect'));
     this.setColor(dict.getArray('C'));
     this.setBorderStyle(dict);
     this.setAppearance(dict);
-    this.data = {};
-    this.data.id = params.id;
-    this.data.subtype = params.subtype;
-    this.data.annotationFlags = this.flags;
-    this.data.rect = this.rectangle;
-    this.data.color = this.color;
-    this.data.borderStyle = this.borderStyle;
-    this.data.hasAppearance = !!this.appearance;
-  }
-  Annotation.prototype = {
-    _hasFlag: function Annotation_hasFlag(flags, flag) {
-      return !!(flags & flag);
-    },
-    _isViewable: function Annotation_isViewable(flags) {
-      return !this._hasFlag(flags, _util.AnnotationFlag.INVISIBLE) && !this._hasFlag(flags, _util.AnnotationFlag.HIDDEN) && !this._hasFlag(flags, _util.AnnotationFlag.NOVIEW);
-    },
-    _isPrintable: function AnnotationFlag_isPrintable(flags) {
-      return this._hasFlag(flags, _util.AnnotationFlag.PRINT) && !this._hasFlag(flags, _util.AnnotationFlag.INVISIBLE) && !this._hasFlag(flags, _util.AnnotationFlag.HIDDEN);
-    },
-    get viewable() {
-      if (this.flags === 0) {
-        return true;
-      }
-      return this._isViewable(this.flags);
-    },
-    get printable() {
-      if (this.flags === 0) {
-        return false;
-      }
-      return this._isPrintable(this.flags);
-    },
-    setFlags: function Annotation_setFlags(flags) {
-      this.flags = (0, _util.isInt)(flags) && flags > 0 ? flags : 0;
-    },
-    hasFlag: function Annotation_hasFlag(flag) {
-      return this._hasFlag(this.flags, flag);
-    },
-    setRectangle: function Annotation_setRectangle(rectangle) {
-      if ((0, _util.isArray)(rectangle) && rectangle.length === 4) {
-        this.rectangle = _util.Util.normalizeRect(rectangle);
-      } else {
-        this.rectangle = [0, 0, 0, 0];
-      }
-    },
-    setColor: function Annotation_setColor(color) {
-      var rgbColor = new Uint8Array(3);
-      if (!(0, _util.isArray)(color)) {
+    this.data = {
+      annotationFlags: this.flags,
+      borderStyle: this.borderStyle,
+      color: this.color,
+      hasAppearance: !!this.appearance,
+      id: params.id,
+      rect: this.rectangle,
+      subtype: params.subtype
+    };
+  }
+  _hasFlag(flags, flag) {
+    return !!(flags & flag);
+  }
+  _isViewable(flags) {
+    return !this._hasFlag(flags, _util.AnnotationFlag.INVISIBLE) && !this._hasFlag(flags, _util.AnnotationFlag.HIDDEN) && !this._hasFlag(flags, _util.AnnotationFlag.NOVIEW);
+  }
+  _isPrintable(flags) {
+    return this._hasFlag(flags, _util.AnnotationFlag.PRINT) && !this._hasFlag(flags, _util.AnnotationFlag.INVISIBLE) && !this._hasFlag(flags, _util.AnnotationFlag.HIDDEN);
+  }
+  get viewable() {
+    if (this.flags === 0) {
+      return true;
+    }
+    return this._isViewable(this.flags);
+  }
+  get printable() {
+    if (this.flags === 0) {
+      return false;
+    }
+    return this._isPrintable(this.flags);
+  }
+  setFlags(flags) {
+    this.flags = (0, _util.isInt)(flags) && flags > 0 ? flags : 0;
+  }
+  hasFlag(flag) {
+    return this._hasFlag(this.flags, flag);
+  }
+  setRectangle(rectangle) {
+    if ((0, _util.isArray)(rectangle) && rectangle.length === 4) {
+      this.rectangle = _util.Util.normalizeRect(rectangle);
+    } else {
+      this.rectangle = [0, 0, 0, 0];
+    }
+  }
+  setColor(color) {
+    let rgbColor = new Uint8Array(3);
+    if (!(0, _util.isArray)(color)) {
+      this.color = rgbColor;
+      return;
+    }
+    switch (color.length) {
+      case 0:
+        this.color = null;
+        break;
+      case 1:
+        _colorspace.ColorSpace.singletons.gray.getRgbItem(color, 0, rgbColor, 0);
+        this.color = rgbColor;
+        break;
+      case 3:
+        _colorspace.ColorSpace.singletons.rgb.getRgbItem(color, 0, rgbColor, 0);
+        this.color = rgbColor;
+        break;
+      case 4:
+        _colorspace.ColorSpace.singletons.cmyk.getRgbItem(color, 0, rgbColor, 0);
+        this.color = rgbColor;
+        break;
+      default:
         this.color = rgbColor;
-        return;
-      }
-      switch (color.length) {
-        case 0:
-          this.color = null;
-          break;
-        case 1:
-          _colorspace.ColorSpace.singletons.gray.getRgbItem(color, 0, rgbColor, 0);
-          this.color = rgbColor;
-          break;
-        case 3:
-          _colorspace.ColorSpace.singletons.rgb.getRgbItem(color, 0, rgbColor, 0);
-          this.color = rgbColor;
-          break;
-        case 4:
-          _colorspace.ColorSpace.singletons.cmyk.getRgbItem(color, 0, rgbColor, 0);
-          this.color = rgbColor;
-          break;
-        default:
-          this.color = rgbColor;
-          break;
-      }
-    },
-    setBorderStyle: function Annotation_setBorderStyle(borderStyle) {
-      this.borderStyle = new AnnotationBorderStyle();
-      if (!(0, _primitives.isDict)(borderStyle)) {
-        return;
-      }
-      if (borderStyle.has('BS')) {
-        var dict = borderStyle.get('BS');
-        var dictType = dict.get('Type');
-        if (!dictType || (0, _primitives.isName)(dictType, 'Border')) {
-          this.borderStyle.setWidth(dict.get('W'));
-          this.borderStyle.setStyle(dict.get('S'));
-          this.borderStyle.setDashArray(dict.getArray('D'));
-        }
-      } else if (borderStyle.has('Border')) {
-        var array = borderStyle.getArray('Border');
-        if ((0, _util.isArray)(array) && array.length >= 3) {
-          this.borderStyle.setHorizontalCornerRadius(array[0]);
-          this.borderStyle.setVerticalCornerRadius(array[1]);
-          this.borderStyle.setWidth(array[2]);
-          if (array.length === 4) {
-            this.borderStyle.setDashArray(array[3]);
-          }
-        }
-      } else {
-        this.borderStyle.setWidth(0);
-      }
-    },
-    setAppearance: function Annotation_setAppearance(dict) {
-      this.appearance = null;
-      var appearanceStates = dict.get('AP');
-      if (!(0, _primitives.isDict)(appearanceStates)) {
-        return;
-      }
-      var normalAppearanceState = appearanceStates.get('N');
-      if ((0, _primitives.isStream)(normalAppearanceState)) {
-        this.appearance = normalAppearanceState;
-        return;
-      }
-      if (!(0, _primitives.isDict)(normalAppearanceState)) {
-        return;
-      }
-      var as = dict.get('AS');
-      if (!(0, _primitives.isName)(as) || !normalAppearanceState.has(as.name)) {
-        return;
-      }
-      this.appearance = normalAppearanceState.get(as.name);
-    },
-    _preparePopup: function Annotation_preparePopup(dict) {
-      if (!dict.has('C')) {
-        this.data.color = null;
-      }
-      this.data.hasPopup = dict.has('Popup');
-      this.data.title = (0, _util.stringToPDFString)(dict.get('T') || '');
-      this.data.contents = (0, _util.stringToPDFString)(dict.get('Contents') || '');
-    },
-    loadResources: function Annotation_loadResources(keys) {
-      return this.appearance.dict.getAsync('Resources').then(resources => {
-        if (!resources) {
-          return;
-        }
-        let objectLoader = new _obj.ObjectLoader(resources, keys, resources.xref);
-        return objectLoader.load().then(function () {
-          return resources;
-        });
-      });
-    },
-    getOperatorList: function Annotation_getOperatorList(evaluator, task, renderForms) {
-      if (!this.appearance) {
-        return Promise.resolve(new _evaluator.OperatorList());
-      }
-      var data = this.data;
-      var appearanceDict = this.appearance.dict;
-      var resourcesPromise = this.loadResources(['ExtGState', 'ColorSpace', 'Pattern', 'Shading', 'XObject', 'Font']);
-      var bbox = appearanceDict.getArray('BBox') || [0, 0, 1, 1];
-      var matrix = appearanceDict.getArray('Matrix') || [1, 0, 0, 1, 0, 0];
-      var transform = getTransformMatrix(data.rect, bbox, matrix);
-      return resourcesPromise.then(resources => {
-        var opList = new _evaluator.OperatorList();
-        opList.addOp(_util.OPS.beginAnnotation, [data.rect, transform, matrix]);
-        return evaluator.getOperatorList({
-          stream: this.appearance,
-          task,
-          resources,
-          operatorList: opList
-        }).then(() => {
-          opList.addOp(_util.OPS.endAnnotation, []);
-          this.appearance.reset();
-          return opList;
-        });
-      });
-    }
-  };
-  return Annotation;
-}();
-var AnnotationBorderStyle = function AnnotationBorderStyleClosure() {
-  function AnnotationBorderStyle() {
+        break;
+    }
+  }
+  setBorderStyle(borderStyle) {
+    this.borderStyle = new AnnotationBorderStyle();
+    if (!(0, _primitives.isDict)(borderStyle)) {
+      return;
+    }
+    if (borderStyle.has('BS')) {
+      let dict = borderStyle.get('BS');
+      let dictType = dict.get('Type');
+      if (!dictType || (0, _primitives.isName)(dictType, 'Border')) {
+        this.borderStyle.setWidth(dict.get('W'));
+        this.borderStyle.setStyle(dict.get('S'));
+        this.borderStyle.setDashArray(dict.getArray('D'));
+      }
+    } else if (borderStyle.has('Border')) {
+      let array = borderStyle.getArray('Border');
+      if ((0, _util.isArray)(array) && array.length >= 3) {
+        this.borderStyle.setHorizontalCornerRadius(array[0]);
+        this.borderStyle.setVerticalCornerRadius(array[1]);
+        this.borderStyle.setWidth(array[2]);
+        if (array.length === 4) {
+          this.borderStyle.setDashArray(array[3]);
+        }
+      }
+    } else {
+      this.borderStyle.setWidth(0);
+    }
+  }
+  setAppearance(dict) {
+    this.appearance = null;
+    let appearanceStates = dict.get('AP');
+    if (!(0, _primitives.isDict)(appearanceStates)) {
+      return;
+    }
+    let normalAppearanceState = appearanceStates.get('N');
+    if ((0, _primitives.isStream)(normalAppearanceState)) {
+      this.appearance = normalAppearanceState;
+      return;
+    }
+    if (!(0, _primitives.isDict)(normalAppearanceState)) {
+      return;
+    }
+    let as = dict.get('AS');
+    if (!(0, _primitives.isName)(as) || !normalAppearanceState.has(as.name)) {
+      return;
+    }
+    this.appearance = normalAppearanceState.get(as.name);
+  }
+  _preparePopup(dict) {
+    if (!dict.has('C')) {
+      this.data.color = null;
+    }
+    this.data.hasPopup = dict.has('Popup');
+    this.data.title = (0, _util.stringToPDFString)(dict.get('T') || '');
+    this.data.contents = (0, _util.stringToPDFString)(dict.get('Contents') || '');
+  }
+  loadResources(keys) {
+    return this.appearance.dict.getAsync('Resources').then(resources => {
+      if (!resources) {
+        return;
+      }
+      let objectLoader = new _obj.ObjectLoader(resources, keys, resources.xref);
+      return objectLoader.load().then(function () {
+        return resources;
+      });
+    });
+  }
+  getOperatorList(evaluator, task, renderForms) {
+    if (!this.appearance) {
+      return Promise.resolve(new _evaluator.OperatorList());
+    }
+    let data = this.data;
+    let appearanceDict = this.appearance.dict;
+    let resourcesPromise = this.loadResources(['ExtGState', 'ColorSpace', 'Pattern', 'Shading', 'XObject', 'Font']);
+    let bbox = appearanceDict.getArray('BBox') || [0, 0, 1, 1];
+    let matrix = appearanceDict.getArray('Matrix') || [1, 0, 0, 1, 0, 0];
+    let transform = getTransformMatrix(data.rect, bbox, matrix);
+    return resourcesPromise.then(resources => {
+      let opList = new _evaluator.OperatorList();
+      opList.addOp(_util.OPS.beginAnnotation, [data.rect, transform, matrix]);
+      return evaluator.getOperatorList({
+        stream: this.appearance,
+        task,
+        resources,
+        operatorList: opList
+      }).then(() => {
+        opList.addOp(_util.OPS.endAnnotation, []);
+        this.appearance.reset();
+        return opList;
+      });
+    });
+  }
+}
+class AnnotationBorderStyle {
+  constructor() {
     this.width = 1;
     this.style = _util.AnnotationBorderStyleType.SOLID;
     this.dashArray = [3];
     this.horizontalCornerRadius = 0;
     this.verticalCornerRadius = 0;
   }
-  AnnotationBorderStyle.prototype = {
-    setWidth: function AnnotationBorderStyle_setWidth(width) {
-      if (width === (width | 0)) {
-        this.width = width;
-      }
-    },
-    setStyle: function AnnotationBorderStyle_setStyle(style) {
-      if (!style) {
-        return;
-      }
-      switch (style.name) {
-        case 'S':
-          this.style = _util.AnnotationBorderStyleType.SOLID;
-          break;
-        case 'D':
-          this.style = _util.AnnotationBorderStyleType.DASHED;
-          break;
-        case 'B':
-          this.style = _util.AnnotationBorderStyleType.BEVELED;
-          break;
-        case 'I':
-          this.style = _util.AnnotationBorderStyleType.INSET;
-          break;
-        case 'U':
-          this.style = _util.AnnotationBorderStyleType.UNDERLINE;
-          break;
-        default:
-          break;
-      }
-    },
-    setDashArray: function AnnotationBorderStyle_setDashArray(dashArray) {
-      if ((0, _util.isArray)(dashArray) && dashArray.length > 0) {
-        var isValid = true;
-        var allZeros = true;
-        for (var i = 0, len = dashArray.length; i < len; i++) {
-          var element = dashArray[i];
-          var validNumber = +element >= 0;
-          if (!validNumber) {
-            isValid = false;
-            break;
-          } else if (element > 0) {
-            allZeros = false;
-          }
-        }
-        if (isValid && !allZeros) {
-          this.dashArray = dashArray;
-        } else {
-          this.width = 0;
-        }
-      } else if (dashArray) {
+  setWidth(width) {
+    if (width === (width | 0)) {
+      this.width = width;
+    }
+  }
+  setStyle(style) {
+    if (!style) {
+      return;
+    }
+    switch (style.name) {
+      case 'S':
+        this.style = _util.AnnotationBorderStyleType.SOLID;
+        break;
+      case 'D':
+        this.style = _util.AnnotationBorderStyleType.DASHED;
+        break;
+      case 'B':
+        this.style = _util.AnnotationBorderStyleType.BEVELED;
+        break;
+      case 'I':
+        this.style = _util.AnnotationBorderStyleType.INSET;
+        break;
+      case 'U':
+        this.style = _util.AnnotationBorderStyleType.UNDERLINE;
+        break;
+      default:
+        break;
+    }
+  }
+  setDashArray(dashArray) {
+    if ((0, _util.isArray)(dashArray) && dashArray.length > 0) {
+      let isValid = true;
+      let allZeros = true;
+      for (let i = 0, len = dashArray.length; i < len; i++) {
+        let element = dashArray[i];
+        let validNumber = +element >= 0;
+        if (!validNumber) {
+          isValid = false;
+          break;
+        } else if (element > 0) {
+          allZeros = false;
+        }
+      }
+      if (isValid && !allZeros) {
+        this.dashArray = dashArray;
+      } else {
         this.width = 0;
       }
-    },
-    setHorizontalCornerRadius: function AnnotationBorderStyle_setHorizontalCornerRadius(radius) {
-      if (radius === (radius | 0)) {
-        this.horizontalCornerRadius = radius;
-      }
-    },
-    setVerticalCornerRadius: function AnnotationBorderStyle_setVerticalCornerRadius(radius) {
-      if (radius === (radius | 0)) {
-        this.verticalCornerRadius = radius;
-      }
-    }
-  };
-  return AnnotationBorderStyle;
-}();
-var WidgetAnnotation = function WidgetAnnotationClosure() {
-  function WidgetAnnotation(params) {
-    Annotation.call(this, params);
-    var dict = params.dict;
-    var data = this.data;
+    } else if (dashArray) {
+      this.width = 0;
+    }
+  }
+  setHorizontalCornerRadius(radius) {
+    if (radius === (radius | 0)) {
+      this.horizontalCornerRadius = radius;
+    }
+  }
+  setVerticalCornerRadius(radius) {
+    if (radius === (radius | 0)) {
+      this.verticalCornerRadius = radius;
+    }
+  }
+}
+class WidgetAnnotation extends Annotation {
+  constructor(params) {
+    super(params);
+    let dict = params.dict;
+    let data = this.data;
     data.annotationType = _util.AnnotationType.WIDGET;
     data.fieldName = this._constructFieldName(dict);
     data.fieldValue = _util.Util.getInheritableProperty(dict, 'V', true);
     data.alternativeText = (0, _util.stringToPDFString)(dict.get('TU') || '');
     data.defaultAppearance = _util.Util.getInheritableProperty(dict, 'DA') || '';
-    var fieldType = _util.Util.getInheritableProperty(dict, 'FT');
+    let fieldType = _util.Util.getInheritableProperty(dict, 'FT');
     data.fieldType = (0, _primitives.isName)(fieldType) ? fieldType.name : null;
     this.fieldResources = _util.Util.getInheritableProperty(dict, 'DR') || _primitives.Dict.empty;
     data.fieldFlags = _util.Util.getInheritableProperty(dict, 'Ff');
     if (!(0, _util.isInt)(data.fieldFlags) || data.fieldFlags < 0) {
       data.fieldFlags = 0;
     }
     data.readOnly = this.hasFieldFlag(_util.AnnotationFieldFlag.READONLY);
     if (data.fieldType === 'Sig') {
       this.setFlags(_util.AnnotationFlag.HIDDEN);
     }
   }
-  _util.Util.inherit(WidgetAnnotation, Annotation, {
-    _constructFieldName: function WidgetAnnotation_constructFieldName(dict) {
-      if (!dict.has('T') && !dict.has('Parent')) {
-        (0, _util.warn)('Unknown field name, falling back to empty field name.');
-        return '';
-      }
-      if (!dict.has('Parent')) {
-        return (0, _util.stringToPDFString)(dict.get('T'));
-      }
-      var fieldName = [];
-      if (dict.has('T')) {
-        fieldName.unshift((0, _util.stringToPDFString)(dict.get('T')));
-      }
-      var loopDict = dict;
-      while (loopDict.has('Parent')) {
-        loopDict = loopDict.get('Parent');
-        if (!(0, _primitives.isDict)(loopDict)) {
-          break;
-        }
-        if (loopDict.has('T')) {
-          fieldName.unshift((0, _util.stringToPDFString)(loopDict.get('T')));
-        }
-      }
-      return fieldName.join('.');
-    },
-    hasFieldFlag: function WidgetAnnotation_hasFieldFlag(flag) {
-      return !!(this.data.fieldFlags & flag);
-    }
-  });
-  return WidgetAnnotation;
-}();
-var TextWidgetAnnotation = function TextWidgetAnnotationClosure() {
-  function TextWidgetAnnotation(params) {
-    WidgetAnnotation.call(this, params);
+  _constructFieldName(dict) {
+    if (!dict.has('T') && !dict.has('Parent')) {
+      (0, _util.warn)('Unknown field name, falling back to empty field name.');
+      return '';
+    }
+    if (!dict.has('Parent')) {
+      return (0, _util.stringToPDFString)(dict.get('T'));
+    }
+    let fieldName = [];
+    if (dict.has('T')) {
+      fieldName.unshift((0, _util.stringToPDFString)(dict.get('T')));
+    }
+    let loopDict = dict;
+    while (loopDict.has('Parent')) {
+      loopDict = loopDict.get('Parent');
+      if (!(0, _primitives.isDict)(loopDict)) {
+        break;
+      }
+      if (loopDict.has('T')) {
+        fieldName.unshift((0, _util.stringToPDFString)(loopDict.get('T')));
+      }
+    }
+    return fieldName.join('.');
+  }
+  hasFieldFlag(flag) {
+    return !!(this.data.fieldFlags & flag);
+  }
+  getOperatorList(evaluator, task, renderForms) {
+    if (renderForms) {
+      return Promise.resolve(new _evaluator.OperatorList());
+    }
+    return super.getOperatorList(evaluator, task, renderForms);
+  }
+}
+class TextWidgetAnnotation extends WidgetAnnotation {
+  constructor(params) {
+    super(params);
     this.data.fieldValue = (0, _util.stringToPDFString)(this.data.fieldValue || '');
-    var alignment = _util.Util.getInheritableProperty(params.dict, 'Q');
+    let alignment = _util.Util.getInheritableProperty(params.dict, 'Q');
     if (!(0, _util.isInt)(alignment) || alignment < 0 || alignment > 2) {
       alignment = null;
     }
     this.data.textAlignment = alignment;
-    var maximumLength = _util.Util.getInheritableProperty(params.dict, 'MaxLen');
+    let maximumLength = _util.Util.getInheritableProperty(params.dict, 'MaxLen');
     if (!(0, _util.isInt)(maximumLength) || maximumLength < 0) {
       maximumLength = null;
     }
     this.data.maxLen = maximumLength;
     this.data.multiLine = this.hasFieldFlag(_util.AnnotationFieldFlag.MULTILINE);
     this.data.comb = this.hasFieldFlag(_util.AnnotationFieldFlag.COMB) && !this.hasFieldFlag(_util.AnnotationFieldFlag.MULTILINE) && !this.hasFieldFlag(_util.AnnotationFieldFlag.PASSWORD) && !this.hasFieldFlag(_util.AnnotationFieldFlag.FILESELECT) && this.data.maxLen !== null;
   }
-  _util.Util.inherit(TextWidgetAnnotation, WidgetAnnotation, {
-    getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator, task, renderForms) {
-      var operatorList = new _evaluator.OperatorList();
-      if (renderForms) {
-        return Promise.resolve(operatorList);
-      }
-      if (this.appearance) {
-        return Annotation.prototype.getOperatorList.call(this, evaluator, task, renderForms);
-      }
-      if (!this.data.defaultAppearance) {
-        return Promise.resolve(operatorList);
-      }
-      var stream = new _stream.Stream((0, _util.stringToBytes)(this.data.defaultAppearance));
-      return evaluator.getOperatorList({
-        stream,
-        task,
-        resources: this.fieldResources,
-        operatorList
-      }).then(function () {
-        return operatorList;
-      });
-    }
-  });
-  return TextWidgetAnnotation;
-}();
-var ButtonWidgetAnnotation = function ButtonWidgetAnnotationClosure() {
-  function ButtonWidgetAnnotation(params) {
-    WidgetAnnotation.call(this, params);
+  getOperatorList(evaluator, task, renderForms) {
+    if (renderForms || this.appearance) {
+      return super.getOperatorList(evaluator, task, renderForms);
+    }
+    let operatorList = new _evaluator.OperatorList();
+    if (!this.data.defaultAppearance) {
+      return Promise.resolve(operatorList);
+    }
+    let stream = new _stream.Stream((0, _util.stringToBytes)(this.data.defaultAppearance));
+    return evaluator.getOperatorList({
+      stream,
+      task,
+      resources: this.fieldResources,
+      operatorList
+    }).then(function () {
+      return operatorList;
+    });
+  }
+}
+class ButtonWidgetAnnotation extends WidgetAnnotation {
+  constructor(params) {
+    super(params);
     this.data.checkBox = !this.hasFieldFlag(_util.AnnotationFieldFlag.RADIO) && !this.hasFieldFlag(_util.AnnotationFieldFlag.PUSHBUTTON);
     if (this.data.checkBox) {
       if (!(0, _primitives.isName)(this.data.fieldValue)) {
         return;
       }
       this.data.fieldValue = this.data.fieldValue.name;
     }
     this.data.radioButton = this.hasFieldFlag(_util.AnnotationFieldFlag.RADIO) && !this.hasFieldFlag(_util.AnnotationFieldFlag.PUSHBUTTON);
     if (this.data.radioButton) {
       this.data.fieldValue = this.data.buttonValue = null;
-      var fieldParent = params.dict.get('Parent');
+      let fieldParent = params.dict.get('Parent');
       if ((0, _primitives.isDict)(fieldParent) && fieldParent.has('V')) {
-        var fieldParentValue = fieldParent.get('V');
+        let fieldParentValue = fieldParent.get('V');
         if ((0, _primitives.isName)(fieldParentValue)) {
           this.data.fieldValue = fieldParentValue.name;
         }
       }
-      var appearanceStates = params.dict.get('AP');
+      let appearanceStates = params.dict.get('AP');
       if (!(0, _primitives.isDict)(appearanceStates)) {
         return;
       }
-      var normalAppearanceState = appearanceStates.get('N');
+      let normalAppearanceState = appearanceStates.get('N');
       if (!(0, _primitives.isDict)(normalAppearanceState)) {
         return;
       }
-      var keys = normalAppearanceState.getKeys();
-      for (var i = 0, ii = keys.length; i < ii; i++) {
+      let keys = normalAppearanceState.getKeys();
+      for (let i = 0, ii = keys.length; i < ii; i++) {
         if (keys[i] !== 'Off') {
           this.data.buttonValue = keys[i];
           break;
         }
       }
     }
   }
-  _util.Util.inherit(ButtonWidgetAnnotation, WidgetAnnotation, {
-    getOperatorList: function ButtonWidgetAnnotation_getOperatorList(evaluator, task, renderForms) {
-      var operatorList = new _evaluator.OperatorList();
-      if (renderForms) {
-        return Promise.resolve(operatorList);
-      }
-      if (this.appearance) {
-        return Annotation.prototype.getOperatorList.call(this, evaluator, task, renderForms);
-      }
-      return Promise.resolve(operatorList);
-    }
-  });
-  return ButtonWidgetAnnotation;
-}();
-var ChoiceWidgetAnnotation = function ChoiceWidgetAnnotationClosure() {
-  function ChoiceWidgetAnnotation(params) {
-    WidgetAnnotation.call(this, params);
+}
+class ChoiceWidgetAnnotation extends WidgetAnnotation {
+  constructor(params) {
+    super(params);
     this.data.options = [];
-    var options = _util.Util.getInheritableProperty(params.dict, 'Opt');
+    let options = _util.Util.getInheritableProperty(params.dict, 'Opt');
     if ((0, _util.isArray)(options)) {
-      var xref = params.xref;
-      for (var i = 0, ii = options.length; i < ii; i++) {
-        var option = xref.fetchIfRef(options[i]);
-        var isOptionArray = (0, _util.isArray)(option);
+      let xref = params.xref;
+      for (let i = 0, ii = options.length; i < ii; i++) {
+        let option = xref.fetchIfRef(options[i]);
+        let isOptionArray = (0, _util.isArray)(option);
         this.data.options[i] = {
           exportValue: isOptionArray ? xref.fetchIfRef(option[0]) : option,
           displayValue: isOptionArray ? xref.fetchIfRef(option[1]) : option
         };
       }
     }
     if (!(0, _util.isArray)(this.data.fieldValue)) {
       this.data.fieldValue = [this.data.fieldValue];
     }
     this.data.combo = this.hasFieldFlag(_util.AnnotationFieldFlag.COMBO);
     this.data.multiSelect = this.hasFieldFlag(_util.AnnotationFieldFlag.MULTISELECT);
   }
-  _util.Util.inherit(ChoiceWidgetAnnotation, WidgetAnnotation, {
-    getOperatorList: function ChoiceWidgetAnnotation_getOperatorList(evaluator, task, renderForms) {
-      var operatorList = new _evaluator.OperatorList();
-      if (renderForms) {
-        return Promise.resolve(operatorList);
-      }
-      return Annotation.prototype.getOperatorList.call(this, evaluator, task, renderForms);
-    }
-  });
-  return ChoiceWidgetAnnotation;
-}();
-var TextAnnotation = function TextAnnotationClosure() {
-  var DEFAULT_ICON_SIZE = 22;
-  function TextAnnotation(parameters) {
-    Annotation.call(this, parameters);
+}
+class TextAnnotation extends Annotation {
+  constructor(parameters) {
+    const DEFAULT_ICON_SIZE = 22;
+    super(parameters);
     this.data.annotationType = _util.AnnotationType.TEXT;
     if (this.data.hasAppearance) {
       this.data.name = 'NoIcon';
     } else {
       this.data.rect[1] = this.data.rect[3] - DEFAULT_ICON_SIZE;
       this.data.rect[2] = this.data.rect[0] + DEFAULT_ICON_SIZE;
       this.data.name = parameters.dict.has('Name') ? parameters.dict.get('Name').name : 'Note';
     }
     this._preparePopup(parameters.dict);
   }
-  _util.Util.inherit(TextAnnotation, Annotation, {});
-  return TextAnnotation;
-}();
-var LinkAnnotation = function LinkAnnotationClosure() {
-  function LinkAnnotation(params) {
-    Annotation.call(this, params);
-    var data = this.data;
-    data.annotationType = _util.AnnotationType.LINK;
+}
+class LinkAnnotation extends Annotation {
+  constructor(params) {
+    super(params);
+    this.data.annotationType = _util.AnnotationType.LINK;
     _obj.Catalog.parseDestDictionary({
       destDict: params.dict,
-      resultObj: data,
+      resultObj: this.data,
       docBaseUrl: params.pdfManager.docBaseUrl
     });
   }
-  _util.Util.inherit(LinkAnnotation, Annotation, {});
-  return LinkAnnotation;
-}();
-var PopupAnnotation = function PopupAnnotationClosure() {
-  function PopupAnnotation(parameters) {
-    Annotation.call(this, parameters);
+}
+class PopupAnnotation extends Annotation {
+  constructor(parameters) {
+    super(parameters);
     this.data.annotationType = _util.AnnotationType.POPUP;
-    var dict = parameters.dict;
-    var parentItem = dict.get('Parent');
+    let dict = parameters.dict;
+    let parentItem = dict.get('Parent');
     if (!parentItem) {
       (0, _util.warn)('Popup annotation has a missing or invalid parent annotation.');
       return;
     }
-    var parentSubtype = parentItem.get('Subtype');
+    let parentSubtype = parentItem.get('Subtype');
     this.data.parentType = (0, _primitives.isName)(parentSubtype) ? parentSubtype.name : null;
     this.data.parentId = dict.getRaw('Parent').toString();
     this.data.title = (0, _util.stringToPDFString)(parentItem.get('T') || '');
     this.data.contents = (0, _util.stringToPDFString)(parentItem.get('Contents') || '');
     if (!parentItem.has('C')) {
       this.data.color = null;
     } else {
       this.setColor(parentItem.getArray('C'));
       this.data.color = this.color;
     }
     if (!this.viewable) {
-      var parentFlags = parentItem.get('F');
+      let parentFlags = parentItem.get('F');
       if (this._isViewable(parentFlags)) {
         this.setFlags(parentFlags);
       }
     }
   }
-  _util.Util.inherit(PopupAnnotation, Annotation, {});
-  return PopupAnnotation;
-}();
-var LineAnnotation = function LineAnnotationClosure() {
-  function LineAnnotation(parameters) {
-    Annotation.call(this, parameters);
+}
+class LineAnnotation extends Annotation {
+  constructor(parameters) {
+    super(parameters);
     this.data.annotationType = _util.AnnotationType.LINE;
-    var dict = parameters.dict;
+    let dict = parameters.dict;
     this.data.lineCoordinates = _util.Util.normalizeRect(dict.getArray('L'));
     this._preparePopup(dict);
   }
-  _util.Util.inherit(LineAnnotation, Annotation, {});
-  return LineAnnotation;
-}();
-var HighlightAnnotation = function HighlightAnnotationClosure() {
-  function HighlightAnnotation(parameters) {
-    Annotation.call(this, parameters);
+}
+class HighlightAnnotation extends Annotation {
+  constructor(parameters) {
+    super(parameters);
     this.data.annotationType = _util.AnnotationType.HIGHLIGHT;
     this._preparePopup(parameters.dict);
   }
-  _util.Util.inherit(HighlightAnnotation, Annotation, {});
-  return HighlightAnnotation;
-}();
-var UnderlineAnnotation = function UnderlineAnnotationClosure() {
-  function UnderlineAnnotation(parameters) {
-    Annotation.call(this, parameters);
+}
+class UnderlineAnnotation extends Annotation {
+  constructor(parameters) {
+    super(parameters);
     this.data.annotationType = _util.AnnotationType.UNDERLINE;
     this._preparePopup(parameters.dict);
   }
-  _util.Util.inherit(UnderlineAnnotation, Annotation, {});
-  return UnderlineAnnotation;
-}();
-var SquigglyAnnotation = function SquigglyAnnotationClosure() {
-  function SquigglyAnnotation(parameters) {
-    Annotation.call(this, parameters);
+}
+class SquigglyAnnotation extends Annotation {
+  constructor(parameters) {
+    super(parameters);
     this.data.annotationType = _util.AnnotationType.SQUIGGLY;
     this._preparePopup(parameters.dict);
   }
-  _util.Util.inherit(SquigglyAnnotation, Annotation, {});
-  return SquigglyAnnotation;
-}();
-var StrikeOutAnnotation = function StrikeOutAnnotationClosure() {
-  function StrikeOutAnnotation(parameters) {
-    Annotation.call(this, parameters);
+}
+class StrikeOutAnnotation extends Annotation {
+  constructor(parameters) {
+    super(parameters);
     this.data.annotationType = _util.AnnotationType.STRIKEOUT;
     this._preparePopup(parameters.dict);
   }
-  _util.Util.inherit(StrikeOutAnnotation, Annotation, {});
-  return StrikeOutAnnotation;
-}();
-var FileAttachmentAnnotation = function FileAttachmentAnnotationClosure() {
-  function FileAttachmentAnnotation(parameters) {
-    Annotation.call(this, parameters);
-    var file = new _obj.FileSpec(parameters.dict.get('FS'), parameters.xref);
+}
+class FileAttachmentAnnotation extends Annotation {
+  constructor(parameters) {
+    super(parameters);
+    let file = new _obj.FileSpec(parameters.dict.get('FS'), parameters.xref);
     this.data.annotationType = _util.AnnotationType.FILEATTACHMENT;
     this.data.file = file.serializable;
     this._preparePopup(parameters.dict);
   }
-  _util.Util.inherit(FileAttachmentAnnotation, Annotation, {});
-  return FileAttachmentAnnotation;
-}();
+}
 exports.Annotation = Annotation;
 exports.AnnotationBorderStyle = AnnotationBorderStyle;
 exports.AnnotationFactory = AnnotationFactory;
 
 /***/ }),
 /* 20 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
@@ -29161,20 +29119,19 @@ var Page = function PageClosure() {
           annotationsData.push(annotations[i].data);
         }
       }
       return annotationsData;
     },
     get annotations() {
       var annotations = [];
       var annotationRefs = this.getInheritedPageProp('Annots') || [];
-      var annotationFactory = new _annotation.AnnotationFactory();
       for (var i = 0, n = annotationRefs.length; i < n; ++i) {
         var annotationRef = annotationRefs[i];
-        var annotation = annotationFactory.create(this.xref, annotationRef, this.pdfManager, this.idFactory);
+        var annotation = _annotation.AnnotationFactory.create(this.xref, annotationRef, this.pdfManager, this.idFactory);
         if (annotation) {
           annotations.push(annotation);
         }
       }
       return (0, _util.shadow)(this, 'annotations', annotations);
     }
   };
   return Page;
@@ -40058,18 +40015,18 @@ exports.Type1Parser = Type1Parser;
 
 /***/ }),
 /* 35 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '1.9.489';
-var pdfjsBuild = 'b7fcaff0';
+var pdfjsVersion = '1.9.512';
+var pdfjsBuild = '066fea9c';
 var pdfjsCoreWorker = __w_pdfjs_require__(17);
 exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;
 
 /***/ }),
 /* 36 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -1318,17 +1318,19 @@ let PDFViewerApplication = {
         if (moreInfo.filename) {
           moreInfoText.push(this.l10n.get('error_file', { file: moreInfo.filename }, 'File: {{file}}'));
         }
         if (moreInfo.lineNumber) {
           moreInfoText.push(this.l10n.get('error_line', { line: moreInfo.lineNumber }, 'Line: {{line}}'));
         }
       }
     }
-    console.error(message + '\n' + moreInfoText);
+    Promise.all(moreInfoText).then(parts => {
+      console.error(message + '\n' + parts.join('\n'));
+    });
     this.fallback();
   },
   progress(level) {
     if (this.downloadComplete) {
       return;
     }
     let percent = Math.round(level * 100);
     if (percent > this.loadingBar.percent || isNaN(percent)) {
@@ -4911,17 +4913,16 @@ class PDFPageView {
     this.viewport = pdfPage.getViewport(this.scale * _ui_utils.CSS_UNITS, totalRotation);
     this.stats = pdfPage.stats;
     this.reset();
   }
   destroy() {
     this.reset();
     if (this.pdfPage) {
       this.pdfPage.cleanup();
-      this.pdfPage = null;
     }
   }
   _resetZoomLayer(removeFromDOM = false) {
     if (!this.zoomLayer) {
       return;
     }
     let zoomLayerCanvas = this.zoomLayer.firstChild;
     this.paintedViewportMap.delete(zoomLayerCanvas);
--- a/config/check_vanilla_allocations.py
+++ b/config/check_vanilla_allocations.py
@@ -123,16 +123,22 @@ def main():
     emit_line_info = False
 
     for line in lines:
         m = re.search(alloc_fns_re, line)
         if m is None:
             continue
 
         filename = m.group(1)
+
+        # mozalloc contains calls to memalign. These are ok, so we whitelist
+        # them.
+        if "mozalloc" in filename:
+            continue
+
         fn = m.group(2)
         if filename == 'jsutil.o':
             jsutil_cpp.add(fn)
         else:
             # An allocation is present in a non-special file.  Fail!
             fail("'" + fn + "' present in " + filename)
             # Try to give more precise information about the offending code.
             emit_line_info = True
@@ -183,9 +189,8 @@ def main():
         sys.exit(1)
 
     print('TEST-PASS | check_vanilla_allocations.py | ok')
     sys.exit(0)
 
 
 if __name__ == '__main__':
     main()
-
--- a/devtools/client/aboutdebugging/aboutdebugging.css
+++ b/devtools/client/aboutdebugging/aboutdebugging.css
@@ -58,16 +58,17 @@ button {
   min-height: 34px;
   display: flex;
   flex-direction: row;
   align-items: start;
 }
 
 .target-icon {
   height: 24px;
+  width: 24px;
   margin-inline-end: 5px;
 }
 
 .target-icon:not([src]) {
   display: none;
 }
 
 .inverted-icons .target-icon {
--- a/devtools/client/responsive.html/test/browser/browser_exit_button.js
+++ b/devtools/client/responsive.html/test/browser/browser_exit_button.js
@@ -21,18 +21,19 @@ add_task(function* () {
   let waitTabIsDetached = Promise.all([
     once(tab, "TabClose"),
     once(tab.linkedBrowser, "SwapDocShells")
   ]);
 
   // Detach the tab with RDM open.
   let newWindow = gBrowser.replaceTabWithWindow(tab);
 
-  // Waiting the tab is detached.
+  // Wait until the tab is detached and the new window is fully initialized.
   yield waitTabIsDetached;
+  yield newWindow.delayedStartupPromise;
 
   // Get the new tab instance.
   tab = newWindow.gBrowser.tabs[0];
 
   // Detaching a tab closes RDM.
   ok(!manager.isActiveForTab(tab),
     "Responsive Design Mode is not active for the tab");
 
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @import url("resource://devtools/client/themes/splitters.css");
 @namespace html url("http://www.w3.org/1999/xhtml");
 
 :root {
   font: message-box;
 
-  --tab-line-selected-color: highlight;
+  --tab-line-selected-color: var(--blue-50);
 }
 
 :root.theme-light {
   --tab-line-hover-color: rgba(0,0,0,.2);
 }
 
 :root.theme-dark {
   --tab-line-hover-color: rgba(255,255,255,.2);
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -101,16 +101,17 @@
   border: none;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
   background-color: transparent;
 }
 
 .devtools-tab-label {
+  font-size: 12px;
   mask-image: linear-gradient(to left, transparent 0, black 6px);
   /* Set the end padding on the label to make sure the label gets faded out properly */
   padding-inline-end: 10px;
   min-width: 1px;
 }
 
 .devtools-tab-label:-moz-locale-dir(rtl) {
   mask-image: linear-gradient(to right, transparent 0, black 6px);
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/accessibility.js
@@ -0,0 +1,538 @@
+/* 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 strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const Services = require("Services");
+const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
+const defer = require("devtools/shared/defer");
+const events = require("devtools/shared/event-emitter");
+const {
+  accessibleSpec,
+  accessibleWalkerSpec,
+  accessibilitySpec
+} = require("devtools/shared/specs/accessibility");
+
+const nsIAccessibleEvent = Ci.nsIAccessibleEvent;
+const nsIAccessibleStateChangeEvent = Ci.nsIAccessibleStateChangeEvent;
+const nsIPropertyElement = Ci.nsIPropertyElement;
+
+const {
+  EVENT_TEXT_CHANGED,
+  EVENT_TEXT_INSERTED,
+  EVENT_TEXT_REMOVED,
+  EVENT_ACCELERATOR_CHANGE,
+  EVENT_ACTION_CHANGE,
+  EVENT_DEFACTION_CHANGE,
+  EVENT_DESCRIPTION_CHANGE,
+  EVENT_DOCUMENT_ATTRIBUTES_CHANGED,
+  EVENT_HELP_CHANGE,
+  EVENT_HIDE,
+  EVENT_NAME_CHANGE,
+  EVENT_OBJECT_ATTRIBUTE_CHANGED,
+  EVENT_REORDER,
+  EVENT_STATE_CHANGE,
+  EVENT_TEXT_ATTRIBUTE_CHANGED,
+  EVENT_VALUE_CHANGE
+} = nsIAccessibleEvent;
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+/**
+ * Set of actors that expose accessibility tree information to the
+ * devtools protocol clients.
+ *
+ * The |Accessibility| actor is the main entry point. It is used to request
+ * an AccessibleWalker actor that caches the tree of Accessible actors.
+ *
+ * The |AccessibleWalker| actor is used to cache all seen Accessible actors as
+ * well as observe all relevant accesible events.
+ *
+ * The |Accessible| actor provides information about a particular accessible
+ * object, its properties, , attributes, states, relations, etc.
+ */
+
+/**
+ * The AccessibleActor provides information about a given accessible object: its
+ * role, name, states, etc.
+ */
+const AccessibleActor = ActorClassWithSpec(accessibleSpec, {
+  initialize(walker, rawAccessible) {
+    Actor.prototype.initialize.call(this, walker.conn);
+    this.walker = walker;
+    this.rawAccessible = rawAccessible;
+
+    /**
+     * Indicates if the raw accessible is no longer alive.
+     *
+     * @return Boolean
+     */
+    Object.defineProperty(this, "isDefunct", {
+      get() {
+        let defunct = false;
+
+        try {
+          let extState = {};
+          this.rawAccessible.getState({}, extState);
+          // extState.value is a bitmask. We are applying bitwise AND to mask out
+          // irrelelvant states.
+          defunct = !!(extState.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT);
+        } catch (e) {
+          defunct = true;
+        }
+
+        if (defunct) {
+          delete this.isDefunct;
+          this.isDefunct = true;
+          return this.isDefunct;
+        }
+
+        return defunct;
+      },
+      configurable: true
+    });
+  },
+
+  /**
+   * Items returned by this actor should belong to the parent walker.
+   */
+  marshallPool() {
+    return this.walker;
+  },
+
+  destroy() {
+    Actor.prototype.destroy.call(this);
+    this.walker = null;
+    this.rawAccessible = null;
+  },
+
+  get role() {
+    if (this.isDefunct) {
+      return null;
+    }
+    return this.walker.a11yService.getStringRole(this.rawAccessible.role);
+  },
+
+  get name() {
+    if (this.isDefunct) {
+      return null;
+    }
+    return this.rawAccessible.name;
+  },
+
+  get value() {
+    if (this.isDefunct) {
+      return null;
+    }
+    return this.rawAccessible.value;
+  },
+
+  get description() {
+    if (this.isDefunct) {
+      return null;
+    }
+    return this.rawAccessible.description;
+  },
+
+  get help() {
+    if (this.isDefunct) {
+      return null;
+    }
+    return this.rawAccessible.help;
+  },
+
+  get keyboardShortcut() {
+    if (this.isDefunct) {
+      return null;
+    }
+    return this.rawAccessible.keyboardShortcut;
+  },
+
+  get childCount() {
+    if (this.isDefunct) {
+      return 0;
+    }
+    return this.rawAccessible.childCount;
+  },
+
+  get domNodeType() {
+    if (this.isDefunct) {
+      return 0;
+    }
+    return this.rawAccessible.DOMNode ? this.rawAccessible.DOMNode.nodeType : 0;
+  },
+
+  children() {
+    let children = [];
+    if (this.isDefunct) {
+      return children;
+    }
+
+    for (let child = this.rawAccessible.firstChild; child; child = child.nextSibling) {
+      children.push(this.walker.addRef(child));
+    }
+    return children;
+  },
+
+  getIndexInParent() {
+    if (this.isDefunct) {
+      return -1;
+    }
+    return this.rawAccessible.indexInParent;
+  },
+
+  getActions() {
+    let actions = [];
+    if (this.isDefunct) {
+      return actions;
+    }
+
+    for (let i = 0; i < this.rawAccessible.actionCount; i++) {
+      actions.push(this.rawAccessible.getActionDescription(i));
+    }
+    return actions;
+  },
+
+  getState() {
+    if (this.isDefunct) {
+      return [];
+    }
+
+    let state = {};
+    let extState = {};
+    this.rawAccessible.getState(state, extState);
+    return [
+      ...this.walker.a11yService.getStringStates(state.value, extState.value)
+    ];
+  },
+
+  getAttributes() {
+    if (this.isDefunct || !this.rawAccessible.attributes) {
+      return {};
+    }
+
+    let attributes = {};
+    let attrsEnum = this.rawAccessible.attributes.enumerate();
+    while (attrsEnum.hasMoreElements()) {
+      let { key, value } = attrsEnum.getNext().QueryInterface(
+        nsIPropertyElement);
+      attributes[key] = value;
+    }
+
+    return attributes;
+  },
+
+  form() {
+    return {
+      actor: this.actorID,
+      role: this.role,
+      name: this.name,
+      value: this.value,
+      description: this.description,
+      help: this.help,
+      keyboardShortcut: this.keyboardShortcut,
+      childCount: this.childCount,
+      domNodeType: this.domNodeType,
+      walker: this.walker.form()
+    };
+  }
+});
+
+/**
+ * The AccessibleWalkerActor stores a cache of AccessibleActors that represent
+ * accessible objects in a given document.
+ *
+ * It is also responsible for implicitely initializing and shutting down
+ * accessibility engine by storing a reference to the XPCOM accessibility
+ * service.
+ */
+const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
+  initialize(conn, tabActor) {
+    Actor.prototype.initialize.call(this, conn);
+    this.tabActor = tabActor;
+    this.rootWin = tabActor.window;
+    this.rootDoc = tabActor.window.document;
+    this.refMap = new Map();
+    // Accessibility Walker should only be considered ready, when raw accessible
+    // object for root document is fully initialized (e.g. does not have a
+    // 'busy' state)
+    this.readyDeferred = defer();
+
+    DevToolsUtils.defineLazyGetter(this, "a11yService", () => {
+      Services.obs.addObserver(this, "accessible-event");
+      return Cc["@mozilla.org/accessibilityService;1"].getService(
+        Ci.nsIAccessibilityService);
+    });
+
+    this.onLoad = this.onLoad.bind(this);
+    this.onUnload = this.onUnload.bind(this);
+
+    events.on(tabActor, "will-navigate", this.onUnload);
+    events.on(tabActor, "window-ready", this.onLoad);
+  },
+
+  onUnload({ window }) {
+    let doc = window.document;
+    let actor = this.getRef(doc);
+
+    // If an accessible actor was never created for document, then there's
+    // nothing to clean up.
+    if (!actor) {
+      return;
+    }
+
+    // Purge document's subtree from accessible actors cache.
+    this.purgeSubtree(this.a11yService.getAccessibleFor(this.doc));
+    // If document is a root document, clear it's reference and cache.
+    if (this.rootDoc === doc) {
+      this.rootDoc = null;
+      this.refMap.clear();
+      this.readyDeferred = defer();
+    }
+  },
+
+  onLoad({ window, isTopLevel }) {
+    if (isTopLevel) {
+      // If root document is dead, unload it and clean up.
+      if (this.rootDoc && !Cu.isDeadWrapper(this.rootDoc) &&
+          this.rootDoc.defaultView) {
+        this.onUnload({ window: this.rootDoc.defaultView });
+      }
+
+      this.rootWin = window;
+      this.rootDoc = window.document;
+    }
+  },
+
+  destroy() {
+    if (this._destroyed) {
+      return;
+    }
+
+    this._destroyed = true;
+
+    try {
+      Services.obs.removeObserver(this, "accessible-event");
+    } catch (e) {
+      // Accessible event observer might not have been initialized if a11y
+      // service was never used.
+    }
+
+    // Clean up accessible actors cache.
+    if (this.refMap.size > 0) {
+      this.purgeSubtree(this.a11yService.getAccessibleFor(this.rootDoc));
+      this.refMap.clear();
+    }
+
+    events.off(this.tabActor, "will-navigate", this.onUnload);
+    events.off(this.tabActor, "window-ready", this.onLoad);
+
+    this.onLoad = null;
+    this.onUnload = null;
+    delete this.a11yService;
+    this.tabActor = null;
+    this.rootDoc = null;
+    this.refMap = null;
+
+    Actor.prototype.destroy.call(this);
+  },
+
+  getRef(rawAccessible) {
+    return this.refMap.get(rawAccessible);
+  },
+
+  addRef(rawAccessible) {
+    let actor = this.refMap.get(rawAccessible);
+    if (actor) {
+      return actor;
+    }
+
+    actor = new AccessibleActor(this, rawAccessible);
+    this.manage(actor);
+    this.refMap.set(rawAccessible, actor);
+
+    return actor;
+  },
+
+  /**
+   * Clean up accessible actors cache for a given accessible's subtree.
+   *
+   * @param  {nsIAccessible} rawAccessible
+   */
+  purgeSubtree(rawAccessible) {
+    let actor = this.getRef(rawAccessible);
+    if (actor && rawAccessible && !actor.isDefunct) {
+      for (let child = rawAccessible.firstChild; child; child = child.nextSibling) {
+        this.purgeSubtree(child);
+      }
+    }
+
+    this.refMap.delete(rawAccessible);
+
+    if (actor) {
+      events.emit(this, "accessible-destroy", actor);
+      actor.destroy();
+    }
+  },
+
+  /**
+   * A helper method. Accessibility walker is assumed to have only 1 child which
+   * is the top level document.
+   */
+  children() {
+    return Promise.all([this.getDocument()]);
+  },
+
+  /**
+   * A promise for a root document accessible actor that only resolves when its
+   * corresponding document accessible object is fully loaded.
+   *
+   * @return {Promise}
+   */
+  getDocument() {
+    let doc = this.addRef(this.a11yService.getAccessibleFor(this.rootDoc));
+    let states = doc.getState();
+
+    if (states.includes("busy")) {
+      return this.readyDeferred.promise.then(() => doc);
+    }
+
+    this.readyDeferred.resolve();
+    return Promise.resolve(doc);
+  },
+
+  getAccessibleFor(domNode) {
+    // We need to make sure that the document is loaded processed by a11y first.
+    return this.getDocument().then(() =>
+      this.addRef(this.a11yService.getAccessibleFor(domNode.rawNode)));
+  },
+
+  /**
+   * Accessible event observer function.
+   *
+   * @param {nsIAccessibleEvent} subject
+   *                                      accessible event object.
+   */
+  observe(subject) {
+    let event = subject.QueryInterface(nsIAccessibleEvent);
+    let rawAccessible = event.accessible;
+    let accessible = this.getRef(rawAccessible);
+
+    switch (event.eventType) {
+      case EVENT_STATE_CHANGE:
+        let { state, isEnabled } = event.QueryInterface(nsIAccessibleStateChangeEvent);
+        let states = [...this.a11yService.getStringStates(state, 0)];
+
+        if (states.includes("busy") && !isEnabled) {
+          let { DOMNode } = event;
+          // If debugging chrome, wait for top level content document loaded,
+          // otherwise wait for root document loaded.
+          if (DOMNode == this.rootDoc || (
+            this.rootDoc.documentElement.namespaceURI === XUL_NS &&
+            this.rootWin.gBrowser.selectedBrowser.contentDocument == DOMNode)) {
+            this.readyDeferred.resolve();
+          }
+        }
+
+        if (accessible) {
+          // Only propagate state change events for active accessibles.
+          if (states.includes("busy") && isEnabled) {
+            return;
+          }
+          events.emit(accessible, "state-change", accessible.getState());
+        }
+
+        break;
+      case EVENT_NAME_CHANGE:
+        if (accessible) {
+          events.emit(accessible, "name-change", rawAccessible.name,
+            event.DOMNode == this.rootDoc ?
+              undefined : this.getRef(rawAccessible.parent));
+        }
+        break;
+      case EVENT_VALUE_CHANGE:
+        if (accessible) {
+          events.emit(accessible, "value-change", rawAccessible.value);
+        }
+        break;
+      case EVENT_DESCRIPTION_CHANGE:
+        if (accessible) {
+          events.emit(accessible, "description-change", rawAccessible.description);
+        }
+        break;
+      case EVENT_HELP_CHANGE:
+        if (accessible) {
+          events.emit(accessible, "help-change", rawAccessible.help);
+        }
+        break;
+      case EVENT_REORDER:
+        if (accessible) {
+          events.emit(accessible, "reorder", rawAccessible.childCount);
+        }
+        break;
+      case EVENT_HIDE:
+        this.purgeSubtree(rawAccessible);
+        break;
+      case EVENT_DEFACTION_CHANGE:
+      case EVENT_ACTION_CHANGE:
+        if (accessible) {
+          events.emit(accessible, "actions-change", accessible.getActions());
+        }
+        break;
+      case EVENT_TEXT_CHANGED:
+      case EVENT_TEXT_INSERTED:
+      case EVENT_TEXT_REMOVED:
+        if (accessible) {
+          events.emit(accessible, "text-change");
+        }
+        break;
+      case EVENT_DOCUMENT_ATTRIBUTES_CHANGED:
+      case EVENT_OBJECT_ATTRIBUTE_CHANGED:
+      case EVENT_TEXT_ATTRIBUTE_CHANGED:
+        if (accessible) {
+          events.emit(accessible, "attributes-change", accessible.getAttributes());
+        }
+        break;
+      case EVENT_ACCELERATOR_CHANGE:
+        if (accessible) {
+          events.emit(accessible, "shortcut-change", rawAccessible.keyboardShortcut);
+        }
+        break;
+      default:
+        break;
+    }
+  }
+});
+
+/**
+ * The AccessibilityActor is a top level container actor that initializes
+ * accessible walker and is the top-most point of interaction for accessibility
+ * tools UI.
+ */
+const AccessibilityActor = ActorClassWithSpec(accessibilitySpec, {
+  initialize(conn, tabActor) {
+    Actor.prototype.initialize.call(this, conn);
+    this.tabActor = tabActor;
+  },
+
+  getWalker() {
+    if (!this.walker) {
+      this.walker = new AccessibleWalkerActor(this.conn, this.tabActor);
+    }
+    return this.walker;
+  },
+
+  destroy() {
+    Actor.prototype.destroy.call(this);
+    this.walker.destroy();
+    this.walker = null;
+    this.tabActor = null;
+  }
+});
+
+exports.AccessibleActor = AccessibleActor;
+exports.AccessibleWalkerActor = AccessibleWalkerActor;
+exports.AccessibilityActor = AccessibilityActor;
--- a/devtools/server/actors/moz.build
+++ b/devtools/server/actors/moz.build
@@ -6,16 +6,17 @@
 
 DIRS += [
     'highlighters',
     'utils',
     'webconsole',
 ]
 
 DevToolsModules(
+    'accessibility.js',
     'actor-registry.js',
     'addon.js',
     'addons.js',
     'animation.js',
     'breakpoint.js',
     'call-watcher.js',
     'canvas.js',
     'child-process.js',
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -580,16 +580,21 @@ var DebuggerServer = {
       constructor: "EmulationActor",
       type: { tab: true }
     });
     this.registerModule("devtools/server/actors/webextension-inspected-window", {
       prefix: "webExtensionInspectedWindow",
       constructor: "WebExtensionInspectedWindowActor",
       type: { tab: true }
     });
+    this.registerModule("devtools/server/actors/accessibility", {
+      prefix: "accessibility",
+      constructor: "AccessibilityActor",
+      type: { tab: true }
+    });
   },
 
   /**
    * Passes a set of options to the BrowserAddonActors for the given ID.
    *
    * @param id string
    *        The ID of the add-on to pass the options to
    * @param options object
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
   animation.html
+  doc_accessibility.html
   doc_allocations.html
   doc_force_cc.html
   doc_force_gc.html
   doc_innerHTML.html
   doc_perf.html
   grid.html
   inspectedwindow-reload-target.sjs
   navigate-first.html
@@ -20,16 +21,20 @@ support-files =
   storage-updates.html
   storage-secured-iframe.html
   stylesheets-nested-iframes.html
   timeline-iframe-child.html
   timeline-iframe-parent.html
   storage-helpers.js
   !/devtools/server/tests/mochitest/hello-actor.js
 
+[browser_accessibility_node_events.js]
+[browser_accessibility_node.js]
+[browser_accessibility_simple.js]
+[browser_accessibility_walker.js]
 [browser_animation_emitMutations.js]
 [browser_animation_getFrames.js]
 [browser_animation_getProperties.js]
 [browser_animation_getMultipleStates.js]
 [browser_animation_getPlayers.js]
 [browser_animation_getStateAfterFinished.js]
 [browser_animation_getSubTreeAnimations.js]
 [browser_animation_keepFinished.js]
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_accessibility_node.js
@@ -0,0 +1,74 @@
+/* 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 strict";
+
+// Checks for the AccessibleActor
+
+add_task(function* () {
+  let {client, walker, accessibility} =
+    yield initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html");
+
+  let a11yWalker = yield accessibility.getWalker(walker);
+  let buttonNode = yield walker.querySelector(walker.rootNode, "#button");
+  let accessibleFront = yield a11yWalker.getAccessibleFor(buttonNode);
+
+  checkA11yFront(accessibleFront, {
+    name: "Accessible Button",
+    role: "pushbutton",
+    value: "",
+    description: "Accessibility Test",
+    help: "",
+    keyboardShortcut: "",
+    childCount: 1,
+    domNodeType: 1
+  });
+
+  info("Actions");
+  let actions = yield accessibleFront.getActions();
+  is(actions.length, 1, "Accessible Front has correct number of actions");
+  is(actions[0], "Press", "Accessible Front default action is correct");
+
+  info("Index in parent");
+  let index = yield accessibleFront.getIndexInParent();
+  is(index, 1, "Accessible Front has correct index in parent");
+
+  info("State");
+  let state = yield accessibleFront.getState();
+  SimpleTest.isDeeply(state,
+    ["focusable", "selectable text", "opaque", "enabled", "sensitive"],
+    "Accessible Front has correct states");
+
+  info("Attributes");
+  let attributes = yield accessibleFront.getAttributes();
+  SimpleTest.isDeeply(attributes, {
+    "margin-top": "0px",
+    display: "inline-block",
+    "text-align": "center",
+    "text-indent": "0px",
+    "margin-left": "0px",
+    tag: "button",
+    "margin-right": "0px",
+    id: "button",
+    "margin-bottom": "0px"
+  }, "Accessible Front has correct attributes");
+
+  info("Children");
+  let children = yield accessibleFront.children();
+  is(children.length, 1, "Accessible Front has correct number of children");
+  checkA11yFront(children[0], {
+    name: "Accessible Button",
+    role: "text leaf"
+  });
+
+  info("DOM Node");
+  let node = yield accessibleFront.getDOMNode(walker);
+  is(node, buttonNode, "Accessible Front has correct DOM node");
+
+  let a11yShutdown = waitForA11yShutdown();
+  yield client.close();
+  forceCollections();
+  yield a11yShutdown;
+  gBrowser.removeCurrentTab();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_accessibility_node_events.js
@@ -0,0 +1,93 @@
+/* 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 strict";
+
+// Checks for the AccessibleActor events
+
+add_task(function* () {
+  let {client, walker, accessibility} =
+    yield initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html");
+
+  let a11yWalker = yield accessibility.getWalker(walker);
+  let a11yDoc = yield a11yWalker.getDocument();
+  let buttonNode = yield walker.querySelector(walker.rootNode, "#button");
+  let accessibleFront = yield a11yWalker.getAccessibleFor(buttonNode);
+  let sliderNode = yield walker.querySelector(walker.rootNode, "#slider");
+  let accessibleSliderFront = yield a11yWalker.getAccessibleFor(sliderNode);
+  let browser = gBrowser.selectedBrowser;
+
+  checkA11yFront(accessibleFront, {
+    name: "Accessible Button",
+    role: "pushbutton",
+    value: "",
+    description: "Accessibility Test",
+    help: "",
+    keyboardShortcut: "",
+    childCount: 1,
+    domNodeType: 1
+  });
+
+  info("Name change event");
+  yield emitA11yEvent(accessibleFront, "name-change",
+    (name, parent) => {
+      checkA11yFront(accessibleFront, { name: "Renamed" });
+      checkA11yFront(parent, { }, a11yDoc);
+    }, () => ContentTask.spawn(browser, null, () =>
+      content.document.getElementById("button").setAttribute(
+        "aria-label", "Renamed")));
+
+  info("Description change event");
+  yield emitA11yEvent(accessibleFront, "description-change",
+    () => checkA11yFront(accessibleFront, { description: "" }),
+    () => ContentTask.spawn(browser, null, () =>
+      content.document.getElementById("button").removeAttribute("aria-describedby")));
+
+  info("State change event");
+  let states = yield accessibleFront.getState();
+  let expectedStates = ["unavailable", "selectable text", "opaque"];
+  SimpleTest.isDeeply(states, ["focusable", "selectable text", "opaque",
+                               "enabled", "sensitive"], "States are correct");
+  yield emitA11yEvent(accessibleFront, "state-change",
+    newStates => SimpleTest.isDeeply(newStates, expectedStates,
+                                     "States are updated"),
+    () => ContentTask.spawn(browser, null, () =>
+      content.document.getElementById("button").setAttribute("disabled", true)));
+  states = yield accessibleFront.getState();
+  SimpleTest.isDeeply(states, expectedStates, "States are updated");
+
+  info("Attributes change event");
+  let attrs = yield accessibleFront.getAttributes();
+  ok(!attrs.live, "Attribute is not present");
+  yield emitA11yEvent(accessibleFront, "attributes-change",
+    newAttrs => is(newAttrs.live, "polite", "Attributes are updated"),
+    () => ContentTask.spawn(browser, null, () =>
+      content.document.getElementById("button").setAttribute("aria-live", "polite")));
+  attrs = yield accessibleFront.getAttributes();
+  is(attrs.live, "polite", "Attributes are updated");
+
+  info("Value change event");
+  checkA11yFront(accessibleSliderFront, { value: "5" });
+  yield emitA11yEvent(accessibleSliderFront, "value-change",
+    () => checkA11yFront(accessibleSliderFront, { value: "6" }),
+    () => ContentTask.spawn(browser, null, () =>
+      content.document.getElementById("slider").setAttribute("aria-valuenow", "6")));
+
+  info("Reorder event");
+  is(accessibleSliderFront.childCount, 1, "Slider has only 1 child");
+  yield emitA11yEvent(accessibleSliderFront, "reorder",
+    childCount => is(childCount, 2, "Child count is updated"),
+    () => ContentTask.spawn(browser, null, () => {
+      let button = content.document.createElement("button");
+      button.innerText = "Slider button";
+      content.document.getElementById("slider").appendChild(button);
+    }));
+  is(accessibleSliderFront.childCount, 2, "Child count is updated");
+
+  let a11yShutdown = waitForA11yShutdown();
+  yield client.close();
+  forceCollections();
+  yield a11yShutdown;
+  gBrowser.removeCurrentTab();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_accessibility_simple.js
@@ -0,0 +1,21 @@
+/* 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 strict";
+
+// Simple checks for the AccessibilityActor and AccessibleWalkerActor
+
+add_task(function* () {
+  let {client, accessibility} = yield initAccessibilityFrontForUrl(
+    "data:text/html;charset=utf-8,<title>test</title><div></div>");
+
+  ok(accessibility, "The AccessibilityFront was created");
+  ok(accessibility.getWalker, "The getWalker method exists");
+
+  let a11yWalker = yield accessibility.getWalker();
+  ok(a11yWalker, "The AccessibleWalkerFront was returned");
+
+  yield client.close();
+  gBrowser.removeCurrentTab();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_accessibility_walker.js
@@ -0,0 +1,75 @@
+/* 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 strict";
+
+// Checks for the AccessibleWalkerActor
+
+add_task(function* () {
+  let {client, walker, accessibility} =
+    yield initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html");
+
+  let a11yWalker = yield accessibility.getWalker(walker);
+  ok(a11yWalker, "The AccessibleWalkerFront was returned");
+
+  let a11yDoc = yield a11yWalker.getDocument();
+  ok(a11yDoc, "The AccessibleFront for root doc is created");
+
+  let children = yield a11yWalker.children();
+  is(children.length, 1,
+    "AccessibleWalker only has 1 child - root doc accessible");
+  is(a11yDoc, children[0],
+    "Root accessible must be AccessibleWalker's only child");
+
+  let buttonNode = yield walker.querySelector(walker.rootNode, "#button");
+  let accessibleFront = yield a11yWalker.getAccessibleFor(buttonNode);
+
+  checkA11yFront(accessibleFront, {
+    name: "Accessible Button",
+    role: "pushbutton"
+  });
+
+  let browser = gBrowser.selectedBrowser;
+
+  // Ensure name-change event is emitted by walker when cached accessible's name
+  // gets updated (via DOM manipularion).
+  yield emitA11yEvent(a11yWalker, "name-change",
+    (front, parent) => {
+      checkA11yFront(front, { name: "Renamed" }, accessibleFront);
+      checkA11yFront(parent, { }, a11yDoc);
+    },
+    () => ContentTask.spawn(browser, null, () =>
+      content.document.getElementById("button").setAttribute(
+      "aria-label", "Renamed")));
+
+  // Ensure reorder event is emitted by walker when DOM tree changes.
+  let docChildren = yield a11yDoc.children();
+  is(docChildren.length, 3, "Root doc should have correct number of children");
+
+  yield emitA11yEvent(a11yWalker, "reorder",
+    front => checkA11yFront(front, { }, a11yDoc),
+    () => ContentTask.spawn(browser, null, () => {
+      let input = content.document.createElement("input");
+      input.type = "text";
+      input.title = "This is a tooltip";
+      input.value = "New input";
+      content.document.body.appendChild(input);
+    }));
+
+  docChildren = yield a11yDoc.children();
+  is(docChildren.length, 4, "Root doc should have correct number of children");
+
+  // Ensure destory event is emitted by walker when cached accessible's raw
+  // accessible gets destroyed.
+  yield emitA11yEvent(a11yWalker, "accessible-destroy",
+    destroyedFront => checkA11yFront(destroyedFront, { }, accessibleFront),
+    () => ContentTask.spawn(browser, null, () =>
+      content.document.getElementById("button").remove()));
+
+  let a11yShutdown = waitForA11yShutdown();
+  yield client.close();
+  forceCollections();
+  yield a11yShutdown;
+  gBrowser.removeCurrentTab();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/doc_accessibility.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset="utf-8">
+  </head>
+<body>
+  <h1 id="h1">Accessibility Test</h1>
+  <button id="button" aria-describedby="h1">Accessible Button</button>
+  <div id="slider" role="slider" aria-valuenow="5"
+       aria-valuemin="0" aria-valuemax="7">slider</div>
+</body>
+</html>
--- a/devtools/server/tests/browser/head.js
+++ b/devtools/server/tests/browser/head.js
@@ -75,16 +75,32 @@ function* initLayoutFrontForUrl(url) {
   let form = yield connectDebuggerClient(client);
   let inspector = InspectorFront(client, form);
   let walker = yield inspector.getWalker();
   let layout = yield walker.getLayoutInspector();
 
   return {inspector, walker, layout, client};
 }
 
+function* initAccessibilityFrontForUrl(url) {
+  const {AccessibilityFront} = require("devtools/shared/fronts/accessibility");
+  const {InspectorFront} = require("devtools/shared/fronts/inspector");
+
+  yield addTab(url);
+
+  initDebuggerServer();
+  let client = new DebuggerClient(DebuggerServer.connectPipe());
+  let form = yield connectDebuggerClient(client);
+  let inspector = InspectorFront(client, form);
+  let walker = yield inspector.getWalker();
+  let accessibility = AccessibilityFront(client, form);
+
+  return {inspector, walker, accessibility, client};
+}
+
 function initDebuggerServer() {
   try {
     // Sometimes debugger server does not get destroyed correctly by previous
     // tests.
     DebuggerServer.destroy();
   } catch (e) {
     info(`DebuggerServer destroy error: ${e}\n${e.stack}`);
   }
@@ -231,8 +247,58 @@ function waitForMarkerType(front, types,
   front.on(eventName, handler);
 
   return promise;
 }
 
 function getCookieId(name, domain, path) {
   return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${path}`;
 }
+
+/**
+ * Trigger DOM activity and wait for the corresponding accessibility event.
+ * @param  {Object} emitter   Devtools event emitter, usually a front.
+ * @param  {Sting} name       Accessibility event in question.
+ * @param  {Function} handler Accessibility event handler function with checks.
+ * @param  {Promise} task     A promise that resolves when DOM activity is done.
+ */
+async function emitA11yEvent(emitter, name, handler, task) {
+  let promise = emitter.once(name, handler);
+  await task();
+  await promise;
+}
+
+/**
+ * Check that accessibilty front is correct and its attributes are also
+ * up-to-date.
+ * @param  {Object} front         Accessibility front to be tested.
+ * @param  {Object} expected      A map of a11y front properties to be verified.
+ * @param  {Object} expectedFront Expected accessibility front.
+ */
+function checkA11yFront(front, expected, expectedFront) {
+  ok(front, "The accessibility front is created");
+
+  if (expectedFront) {
+    is(front, expectedFront, "Matching accessibility front");
+  }
+
+  for (let key in expected) {
+    is(front[key], expected[key], `accessibility front has correct ${key}`);
+  }
+}
+
+/**
+ * Wait for accessibility service to shut down. We consider it shut down when
+ * an "a11y-init-or-shutdown" event is received with a value of "0".
+ */
+async function waitForA11yShutdown() {
+  await ContentTask.spawn(gBrowser.selectedBrowser, {}, () =>
+    new Promise(resolve => {
+      let observe = (subject, topic, data) => {
+        Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
+
+        if (data === "0") {
+          resolve();
+        }
+      };
+      Services.obs.addObserver(observe, "a11y-init-or-shutdown");
+    }));
+}
new file mode 100644
--- /dev/null
+++ b/devtools/shared/fronts/accessibility.js
@@ -0,0 +1,136 @@
+/* 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 strict";
+
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const {
+  Front,
+  FrontClassWithSpec,
+  preEvent,
+  types
+} = require("devtools/shared/protocol.js");
+const {
+  accessibleSpec,
+  accessibleWalkerSpec,
+  accessibilitySpec
+} = require("devtools/shared/specs/accessibility");
+
+const events = require("devtools/shared/event-emitter");
+const ACCESSIBLE_PROPERTIES = [
+  "role",
+  "name",
+  "value",
+  "description",
+  "help",
+  "keyboardShortcut",
+  "childCount",
+  "domNodeType"
+];
+
+const AccessibleFront = FrontClassWithSpec(accessibleSpec, {
+  initialize(client, form) {
+    Front.prototype.initialize.call(this, client, form);
+
+    // Define getters for accesible properties that are received from the actor.
+    // Note: we would like accessible properties to be iterable for a11y
+    // clients.
+    for (let key of ACCESSIBLE_PROPERTIES) {
+      Object.defineProperty(this, key, {
+        get() {
+          return this._form[key];
+        },
+        enumerable: true
+      });
+    }
+  },
+
+  marshallPool() {
+    return this.walker;
+  },
+
+  form(form, detail) {
+    if (detail === "actorid") {
+      this.actorID = form;
+      return;
+    }
+
+    this.actorID = form.actor;
+    this._form = form;
+    DevToolsUtils.defineLazyGetter(this, "walker", () =>
+      types.getType("accessiblewalker").read(this._form.walker, this));
+  },
+
+  /**
+   * Get a dom node front from accessible actor's raw accessible object's
+   * DONNode property.
+   */
+  getDOMNode(domWalker) {
+    return domWalker.getNodeFromActor(this.actorID,
+                                      ["rawAccessible", "DOMNode"]);
+  },
+
+  nameChange: preEvent("name-change", function (name, parent) {
+    this._form.name = name;
+    // Name change event affects the tree rendering, we fire this event on
+    // accessibility walker as the point of interaction for UI.
+    if (this.walker) {
+      events.emit(this.walker, "name-change", this, parent);
+    }
+  }),
+
+  valueChange: preEvent("value-change", function (value) {
+    this._form.value = value;
+  }),
+
+  descriptionChange: preEvent("description-change", function (description) {
+    this._form.description = description;
+  }),
+
+  helpChange: preEvent("help-change", function (help) {
+    this._form.help = help;
+  }),
+
+  shortcutChange: preEvent("shortcut-change", function (keyboardShortcut) {
+    this._form.keyboardShortcut = keyboardShortcut;
+  }),
+
+  reorder: preEvent("reorder", function (childCount) {
+    this._form.childCount = childCount;
+    // Reorder event affects the tree rendering, we fire this event on
+    // accessibility walker as the point of interaction for UI.
+    if (this.walker) {
+      events.emit(this.walker, "reorder", this);
+    }
+  }),
+
+  textChange: preEvent("text-change", function () {
+    // Text event affects the tree rendering, we fire this event on
+    // accessibility walker as the point of interaction for UI.
+    if (this.walker) {
+      events.emit(this.walker, "text-change", this);
+    }
+  })
+});
+
+const AccessibleWalkerFront = FrontClassWithSpec(accessibleWalkerSpec, {
+  accessibleDestroy: preEvent("accessible-destroy", function (accessible) {
+    accessible.destroy();
+  }),
+
+  form(json) {
+    this.actorID = json.actor;
+  }
+});
+
+const AccessibilityFront = FrontClassWithSpec(accessibilitySpec, {
+  initialize(client, form) {
+    Front.prototype.initialize.call(this, client, form);
+    this.actorID = form.accessibilityActor;
+    this.manage(this);
+  }
+});
+
+exports.AccessibleFront = AccessibleFront;
+exports.AccessibleWalkerFront = AccessibleWalkerFront;
+exports.AccessibilityFront = AccessibilityFront;
--- a/devtools/shared/fronts/moz.build
+++ b/devtools/shared/fronts/moz.build
@@ -1,15 +1,16 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
+    'accessibility.js',
     'actor-registry.js',
     'addons.js',
     'animation.js',
     'call-watcher.js',
     'canvas.js',
     'css-properties.js',
     'csscoverage.js',
     'device.js',
new file mode 100644
--- /dev/null
+++ b/devtools/shared/specs/accessibility.js
@@ -0,0 +1,141 @@
+/* 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 strict";
+
+const protocol = require("devtools/shared/protocol");
+const { Arg, generateActorSpec, RetVal, types } = protocol;
+// eslint-disable-next-line no-unused-vars
+const { nodeSpec } = require("devtools/shared/specs/inspector");
+
+types.addActorType("accessible");
+
+const accessibleSpec = generateActorSpec({
+  typeName: "accessible",
+
+  events: {
+    "actions-change": {
+      type: "actionsChange",
+      actions: Arg(0, "array:string")
+    },
+    "name-change": {
+      type: "nameChange",
+      name: Arg(0, "string"),
+      parent: Arg(1, "nullable:accessible")
+    },
+    "value-change": {
+      type: "valueChange",
+      value: Arg(0, "string")
+    },
+    "description-change": {
+      type: "descriptionChange",
+      description: Arg(0, "string")
+    },
+    "state-change": {
+      type: "stateChange",
+      states: Arg(0, "array:string")
+    },
+    "attributes-change": {
+      type: "attributesChange",
+      states: Arg(0, "json")
+    },
+    "help-change": {
+      type: "helpChange",
+      help: Arg(0, "string")
+    },
+    "shortcut-change": {
+      type: "shortcutChange",
+      shortcut: Arg(0, "string")
+    },
+    "reorder": {
+      type: "reorder",
+      childCount: Arg(0, "number")
+    },
+    "text-change": {
+      type: "textChange"
+    }
+  },
+
+  methods: {
+    getActions: {
+      request: {},
+      response: {
+        actions: RetVal("array:string")
+      }
+    },
+    getIndexInParent: {
+      request: {},
+      response: {
+        indexInParent: RetVal("number")
+      }
+    },
+    getState: {
+      request: {},
+      response: {
+        states: RetVal("array:string")
+      }
+    },
+    getAttributes: {
+      request: {},
+      response: {
+        attributes: RetVal("json")
+      }
+    },
+    children: {
+      request: {},
+      response: {
+        children: RetVal("array:accessible")
+      }
+    }
+  }
+});
+
+const accessibleWalkerSpec = generateActorSpec({
+  typeName: "accessiblewalker",
+
+  events: {
+    "accessible-destroy": {
+      type: "accessibleDestroy",
+      accessible: Arg(0, "accessible")
+    }
+  },
+
+  methods: {
+    children: {
+      request: {},
+      response: {
+        children: RetVal("array:accessible")
+      }
+    },
+    getDocument: {
+      request: {},
+      response: {
+        document: RetVal("accessible")
+      }
+    },
+    getAccessibleFor: {
+      request: { node: Arg(0, "domnode") },
+      response: {
+        accessible: RetVal("accessible")
+      }
+    }
+  }
+});
+
+const accessibilitySpec = generateActorSpec({
+  typeName: "accessibility",
+
+  methods: {
+    getWalker: {
+      request: {},
+      response: {
+        walker: RetVal("accessiblewalker")
+      }
+    }
+  }
+});
+
+exports.accessibleSpec = accessibleSpec;
+exports.accessibleWalkerSpec = accessibleWalkerSpec;
+exports.accessibilitySpec = accessibilitySpec;
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -1,15 +1,16 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
+    'accessibility.js',
     'actor-registry.js',
     'addons.js',
     'animation.js',
     'breakpoint.js',
     'call-watcher.js',
     'canvas.js',
     'css-properties.js',
     'csscoverage.js',
--- a/dom/base/SubtleCrypto.cpp
+++ b/dom/base/SubtleCrypto.cpp
@@ -53,17 +53,17 @@ SubtleCrypto::RecordTelemetryOnce() {
   RefPtr<Promise> p = Promise::Create(mParent, aRv);                    \
   if (aRv.Failed()) {                                                   \
     return nullptr;                                                     \
   }                                                                     \
   RecordTelemetryOnce();                                                \
   RefPtr<WebCryptoTask> task =                                          \
     WebCryptoTask::Create ## Operation ## Task(__VA_ARGS__);            \
   if (!task) {                                                          \
-    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);                                  \
+    aRv.Throw(NS_ERROR_NULL_POINTER);                                   \
     return nullptr;                                                     \
   }                                                                     \
   task->DispatchWithPromise(p);                                         \
   return p.forget();
 
 already_AddRefed<Promise>
 SubtleCrypto::Encrypt(JSContext* cx,
                       const ObjectOrString& algorithm,
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -300,16 +300,17 @@ bool nsContentUtils::sDevToolsEnabled = 
 bool nsContentUtils::sSendPerformanceTimingNotifications = false;
 bool nsContentUtils::sUseActivityCursor = false;
 bool nsContentUtils::sAnimationsAPICoreEnabled = false;
 bool nsContentUtils::sAnimationsAPIElementAnimateEnabled = false;
 bool nsContentUtils::sGetBoxQuadsEnabled = false;
 bool nsContentUtils::sSkipCursorMoveForSameValueSet = false;
 bool nsContentUtils::sRequestIdleCallbackEnabled = false;
 bool nsContentUtils::sLowerNetworkPriority = false;
+bool nsContentUtils::sTailingEnabled = false;
 bool nsContentUtils::sShowInputPlaceholderOnFocus = true;
 bool nsContentUtils::sAutoFocusEnabled = true;
 #ifndef RELEASE_OR_BETA
 bool nsContentUtils::sBypassCSSOMOriginCheck = false;
 #endif
 bool nsContentUtils::sIsScopedStyleEnabled = false;
 
 bool nsContentUtils::sIsBytecodeCacheEnabled = false;
@@ -764,16 +765,19 @@ nsContentUtils::Init()
 #endif
 
   Preferences::AddBoolVarCache(&sIsScopedStyleEnabled,
                                "layout.css.scoped-style.enabled", false);
 
   Preferences::AddBoolVarCache(&sLowerNetworkPriority,
                                "privacy.trackingprotection.lower_network_priority", false);
 
+  Preferences::AddBoolVarCache(&sTailingEnabled,
+                               "network.http.tailing.enabled", true);
+
   Preferences::AddBoolVarCache(&sShowInputPlaceholderOnFocus,
                                "dom.placeholder.show_on_focus", true);
 
   Preferences::AddBoolVarCache(&sAutoFocusEnabled,
                                "browser.autofocus", true);
 
   Preferences::AddBoolVarCache(&sIsBytecodeCacheEnabled,
                                "dom.script_loader.bytecode_cache.enabled", false);
@@ -3080,49 +3084,42 @@ nsContentUtils::GenerateStateKey(nsICont
       // list here and it doesn't need to listen to mutation events.
 
       // Please keep this in sync with nsHTMLDocument::GetForms().
       htmlForms = new nsContentList(aDocument, kNameSpaceID_XHTML,
                                     nsGkAtoms::form, nsGkAtoms::form,
                                     /* aDeep = */ true,
                                     /* aLiveList = */ false);
     }
-    RefPtr<nsContentList> htmlFormControls = htmlDoc->GetExistingFormControls();
-    if (!htmlFormControls) {
-      // If the document doesn't have an existing form controls content list,
-      // create a new one, but avoid creating a live list since we only need to
-      // use the list here and it doesn't need to listen to mutation events.
-      htmlFormControls = new nsContentList(aDocument,
-                                           nsHTMLDocument::MatchFormControls,
-                                           nullptr, nullptr,
-                                           /* aDeep = */ true,
-                                           /* aMatchAtom = */ nullptr,
-                                           /* aMatchNameSpaceId = */ kNameSpaceID_None,
-                                           /* aFuncMayDependOnAttr = */ true,
-                                           /* aLiveList = */ false);
-    }
-
-    NS_ENSURE_TRUE(htmlForms && htmlFormControls, NS_ERROR_OUT_OF_MEMORY);
+    RefPtr<nsContentList> htmlFormControls =
+      new nsContentList(aDocument,
+                        nsHTMLDocument::MatchFormControls,
+                        nullptr, nullptr,
+                        /* aDeep = */ true,
+                        /* aMatchAtom = */ nullptr,
+                        /* aMatchNameSpaceId = */ kNameSpaceID_None,
+                        /* aFuncMayDependOnAttr = */ true,
+                        /* aLiveList = */ false);
 
     // If we have a form control and can calculate form information, use that
     // as the key - it is more reliable than just recording position in the
     // DOM.
     // XXXbz Is it, really?  We have bugs on this, I think...
     // Important to have a unique key, and tag/type/name may not be.
     //
     // If the control has a form, the format of the key is:
     // f>type>IndOfFormInDoc>IndOfControlInForm>FormName>name
     // else:
     // d>type>IndOfControlInDoc>name
     //
     // XXX We don't need to use index if name is there
     // XXXbz We don't?  Why not?  I don't follow.
     //
     nsCOMPtr<nsIFormControl> control(do_QueryInterface(aContent));
-    if (control && htmlFormControls && htmlForms) {
+    if (control) {
 
       // Append the control type
       KeyAppendInt(control->ControlType(), aKey);
 
       // If in a form, add form name / index of form / index in form
       int32_t index = -1;
       Element *formElement = control->GetFormElement();
       if (formElement) {
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -3105,16 +3105,19 @@ public:
    */
   static bool
   GetUserIsInteracting();
 
   // Check pref "privacy.trackingprotection.lower_network_priority" to see
   // if we want to lower the priority of the channel.
   static bool IsLowerNetworkPriority() { return sLowerNetworkPriority; }
 
+  // Whether tracker tailing is turned on - "network.http.tailing.enabled".
+  static bool IsTailingEnabled() { return sTailingEnabled; }
+
   // Check pref "dom.placeholder.show_on_focus" to see
   // if we want to show the placeholder inside input elements
   // when they have focus.
   static bool ShowInputPlaceholderOnFocus() { return sShowInputPlaceholderOnFocus; }
 
   // Check pref "browser.autofocus" to see if we want to enable autofocusing elements
   // when the page requests it.
   static bool AutoFocusEnabled() { return sAutoFocusEnabled; }
@@ -3296,16 +3299,17 @@ private:
   static bool sSendPerformanceTimingNotifications;
   static bool sUseActivityCursor;
   static bool sAnimationsAPICoreEnabled;
   static bool sAnimationsAPIElementAnimateEnabled;
   static bool sGetBoxQuadsEnabled;
   static bool sSkipCursorMoveForSameValueSet;
   static bool sRequestIdleCallbackEnabled;
   static bool sLowerNetworkPriority;
+  static bool sTailingEnabled;
   static bool sShowInputPlaceholderOnFocus;
   static bool sAutoFocusEnabled;
 #ifndef RELEASE_OR_BETA
   static bool sBypassCSSOMOriginCheck;
 #endif
   static bool sIsScopedStyleEnabled;
   static bool sIsBytecodeCacheEnabled;
   static int32_t sBytecodeCacheStrategy;
--- a/dom/base/test/browser_bug1058164.js
+++ b/dom/base/test/browser_bug1058164.js
@@ -64,17 +64,17 @@ add_task(async function test_swap_framel
   let firstBrowser = tab.linkedBrowser;
   await BrowserTestUtils.browserLoaded(firstBrowser);
 
   // Swap the browser out to a new window
   let newWindow = gBrowser.replaceTabWithWindow(tab);
 
   // We have to wait for the window to load so we can get the selected browser
   // to listen to.
-  await BrowserTestUtils.waitForEvent(newWindow, "load");
+  await BrowserTestUtils.waitForEvent(newWindow, "DOMContentLoaded");
   let newWindowBrowser = newWindow.gBrowser.selectedBrowser;
 
   // Wait for the expected pagehide and pageshow events on the initial browser
   await prepareForVisibilityEvents(newWindowBrowser, ["pagehide", "pageshow"]);
 
   // Now let's send the browser back to the original window
 
   // First, create a new, empty browser tab to replace the window with
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -14952,19 +14952,25 @@ class CGBindingImplClass(CGClass):
         if iface.ctor():
             appendMethod(iface.ctor(), isConstructor=True)
         for n in iface.namedConstructors:
             appendMethod(n, isConstructor=True)
         for m in iface.members:
             if m.isMethod():
                 if m.isIdentifierLess():
                     continue
+                if m.isMaplikeOrSetlikeOrIterableMethod():
+                    # Handled by generated code already
+                    continue
                 if not m.isStatic() or not skipStaticMethods:
                     appendMethod(m)
             elif m.isAttr():
+                if m.isMaplikeOrSetlikeAttr():
+                    # Handled by generated code already
+                    continue
                 self.methodDecls.append(cgGetter(descriptor, m))
                 if not m.readonly:
                     self.methodDecls.append(cgSetter(descriptor, m))
 
         # Now do the special operations
         def appendSpecialOperation(name, op):
             if op is None:
                 return
@@ -15208,18 +15214,19 @@ class CGExampleRoot(CGThing):
         self.root = CGNamespace.build(["mozilla", "dom"], self.root)
 
         builder = ForwardDeclarationBuilder()
         for member in descriptor.interface.members:
             if not member.isAttr() and not member.isMethod():
                 continue
             if member.isStatic():
                 builder.addInMozillaDom("GlobalObject")
-            if member.isAttr() and not member.isMaplikeOrSetlikeAttr():
-                builder.forwardDeclareForType(member.type, config)
+            if member.isAttr():
+                if not member.isMaplikeOrSetlikeAttr():
+                    builder.forwardDeclareForType(member.type, config)
             else:
                 assert member.isMethod()
                 if not member.isMaplikeOrSetlikeOrIterableMethod():
                     for sig in member.signatures():
                         builder.forwardDeclareForType(sig[0], config)
                         for arg in sig[1]:
                             builder.forwardDeclareForType(arg.type, config)
 
@@ -15558,16 +15565,27 @@ class CGJSImplClass(CGBindingImplClass):
 
         self.methodDecls.append(
             ClassMethod("_Create",
                         "bool",
                         JSNativeArguments(),
                         static=True,
                         body=self.getCreateFromExistingBody()))
 
+        if (descriptor.interface.isJSImplemented() and
+            descriptor.interface.maplikeOrSetlikeOrIterable and
+            descriptor.interface.maplikeOrSetlikeOrIterable.isMaplike()):
+            self.methodDecls.append(
+                ClassMethod("__OnGet",
+                            "void",
+                            [Argument("JS::Handle<JS::Value>", "aKey"),
+                             Argument("JS::Handle<JS::Value>", "aValue"),
+                             Argument("ErrorResult&", "aRv")],
+                            body="mImpl->__OnGet(aKey, aValue, aRv);\n"))
+
         CGClass.__init__(self, descriptor.name,
                          bases=baseClasses,
                          constructors=[constructor],
                          destructor=destructor,
                          methods=self.methodDecls,
                          decorators=decorators,
                          extradeclarations=extradeclarations,
                          extradefinitions=extradefinitions)
@@ -15904,35 +15922,56 @@ class CGFastCallback(CGClass):
 
     def deps(self):
         return self._deps
 
 
 class CGCallbackInterface(CGCallback):
     def __init__(self, descriptor, spiderMonkeyInterfacesAreStructs=False):
         iface = descriptor.interface
-        attrs = [m for m in iface.members if m.isAttr() and not m.isStatic()]
+        attrs = [m for m in iface.members
+                 if (m.isAttr() and not m.isStatic() and
+                     (not m.isMaplikeOrSetlikeAttr() or
+                      not iface.isJSImplemented()))]
         getters = [CallbackGetter(a, descriptor, spiderMonkeyInterfacesAreStructs)
                    for a in attrs]
         setters = [CallbackSetter(a, descriptor, spiderMonkeyInterfacesAreStructs)
                    for a in attrs if not a.readonly]
         methods = [m for m in iface.members
-                   if m.isMethod() and not m.isStatic() and not m.isIdentifierLess()]
+                   if (m.isMethod() and not m.isStatic() and
+                       not m.isIdentifierLess() and
+                       (not m.isMaplikeOrSetlikeOrIterableMethod() or
+                        not iface.isJSImplemented()))]
         methods = [CallbackOperation(m, sig, descriptor, spiderMonkeyInterfacesAreStructs)
                    for m in methods for sig in m.signatures()]
+
+        needInitId = False
         if iface.isJSImplemented() and iface.ctor():
             sigs = descriptor.interface.ctor().signatures()
             if len(sigs) != 1:
                 raise TypeError("We only handle one constructor.  See bug 869268.")
             methods.append(CGJSImplInitOperation(sigs[0], descriptor))
-        if any(m.isAttr() or m.isMethod() for m in iface.members) or (iface.isJSImplemented() and iface.ctor()):
-            methods.append(initIdsClassMethod([descriptor.binaryNameFor(m.identifier.name)
-                                               for m in iface.members
-                                               if m.isAttr() or m.isMethod()] +
-                                              (["__init"] if iface.isJSImplemented() and iface.ctor() else []),
+            needInitId = True
+
+        needOnGetId = False
+        if (iface.isJSImplemented() and
+            iface.maplikeOrSetlikeOrIterable and
+            iface.maplikeOrSetlikeOrIterable.isMaplike()):
+            methods.append(CGJSImplOnGetOperation(descriptor))
+            needOnGetId = True
+
+        idlist = [descriptor.binaryNameFor(m.identifier.name)
+                  for m in iface.members
+                  if m.isAttr() or m.isMethod()]
+        if needInitId:
+            idlist.append("__init")
+        if needOnGetId:
+            idlist.append("__onget")
+        if len(idlist) != 0:
+            methods.append(initIdsClassMethod(idlist,
                                               iface.identifier.name + "Atoms"))
         CGCallback.__init__(self, iface, descriptor, "CallbackInterface",
                             methods, getters=getters, setters=setters)
 
 
 class FakeMember():
     def __init__(self, name=None):
         self.treatNullAs = "Default"
@@ -16441,16 +16480,42 @@ class CGJSImplInitOperation(CallbackOper
                                        singleOperation=False,
                                        rethrowContentException=True,
                                        spiderMonkeyInterfacesAreStructs=True)
 
     def getPrettyName(self):
         return "__init"
 
 
+class CGJSImplOnGetOperation(CallbackOperationBase):
+    """
+    Codegen the __OnGet() method used to notify the JS impl that a get() is
+    happening on a JS-implemented maplike.  This method takes two arguments
+    (key and value) and returns nothing.
+    """
+    def __init__(self, descriptor):
+        CallbackOperationBase.__init__(
+            self,
+            (BuiltinTypes[IDLBuiltinType.Types.void],
+             [FakeArgument(BuiltinTypes[IDLBuiltinType.Types.any],
+                           None,
+                           "key"),
+              FakeArgument(BuiltinTypes[IDLBuiltinType.Types.any],
+                           None,
+                           "value")]),
+            "__onget", "__OnGet",
+            descriptor,
+            singleOperation=False,
+            rethrowContentException=True,
+            spiderMonkeyInterfacesAreStructs=True)
+
+    def getPrettyName(self):
+        return "__onget"
+
+
 def getMaplikeOrSetlikeErrorReturn(helperImpl):
     """
     Generate return values based on whether a maplike or setlike generated
     method is an interface method (which returns bool) or a helper function
     (which uses ErrorResult).
     """
     if helperImpl:
         return dedent(
@@ -16723,17 +16788,31 @@ class CGMaplikeOrSetlikeMethodGenerator(
         """
         assert self.maplikeOrSetlike.isMaplike()
         r = self.appendKeyArgConversion()
         code = [CGGeneric(dedent(
             """
             JS::Rooted<JS::Value> result(cx);
             """))]
         arguments = ["&result"]
-        return self.mergeTuples(r, (code, arguments, []))
+        if self.descriptor.interface.isJSImplemented():
+            callOnGet = [CGGeneric(dedent(
+                """
+                {
+                  JS::ExposeValueToActiveJS(result);
+                  ErrorResult onGetResult;
+                  self->__OnGet(arg0Val, result, onGetResult);
+                  if (onGetResult.MaybeSetPendingException(cx)) {
+                    return false;
+                  }
+                }
+                """))]
+        else:
+            callOnGet = []
+        return self.mergeTuples(r, (code, arguments, callOnGet))
 
     def has(self):
         """
         bool has(key);
 
         Check if an entry exists in the backing object. Returns true if value
         exists in backing object, false otherwise.
         """
@@ -17023,16 +17102,21 @@ class GlobalGenRoots():
             structs.append(buildAtomCacheStructure(dict, lambda x: x, dict.members))
 
         for d in (config.getDescriptors(isJSImplemented=True) +
                   config.getDescriptors(isCallback=True)):
             members = [m for m in d.interface.members if m.isAttr() or m.isMethod()]
             if d.interface.isJSImplemented() and d.interface.ctor():
                 # We'll have an __init() method.
                 members.append(FakeMember('__init'))
+            if (d.interface.isJSImplemented() and
+                d.interface.maplikeOrSetlikeOrIterable and
+                d.interface.maplikeOrSetlikeOrIterable.isMaplike()):
+                # We'll have an __onget() method.
+                members.append(FakeMember('__onget'))
             if len(members) == 0:
                 continue
 
             structs.append(buildAtomCacheStructure(d.interface,
                                                    lambda x: d.binaryNameFor(x),
                                                    members))
 
         structs.sort()
--- a/dom/bindings/test/TestInterfaceJSMaplike.js
+++ b/dom/bindings/test/TestInterfaceJSMaplike.js
@@ -27,12 +27,16 @@ TestInterfaceJSMaplike.prototype = {
   },
 
   deleteInternal: function(aKey) {
     return this.__DOM_IMPL__.__delete(aKey);
   },
 
   clearInternal: function() {
     return this.__DOM_IMPL__.__clear();
-  }
+  },
+
+  __onget: function(key, value) {
+    /* no-op */
+  },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestInterfaceJSMaplike])
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -373,20 +373,22 @@ FetchDriver::HttpFetch()
     AutoTArray<nsCString, 5> unsafeHeaders;
     mRequest->Headers()->GetUnsafeHeaders(unsafeHeaders);
     nsCOMPtr<nsILoadInfo> loadInfo = chan->GetLoadInfo();
     if (loadInfo) {
       loadInfo->SetCorsPreflightInfo(unsafeHeaders, false);
     }
   }
 
-  if (mIsTrackingFetch && nsContentUtils::IsLowerNetworkPriority()) {
+  if (mIsTrackingFetch && nsContentUtils::IsTailingEnabled()) {
     cos->AddClassFlags(nsIClassOfService::Throttleable |
                        nsIClassOfService::Tail);
+  }
 
+  if (mIsTrackingFetch && nsContentUtils::IsLowerNetworkPriority()) {
     nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(chan);
     if (p) {
       p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
     }
   }
 
   rv = chan->AsyncOpen2(this);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -198,17 +198,16 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(nsHTM
                                    mAll,
                                    mImages,
                                    mApplets,
                                    mEmbeds,
                                    mLinks,
                                    mAnchors,
                                    mScripts,
                                    mForms,
-                                   mFormControls,
                                    mWyciwygChannel,
                                    mMidasCommandManager)
 
 NS_IMPL_ADDREF_INHERITED(nsHTMLDocument, nsDocument)
 NS_IMPL_RELEASE_INHERITED(nsHTMLDocument, nsDocument)
 
 // QueryInterface implementation for nsHTMLDocument
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHTMLDocument)
@@ -3714,17 +3713,16 @@ nsHTMLDocument::DocAddSizeOfExcludingThi
   // worthwhile:
   // - mImages
   // - mApplets
   // - mEmbeds
   // - mLinks
   // - mAnchors
   // - mScripts
   // - mForms
-  // - mFormControls
   // - mWyciwygChannel
   // - mMidasCommandManager
 }
 
 bool
 nsHTMLDocument::WillIgnoreCharsetOverride()
 {
   if (mType != eHTML) {
--- a/dom/html/nsHTMLDocument.h
+++ b/dom/html/nsHTMLDocument.h
@@ -77,21 +77,16 @@ public:
 
   nsContentList* GetForms();
 
   nsContentList* GetExistingForms() const
   {
     return mForms;
   }
 
-  nsContentList* GetExistingFormControls() const
-  {
-    return mFormControls;
-  }
-
   // nsIDOMDocument interface
   using nsDocument::CreateElement;
   using nsDocument::CreateElementNS;
   NS_FORWARD_NSIDOMDOCUMENT(nsDocument::)
 
   // And explicitly import the things from nsDocument that we just shadowed
   using nsDocument::GetImplementation;
   using nsDocument::GetTitle;
@@ -319,17 +314,16 @@ protected:
 
   RefPtr<nsContentList> mImages;
   RefPtr<nsEmptyContentList> mApplets;
   RefPtr<nsContentList> mEmbeds;
   RefPtr<nsContentList> mLinks;
   RefPtr<nsContentList> mAnchors;
   RefPtr<nsContentList> mScripts;
   RefPtr<nsContentList> mForms;
-  RefPtr<nsContentList> mFormControls;
 
   RefPtr<mozilla::dom::HTMLAllCollection> mAll;
 
   /** # of forms in the document, synchronously set */
   int32_t mNumForms;
 
   static uint32_t gWyciwygSessionCnt;
 
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -322,16 +322,20 @@ class RTCStatsReport {
           return value;
         }, this.__DOM_IMPL__.wrappedJSObject)
       };
     }
     Object.defineProperties(this.__DOM_IMPL__.wrappedJSObject, legacyProps);
   }
 
   get mozPcid() { return this._pcid; }
+
+  __onget(key, value) {
+    /* Do whatever here */
+  }
 }
 setupPrototype(RTCStatsReport, {
   classID: PC_STATS_CID,
   contractID: PC_STATS_CONTRACT,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
   _specToLegacyFieldMapping: {
         "inbound-rtp": "inboundrtp",
         "outbound-rtp": "outboundrtp",
--- a/dom/media/webrtc/RTCCertificate.cpp
+++ b/dom/media/webrtc/RTCCertificate.cpp
@@ -275,20 +275,16 @@ RTCCertificate::GenerateCertificate(
 
   PRTime expires = ReadExpires(aGlobal.Context(), aOptions, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
   RefPtr<WebCryptoTask> task =
       new GenerateRTCCertificateTask(global, aGlobal.Context(),
                                      aOptions, usages, expires);
-  if (!task) {
-    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
-    return nullptr;
-  }
   task->DispatchWithPromise(p);
   return p.forget();
 }
 
 RTCCertificate::RTCCertificate(nsIGlobalObject* aGlobal)
     : mGlobal(aGlobal),
       mPrivateKey(nullptr),
       mCertificate(nullptr),
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -35,17 +35,16 @@
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/dom/quota/PQuotaParent.h"
 #include "mozilla/dom/quota/PQuotaRequestParent.h"
 #include "mozilla/dom/quota/PQuotaUsageRequestParent.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/IntegerRange.h"
 #include "mozilla/Mutex.h"
-#include "mozilla/LazyIdleThread.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/Unused.h"
 #include "mozStorageCID.h"
 #include "mozStorageHelper.h"
 #include "nsAppDirectoryServiceDefs.h"
@@ -3588,21 +3587,20 @@ QuotaManager::Init(const nsAString& aBas
 
   rv = CloneStoragePath(baseDir,
                         NS_LITERAL_STRING(DEFAULT_DIRECTORY_NAME),
                         mDefaultStoragePath);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  // Make a lazy thread for any IO we need (like clearing or enumerating the
-  // contents of storage directories).
-  mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
-                                 NS_LITERAL_CSTRING("Storage I/O"),
-                                 LazyIdleThread::ManualShutdown);
+  rv = NS_NewNamedThread("QuotaManager IO", getter_AddRefs(mIOThread));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   // Make a timer here to avoid potential failures later. We don't actually
   // initialize the timer until shutdown.
   mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
   if (NS_WARN_IF(!mShutdownTimer)) {
     return NS_ERROR_FAILURE;
   }
 
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -1062,17 +1062,22 @@ ScriptLoader::StartLoad(ScriptLoadReques
        aRequest, async, defer, aRequest->IsTracking()));
 
   nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
   if (cos) {
     if (aRequest->mScriptFromHead && !async && !defer) {
       // synchronous head scripts block loading of most other non js/css
       // content such as images, Leader implicitely disallows tailing
       cos->AddClassFlags(nsIClassOfService::Leader);
-    } else if (defer && !async) {
+    } else if (defer && (!async || !nsContentUtils::IsTailingEnabled())) {
+      // Bug 1395525 and the !nsContentUtils::IsTailingEnabled() bit:
+      // We want to make sure that turing tailing off by the pref makes
+      // the browser behave exactly the same way as before landing
+      // the tailing patch, which has added the "&& !async" part.
+
       // head/body deferred scripts are blocked by leaders but are not
       // allowed tailing because they block DOMContentLoaded
       cos->AddClassFlags(nsIClassOfService::TailForbidden);
     } else {
       // other scripts (=body sync or head/body async) are neither blocked
       // nor prioritized
       cos->AddClassFlags(nsIClassOfService::Unblocked);
 
--- a/dom/system/nsDeviceSensors.cpp
+++ b/dom/system/nsDeviceSensors.cpp
@@ -278,49 +278,61 @@ struct Orientation
   }
 
   double alpha;
   double beta;
   double gamma;
 };
 
 static Orientation
-RotationVectorToOrientation(double aX, double aY, double aZ, double aW)
-{
-  static const double kFuzzyOne = 1.0 - 1e-6;
-  static const double kCircleRad = 2.0 * M_PI;
+RotationVectorToOrientation(double aX, double aY, double aZ, double aW) {
+  double mat[9];
+
+  mat[0] = 1 - 2*aY*aY - 2*aZ*aZ;
+  mat[1] = 2*aX*aY - 2*aZ*aW;
+  mat[2] = 2*aX*aZ + 2*aY*aW;
 
-  Orientation orient = { 2.0 * std::atan2(aY, aW),
-                         M_PI_2,
-                         0.0 };
+  mat[3] = 2*aX*aY + 2*aZ*aW;
+  mat[4] = 1 - 2*aX*aX - 2*aZ*aZ;
+  mat[5] = 2*aY*aZ - 2*aX*aW;
 
-  const double sqX = aX * aX;
-  const double sqY = aY * aY;
-  const double sqZ = aZ * aZ;
-  const double sqW = aW * aW;
-  const double unitLength = sqX + sqY + sqZ + sqW;
-  const double xwyz = 2.0 * (aX * aW + aY * aZ) / unitLength;
+  mat[6] = 2*aX*aZ - 2*aY*aW;
+  mat[7] = 2*aY*aZ + 2*aX*aW;
+  mat[8] = 1 - 2*aX*aX - 2*aY*aY;
+
+  Orientation orient;
 
-  if (xwyz < -kFuzzyOne) {
-    orient.alpha *= -1.0;
-    orient.beta *= -1.0;
-  } else if (xwyz <= kFuzzyOne) {
-    const double gammaX = -sqX - sqY + sqZ + sqW;
-    const double gammaY = 2.0 * (aY * aW - aX * aZ);
-    const double alphaX = -sqX + sqY - sqZ + sqW;
-    const double alphaY = 2.0 * (aZ * aW - aX * aY);
-    const double fac = gammaX > 0 ? 1.0 : -1.0;
+  if (mat[8] > 0) {
+    orient.alpha = atan2(-mat[1], mat[4]);
+    orient.beta = asin(mat[7]);
+    orient.gamma = atan2(-mat[6], mat[8]);
+  } else if (mat[8] < 0) {
+    orient.alpha = atan2(mat[1], -mat[4]);
+    orient.beta = -asin(mat[7]);
+    orient.beta += (orient.beta >= 0) ? -M_PI : M_PI;
+    orient.gamma = atan2(mat[6], -mat[8]);
+  } else {
+    if (mat[6] > 0) {
+      orient.alpha = atan2(-mat[1], mat[4]);
+      orient.beta = asin(mat[7]);
+      orient.gamma = -M_PI_2;
+    } else if (mat[6] < 0) {
+      orient.alpha = atan2(mat[1], -mat[4]);
+      orient.beta = -asin(mat[7]);
+      orient.beta += (orient.beta >= 0) ? -M_PI : M_PI;
+      orient.gamma = -M_PI_2;
+    } else {
+      orient.alpha = atan2(mat[3], mat[0]);
+      orient.beta = (mat[7] > 0) ? M_PI_2 : -M_PI_2;
+      orient.gamma = 0;
+    }
+  }
 
-    orient.alpha = std::fmod(kCircleRad + std::atan2(fac * alphaY, fac * alphaX),
-                             kCircleRad);
-    orient.beta = fac * std::asin(xwyz);
-    orient.gamma = std::atan2(fac * gammaY, fac * gammaX);
-    if (fac < 0.0) {
-      orient.beta = fmod(M_PI + orient.beta, M_PI);
-    }
+  if (orient.alpha < 0) {
+    orient.alpha += 2*M_PI;
   }
 
   return Orientation::RadToDeg(orient);
 }
 
 void
 nsDeviceSensors::Notify(const mozilla::hal::SensorData& aSensorData)
 {
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/libudev-sys/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "libudev-sys"
+version = "0.1.3"
+authors = ["Tim Taubert <ttaubert@mozilla.com>"]
+description = "FFI bindings to libudev"
+
+[dependencies]
+lazy_static = "0.2"
+libc = "0.2"
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/libudev-sys/src/lib.rs
@@ -0,0 +1,180 @@
+/* -*- Mode: rust; rust-indent-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/. */
+
+#![allow(non_camel_case_types)]
+#![allow(non_upper_case_globals)]
+
+#[macro_use]
+extern crate lazy_static;
+extern crate libc;
+
+use libc::{c_void,c_int,c_char,c_ulonglong,dev_t};
+use libc::{RTLD_GLOBAL,RTLD_LAZY,RTLD_NOLOAD};
+use libc::{dlopen,dlclose,dlsym};
+use std::ffi::CString;
+use std::{marker,mem,ops,ptr};
+
+#[repr(C)]
+pub struct udev {
+  __private: c_void
+}
+
+#[repr(C)]
+pub struct udev_list_entry {
+  __private: c_void
+}
+
+#[repr(C)]
+pub struct udev_device {
+  __private: c_void
+}
+
+#[repr(C)]
+pub struct udev_monitor {
+  __private: c_void
+}
+
+#[repr(C)]
+pub struct udev_enumerate {
+  __private: c_void
+}
+
+macro_rules! ifnull {
+  ($a:expr, $b:expr) => {
+    if $a.is_null() { $b } else { $a }
+  }
+}
+
+struct Library(*mut c_void);
+
+impl Library {
+  fn open(name: &'static str) -> Library {
+    let flags = RTLD_LAZY | RTLD_GLOBAL;
+    let flags_noload = flags | RTLD_NOLOAD;
+    let name = CString::new(name).unwrap();
+    let name = name.as_ptr();
+
+    Library(unsafe {
+      ifnull!(dlopen(name, flags_noload), dlopen(name, flags))
+    })
+  }
+
+  fn get(&self, name: &'static str) -> *mut c_void {
+    let name = CString::new(name).unwrap();
+    unsafe { dlsym(self.0, name.as_ptr()) }
+  }
+}
+
+impl Drop for Library {
+  fn drop(&mut self) {
+    unsafe { dlclose(self.0); }
+  }
+}
+
+unsafe impl Sync for Library {}
+
+lazy_static! {
+  static ref LIBRARY: Library = {
+    Library::open("libudev.so.1")
+  };
+}
+
+pub struct Symbol<T> {
+  ptr: *mut c_void,
+  pd: marker::PhantomData<T>
+}
+
+impl<T> Symbol<T> {
+  fn new(ptr: *mut c_void) -> Self {
+    let default = Self::default as *mut c_void;
+    Self { ptr: ifnull!(ptr, default), pd: marker::PhantomData }
+  }
+
+  // This is the default symbol, used whenever dlopen() fails.
+  // Users of this library are expected to check whether udev_new() returns
+  // a nullptr, and if so they MUST NOT call any other exported functions.
+  extern "C" fn default() -> *mut c_void {
+    ptr::null_mut()
+  }
+}
+
+impl<T> ops::Deref for Symbol<T> {
+  type Target = T;
+
+  fn deref(&self) -> &T {
+    unsafe { mem::transmute(&self.ptr) }
+  }
+}
+
+unsafe impl<T: Sync> Sync for Symbol<T> {}
+
+macro_rules! define {
+  ($name:ident, $type:ty) => {
+    lazy_static! {
+      pub static ref $name : Symbol<$type> = {
+        Symbol::new(LIBRARY.get(stringify!($name)))
+      };
+    }
+  };
+}
+
+// udev
+define!(udev_new, extern "C" fn () -> *mut udev);
+define!(udev_unref, extern "C" fn (*mut udev) -> *mut udev);
+
+// udev_list
+define!(udev_list_entry_get_next, extern "C" fn (*mut udev_list_entry) -> *mut udev_list_entry);
+define!(udev_list_entry_get_name, extern "C" fn (*mut udev_list_entry) -> *const c_char);
+define!(udev_list_entry_get_value, extern "C" fn (*mut udev_list_entry) -> *const c_char);
+
+// udev_device
+define!(udev_device_ref, extern "C" fn (*mut udev_device) -> *mut udev_device);
+define!(udev_device_unref, extern "C" fn (*mut udev_device) -> *mut udev_device);
+define!(udev_device_new_from_syspath, extern "C" fn (*mut udev, *const c_char) -> *mut udev_device);
+define!(udev_device_get_parent, extern "C" fn (*mut udev_device) -> *mut udev_device);
+define!(udev_device_get_devpath, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_subsystem, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_devtype, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_syspath, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_sysname, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_sysnum, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_devnode, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_is_initialized, extern "C" fn (*mut udev_device) -> c_int);
+define!(udev_device_get_properties_list_entry, extern "C" fn (*mut udev_device) -> *mut udev_list_entry);
+define!(udev_device_get_property_value, extern "C" fn (*mut udev_device, *const c_char) -> *const c_char);
+define!(udev_device_get_driver, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_devnum, extern "C" fn (*mut udev_device) -> dev_t);
+define!(udev_device_get_action, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_sysattr_value, extern "C" fn (*mut udev_device, *const c_char) -> *const c_char);
+define!(udev_device_set_sysattr_value, extern "C" fn (*mut udev_device, *const c_char, *mut c_char) -> c_int);
+define!(udev_device_get_sysattr_list_entry, extern "C" fn (*mut udev_device) -> *mut udev_list_entry);
+define!(udev_device_get_seqnum, extern "C" fn (*mut udev_device) -> c_ulonglong);
+
+// udev_monitor
+define!(udev_monitor_ref, extern "C" fn (*mut udev_monitor) -> *mut udev_monitor);
+define!(udev_monitor_unref, extern "C" fn (*mut udev_monitor) -> *mut udev_monitor);
+define!(udev_monitor_new_from_netlink, extern "C" fn (*mut udev, *const c_char) -> *mut udev_monitor);
+define!(udev_monitor_enable_receiving, extern "C" fn (*mut udev_monitor) -> c_int);
+define!(udev_monitor_get_fd, extern "C" fn (*mut udev_monitor) -> c_int);
+define!(udev_monitor_receive_device, extern "C" fn (*mut udev_monitor) -> *mut udev_device);
+define!(udev_monitor_filter_add_match_subsystem_devtype, extern "C" fn (*mut udev_monitor, *const c_char, *const c_char) -> c_int);
+define!(udev_monitor_filter_add_match_tag, extern "C" fn (*mut udev_monitor, *const c_char) -> c_int);
+define!(udev_monitor_filter_remove, extern "C" fn (*mut udev_monitor) -> c_int);
+
+// udev_enumerate
+define!(udev_enumerate_unref, extern "C" fn (*mut udev_enumerate) -> *mut udev_enumerate);
+define!(udev_enumerate_new, extern "C" fn (*mut udev) -> *mut udev_enumerate);
+define!(udev_enumerate_add_match_subsystem, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
+define!(udev_enumerate_add_nomatch_subsystem, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
+define!(udev_enumerate_add_match_sysattr, extern "C" fn (*mut udev_enumerate, *const c_char, *const c_char) -> c_int);
+define!(udev_enumerate_add_nomatch_sysattr, extern "C" fn (*mut udev_enumerate, *const c_char, *const c_char) -> c_int);
+define!(udev_enumerate_add_match_property, extern "C" fn (*mut udev_enumerate, *const c_char, *const c_char) -> c_int);
+define!(udev_enumerate_add_match_tag, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
+define!(udev_enumerate_add_match_parent, extern "C" fn (*mut udev_enumerate, *mut udev_device) -> c_int);
+define!(udev_enumerate_add_match_is_initialized, extern "C" fn (*mut udev_enumerate) -> c_int);
+define!(udev_enumerate_add_match_sysname, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
+define!(udev_enumerate_add_syspath, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
+define!(udev_enumerate_scan_devices, extern "C" fn (*mut udev_enumerate) -> c_int);
+define!(udev_enumerate_get_list_entry, extern "C" fn (*mut udev_enumerate) -> *mut udev_list_entry);
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -2616,23 +2616,25 @@ XMLHttpRequestMainThread::MaybeLowerChan
   if (!nsJSUtils::GetCallingLocation(cx, fileNameString)) {
     return;
   }
 
   if (!doc->IsScriptTracking(fileNameString)) {
     return;
   }
 
-  nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(mChannel);
-  if (cos) {
-    // Adding TailAllowed to overrule the Unblocked flag, but to preserve
-    // the effect of Unblocked when tailing is off.
-    cos->AddClassFlags(nsIClassOfService::Throttleable |
-                       nsIClassOfService::Tail |
-                       nsIClassOfService::TailAllowed);
+  if (nsContentUtils::IsTailingEnabled()) {
+    nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(mChannel);
+    if (cos) {
+      // Adding TailAllowed to overrule the Unblocked flag, but to preserve
+      // the effect of Unblocked when tailing is off.
+      cos->AddClassFlags(nsIClassOfService::Throttleable |
+                         nsIClassOfService::Tail |
+                         nsIClassOfService::TailAllowed);
+    }
   }
 
   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
   if (p) {
     p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
   }
 }
 
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -69,31 +69,41 @@ struct CGContext;
 typedef struct CGContext *CGContextRef;
 
 namespace mozilla {
 
 class Mutex;
 
 namespace gfx {
 class UnscaledFont;
+class ScaledFont;
 }
 
 template<>
 struct WeakPtrTraits<gfx::UnscaledFont>
 {
   static void AssertSafeToAccessFromNonOwningThread()
   {
     // We want to allow UnscaledFont objects that were created on the main
     // thread to be accessed from other threads if the Servo font metrics
     // mutex is locked, and for objects created on Servo style worker threads
     // to be accessed later back on the main thread.
     AssertIsMainThreadOrServoFontMetricsLocked();
   }
 };
 
+template<>
+struct WeakPtrTraits<gfx::ScaledFont>
+{
+  static void AssertSafeToAccessFromNonOwningThread()
+  {
+    AssertIsMainThreadOrServoFontMetricsLocked();
+  }
+};
+
 namespace gfx {
 
 class ScaledFont;
 class SourceSurface;
 class DataSourceSurface;
 class DrawTarget;
 class DrawEventRecorder;
 class FilterNode;
@@ -764,33 +774,39 @@ public:
   {
     return nullptr;
   }
 
 protected:
   UnscaledFont() {}
 
 private:
-  static uint32_t sDeletionCounter;
+  static Atomic<uint32_t> sDeletionCounter;
 };
 
 /** This class is an abstraction of a backend/platform specific font object
  * at a particular size. It is passed into text drawing calls to describe
  * the font used for the drawing call.
  */
-class ScaledFont : public external::AtomicRefCounted<ScaledFont>
+class ScaledFont
+  : public external::AtomicRefCounted<ScaledFont>
+  , public SupportsWeakPtr<ScaledFont>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFont)
-  virtual ~ScaledFont() {}
+  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(ScaledFont)
+
+  virtual ~ScaledFont();
 
   virtual FontType GetType() const = 0;
   virtual Float GetSize() const = 0;
   virtual AntialiasMode GetDefaultAAMode();
 
+  static uint32_t DeletionCounter() { return sDeletionCounter; }
+
   /** This allows getting a path that describes the outline of a set of glyphs.
    * A target is passed in so that the guarantee is made the returned path
    * can be used with any DrawTarget that has the same backend as the one
    * passed in.
    */
   virtual already_AddRefed<Path> GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget) = 0;
 
   /** This copies the path describing the glyphs into a PathBuilder. We use this
@@ -828,16 +844,19 @@ public:
 
 protected:
   explicit ScaledFont(const RefPtr<UnscaledFont>& aUnscaledFont)
     : mUnscaledFont(aUnscaledFont)
   {}
 
   UserData mUserData;
   RefPtr<UnscaledFont> mUnscaledFont;
+
+private:
+  static Atomic<uint32_t> sDeletionCounter;
 };
 
 /**
  * Derived classes hold a native font resource from which to create
  * ScaledFonts.
  */
 class NativeFontResource : public external::AtomicRefCounted<NativeFontResource>
 {
--- a/gfx/2d/ScaledFontBase.cpp
+++ b/gfx/2d/ScaledFontBase.cpp
@@ -21,23 +21,30 @@
 #include <vector>
 #include <cmath>
 
 using namespace std;
 
 namespace mozilla {
 namespace gfx {
 
-uint32_t UnscaledFont::sDeletionCounter = 0;
+Atomic<uint32_t> UnscaledFont::sDeletionCounter(0);
 
 UnscaledFont::~UnscaledFont()
 {
   sDeletionCounter++;
 }
 
+Atomic<uint32_t> ScaledFont::sDeletionCounter(0);
+
+ScaledFont::~ScaledFont()
+{
+  sDeletionCounter++;
+}
+
 AntialiasMode
 ScaledFont::GetDefaultAAMode()
 {
   if (gfxPrefs::DisableAllTextAA()) {
     return AntialiasMode::NONE;
   }
 
   return AntialiasMode::DEFAULT;
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 5edd3da7ee11e1d0caaf0b53cb7f04cfab20e585
+Latest Commit: 81cba6b139c4c1061cab6a1c38acf2ae7f50445d
--- a/gfx/layers/LayerTreeInvalidation.cpp
+++ b/gfx/layers/LayerTreeInvalidation.cpp
@@ -310,16 +310,21 @@ public:
   {
     return Some(TransformRect(mVisibleRegion.ToUnknownRegion().GetBounds(), mTransform));
   }
 
   virtual bool ComputeChangeInternal(const char* aPrefix,
                                      nsIntRegion& aOutRegion,
                                      NotifySubDocInvalidationFunc aCallback)
   {
+    if (mLayer->AsHostLayer() && !mLayer->GetLocalVisibleRegion().ToUnknownRegion().IsEqual(mVisibleRegion)) {
+      IntRect result = NewTransformedBoundsForLeaf();
+      result = result.Union(OldTransformedBoundsForLeaf());
+      aOutRegion = result;
+    }
     return true;
   }
 
   RefPtr<Layer> mLayer;
   UniquePtr<LayerPropertiesBase> mMaskLayer;
   nsTArray<UniquePtr<LayerPropertiesBase>> mAncestorMaskLayers;
   nsIntRegion mVisibleRegion;
   Matrix4x4 mTransform;
--- a/gfx/layers/ipc/PWebRenderBridge.ipdl
+++ b/gfx/layers/ipc/PWebRenderBridge.ipdl
@@ -17,16 +17,19 @@ include protocol PTexture;
 using mozilla::layers::APZTestData from "mozilla/layers/APZTestData.h";
 using struct mozilla::layers::ScrollableLayerGuid from "FrameMetrics.h";
 using struct mozilla::layers::TextureInfo from "mozilla/layers/CompositorTypes.h";
 using mozilla::layers::CompositableHandle from "mozilla/layers/LayersTypes.h";
 using mozilla::wr::ByteBuffer from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ExternalImageId from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ImageKey from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::FontKey from "mozilla/webrender/WebRenderTypes.h";
+using mozilla::wr::FontInstanceKey from "mozilla/webrender/WebRenderTypes.h";
+using mozilla::wr::MaybeFontInstanceOptions from "mozilla/webrender/WebRenderTypes.h";
+using mozilla::wr::MaybeFontInstancePlatformOptions from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::PipelineId from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::BuiltDisplayListDescriptor from "mozilla/webrender/webrender_ffi.h";
 using mozilla::wr::IdNamespace from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::layers::WebRenderScrollData from "mozilla/layers/WebRenderScrollData.h";
 
 namespace mozilla {
 namespace layers {
 
@@ -49,16 +52,19 @@ parent:
   async AddBlobImage(ImageKey aImageKey, IntSize aSize, uint32_t aStride,
                      SurfaceFormat aFormat, ByteBuffer aBytes);
   async UpdateImage(ImageKey aImageKey, IntSize aSize,
                    SurfaceFormat aFormat, ByteBuffer aBytes);
   async DeleteImage(ImageKey aImageKey);
   async DeleteCompositorAnimations(uint64_t[] aIds);
   async AddRawFont(FontKey aFontKey, ByteBuffer aBytes, uint32_t aFontIndex);
   async DeleteFont(FontKey aFontKey);
+  async AddFontInstance(FontInstanceKey aInstanceKey, FontKey aFontKey, float aGlyphSize,
+                        MaybeFontInstanceOptions aOptions, MaybeFontInstancePlatformOptions aPlatformOptions);
+  async DeleteFontInstance(FontInstanceKey aInstanceKey);
   async DPBegin(IntSize aSize);
   async DPEnd(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, uint64_t transactionId,
               LayoutSize aContentSize, ByteBuffer aDL, BuiltDisplayListDescriptor aDLDesc,
               WebRenderScrollData aScrollData, IdNamespace aIdNamespace, TimeStamp txnStartTime, TimeStamp fwdTime);
   sync DPSyncEnd(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, uint64_t transactionId,
                  LayoutSize aContentSize, ByteBuffer aDL, BuiltDisplayListDescriptor aDLDesc,
                  WebRenderScrollData aScrollData, IdNamespace aIdNamespace, TimeStamp txnStartTime, TimeStamp fwdTime);
   async ParentCommands(WebRenderParentCommand[] commands);
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -24,16 +24,17 @@ WebRenderBridgeChild::WebRenderBridgeChi
   , mIsInTransaction(false)
   , mIsInClearCachedResources(false)
   , mIdNamespace{0}
   , mResourceId(0)
   , mPipelineId(aPipelineId)
   , mIPCOpen(false)
   , mDestroyed(false)
   , mFontKeysDeleted(0)
+  , mFontInstanceKeysDeleted(0)
 {
 }
 
 void
 WebRenderBridgeChild::Destroy(bool aIsSync)
 {
   if (!IPCOpen()) {
     return;
@@ -214,73 +215,92 @@ WriteFontFileData(const uint8_t* aData, 
 void
 WebRenderBridgeChild::PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArray<gfx::Glyph>& aGlyphs,
                                  gfx::ScaledFont* aFont, const gfx::Color& aColor, const StackingContextHelper& aSc,
                                  const LayerRect& aBounds, const LayerRect& aClip)
 {
   MOZ_ASSERT(aFont);
   MOZ_ASSERT(!aGlyphs.IsEmpty());
 
-  wr::WrFontKey key = GetFontKeyForScaledFont(aFont);
+  wr::WrFontInstanceKey key = GetFontKeyForScaledFont(aFont);
   MOZ_ASSERT(key.mNamespace.mHandle && key.mHandle);
 
   nsTArray<wr::GlyphInstance> wr_glyph_instances;
   wr_glyph_instances.SetLength(aGlyphs.Length());
 
   for (size_t j = 0; j < aGlyphs.Length(); j++) {
     wr_glyph_instances[j].index = aGlyphs[j].mIndex;
     wr_glyph_instances[j].point = aSc.ToRelativeLayoutPoint(
             LayerPoint::FromUnknownPoint(aGlyphs[j].mPosition));
   }
 
   aBuilder.PushText(aSc.ToRelativeLayoutRect(aBounds),
                     aSc.ToRelativeLayoutRect(aClip),
                     aColor,
                     key,
-                    Range<const wr::GlyphInstance>(wr_glyph_instances.Elements(), wr_glyph_instances.Length()),
-                    aFont->GetSize());
+                    Range<const wr::GlyphInstance>(wr_glyph_instances.Elements(), wr_glyph_instances.Length()));
 }
 
-wr::FontKey
+wr::FontInstanceKey
 WebRenderBridgeChild::GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont)
 {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(aScaledFont);
   MOZ_ASSERT((aScaledFont->GetType() == gfx::FontType::DWRITE) ||
              (aScaledFont->GetType() == gfx::FontType::MAC) ||
              (aScaledFont->GetType() == gfx::FontType::FONTCONFIG));
 
+  wr::FontInstanceKey instanceKey = { wr::IdNamespace { 0 }, 0 };
+  if (mFontInstanceKeys.Get(aScaledFont, &instanceKey)) {
+    return instanceKey;
+  }
+
   RefPtr<gfx::UnscaledFont> unscaled = aScaledFont->GetUnscaledFont();
   MOZ_ASSERT(unscaled);
 
-  wr::FontKey key = { wr::IdNamespace { 0 }, 0};
-  if (mFontKeys.Get(unscaled, &key)) {
-    return key;
+  wr::FontKey fontKey = { wr::IdNamespace { 0 }, 0};
+  if (!mFontKeys.Get(unscaled, &fontKey)) {
+    FontFileData data;
+    if (!unscaled->GetFontFileData(WriteFontFileData, &data) ||
+        !data.mFontBuffer.mData) {
+      return instanceKey;
+    }
+
+    fontKey.mNamespace = GetNamespace();
+    fontKey.mHandle = GetNextResourceId();
+
+    SendAddRawFont(fontKey, data.mFontBuffer, data.mFontIndex);
+
+    mFontKeys.Put(unscaled, fontKey);
   }
 
-  FontFileData data;
-  if (!unscaled->GetFontFileData(WriteFontFileData, &data) ||
-      !data.mFontBuffer.mData) {
-    return key;
-  }
+  instanceKey.mNamespace = GetNamespace();
+  instanceKey.mHandle = GetNextResourceId();
 
-  key.mNamespace = GetNamespace();
-  key.mHandle = GetNextResourceId();
+  SendAddFontInstance(instanceKey, fontKey, aScaledFont->GetSize(), Nothing(), Nothing());
 
-  SendAddRawFont(key, data.mFontBuffer, data.mFontIndex);
+  mFontInstanceKeys.Put(aScaledFont, instanceKey);
 
-  mFontKeys.Put(unscaled, key);
-
-  return key;
+  return instanceKey;
 }
 
 void
 WebRenderBridgeChild::RemoveExpiredFontKeys()
 {
-  uint32_t counter = gfx::UnscaledFont::DeletionCounter();
+  uint32_t counter = gfx::ScaledFont::DeletionCounter();
+  if (mFontInstanceKeysDeleted != counter) {
+    mFontInstanceKeysDeleted = counter;
+    for (auto iter = mFontInstanceKeys.Iter(); !iter.Done(); iter.Next()) {
+      if (!iter.Key()) {
+        SendDeleteFontInstance(iter.Data());
+        iter.Remove();
+      }
+    }
+  }
+  counter = gfx::UnscaledFont::DeletionCounter();
   if (mFontKeysDeleted != counter) {
     mFontKeysDeleted = counter;
     for (auto iter = mFontKeys.Iter(); !iter.Done(); iter.Next()) {
       if (!iter.Key()) {
         SendDeleteFont(iter.Data());
         iter.Remove();
       }
     }
@@ -454,17 +474,18 @@ WebRenderBridgeChild::InForwarderThread(
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeChild::RecvWrUpdated(const wr::IdNamespace& aNewIdNamespace)
 {
   // Update mIdNamespace to identify obsolete keys and messages by WebRenderBridgeParent.
   // Since usage of invalid keys could cause crash in webrender.
   mIdNamespace = aNewIdNamespace;
-  // Just clear FontKeys, they are removed during WebRenderAPI destruction.
+  // Just clear FontInstaceKeys/FontKeys, they are removed during WebRenderAPI destruction.
+  mFontInstanceKeys.Clear();
   mFontKeys.Clear();
   GetCompositorBridgeChild()->RecvInvalidateLayers(wr::AsUint64(mPipelineId));
   return IPC_OK();
 }
 
 void
 WebRenderBridgeChild::BeginClearCachedResources()
 {
--- a/gfx/layers/wr/WebRenderBridgeChild.h
+++ b/gfx/layers/wr/WebRenderBridgeChild.h
@@ -22,38 +22,42 @@ class DisplayListBuilder;
 
 namespace layers {
 
 class CompositableClient;
 class CompositorBridgeChild;
 class StackingContextHelper;
 class TextureForwarder;
 
-class UnscaledFontHashKey : public PLDHashEntryHdr
+template<class T>
+class WeakPtrHashKey : public PLDHashEntryHdr
 {
 public:
-  typedef gfx::UnscaledFont* KeyType;
-  typedef const gfx::UnscaledFont* KeyTypePointer;
+  typedef T* KeyType;
+  typedef const T* KeyTypePointer;
 
-  explicit UnscaledFontHashKey(KeyTypePointer aKey) : mKey(const_cast<KeyType>(aKey)) {}
+  explicit WeakPtrHashKey(KeyTypePointer aKey) : mKey(const_cast<KeyType>(aKey)) {}
 
   KeyType GetKey() const { return mKey; }
   bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; }
 
   static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
   static PLDHashNumber HashKey(KeyTypePointer aKey)
   {
     return NS_PTR_TO_UINT32(aKey) >> 2;
   }
   enum { ALLOW_MEMMOVE = true };
 
 private:
-  WeakPtr<gfx::UnscaledFont> mKey;
+  WeakPtr<T> mKey;
 };
 
+typedef WeakPtrHashKey<gfx::UnscaledFont> UnscaledFontHashKey;
+typedef WeakPtrHashKey<gfx::ScaledFont> ScaledFontHashKey;
+
 class WebRenderBridgeChild final : public PWebRenderBridgeChild
                                  , public CompositableForwarder
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebRenderBridgeChild, override)
 
 public:
   explicit WebRenderBridgeChild(const wr::PipelineId& aPipelineId);
 
@@ -104,17 +108,17 @@ public:
     return wr::WrImageKey{ GetNamespace(), GetNextResourceId() };
   }
 
   void PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArray<gfx::Glyph>& aGlyphs,
                   gfx::ScaledFont* aFont, const gfx::Color& aColor,
                   const StackingContextHelper& aSc,
                   const LayerRect& aBounds, const LayerRect& aClip);
 
-  wr::FontKey GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont);
+  wr::FontInstanceKey GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont);
 
   void RemoveExpiredFontKeys();
   void ClearReadLocks();
 
   void BeginClearCachedResources();
   void EndClearCachedResources();
 
 private:
@@ -174,14 +178,17 @@ private:
   uint32_t mResourceId;
   wr::PipelineId mPipelineId;
 
   bool mIPCOpen;
   bool mDestroyed;
 
   uint32_t mFontKeysDeleted;
   nsDataHashtable<UnscaledFontHashKey, wr::FontKey> mFontKeys;
+
+  uint32_t mFontInstanceKeysDeleted;
+  nsDataHashtable<ScaledFontHashKey, wr::FontInstanceKey> mFontInstanceKeys;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_WebRenderBridgeChild_h
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -307,27 +307,74 @@ WebRenderBridgeParent::RecvDeleteFont(co
   }
   MOZ_ASSERT(mApi);
 
   // Check if key is obsoleted.
   if (aFontKey.mNamespace != mIdNamespace) {
     return IPC_OK();
   }
 
-  if (mFontKeys.find(wr::AsUint64(aFontKey)) != mFontKeys.end()) {
-    mFontKeys.erase(wr::AsUint64(aFontKey));
+  if (mFontKeys.erase(wr::AsUint64(aFontKey)) > 0) {
     mApi->DeleteFont(aFontKey);
   } else {
     MOZ_ASSERT_UNREACHABLE("invalid FontKey");
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+WebRenderBridgeParent::RecvAddFontInstance(const wr::FontInstanceKey& aInstanceKey,
+                                           const wr::FontKey& aFontKey,
+                                           const float& aGlyphSize,
+                                           const MaybeFontInstanceOptions& aOptions,
+                                           const MaybeFontInstancePlatformOptions& aPlatformOptions)
+{
+  if (mDestroyed) {
+    return IPC_OK();
+  }
+
+  // Check if key is obsoleted.
+  if (aInstanceKey.mNamespace != mIdNamespace) {
+    return IPC_OK();
+  }
+
+  MOZ_ASSERT(mApi);
+  MOZ_ASSERT(mFontInstanceKeys.find(wr::AsUint64(aInstanceKey)) == mFontInstanceKeys.end());
+
+  mFontInstanceKeys.insert(wr::AsUint64(aInstanceKey));
+  mApi->AddFontInstance(aInstanceKey, aFontKey, aGlyphSize,
+                        aOptions.ptrOr(nullptr), aPlatformOptions.ptrOr(nullptr));
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+WebRenderBridgeParent::RecvDeleteFontInstance(const wr::FontInstanceKey& aInstanceKey)
+{
+  if (mDestroyed) {
+    return IPC_OK();
+  }
+  MOZ_ASSERT(mApi);
+
+  // Check if key is obsoleted.
+  if (aInstanceKey.mNamespace != mIdNamespace) {
+    return IPC_OK();
+  }
+
+  if (mFontInstanceKeys.erase(wr::AsUint64(aInstanceKey)) > 0) {
+    mApi->DeleteFontInstance(aInstanceKey);
+  } else {
+    MOZ_ASSERT_UNREACHABLE("invalid FontInstanceKey");
+  }
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvUpdateImage(const wr::ImageKey& aImageKey,
                                        const gfx::IntSize& aSize,
                                        const gfx::SurfaceFormat& aFormat,
                                        const ByteBuffer& aBuffer)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
@@ -352,36 +399,34 @@ WebRenderBridgeParent::RecvDeleteImage(c
   }
   MOZ_ASSERT(mApi);
 
   // Check if key is obsoleted.
   if (aImageKey.mNamespace != mIdNamespace) {
     return IPC_OK();
   }
 
-  if (mActiveImageKeys.find(wr::AsUint64(aImageKey)) != mActiveImageKeys.end()) {
-    mActiveImageKeys.erase(wr::AsUint64(aImageKey));
+  if (mActiveImageKeys.erase(wr::AsUint64(aImageKey)) > 0) {
     mKeysToDelete.push_back(aImageKey);
   } else {
     MOZ_ASSERT_UNREACHABLE("invalid ImageKey");
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvDeleteCompositorAnimations(InfallibleTArray<uint64_t>&& aIds)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
 
   for (uint32_t i = 0; i < aIds.Length(); i++) {
-    if (mActiveAnimations.find(aIds[i]) != mActiveAnimations.end()) {
+    if (mActiveAnimations.erase(aIds[i]) > 0) {
       mAnimStorage->ClearById(aIds[i]);
-      mActiveAnimations.erase(aIds[i]);
     } else {
       NS_ERROR("Tried to delete invalid animation");
     }
   }
 
   return IPC_OK();
 }
 
@@ -1302,16 +1347,17 @@ WebRenderBridgeParent::ClearResources()
     return;
   }
 
   uint32_t wrEpoch = GetNextWrEpoch();
   mApi->ClearRootDisplayList(wr::NewEpoch(wrEpoch), mPipelineId);
   // Schedule composition to clean up Pipeline
   mCompositorScheduler->ScheduleComposition();
   // WrFontKeys and WrImageKeys are deleted during WebRenderAPI destruction.
+  mFontInstanceKeys.clear();
   mFontKeys.clear();
   mActiveImageKeys.clear();
   mKeysToDelete.clear();
   for (auto iter = mExternalImageIds.Iter(); !iter.Done(); iter.Next()) {
     iter.Data()->ClearWrBridge();
   }
   mExternalImageIds.Clear();
   for (auto iter = mAsyncCompositables.Iter(); !iter.Done(); iter.Next()) {
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -87,16 +87,22 @@ public:
                                           const gfx::SurfaceFormat& aFormat,
                                           const ByteBuffer& aBuffer) override;
   mozilla::ipc::IPCResult RecvDeleteImage(const wr::ImageKey& a1) override;
   mozilla::ipc::IPCResult RecvDeleteCompositorAnimations(InfallibleTArray<uint64_t>&& aIds) override;
   mozilla::ipc::IPCResult RecvAddRawFont(const wr::FontKey& aFontKey,
                                          const ByteBuffer& aBuffer,
                                          const uint32_t& aFontIndex) override;
   mozilla::ipc::IPCResult RecvDeleteFont(const wr::FontKey& aFontKey) override;
+  mozilla::ipc::IPCResult RecvAddFontInstance(const wr::FontInstanceKey& aInstanceKey,
+                                              const wr::FontKey& aFontKey,
+                                              const float& aGlyphSize,
+                                              const MaybeFontInstanceOptions& aOptions,
+                                              const MaybeFontInstancePlatformOptions& aPlatformOptions) override;
+  mozilla::ipc::IPCResult RecvDeleteFontInstance(const wr::FontInstanceKey& aInstanceKey) override;
   mozilla::ipc::IPCResult RecvDPBegin(const gfx::IntSize& aSize) override;
   mozilla::ipc::IPCResult RecvDPEnd(const gfx::IntSize& aSize,
                                     InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                     InfallibleTArray<OpDestroy>&& aToDestroy,
                                     const uint64_t& aFwdTransactionId,
                                     const uint64_t& aTransactionId,
                                     const wr::LayoutSize& aContentSize,
                                     const wr::ByteBuffer& dl,
@@ -267,16 +273,17 @@ private:
   RefPtr<AsyncImagePipelineManager> mAsyncImageManager;
   RefPtr<CompositorVsyncScheduler> mCompositorScheduler;
   RefPtr<CompositorAnimationStorage> mAnimStorage;
   std::vector<wr::ImageKey> mKeysToDelete;
   // mActiveImageKeys and mFontKeys are used to avoid leaking animations when
   // WebRenderBridgeParent is destroyed abnormally and Tab move between different windows.
   std::unordered_set<uint64_t> mActiveImageKeys;
   std::unordered_set<uint64_t> mFontKeys;
+  std::unordered_set<uint64_t> mFontInstanceKeys;
   // mActiveAnimations is used to avoid leaking animations when WebRenderBridgeParent is
   // destroyed abnormally and Tab move between different windows.
   std::unordered_set<uint64_t> mActiveAnimations;
   nsDataHashtable<nsUint64HashKey, RefPtr<WebRenderImageHost>> mAsyncCompositables;
   nsDataHashtable<nsUint64HashKey, RefPtr<WebRenderImageHost>> mExternalImageIds;
 
   TimeStamp mPreviousFrameTimeStamp;
   // These fields keep track of the latest layer observer epoch values in the child and the
--- a/gfx/layers/wr/WebRenderMessageUtils.h
+++ b/gfx/layers/wr/WebRenderMessageUtils.h
@@ -49,16 +49,34 @@ struct ParamTraits<mozilla::wr::ImageKey
 
 template<>
 struct ParamTraits<mozilla::wr::FontKey>
   : public PlainOldDataSerializer<mozilla::wr::FontKey>
 {
 };
 
 template<>
+struct ParamTraits<mozilla::wr::FontInstanceKey>
+  : public PlainOldDataSerializer<mozilla::wr::FontInstanceKey>
+{
+};
+
+template<>
+struct ParamTraits<mozilla::wr::FontInstanceOptions>
+  : public PlainOldDataSerializer<mozilla::wr::FontInstanceOptions>
+{
+};
+
+template<>
+struct ParamTraits<mozilla::wr::FontInstancePlatformOptions>
+  : public PlainOldDataSerializer<mozilla::wr::FontInstancePlatformOptions>
+{
+};
+
+template<>
 struct ParamTraits<mozilla::wr::ExternalImageId>
   : public PlainOldDataSerializer<mozilla::wr::ExternalImageId>
 {
 };
 
 template<>
 struct ParamTraits<mozilla::wr::PipelineId>
   : public PlainOldDataSerializer<mozilla::wr::PipelineId>
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender"
-version = "0.49.0"
+version = "0.50.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
 default = ["freetype-lib"]
 freetype-lib = ["freetype/servo-freetype-sys"]
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -236,16 +236,19 @@ impl Example for App {
         builder.push_border(bounds, None, border_widths, border_details);
 
 
         if false { // draw text?
             let font_key = api.generate_font_key();
             let font_bytes = load_file("res/FreeSans.ttf");
             resources.add_raw_font(font_key, font_bytes, 0);
 
+            let font_instance_key = api.generate_font_instance_key();
+            resources.add_font_instance(font_instance_key, font_key, Au::from_px(32), None, None);
+
             let text_bounds = (100, 200).by(700, 300);
             let glyphs = vec![
                 GlyphInstance {
                     index: 48,
                     point: LayoutPoint::new(100.0, 100.0),
                 },
                 GlyphInstance {
                     index: 68,
@@ -291,19 +294,18 @@ impl Example for App {
                     index: 17,
                     point: LayoutPoint::new(650.0, 100.0),
                 },
             ];
 
             builder.push_text(text_bounds,
                               None,
                               &glyphs,
-                              font_key,
+                              font_instance_key,
                               ColorF::new(1.0, 1.0, 0.0, 1.0),
-                              Au::from_px(32),
                               None);
         }
 
         if false { // draw box shadow?
             let rect = LayoutRect::zero();
             let simple_box_bounds = (20, 200).by(50, 50);
             let offset = vec2(10.0, 10.0);
             let color = ColorF::new(1.0, 1.0, 1.0, 1.0);
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -205,16 +205,17 @@ impl api::BlobImageRenderer for Checkerb
             }
             self.rendered_images.insert(req, Some(result));
         }
 
         // If we break out of the loop above it means the channel closed unexpectedly.
         Err(api::BlobImageError::Other("Channel closed".into()))
     }
     fn delete_font(&mut self, _font: api::FontKey) { }
+    fn delete_font_instance(&mut self, _instance: api::FontInstanceKey) { }
 }
 
 struct App {
 
 }
 
 impl Example for App {
     fn render(&mut self,
--- a/gfx/webrender/res/cs_box_shadow.glsl
+++ b/gfx/webrender/res/cs_box_shadow.glsl
@@ -178,11 +178,11 @@ float color(vec2 pos, vec2 p0Rect, vec2 
 void main(void) {
     vec2 pos = vPos.xy;
     vec2 p0Rect = vBoxShadowRect.xy, p1Rect = vBoxShadowRect.zw;
     vec2 radii = vBorderRadii.xy;
     float sigma = vBlurRadius / 2.0;
     float value = color(pos, p0Rect, p1Rect, radii, sigma);
 
     value = max(value, 0.0);
-    oFragColor = dither(vec4(1.0, 1.0, 1.0, vInverted == 1.0 ? 1.0 - value : value));
+    oFragColor = dither(vec4(vInverted == 1.0 ? 1.0 - value : value));
 }
 #endif
deleted file mode 100644
--- a/gfx/webrender/res/cs_clip_rectangle.fs.glsl
+++ /dev/null
@@ -1,65 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-float clip_against_ellipse_if_needed(vec2 pos,
-                                     float current_distance,
-                                     vec4 ellipse_center_radius,
-                                     vec2 sign_modifier,
-                                     float afwidth) {
-    float ellipse_distance = distance_to_ellipse(pos - ellipse_center_radius.xy,
-                                                 ellipse_center_radius.zw);
-
-    return mix(current_distance,
-               ellipse_distance + afwidth,
-               all(lessThan(sign_modifier * pos, sign_modifier * ellipse_center_radius.xy)));
-}
-
-float rounded_rect(vec2 pos) {
-    float current_distance = 0.0;
-
-    // Apply AA
-    float afwidth = 0.5 * length(fwidth(pos));
-
-    // Clip against each ellipse.
-    current_distance = clip_against_ellipse_if_needed(pos,
-                                                      current_distance,
-                                                      vClipCenter_Radius_TL,
-                                                      vec2(1.0),
-                                                      afwidth);
-
-    current_distance = clip_against_ellipse_if_needed(pos,
-                                                      current_distance,
-                                                      vClipCenter_Radius_TR,
-                                                      vec2(-1.0, 1.0),
-                                                      afwidth);
-
-    current_distance = clip_against_ellipse_if_needed(pos,
-                                                      current_distance,
-                                                      vClipCenter_Radius_BR,
-                                                      vec2(-1.0),
-                                                      afwidth);
-
-    current_distance = clip_against_ellipse_if_needed(pos,
-                                                      current_distance,
-                                                      vClipCenter_Radius_BL,
-                                                      vec2(1.0, -1.0),
-                                                      afwidth);
-
-    return smoothstep(0.0, afwidth, 1.0 - current_distance);
-}
-
-
-void main(void) {
-    float alpha = 1.f;
-    vec2 local_pos = init_transform_fs(vPos, alpha);
-
-    float clip_alpha = rounded_rect(local_pos);
-
-    float combined_alpha = min(alpha, clip_alpha);
-
-    // Select alpha or inverse alpha depending on clip in/out.
-    float final_alpha = mix(combined_alpha, 1.0 - combined_alpha, vClipMode);
-
-    oFragColor = vec4(final_alpha, 0.0, 0.0, 1.0);
-}
--- a/gfx/webrender/res/cs_clip_rectangle.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.glsl
@@ -1,12 +1,151 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include shared,prim_shared,clip_shared
+#include shared,prim_shared,clip_shared,ellipse
 
 varying vec3 vPos;
 flat varying float vClipMode;
 flat varying vec4 vClipCenter_Radius_TL;
 flat varying vec4 vClipCenter_Radius_TR;
 flat varying vec4 vClipCenter_Radius_BL;
 flat varying vec4 vClipCenter_Radius_BR;
+
+#ifdef WR_VERTEX_SHADER
+struct ClipRect {
+    RectWithSize rect;
+    vec4 mode;
+};
+
+ClipRect fetch_clip_rect(ivec2 address) {
+    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
+    return ClipRect(RectWithSize(data[0].xy, data[0].zw), data[1]);
+}
+
+struct ClipCorner {
+    RectWithSize rect;
+    vec4 outer_inner_radius;
+};
+
+ClipCorner fetch_clip_corner(ivec2 address, int index) {
+    address += ivec2(2 + 2 * index, 0);
+    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
+    return ClipCorner(RectWithSize(data[0].xy, data[0].zw), data[1]);
+}
+
+struct ClipData {
+    ClipRect rect;
+    ClipCorner top_left;
+    ClipCorner top_right;
+    ClipCorner bottom_left;
+    ClipCorner bottom_right;
+};
+
+ClipData fetch_clip(ivec2 address) {
+    ClipData clip;
+
+    clip.rect = fetch_clip_rect(address);
+    clip.top_left = fetch_clip_corner(address, 0);
+    clip.top_right = fetch_clip_corner(address, 1);
+    clip.bottom_left = fetch_clip_corner(address, 2);
+    clip.bottom_right = fetch_clip_corner(address, 3);
+
+    return clip;
+}
+
+void main(void) {
+    CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
+    ClipArea area = fetch_clip_area(cci.render_task_index);
+    Layer layer = fetch_layer(cci.layer_index);
+    ClipData clip = fetch_clip(cci.clip_data_address);
+    RectWithSize local_rect = clip.rect.rect;
+
+    ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
+                                               layer,
+                                               area,
+                                               cci.segment);
+    vPos = vi.local_pos;
+
+    vClipMode = clip.rect.mode.x;
+
+    RectWithEndpoint clip_rect = to_rect_with_endpoint(local_rect);
+
+    vClipCenter_Radius_TL = vec4(clip_rect.p0 + clip.top_left.outer_inner_radius.xy,
+                                 clip.top_left.outer_inner_radius.xy);
+
+    vClipCenter_Radius_TR = vec4(clip_rect.p1.x - clip.top_right.outer_inner_radius.x,
+                                 clip_rect.p0.y + clip.top_right.outer_inner_radius.y,
+                                 clip.top_right.outer_inner_radius.xy);
+
+    vClipCenter_Radius_BR = vec4(clip_rect.p1 - clip.bottom_right.outer_inner_radius.xy,
+                                 clip.bottom_right.outer_inner_radius.xy);
+
+    vClipCenter_Radius_BL = vec4(clip_rect.p0.x + clip.bottom_left.outer_inner_radius.x,
+                                 clip_rect.p1.y - clip.bottom_left.outer_inner_radius.y,
+                                 clip.bottom_left.outer_inner_radius.xy);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+float clip_against_ellipse_if_needed(vec2 pos,
+                                     float current_distance,
+                                     vec4 ellipse_center_radius,
+                                     vec2 sign_modifier,
+                                     float afwidth) {
+    float ellipse_distance = distance_to_ellipse(pos - ellipse_center_radius.xy,
+                                                 ellipse_center_radius.zw);
+
+    return mix(current_distance,
+               ellipse_distance + afwidth,
+               all(lessThan(sign_modifier * pos, sign_modifier * ellipse_center_radius.xy)));
+}
+
+float rounded_rect(vec2 pos) {
+    float current_distance = 0.0;
+
+    // Apply AA
+    float afwidth = 0.5 * length(fwidth(pos));
+
+    // Clip against each ellipse.
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_TL,
+                                                      vec2(1.0),
+                                                      afwidth);
+
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_TR,
+                                                      vec2(-1.0, 1.0),
+                                                      afwidth);
+
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_BR,
+                                                      vec2(-1.0),
+                                                      afwidth);
+
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_BL,
+                                                      vec2(1.0, -1.0),
+                                                      afwidth);
+
+    return smoothstep(0.0, afwidth, 1.0 - current_distance);
+}
+
+
+void main(void) {
+    float alpha = 1.f;
+    vec2 local_pos = init_transform_fs(vPos, alpha);
+
+    float clip_alpha = rounded_rect(local_pos);
+
+    float combined_alpha = min(alpha, clip_alpha);
+
+    // Select alpha or inverse alpha depending on clip in/out.
+    float final_alpha = mix(combined_alpha, 1.0 - combined_alpha, vClipMode);
+
+    oFragColor = vec4(final_alpha, 0.0, 0.0, 1.0);
+}
+#endif
deleted file mode 100644
--- a/gfx/webrender/res/cs_clip_rectangle.vs.glsl
+++ /dev/null
@@ -1,76 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-struct ClipRect {
-    RectWithSize rect;
-    vec4 mode;
-};
-
-ClipRect fetch_clip_rect(ivec2 address) {
-    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
-    return ClipRect(RectWithSize(data[0].xy, data[0].zw), data[1]);
-}
-
-struct ClipCorner {
-    RectWithSize rect;
-    vec4 outer_inner_radius;
-};
-
-ClipCorner fetch_clip_corner(ivec2 address, int index) {
-    address += ivec2(2 + 2 * index, 0);
-    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
-    return ClipCorner(RectWithSize(data[0].xy, data[0].zw), data[1]);
-}
-
-struct ClipData {
-    ClipRect rect;
-    ClipCorner top_left;
-    ClipCorner top_right;
-    ClipCorner bottom_left;
-    ClipCorner bottom_right;
-};
-
-ClipData fetch_clip(ivec2 address) {
-    ClipData clip;
-
-    clip.rect = fetch_clip_rect(address);
-    clip.top_left = fetch_clip_corner(address, 0);
-    clip.top_right = fetch_clip_corner(address, 1);
-    clip.bottom_left = fetch_clip_corner(address, 2);
-    clip.bottom_right = fetch_clip_corner(address, 3);
-
-    return clip;
-}
-
-void main(void) {
-    CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
-    ClipArea area = fetch_clip_area(cci.render_task_index);
-    Layer layer = fetch_layer(cci.layer_index);
-    ClipData clip = fetch_clip(cci.clip_data_address);
-    RectWithSize local_rect = clip.rect.rect;
-
-    ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
-                                               layer,
-                                               area,
-                                               cci.segment);
-    vPos = vi.local_pos;
-
-    vClipMode = clip.rect.mode.x;
-
-    RectWithEndpoint clip_rect = to_rect_with_endpoint(local_rect);
-
-    vClipCenter_Radius_TL = vec4(clip_rect.p0 + clip.top_left.outer_inner_radius.xy,
-                                 clip.top_left.outer_inner_radius.xy);
-
-    vClipCenter_Radius_TR = vec4(clip_rect.p1.x - clip.top_right.outer_inner_radius.x,
-                                 clip_rect.p0.y + clip.top_right.outer_inner_radius.y,
-                                 clip.top_right.outer_inner_radius.xy);
-
-    vClipCenter_Radius_BR = vec4(clip_rect.p1 - clip.bottom_right.outer_inner_radius.xy,
-                                 clip.bottom_right.outer_inner_radius.xy);
-
-    vClipCenter_Radius_BL = vec4(clip_rect.p0.x + clip.bottom_left.outer_inner_radius.x,
-                                 clip_rect.p1.y - clip.bottom_left.outer_inner_radius.y,
-                                 clip.bottom_left.outer_inner_radius.xy);
-}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ellipse.glsl
@@ -0,0 +1,69 @@
+/* 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 WR_FRAGMENT_SHADER
+
+//
+// Signed distance to an ellipse.
+// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
+// Note that this fails for exact circles.
+//
+float sdEllipse( vec2 p, in vec2 ab ) {
+    p = abs( p ); if( p.x > p.y ){ p=p.yx; ab=ab.yx; }
+    float l = ab.y*ab.y - ab.x*ab.x;
+
+    float m = ab.x*p.x/l;
+    float n = ab.y*p.y/l;
+    float m2 = m*m;
+    float n2 = n*n;
+
+    float c = (m2 + n2 - 1.0)/3.0;
+    float c3 = c*c*c;
+
+    float q = c3 + m2*n2*2.0;
+    float d = c3 + m2*n2;
+    float g = m + m*n2;
+
+    float co;
+
+    if( d<0.0 )
+    {
+        float p = acos(q/c3)/3.0;
+        float s = cos(p);
+        float t = sin(p)*sqrt(3.0);
+        float rx = sqrt( -c*(s + t + 2.0) + m2 );
+        float ry = sqrt( -c*(s - t + 2.0) + m2 );
+        co = ( ry + sign(l)*rx + abs(g)/(rx*ry) - m)/2.0;
+    }
+    else
+    {
+        float h = 2.0*m*n*sqrt( d );
+        float s = sign(q+h)*pow( abs(q+h), 1.0/3.0 );
+        float u = sign(q-h)*pow( abs(q-h), 1.0/3.0 );
+        float rx = -s - u - c*4.0 + 2.0*m2;
+        float ry = (s - u)*sqrt(3.0);
+        float rm = sqrt( rx*rx + ry*ry );
+        float p = ry/sqrt(rm-rx);
+        co = (p + 2.0*g/rm - m)/2.0;
+    }
+
+    float si = sqrt( 1.0 - co*co );
+
+    vec2 r = vec2( ab.x*co, ab.y*si );
+
+    return length(r - p ) * sign(p.y-r.y);
+}
+
+float distance_to_ellipse(vec2 p, vec2 radii) {
+    // sdEllipse fails on exact circles, so handle equal
+    // radii here. The branch coherency should make this
+    // a performance win for the circle case too.
+    if (radii.x == radii.y) {
+        return length(p) - radii.x;
+    } else {
+        return sdEllipse(p, radii);
+    }
+}
+
+#endif
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -850,71 +850,9 @@ vec4 sample_gradient(int address, float 
 
     // Fetch the start and end color.
     vec4 texels[2] = fetch_from_resource_cache_2(address + lut_offset);
 
     // Finally interpolate and apply dithering
     return dither(mix(texels[0], texels[1], fract(x)));
 }
 
-//
-// Signed distance to an ellipse.
-// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
-// Note that this fails for exact circles.
-//
-float sdEllipse( vec2 p, in vec2 ab ) {
-    p = abs( p ); if( p.x > p.y ){ p=p.yx; ab=ab.yx; }
-    float l = ab.y*ab.y - ab.x*ab.x;
-
-    float m = ab.x*p.x/l;
-    float n = ab.y*p.y/l;
-    float m2 = m*m;
-    float n2 = n*n;
-
-    float c = (m2 + n2 - 1.0)/3.0;
-    float c3 = c*c*c;
-
-    float q = c3 + m2*n2*2.0;
-    float d = c3 + m2*n2;
-    float g = m + m*n2;
-
-    float co;
-
-    if( d<0.0 )
-    {
-        float p = acos(q/c3)/3.0;
-        float s = cos(p);
-        float t = sin(p)*sqrt(3.0);
-        float rx = sqrt( -c*(s + t + 2.0) + m2 );
-        float ry = sqrt( -c*(s - t + 2.0) + m2 );
-        co = ( ry + sign(l)*rx + abs(g)/(rx*ry) - m)/2.0;
-    }
-    else
-    {
-        float h = 2.0*m*n*sqrt( d );
-        float s = sign(q+h)*pow( abs(q+h), 1.0/3.0 );
-        float u = sign(q-h)*pow( abs(q-h), 1.0/3.0 );
-        float rx = -s - u - c*4.0 + 2.0*m2;
-        float ry = (s - u)*sqrt(3.0);
-        float rm = sqrt( rx*rx + ry*ry );
-        float p = ry/sqrt(rm-rx);
-        co = (p + 2.0*g/rm - m)/2.0;
-    }
-
-    float si = sqrt( 1.0 - co*co );
-
-    vec2 r = vec2( ab.x*co, ab.y*si );
-
-    return length(r - p ) * sign(p.y-r.y);
-}
-
-float distance_to_ellipse(vec2 p, vec2 radii) {
-    // sdEllipse fails on exact circles, so handle equal
-    // radii here. The branch coherency should make this
-    // a performance win for the circle case too.
-    if (radii.x == radii.y) {
-        return length(p) - radii.x;
-    } else {
-        return sdEllipse(p, radii);
-    }
-}
-
 #endif //WR_FRAGMENT_SHADER
deleted file mode 100644
--- a/gfx/webrender/res/ps_border_corner.fs.glsl
+++ /dev/null
@@ -1,85 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-void main(void) {
-    float alpha = 1.0;
-#ifdef WR_FEATURE_TRANSFORM
-    alpha = 0.0;
-    vec2 local_pos = init_transform_fs(vLocalPos, alpha);
-#else
-    vec2 local_pos = vLocalPos;
-#endif
-
-    alpha = min(alpha, do_clip());
-
-    // Find the appropriate distance to apply the AA smoothstep over.
-    vec2 fw = fwidth(local_pos);
-    float afwidth = length(fw);
-    float distance_for_color;
-    float color_mix_factor;
-
-    // Only apply the clip AA if inside the clip region. This is
-    // necessary for correctness when the border width is greater
-    // than the border radius.
-    if (all(lessThan(local_pos * vClipSign, vClipCenter * vClipSign))) {
-        vec2 p = local_pos - vClipCenter;
-
-        // Get signed distance from the inner/outer clips.
-        float d0 = distance_to_ellipse(p, vRadii0.xy);
-        float d1 = distance_to_ellipse(p, vRadii0.zw);
-        float d2 = distance_to_ellipse(p, vRadii1.xy);
-        float d3 = distance_to_ellipse(p, vRadii1.zw);
-
-        // SDF subtract main radii
-        float d_main = max(d0, 0.5 * afwidth - d1);
-
-        // SDF subtract inner radii (double style borders)
-        float d_inner = max(d2 - 0.5 * afwidth, -d3);
-
-        // Select how to combine the SDF based on border style.
-        float d = mix(max(d_main, -d_inner), d_main, vSDFSelect);
-
-        // Only apply AA to fragments outside the signed distance field.
-        alpha = min(alpha, 1.0 - smoothstep(0.0, 0.5 * afwidth, d));
-
-        // Get the groove/ridge mix factor.
-        color_mix_factor = smoothstep(-0.5 * afwidth,
-                                      0.5 * afwidth,
-                                      -d2);
-    } else {
-        // Handle the case where the fragment is outside the clip
-        // region in a corner. This occurs when border width is
-        // greater than border radius.
-
-        // Get linear distances along horizontal and vertical edges.
-        vec2 d0 = vClipSign.xx * (local_pos.xx - vEdgeDistance.xz);
-        vec2 d1 = vClipSign.yy * (local_pos.yy - vEdgeDistance.yw);
-        // Apply union to get the outer edge signed distance.
-        float da = min(d0.x, d1.x);
-        // Apply intersection to get the inner edge signed distance.
-        float db = max(-d0.y, -d1.y);
-        // Apply union to get both edges.
-        float d = min(da, db);
-        // Select fragment on/off based on signed distance.
-        // No AA here, since we know we're on a straight edge
-        // and the width is rounded to a whole CSS pixel.
-        alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
-
-        // Get the groove/ridge mix factor.
-        // TODO(gw): Support AA for groove/ridge border edge with transforms.
-        color_mix_factor = mix(0.0, 1.0, da > 0.0);
-    }
-
-    // Mix inner/outer color.
-    vec4 color0 = mix(vColor00, vColor01, color_mix_factor);
-    vec4 color1 = mix(vColor10, vColor11, color_mix_factor);
-
-    // Select color based on side of line. Get distance from the
-    // reference line, and then apply AA along the edge.
-    float ld = distance_to_line(vColorEdgeLine.xy, vColorEdgeLine.zw, local_pos);
-    float m = smoothstep(-0.5 * afwidth, 0.5 * afwidth, ld);
-    vec4 color = mix(color0, color1, m);
-
-    oFragColor = color * vec4(1.0, 1.0, 1.0, alpha);
-}
--- a/gfx/webrender/res/ps_border_corner.glsl
+++ b/gfx/webrender/res/ps_border_corner.glsl
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include shared,prim_shared,shared_border
+#include shared,prim_shared,shared_border,ellipse
 
 // Edge color transition
 flat varying vec4 vColor00;
 flat varying vec4 vColor01;
 flat varying vec4 vColor10;
 flat varying vec4 vColor11;
 flat varying vec4 vColorEdgeLine;
 
@@ -22,8 +22,376 @@ flat varying float vSDFSelect;
 // Border style
 flat varying float vAlphaSelect;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 #else
 varying vec2 vLocalPos;
 #endif
+
+#ifdef WR_VERTEX_SHADER
+// Matches BorderCornerSide enum in border.rs
+#define SIDE_BOTH       0
+#define SIDE_FIRST      1
+#define SIDE_SECOND     2
+
+vec2 get_radii(vec2 radius, vec2 invalid) {
+    if (all(greaterThan(radius, vec2(0.0)))) {
+        return radius;
+    }
+
+    return invalid;
+}
+
+void set_radii(int style,
+               vec2 radii,
+               vec2 widths,
+               vec2 adjusted_widths) {
+    vRadii0.xy = get_radii(radii, 2.0 * widths);
+    vRadii0.zw = get_radii(radii - widths, -widths);
+
+    switch (style) {
+        case BORDER_STYLE_RIDGE:
+        case BORDER_STYLE_GROOVE:
+            vRadii1.xy = radii - adjusted_widths;
+            // See comment in default branch
+            vRadii1.zw = vec2(-100.0);
+            break;
+        case BORDER_STYLE_DOUBLE:
+            vRadii1.xy = get_radii(radii - adjusted_widths, -widths);
+            vRadii1.zw = get_radii(radii - widths + adjusted_widths, -widths);
+            break;
+        default:
+            // These aren't needed, so we set them to some reasonably large
+            // negative value so later computations will discard them. This
+            // avoids branches and numerical issues in the fragment shader.
+            vRadii1.xy = vec2(-100.0);
+            vRadii1.zw = vec2(-100.0);
+            break;
+    }
+}
+
+void set_edge_line(vec2 border_width,
+                   vec2 outer_corner,
+                   vec2 gradient_sign) {
+    vec2 gradient = border_width * gradient_sign;
+    vColorEdgeLine = vec4(outer_corner, vec2(-gradient.y, gradient.x));
+}
+
+void write_color(vec4 color0, vec4 color1, int style, vec2 delta, int instance_kind) {
+    vec4 modulate;
+
+    switch (style) {
+        case BORDER_STYLE_GROOVE:
+            modulate = vec4(1.0 - 0.3 * delta.x,
+                            1.0 + 0.3 * delta.x,
+                            1.0 - 0.3 * delta.y,
+                            1.0 + 0.3 * delta.y);
+
+            break;
+        case BORDER_STYLE_RIDGE:
+            modulate = vec4(1.0 + 0.3 * delta.x,
+                            1.0 - 0.3 * delta.x,
+                            1.0 + 0.3 * delta.y,
+                            1.0 - 0.3 * delta.y);
+            break;
+        default:
+            modulate = vec4(1.0);
+            break;
+    }
+
+    // Optionally mask out one side of the border corner,
+    // depending on the instance kind.
+    switch (instance_kind) {
+        case SIDE_FIRST:
+            color0.a = 0.0;
+            break;
+        case SIDE_SECOND:
+            color1.a = 0.0;
+            break;
+    }
+
+    vColor00 = vec4(color0.rgb * modulate.x, color0.a);
+    vColor01 = vec4(color0.rgb * modulate.y, color0.a);
+    vColor10 = vec4(color1.rgb * modulate.z, color1.a);
+    vColor11 = vec4(color1.rgb * modulate.w, color1.a);
+}
+
+int select_style(int color_select, vec2 fstyle) {
+    ivec2 style = ivec2(fstyle);
+
+    switch (color_select) {
+        case SIDE_BOTH:
+        {
+            // TODO(gw): A temporary hack! While we don't support
+            //           border corners that have dots or dashes
+            //           with another style, pretend they are solid
+            //           border corners.
+            bool has_dots = style.x == BORDER_STYLE_DOTTED ||
+                            style.y == BORDER_STYLE_DOTTED;
+            bool has_dashes = style.x == BORDER_STYLE_DASHED ||
+                              style.y == BORDER_STYLE_DASHED;
+            if (style.x != style.y && (has_dots || has_dashes))
+                return BORDER_STYLE_SOLID;
+            return style.x;
+        }
+        case SIDE_FIRST:
+            return style.x;
+        case SIDE_SECOND:
+            return style.y;
+    }
+}
+
+void main(void) {
+    Primitive prim = load_primitive();
+    Border border = fetch_border(prim.specific_prim_address);
+    int sub_part = prim.user_data0;
+    BorderCorners corners = get_border_corners(border, prim.local_rect);
+
+    vec2 p0, p1;
+
+    // TODO(gw): We'll need to pass through multiple styles
+    //           once we support style transitions per corner.
+    int style;
+    vec4 edge_distances;
+    vec4 color0, color1;
+    vec2 color_delta;
+
+    // TODO(gw): Now that all border styles are supported, the switch
+    //           statement below can be tidied up quite a bit.
+
+    switch (sub_part) {
+        case 0: {
+            p0 = corners.tl_outer;
+            p1 = corners.tl_inner;
+            color0 = border.colors[0];
+            color1 = border.colors[1];
+            vClipCenter = corners.tl_outer + border.radii[0].xy;
+            vClipSign = vec2(1.0);
+            style = select_style(prim.user_data1, border.style.yx);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
+                      border.radii[0].xy,
+                      border.widths.xy,
+                      adjusted_widths.xy);
+            set_edge_line(border.widths.xy,
+                          corners.tl_outer,
+                          vec2(1.0, 1.0));
+            edge_distances = vec4(p0 + adjusted_widths.xy,
+                                  p0 + inv_adjusted_widths.xy);
+            color_delta = vec2(1.0);
+            break;
+        }
+        case 1: {
+            p0 = vec2(corners.tr_inner.x, corners.tr_outer.y);
+            p1 = vec2(corners.tr_outer.x, corners.tr_inner.y);
+            color0 = border.colors[1];
+            color1 = border.colors[2];
+            vClipCenter = corners.tr_outer + vec2(-border.radii[0].z, border.radii[0].w);
+            vClipSign = vec2(-1.0, 1.0);
+            style = select_style(prim.user_data1, border.style.zy);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
+                      border.radii[0].zw,
+                      border.widths.zy,
+                      adjusted_widths.zy);
+            set_edge_line(border.widths.zy,
+                          corners.tr_outer,
+                          vec2(-1.0, 1.0));
+            edge_distances = vec4(p1.x - adjusted_widths.z,
+                                  p0.y + adjusted_widths.y,
+                                  p1.x - border.widths.z + adjusted_widths.z,
+                                  p0.y + inv_adjusted_widths.y);
+            color_delta = vec2(1.0, -1.0);
+            break;
+        }
+        case 2: {
+            p0 = corners.br_inner;
+            p1 = corners.br_outer;
+            color0 = border.colors[2];
+            color1 = border.colors[3];
+            vClipCenter = corners.br_outer - border.radii[1].xy;
+            vClipSign = vec2(-1.0, -1.0);
+            style = select_style(prim.user_data1, border.style.wz);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
+                      border.radii[1].xy,
+                      border.widths.zw,
+                      adjusted_widths.zw);
+            set_edge_line(border.widths.zw,
+                          corners.br_outer,
+                          vec2(-1.0, -1.0));
+            edge_distances = vec4(p1.x - adjusted_widths.z,
+                                  p1.y - adjusted_widths.w,
+                                  p1.x - border.widths.z + adjusted_widths.z,
+                                  p1.y - border.widths.w + adjusted_widths.w);
+            color_delta = vec2(-1.0);
+            break;
+        }
+        case 3: {
+            p0 = vec2(corners.bl_outer.x, corners.bl_inner.y);
+            p1 = vec2(corners.bl_inner.x, corners.bl_outer.y);
+            color0 = border.colors[3];
+            color1 = border.colors[0];
+            vClipCenter = corners.bl_outer + vec2(border.radii[1].z, -border.radii[1].w);
+            vClipSign = vec2(1.0, -1.0);
+            style = select_style(prim.user_data1, border.style.xw);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
+                      border.radii[1].zw,
+                      border.widths.xw,
+                      adjusted_widths.xw);
+            set_edge_line(border.widths.xw,
+                          corners.bl_outer,
+                          vec2(1.0, -1.0));
+            edge_distances = vec4(p0.x + adjusted_widths.x,
+                                  p1.y - adjusted_widths.w,
+                                  p0.x + inv_adjusted_widths.x,
+                                  p1.y - border.widths.w + adjusted_widths.w);
+            color_delta = vec2(-1.0, 1.0);
+            break;
+        }
+    }
+
+    switch (style) {
+        case BORDER_STYLE_DOUBLE: {
+            vEdgeDistance = edge_distances;
+            vAlphaSelect = 0.0;
+            vSDFSelect = 0.0;
+            break;
+        }
+        case BORDER_STYLE_GROOVE:
+        case BORDER_STYLE_RIDGE:
+            vEdgeDistance = vec4(edge_distances.xy, 0.0, 0.0);
+            vAlphaSelect = 1.0;
+            vSDFSelect = 1.0;
+            break;
+        case BORDER_STYLE_DOTTED:
+            // Disable normal clip radii for dotted corners, since
+            // all the clipping is handled by the clip mask.
+            vClipSign = vec2(0.0);
+            vEdgeDistance = vec4(0.0);
+            vAlphaSelect = 1.0;
+            vSDFSelect = 0.0;
+            break;
+        default: {
+            vEdgeDistance = vec4(0.0);
+            vAlphaSelect = 1.0;
+            vSDFSelect = 0.0;
+            break;
+        }
+    }
+
+    write_color(color0, color1, style, color_delta, prim.user_data1);
+
+    RectWithSize segment_rect;
+    segment_rect.p0 = p0;
+    segment_rect.size = p1 - p0;
+
+#ifdef WR_FEATURE_TRANSFORM
+    TransformVertexInfo vi = write_transform_vertex(segment_rect,
+                                                    prim.local_clip_rect,
+                                                    prim.z,
+                                                    prim.layer,
+                                                    prim.task,
+                                                    prim.local_rect);
+#else
+    VertexInfo vi = write_vertex(segment_rect,
+                                 prim.local_clip_rect,
+                                 prim.z,
+                                 prim.layer,
+                                 prim.task,
+                                 prim.local_rect);
+#endif
+
+    vLocalPos = vi.local_pos;
+    write_clip(vi.screen_pos, prim.clip_area);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+    float alpha = 1.0;
+#ifdef WR_FEATURE_TRANSFORM
+    alpha = 0.0;
+    vec2 local_pos = init_transform_fs(vLocalPos, alpha);
+#else
+    vec2 local_pos = vLocalPos;
+#endif
+
+    alpha = min(alpha, do_clip());
+
+    // Find the appropriate distance to apply the AA smoothstep over.
+    vec2 fw = fwidth(local_pos);
+    float afwidth = length(fw);
+    float distance_for_color;
+    float color_mix_factor;
+
+    // Only apply the clip AA if inside the clip region. This is
+    // necessary for correctness when the border width is greater
+    // than the border radius.
+    if (all(lessThan(local_pos * vClipSign, vClipCenter * vClipSign))) {
+        vec2 p = local_pos - vClipCenter;
+
+        // Get signed distance from the inner/outer clips.
+        float d0 = distance_to_ellipse(p, vRadii0.xy);
+        float d1 = distance_to_ellipse(p, vRadii0.zw);
+        float d2 = distance_to_ellipse(p, vRadii1.xy);
+        float d3 = distance_to_ellipse(p, vRadii1.zw);
+
+        // SDF subtract main radii
+        float d_main = max(d0, 0.5 * afwidth - d1);
+
+        // SDF subtract inner radii (double style borders)
+        float d_inner = max(d2 - 0.5 * afwidth, -d3);
+
+        // Select how to combine the SDF based on border style.
+        float d = mix(max(d_main, -d_inner), d_main, vSDFSelect);
+
+        // Only apply AA to fragments outside the signed distance field.
+        alpha = min(alpha, 1.0 - smoothstep(0.0, 0.5 * afwidth, d));
+
+        // Get the groove/ridge mix factor.
+        color_mix_factor = smoothstep(-0.5 * afwidth,
+                                      0.5 * afwidth,
+                                      -d2);
+    } else {
+        // Handle the case where the fragment is outside the clip
+        // region in a corner. This occurs when border width is
+        // greater than border radius.
+
+        // Get linear distances along horizontal and vertical edges.
+        vec2 d0 = vClipSign.xx * (local_pos.xx - vEdgeDistance.xz);
+        vec2 d1 = vClipSign.yy * (local_pos.yy - vEdgeDistance.yw);
+        // Apply union to get the outer edge signed distance.
+        float da = min(d0.x, d1.x);
+        // Apply intersection to get the inner edge signed distance.
+        float db = max(-d0.y, -d1.y);
+        // Apply union to get both edges.
+        float d = min(da, db);
+        // Select fragment on/off based on signed distance.
+        // No AA here, since we know we're on a straight edge
+        // and the width is rounded to a whole CSS pixel.
+        alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
+
+        // Get the groove/ridge mix factor.
+        // TODO(gw): Support AA for groove/ridge border edge with transforms.
+        color_mix_factor = mix(0.0, 1.0, da > 0.0);
+    }
+
+    // Mix inner/outer color.
+    vec4 color0 = mix(vColor00, vColor01, color_mix_factor);
+    vec4 color1 = mix(vColor10, vColor11, color_mix_factor);
+
+    // Select color based on side of line. Get distance from the
+    // reference line, and then apply AA along the edge.
+    float ld = distance_to_line(vColorEdgeLine.xy, vColorEdgeLine.zw, local_pos);
+    float m = smoothstep(-0.5 * afwidth, 0.5 * afwidth, ld);
+    vec4 color = mix(color0, color1, m);
+
+    oFragColor = color * vec4(1.0, 1.0, 1.0, alpha);
+}
+#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_border_corner.vs.glsl
+++ /dev/null
@@ -1,285 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-// Matches BorderCornerSide enum in border.rs
-#define SIDE_BOTH       0
-#define SIDE_FIRST      1
-#define SIDE_SECOND     2
-
-vec2 get_radii(vec2 radius, vec2 invalid) {
-    if (all(greaterThan(radius, vec2(0.0)))) {
-        return radius;
-    }
-
-    return invalid;
-}
-
-void set_radii(int style,
-               vec2 radii,
-               vec2 widths,
-               vec2 adjusted_widths) {
-    vRadii0.xy = get_radii(radii, 2.0 * widths);
-    vRadii0.zw = get_radii(radii - widths, -widths);
-
-    switch (style) {
-        case BORDER_STYLE_RIDGE:
-        case BORDER_STYLE_GROOVE:
-            vRadii1.xy = radii - adjusted_widths;
-            // See comment in default branch
-            vRadii1.zw = vec2(-100.0);
-            break;
-        case BORDER_STYLE_DOUBLE:
-            vRadii1.xy = get_radii(radii - adjusted_widths, -widths);
-            vRadii1.zw = get_radii(radii - widths + adjusted_widths, -widths);
-            break;
-        default:
-            // These aren't needed, so we set them to some reasonably large
-            // negative value so later computations will discard them. This
-            // avoids branches and numerical issues in the fragment shader.
-            vRadii1.xy = vec2(-100.0);
-            vRadii1.zw = vec2(-100.0);
-            break;
-    }
-}
-
-void set_edge_line(vec2 border_width,
-                   vec2 outer_corner,
-                   vec2 gradient_sign) {
-    vec2 gradient = border_width * gradient_sign;
-    vColorEdgeLine = vec4(outer_corner, vec2(-gradient.y, gradient.x));
-}
-
-void write_color(vec4 color0, vec4 color1, int style, vec2 delta, int instance_kind) {
-    vec4 modulate;
-
-    switch (style) {
-        case BORDER_STYLE_GROOVE:
-            modulate = vec4(1.0 - 0.3 * delta.x,
-                            1.0 + 0.3 * delta.x,
-                            1.0 - 0.3 * delta.y,
-                            1.0 + 0.3 * delta.y);
-
-            break;
-        case BORDER_STYLE_RIDGE:
-            modulate = vec4(1.0 + 0.3 * delta.x,
-                            1.0 - 0.3 * delta.x,
-                            1.0 + 0.3 * delta.y,
-                            1.0 - 0.3 * delta.y);
-            break;
-        default:
-            modulate = vec4(1.0);
-            break;
-    }
-
-    // Optionally mask out one side of the border corner,
-    // depending on the instance kind.
-    switch (instance_kind) {
-        case SIDE_FIRST:
-            color0.a = 0.0;
-            break;
-        case SIDE_SECOND:
-            color1.a = 0.0;
-            break;
-    }
-
-    vColor00 = vec4(color0.rgb * modulate.x, color0.a);
-    vColor01 = vec4(color0.rgb * modulate.y, color0.a);
-    vColor10 = vec4(color1.rgb * modulate.z, color1.a);
-    vColor11 = vec4(color1.rgb * modulate.w, color1.a);
-}
-
-int select_style(int color_select, vec2 fstyle) {
-    ivec2 style = ivec2(fstyle);
-
-    switch (color_select) {
-        case SIDE_BOTH:
-        {
-            // TODO(gw): A temporary hack! While we don't support
-            //           border corners that have dots or dashes
-            //           with another style, pretend they are solid
-            //           border corners.
-            bool has_dots = style.x == BORDER_STYLE_DOTTED ||
-                            style.y == BORDER_STYLE_DOTTED;
-            bool has_dashes = style.x == BORDER_STYLE_DASHED ||
-                              style.y == BORDER_STYLE_DASHED;
-            if (style.x != style.y && (has_dots || has_dashes))
-                return BORDER_STYLE_SOLID;
-            return style.x;
-        }
-        case SIDE_FIRST:
-            return style.x;
-        case SIDE_SECOND:
-            return style.y;
-    }
-}
-
-void main(void) {
-    Primitive prim = load_primitive();
-    Border border = fetch_border(prim.specific_prim_address);
-    int sub_part = prim.user_data0;
-    BorderCorners corners = get_border_corners(border, prim.local_rect);
-
-    vec2 p0, p1;
-
-    // TODO(gw): We'll need to pass through multiple styles
-    //           once we support style transitions per corner.
-    int style;
-    vec4 edge_distances;
-    vec4 color0, color1;
-    vec2 color_delta;
-
-    // TODO(gw): Now that all border styles are supported, the switch
-    //           statement below can be tidied up quite a bit.
-
-    switch (sub_part) {
-        case 0: {
-            p0 = corners.tl_outer;
-            p1 = corners.tl_inner;
-            color0 = border.colors[0];
-            color1 = border.colors[1];
-            vClipCenter = corners.tl_outer + border.radii[0].xy;
-            vClipSign = vec2(1.0);
-            style = select_style(prim.user_data1, border.style.yx);
-            vec4 adjusted_widths = get_effective_border_widths(border, style);
-            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
-            set_radii(style,
-                      border.radii[0].xy,
-                      border.widths.xy,
-                      adjusted_widths.xy);
-            set_edge_line(border.widths.xy,
-                          corners.tl_outer,
-                          vec2(1.0, 1.0));
-            edge_distances = vec4(p0 + adjusted_widths.xy,
-                                  p0 + inv_adjusted_widths.xy);
-            color_delta = vec2(1.0);
-            break;
-        }
-        case 1: {
-            p0 = vec2(corners.tr_inner.x, corners.tr_outer.y);
-            p1 = vec2(corners.tr_outer.x, corners.tr_inner.y);
-            color0 = border.colors[1];
-            color1 = border.colors[2];
-            vClipCenter = corners.tr_outer + vec2(-border.radii[0].z, border.radii[0].w);
-            vClipSign = vec2(-1.0, 1.0);
-            style = select_style(prim.user_data1, border.style.zy);
-            vec4 adjusted_widths = get_effective_border_widths(border, style);
-            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
-            set_radii(style,
-                      border.radii[0].zw,
-                      border.widths.zy,
-                      adjusted_widths.zy);
-            set_edge_line(border.widths.zy,
-                          corners.tr_outer,
-                          vec2(-1.0, 1.0));
-            edge_distances = vec4(p1.x - adjusted_widths.z,
-                                  p0.y + adjusted_widths.y,
-                                  p1.x - border.widths.z + adjusted_widths.z,
-                                  p0.y + inv_adjusted_widths.y);
-            color_delta = vec2(1.0, -1.0);
-            break;
-        }
-        case 2: {
-            p0 = corners.br_inner;
-            p1 = corners.br_outer;
-            color0 = border.colors[2];
-            color1 = border.colors[3];
-            vClipCenter = corners.br_outer - border.radii[1].xy;
-            vClipSign = vec2(-1.0, -1.0);
-            style = select_style(prim.user_data1, border.style.wz);
-            vec4 adjusted_widths = get_effective_border_widths(border, style);
-            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
-            set_radii(style,
-                      border.radii[1].xy,
-                      border.widths.zw,
-                      adjusted_widths.zw);
-            set_edge_line(border.widths.zw,
-                          corners.br_outer,
-                          vec2(-1.0, -1.0));
-            edge_distances = vec4(p1.x - adjusted_widths.z,
-                                  p1.y - adjusted_widths.w,
-                                  p1.x - border.widths.z + adjusted_widths.z,
-                                  p1.y - border.widths.w + adjusted_widths.w);
-            color_delta = vec2(-1.0);
-            break;
-        }
-        case 3: {
-            p0 = vec2(corners.bl_outer.x, corners.bl_inner.y);
-            p1 = vec2(corners.bl_inner.x, corners.bl_outer.y);
-            color0 = border.colors[3];
-            color1 = border.colors[0];
-            vClipCenter = corners.bl_outer + vec2(border.radii[1].z, -border.radii[1].w);
-            vClipSign = vec2(1.0, -1.0);
-            style = select_style(prim.user_data1, border.style.xw);
-            vec4 adjusted_widths = get_effective_border_widths(border, style);
-            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
-            set_radii(style,
-                      border.radii[1].zw,
-                      border.widths.xw,
-                      adjusted_widths.xw);
-            set_edge_line(border.widths.xw,
-                          corners.bl_outer,
-                          vec2(1.0, -1.0));
-            edge_distances = vec4(p0.x + adjusted_widths.x,
-                                  p1.y - adjusted_widths.w,
-                                  p0.x + inv_adjusted_widths.x,
-                                  p1.y - border.widths.w + adjusted_widths.w);
-            color_delta = vec2(-1.0, 1.0);
-            break;
-        }
-    }
-
-    switch (style) {
-        case BORDER_STYLE_DOUBLE: {
-            vEdgeDistance = edge_distances;
-            vAlphaSelect = 0.0;
-            vSDFSelect = 0.0;
-            break;
-        }
-        case BORDER_STYLE_GROOVE:
-        case BORDER_STYLE_RIDGE:
-            vEdgeDistance = vec4(edge_distances.xy, 0.0, 0.0);
-            vAlphaSelect = 1.0;
-            vSDFSelect = 1.0;
-            break;
-        case BORDER_STYLE_DOTTED:
-            // Disable normal clip radii for dotted corners, since
-            // all the clipping is handled by the clip mask.
-            vClipSign = vec2(0.0);
-            vEdgeDistance = vec4(0.0);
-            vAlphaSelect = 1.0;
-            vSDFSelect = 0.0;
-            break;
-        default: {
-            vEdgeDistance = vec4(0.0);
-            vAlphaSelect = 1.0;
-            vSDFSelect = 0.0;
-            break;
-        }
-    }
-
-    write_color(color0, color1, style, color_delta, prim.user_data1);
-
-    RectWithSize segment_rect;
-    segment_rect.p0 = p0;
-    segment_rect.size = p1 - p0;
-
-#ifdef WR_FEATURE_TRANSFORM
-    TransformVertexInfo vi = write_transform_vertex(segment_rect,
-                                                    prim.local_clip_rect,
-                                                    prim.z,
-                                                    prim.layer,
-                                                    prim.task,
-                                                    prim.local_rect);
-#else
-    VertexInfo vi = write_vertex(segment_rect,
-                                 prim.local_clip_rect,
-                                 prim.z,
-                                 prim.layer,
-                                 prim.task,
-                                 prim.local_rect);
-#endif
-
-    vLocalPos = vi.local_pos;
-    write_clip(vi.screen_pos, prim.clip_area);
-}
deleted file mode 100644
--- a/gfx/webrender/res/ps_border_edge.fs.glsl
+++ /dev/null
@@ -1,63 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-void main(void) {
-    float alpha = 1.0;
-#ifdef WR_FEATURE_TRANSFORM
-    alpha = 0.0;
-    vec2 local_pos = init_transform_fs(vLocalPos, alpha);
-#else
-    vec2 local_pos = vLocalPos;
-#endif
-
-    alpha = min(alpha, do_clip());
-
-    // Find the appropriate distance to apply the step over.
-    vec2 fw = fwidth(local_pos);
-    float afwidth = length(fw);
-
-    // Applies the math necessary to draw a style: double
-    // border. In the case of a solid border, the vertex
-    // shader sets interpolator values that make this have
-    // no effect.
-
-    // Select the x/y coord, depending on which axis this edge is.
-    vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
-
-    // Get signed distance from each of the inner edges.
-    float d0 = pos.x - vEdgeDistance.x;
-    float d1 = vEdgeDistance.y - pos.x;
-
-    // SDF union to select both outer edges.
-    float d = min(d0, d1);
-
-    // Select fragment on/off based on signed distance.
-    // No AA here, since we know we're on a straight edge
-    // and the width is rounded to a whole CSS pixel.
-    alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
-
-    // Mix color based on first distance.
-    // TODO(gw): Support AA for groove/ridge border edge with transforms.
-    vec4 color = mix(vColor0, vColor1, bvec4(d0 * vEdgeDistance.y > 0.0));
-
-    // Apply dashing / dotting parameters.
-
-    // Get the main-axis position relative to closest dot or dash.
-    float x = mod(pos.y - vClipParams.x, vClipParams.y);
-
-    // Calculate dash alpha (on/off) based on dash length
-    float dash_alpha = step(x, vClipParams.z);
-
-    // Get the dot alpha
-    vec2 dot_relative_pos = vec2(x, pos.x) - vClipParams.zw;
-    float dot_distance = length(dot_relative_pos) - vClipParams.z;
-    float dot_alpha = 1.0 - smoothstep(-0.5 * afwidth,
-                                        0.5 * afwidth,
-                                        dot_distance);
-
-    // Select between dot/dash alpha based on clip mode.
-    alpha = min(alpha, mix(dash_alpha, dot_alpha, vClipSelect));
-
-    oFragColor = color * vec4(1.0, 1.0, 1.0, alpha);
-}
--- a/gfx/webrender/res/ps_border_edge.glsl
+++ b/gfx/webrender/res/ps_border_edge.glsl
@@ -12,8 +12,292 @@ flat varying float vAlphaSelect;
 flat varying vec4 vClipParams;
 flat varying float vClipSelect;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 #else
 varying vec2 vLocalPos;
 #endif
+
+#ifdef WR_VERTEX_SHADER
+void write_edge_distance(float p0,
+                         float original_width,
+                         float adjusted_width,
+                         float style,
+                         float axis_select,
+                         float sign_adjust) {
+    switch (int(style)) {
+        case BORDER_STYLE_DOUBLE:
+            vEdgeDistance = vec2(p0 + adjusted_width,
+                                 p0 + original_width - adjusted_width);
+            break;
+        case BORDER_STYLE_GROOVE:
+        case BORDER_STYLE_RIDGE:
+            vEdgeDistance = vec2(p0 + adjusted_width, sign_adjust);
+            break;
+        default:
+            vEdgeDistance = vec2(0.0);
+            break;
+    }
+
+    vAxisSelect = axis_select;
+}
+
+void write_alpha_select(float style) {
+    switch (int(style)) {
+        case BORDER_STYLE_DOUBLE:
+            vAlphaSelect = 0.0;
+            break;
+        default:
+            vAlphaSelect = 1.0;
+            break;
+    }
+}
+
+// write_color function is duplicated to work around a Mali-T880 GPU driver program link error.
+// See https://github.com/servo/webrender/issues/1403 for more info.
+// TODO: convert back to a single function once the driver issues are resolved, if ever.
+void write_color0(vec4 color, float style, bool flip) {
+    vec2 modulate;
+
+    switch (int(style)) {
+        case BORDER_STYLE_GROOVE:
+        {
+            modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
+            break;
+        }
+        case BORDER_STYLE_RIDGE:
+        {
+            modulate = flip ? vec2(0.7, 1.3) : vec2(1.3, 0.7);
+            break;
+        }
+        default:
+            modulate = vec2(1.0);
+            break;
+    }
+
+    vColor0 = vec4(color.rgb * modulate.x, color.a);
+}
+
+void write_color1(vec4 color, float style, bool flip) {
+    vec2 modulate;
+
+    switch (int(style)) {
+        case BORDER_STYLE_GROOVE:
+        {
+            modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
+            break;
+        }
+        case BORDER_STYLE_RIDGE:
+        {
+            modulate = flip ? vec2(0.7, 1.3) : vec2(1.3, 0.7);
+            break;
+        }
+        default:
+            modulate = vec2(1.0);
+            break;
+    }
+
+    vColor1 = vec4(color.rgb * modulate.y, color.a);
+}
+
+void write_clip_params(float style,
+                       float border_width,
+                       float edge_length,
+                       float edge_offset,
+                       float center_line) {
+    // x = offset
+    // y = dash on + off length
+    // z = dash length
+    // w = center line of edge cross-axis (for dots only)
+    switch (int(style)) {
+        case BORDER_STYLE_DASHED: {
+            float desired_dash_length = border_width * 3.0;
+            // Consider half total length since there is an equal on/off for each dash.
+            float dash_count = ceil(0.5 * edge_length / desired_dash_length);
+            float dash_length = 0.5 * edge_length / dash_count;
+            vClipParams = vec4(edge_offset - 0.5 * dash_length,
+                               2.0 * dash_length,
+                               dash_length,
+                               0.0);
+            vClipSelect = 0.0;
+            break;
+        }
+        case BORDER_STYLE_DOTTED: {
+            float diameter = border_width;
+            float radius = 0.5 * diameter;
+            float dot_count = ceil(0.5 * edge_length / diameter);
+            float empty_space = edge_length - dot_count * diameter;
+            float distance_between_centers = diameter + empty_space / dot_count;
+            vClipParams = vec4(edge_offset - radius,
+                               distance_between_centers,
+                               radius,
+                               center_line);
+            vClipSelect = 1.0;
+            break;
+        }
+        default:
+            vClipParams = vec4(1.0);
+            vClipSelect = 0.0;
+            break;
+    }
+}
+
+void main(void) {
+    Primitive prim = load_primitive();
+    Border border = fetch_border(prim.specific_prim_address);
+    int sub_part = prim.user_data0;
+    BorderCorners corners = get_border_corners(border, prim.local_rect);
+    vec4 color = border.colors[sub_part];
+
+    // TODO(gw): Now that all border styles are supported, the switch
+    //           statement below can be tidied up quite a bit.
+
+    float style;
+    bool color_flip;
+
+    RectWithSize segment_rect;
+    switch (sub_part) {
+        case 0: {
+            segment_rect.p0 = vec2(corners.tl_outer.x, corners.tl_inner.y);
+            segment_rect.size = vec2(border.widths.x, corners.bl_inner.y - corners.tl_inner.y);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.x));
+            write_edge_distance(segment_rect.p0.x, border.widths.x, adjusted_widths.x, border.style.x, 0.0, 1.0);
+            style = border.style.x;
+            color_flip = false;
+            write_clip_params(border.style.x,
+                              border.widths.x,
+                              segment_rect.size.y,
+                              segment_rect.p0.y,
+                              segment_rect.p0.x + 0.5 * segment_rect.size.x);
+            break;
+        }
+        case 1: {
+            segment_rect.p0 = vec2(corners.tl_inner.x, corners.tl_outer.y);
+            segment_rect.size = vec2(corners.tr_inner.x - corners.tl_inner.x, border.widths.y);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.y));
+            write_edge_distance(segment_rect.p0.y, border.widths.y, adjusted_widths.y, border.style.y, 1.0, 1.0);
+            style = border.style.y;
+            color_flip = false;
+            write_clip_params(border.style.y,
+                              border.widths.y,
+                              segment_rect.size.x,
+                              segment_rect.p0.x,
+                              segment_rect.p0.y + 0.5 * segment_rect.size.y);
+            break;
+        }
+        case 2: {
+            segment_rect.p0 = vec2(corners.tr_outer.x - border.widths.z, corners.tr_inner.y);
+            segment_rect.size = vec2(border.widths.z, corners.br_inner.y - corners.tr_inner.y);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.z));
+            write_edge_distance(segment_rect.p0.x, border.widths.z, adjusted_widths.z, border.style.z, 0.0, -1.0);
+            style = border.style.z;
+            color_flip = true;
+            write_clip_params(border.style.z,
+                              border.widths.z,
+                              segment_rect.size.y,
+                              segment_rect.p0.y,
+                              segment_rect.p0.x + 0.5 * segment_rect.size.x);
+            break;
+        }
+        case 3: {
+            segment_rect.p0 = vec2(corners.bl_inner.x, corners.bl_outer.y - border.widths.w);
+            segment_rect.size = vec2(corners.br_inner.x - corners.bl_inner.x, border.widths.w);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.w));
+            write_edge_distance(segment_rect.p0.y, border.widths.w, adjusted_widths.w, border.style.w, 1.0, -1.0);
+            style = border.style.w;
+            color_flip = true;
+            write_clip_params(border.style.w,
+                              border.widths.w,
+                              segment_rect.size.x,
+                              segment_rect.p0.x,
+                              segment_rect.p0.y + 0.5 * segment_rect.size.y);
+            break;
+        }
+    }
+
+    write_alpha_select(style);
+    write_color0(color, style, color_flip);
+    write_color1(color, style, color_flip);
+
+#ifdef WR_FEATURE_TRANSFORM
+    TransformVertexInfo vi = write_transform_vertex(segment_rect,
+                                                    prim.local_clip_rect,
+                                                    prim.z,
+                                                    prim.layer,
+                                                    prim.task,
+                                                    prim.local_rect);
+#else
+    VertexInfo vi = write_vertex(segment_rect,
+                                 prim.local_clip_rect,
+                                 prim.z,
+                                 prim.layer,
+                                 prim.task,
+                                 prim.local_rect);
+#endif
+
+    vLocalPos = vi.local_pos;
+    write_clip(vi.screen_pos, prim.clip_area);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+    float alpha = 1.0;
+#ifdef WR_FEATURE_TRANSFORM
+    alpha = 0.0;
+    vec2 local_pos = init_transform_fs(vLocalPos, alpha);
+#else
+    vec2 local_pos = vLocalPos;
+#endif
+
+    alpha = min(alpha, do_clip());
+
+    // Find the appropriate distance to apply the step over.
+    vec2 fw = fwidth(local_pos);
+    float afwidth = length(fw);
+
+    // Applies the math necessary to draw a style: double
+    // border. In the case of a solid border, the vertex
+    // shader sets interpolator values that make this have
+    // no effect.
+
+    // Select the x/y coord, depending on which axis this edge is.
+    vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
+
+    // Get signed distance from each of the inner edges.
+    float d0 = pos.x - vEdgeDistance.x;
+    float d1 = vEdgeDistance.y - pos.x;
+
+    // SDF union to select both outer edges.
+    float d = min(d0, d1);
+
+    // Select fragment on/off based on signed distance.
+    // No AA here, since we know we're on a straight edge
+    // and the width is rounded to a whole CSS pixel.
+    alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
+
+    // Mix color based on first distance.
+    // TODO(gw): Support AA for groove/ridge border edge with transforms.
+    vec4 color = mix(vColor0, vColor1, bvec4(d0 * vEdgeDistance.y > 0.0));
+
+    // Apply dashing / dotting parameters.
+
+    // Get the main-axis position relative to closest dot or dash.
+    float x = mod(pos.y - vClipParams.x, vClipParams.y);
+
+    // Calculate dash alpha (on/off) based on dash length
+    float dash_alpha = step(x, vClipParams.z);
+
+    // Get the dot alpha
+    vec2 dot_relative_pos = vec2(x, pos.x) - vClipParams.zw;
+    float dot_distance = length(dot_relative_pos) - vClipParams.z;
+    float dot_alpha = 1.0 - smoothstep(-0.5 * afwidth,
+                                        0.5 * afwidth,
+                                        dot_distance);
+
+    // Select between dot/dash alpha based on clip mode.
+    alpha = min(alpha, mix(dash_alpha, dot_alpha, vClipSelect));
+
+    oFragColor = color * vec4(1.0, 1.0, 1.0, alpha);
+}
+#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_border_edge.vs.glsl
+++ /dev/null
@@ -1,223 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-void write_edge_distance(float p0,
-                         float original_width,
-                         float adjusted_width,
-                         float style,
-                         float axis_select,
-                         float sign_adjust) {
-    switch (int(style)) {
-        case BORDER_STYLE_DOUBLE:
-            vEdgeDistance = vec2(p0 + adjusted_width,
-                                 p0 + original_width - adjusted_width);
-            break;
-        case BORDER_STYLE_GROOVE:
-        case BORDER_STYLE_RIDGE:
-            vEdgeDistance = vec2(p0 + adjusted_width, sign_adjust);
-            break;
-        default:
-            vEdgeDistance = vec2(0.0);
-            break;
-    }
-
-    vAxisSelect = axis_select;
-}
-
-void write_alpha_select(float style) {
-    switch (int(style)) {
-        case BORDER_STYLE_DOUBLE:
-            vAlphaSelect = 0.0;
-            break;
-        default:
-            vAlphaSelect = 1.0;
-            break;
-    }
-}
-
-// write_color function is duplicated to work around a Mali-T880 GPU driver program link error.
-// See https://github.com/servo/webrender/issues/1403 for more info.
-// TODO: convert back to a single function once the driver issues are resolved, if ever.
-void write_color0(vec4 color, float style, bool flip) {
-    vec2 modulate;
-
-    switch (int(style)) {
-        case BORDER_STYLE_GROOVE:
-        {
-            modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
-            break;
-        }
-        case BORDER_STYLE_RIDGE:
-        {
-            modulate = flip ? vec2(0.7, 1.3) : vec2(1.3, 0.7);
-            break;
-        }
-        default:
-            modulate = vec2(1.0);
-            break;
-    }
-
-    vColor0 = vec4(color.rgb * modulate.x, color.a);
-}
-
-void write_color1(vec4 color, float style, bool flip) {
-    vec2 modulate;
-
-    switch (int(style)) {
-        case BORDER_STYLE_GROOVE:
-        {
-            modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
-            break;
-        }
-        case BORDER_STYLE_RIDGE:
-        {
-            modulate = flip ? vec2(0.7, 1.3) : vec2(1.3, 0.7);
-            break;
-        }
-        default:
-            modulate = vec2(1.0);
-            break;
-    }
-
-    vColor1 = vec4(color.rgb * modulate.y, color.a);
-}
-
-void write_clip_params(float style,
-                       float border_width,
-                       float edge_length,
-                       float edge_offset,
-                       float center_line) {
-    // x = offset
-    // y = dash on + off length
-    // z = dash length
-    // w = center line of edge cross-axis (for dots only)
-    switch (int(style)) {
-        case BORDER_STYLE_DASHED: {
-            float desired_dash_length = border_width * 3.0;
-            // Consider half total length since there is an equal on/off for each dash.
-            float dash_count = ceil(0.5 * edge_length / desired_dash_length);
-            float dash_length = 0.5 * edge_length / dash_count;
-            vClipParams = vec4(edge_offset - 0.5 * dash_length,
-                               2.0 * dash_length,
-                               dash_length,
-                               0.0);
-            vClipSelect = 0.0;
-            break;
-        }
-        case BORDER_STYLE_DOTTED: {
-            float diameter = border_width;
-            float radius = 0.5 * diameter;
-            float dot_count = ceil(0.5 * edge_length / diameter);
-            float empty_space = edge_length - dot_count * diameter;
-            float distance_between_centers = diameter + empty_space / dot_count;
-            vClipParams = vec4(edge_offset - radius,
-                               distance_between_centers,
-                               radius,
-                               center_line);
-            vClipSelect = 1.0;
-            break;
-        }
-        default:
-            vClipParams = vec4(1.0);
-            vClipSelect = 0.0;
-            break;
-    }
-}
-
-void main(void) {
-    Primitive prim = load_primitive();
-    Border border = fetch_border(prim.specific_prim_address);
-    int sub_part = prim.user_data0;
-    BorderCorners corners = get_border_corners(border, prim.local_rect);
-    vec4 color = border.colors[sub_part];
-
-    // TODO(gw): Now that all border styles are supported, the switch
-    //           statement below can be tidied up quite a bit.
-
-    float style;
-    bool color_flip;
-
-    RectWithSize segment_rect;
-    switch (sub_part) {
-        case 0: {
-            segment_rect.p0 = vec2(corners.tl_outer.x, corners.tl_inner.y);
-            segment_rect.size = vec2(border.widths.x, corners.bl_inner.y - corners.tl_inner.y);
-            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.x));
-            write_edge_distance(segment_rect.p0.x, border.widths.x, adjusted_widths.x, border.style.x, 0.0, 1.0);
-            style = border.style.x;
-            color_flip = false;
-            write_clip_params(border.style.x,
-                              border.widths.x,
-                              segment_rect.size.y,
-                              segment_rect.p0.y,
-                              segment_rect.p0.x + 0.5 * segment_rect.size.x);
-            break;
-        }
-        case 1: {
-            segment_rect.p0 = vec2(corners.tl_inner.x, corners.tl_outer.y);
-            segment_rect.size = vec2(corners.tr_inner.x - corners.tl_inner.x, border.widths.y);
-            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.y));
-            write_edge_distance(segment_rect.p0.y, border.widths.y, adjusted_widths.y, border.style.y, 1.0, 1.0);
-            style = border.style.y;
-            color_flip = false;
-            write_clip_params(border.style.y,
-                              border.widths.y,
-                              segment_rect.size.x,
-                              segment_rect.p0.x,
-                              segment_rect.p0.y + 0.5 * segment_rect.size.y);
-            break;
-        }
-        case 2: {
-            segment_rect.p0 = vec2(corners.tr_outer.x - border.widths.z, corners.tr_inner.y);
-            segment_rect.size = vec2(border.widths.z, corners.br_inner.y - corners.tr_inner.y);
-            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.z));
-            write_edge_distance(segment_rect.p0.x, border.widths.z, adjusted_widths.z, border.style.z, 0.0, -1.0);
-            style = border.style.z;
-            color_flip = true;
-            write_clip_params(border.style.z,
-                              border.widths.z,
-                              segment_rect.size.y,
-                              segment_rect.p0.y,
-                              segment_rect.p0.x + 0.5 * segment_rect.size.x);
-            break;
-        }
-        case 3: {
-            segment_rect.p0 = vec2(corners.bl_inner.x, corners.bl_outer.y - border.widths.w);
-            segment_rect.size = vec2(corners.br_inner.x - corners.bl_inner.x, border.widths.w);
-            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.w));
-            write_edge_distance(segment_rect.p0.y, border.widths.w, adjusted_widths.w, border.style.w, 1.0, -1.0);
-            style = border.style.w;
-            color_flip = true;
-            write_clip_params(border.style.w,
-                              border.widths.w,
-                              segment_rect.size.x,
-                              segment_rect.p0.x,
-                              segment_rect.p0.y + 0.5 * segment_rect.size.y);
-            break;
-        }
-    }
-
-    write_alpha_select(style);
-    write_color0(color, style, color_flip);
-    write_color1(color, style, color_flip);
-
-#ifdef WR_FEATURE_TRANSFORM
-    TransformVertexInfo vi = write_transform_vertex(segment_rect,
-                                                    prim.local_clip_rect,
-                                                    prim.z,
-                                                    prim.layer,
-                                                    prim.task,
-                                                    prim.local_rect);
-#else
-    VertexInfo vi = write_vertex(segment_rect,
-                                 prim.local_clip_rect,
-                                 prim.z,
-                                 prim.layer,
-                                 prim.task,
-                                 prim.local_rect);
-#endif
-
-    vLocalPos = vi.local_pos;
-    write_clip(vi.screen_pos, prim.clip_area);
-}
--- a/gfx/webrender/res/ps_box_shadow.fs.glsl
+++ b/gfx/webrender/res/ps_box_shadow.fs.glsl
@@ -13,10 +13,11 @@ void main(void) {
     // shadow corner. This can happen, for example, when
     // drawing the outer parts of an inset box shadow.
     uv = clamp(uv, vec2(0.0), vec2(1.0));
 
     // Map the unit UV to the actual UV rect in the cache.
     uv = mix(vCacheUvRectCoords.xy, vCacheUvRectCoords.zw, uv);
 
     // Modulate the box shadow by the color.
-    oFragColor = clip_scale * dither(vColor * texture(sCacheRGBA8, vec3(uv, vUv.z)));
+    float mask = texture(sColor1, vec3(uv, vUv.z)).r;
+    oFragColor = clip_scale * dither(vColor * vec4(1.0, 1.0, 1.0, mask));
 }
--- a/gfx/webrender/res/ps_box_shadow.vs.glsl
+++ b/gfx/webrender/res/ps_box_shadow.vs.glsl
@@ -22,15 +22,15 @@ void main(void) {
     // Constant offsets to inset from bilinear filtering border.
     vec2 patch_origin = child_task.data0.xy + vec2(1.0);
     vec2 patch_size_device_pixels = child_task.data0.zw - vec2(2.0);
     vec2 patch_size = patch_size_device_pixels / uDevicePixelRatio;
 
     vUv.xy = (vi.local_pos - prim.local_rect.p0) / patch_size;
     vMirrorPoint = 0.5 * prim.local_rect.size / patch_size;
 
-    vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
+    vec2 texture_size = vec2(textureSize(sCacheA8, 0));
     vCacheUvRectCoords = vec4(patch_origin, patch_origin + patch_size_device_pixels) / texture_size.xyxy;
 
     vColor = bs.color;
 
     write_clip(vi.screen_pos, prim.clip_area);
 }
--- a/gfx/webrender/src/debug_server.rs
+++ b/gfx/webrender/src/debug_server.rs
@@ -9,28 +9,39 @@ use std::thread;
 use std::sync::mpsc::Sender;
 
 use ws;
 
 // Messages that are sent from the render backend to the renderer
 // debug command queue. These are sent in a separate queue so
 // that none of these types are exposed to the RenderApi interfaces.
 // We can't use select!() as it's not stable...
-pub enum DebugMsg {
-    FetchPasses(ws::Sender),
+enum DebugMsg {
+    AddSender(ws::Sender),
+    RemoveSender(ws::util::Token),
 }
 
 // Represents a connection to a client.
 struct Server {
     ws: ws::Sender,
     debug_tx: Sender<DebugMsg>,
     api_tx: MsgSender<ApiMsg>,
 }
 
 impl ws::Handler for Server {
+    fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> {
+        self.debug_tx.send(DebugMsg::AddSender(self.ws.clone())).ok();
+
+        Ok(())
+    }
+
+    fn on_close(&mut self, _: ws::CloseCode, _: &str) {
+        self.debug_tx.send(DebugMsg::RemoveSender(self.ws.token())).ok();
+    }
+
     fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
         match msg {
             ws::Message::Text(string) => {
                 let cmd = match string.as_str() {
                     "enable_profiler" => {
                         DebugCommand::EnableProfiler(true)
                     }
                     "disable_profiler" => {
@@ -44,19 +55,20 @@ impl ws::Handler for Server {
                     }
                     "enable_render_target_debug" => {
                         DebugCommand::EnableRenderTargetDebug(true)
                     }
                     "disable_render_target_debug" => {
                         DebugCommand::EnableRenderTargetDebug(false)
                     }
                     "fetch_passes" => {
-                        let msg = DebugMsg::FetchPasses(self.ws.clone());
-                        self.debug_tx.send(msg).unwrap();
-                        DebugCommand::Flush
+                        DebugCommand::FetchPasses
+                    }
+                    "fetch_documents" => {
+                        DebugCommand::FetchDocuments
                     }
                     msg => {
                         println!("unknown msg {}", msg);
                         return Ok(());
                     }
                 };
 
                 let msg = ApiMsg::DebugCommand(cmd);
@@ -69,17 +81,18 @@ impl ws::Handler for Server {
     }
 }
 
 // Spawn a thread for a given renderer, and wait for
 // client connections.
 pub struct DebugServer {
     join_handle: Option<thread::JoinHandle<()>>,
     broadcaster: ws::Sender,
-    pub debug_rx: Receiver<DebugMsg>,
+    debug_rx: Receiver<DebugMsg>,
+    senders: Vec<ws::Sender>,
 }
 
 impl DebugServer {
     pub fn new(api_tx: MsgSender<ApiMsg>) -> DebugServer {
         let (debug_tx, debug_rx) = channel();
 
         let socket = ws::Builder::new().build(move |out| {
             Server {
@@ -96,16 +109,52 @@ impl DebugServer {
                 println!("ERROR: Unable to bind debugger websocket (port may be in use).");
             }
         }));
 
         DebugServer {
             join_handle,
             broadcaster,
             debug_rx,
+            senders: Vec::new(),
+        }
+    }
+
+    pub fn send(&mut self, message: String) {
+        // Add any new connections that have been queued.
+        while let Ok(msg) = self.debug_rx.try_recv() {
+            match msg {
+                DebugMsg::AddSender(sender) => {
+                    self.senders.push(sender);
+                }
+                DebugMsg::RemoveSender(token) => {
+                    self.senders.retain(|sender| {
+                        sender.token() != token
+                    });
+                }
+            }
+        }
+
+        // Broadcast the message to all senders. Keep
+        // track of the ones that failed, so they can
+        // be removed from the active sender list.
+        let mut disconnected_senders = Vec::new();
+
+        for (i, sender) in self.senders.iter().enumerate() {
+            if let Err(..) = sender.send(message.clone()) {
+                disconnected_senders.push(i);
+            }
+        }
+
+        // Remove the broken senders from the list
+        // for next broadcast. Remove in reverse
+        // order so the indices are valid for the
+        // entire loop.
+        for i in disconnected_senders.iter().rev() {
+            self.senders.remove(*i);
         }
     }
 }
 
 impl Drop for DebugServer {
     fn drop(&mut self) {
         self.broadcaster.shutdown().ok();
         self.join_handle.take().unwrap().join().ok();
@@ -185,8 +234,50 @@ impl Target {
 }
 
 #[derive(Serialize)]
 struct Batch {
     kind: BatchKind,
     description: String,
     count: usize,
 }
+
+#[derive(Serialize)]
+pub struct TreeNode {
+    description: String,
+    children: Vec<TreeNode>,
+}
+
+impl TreeNode {
+    pub fn new(description: &str) -> TreeNode {
+        TreeNode {
+            description: description.to_owned(),
+            children: Vec::new(),
+        }
+    }
+
+    pub fn add_child(&mut self, child: TreeNode) {
+        self.children.push(child);
+    }
+
+    pub fn add_item(&mut self, description: &str) {
+        self.children.push(TreeNode::new(description));
+    }
+}
+
+#[derive(Serialize)]
+pub struct DocumentList {
+    kind: &'static str,
+    root: TreeNode,
+}
+
+impl DocumentList {
+    pub fn new() -> DocumentList {
+        DocumentList {
+            kind: "documents",
+            root: TreeNode::new("root"),
+        }
+    }
+
+    pub fn add(&mut self, item: TreeNode) {
+        self.root.add_child(item);
+    }
+}
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -1171,16 +1171,17 @@ impl Device {
                                   dest_rect.origin.x + dest_rect.size.width,
                                   dest_rect.origin.y + dest_rect.size.height,
                                   gl::COLOR_BUFFER_BIT,
                                   gl::LINEAR);
     }
 
     pub fn free_texture_storage(&mut self, texture: &mut Texture) {
         debug_assert!(self.inside_frame);
+        debug_assert_eq!(self.bound_pbo, PBOId(0));
 
         if texture.format == ImageFormat::Invalid {
             return;
         }
 
         self.bind_texture(DEFAULT_TEXTURE, texture);
 
         let (internal_format, gl_format) = gl_texture_formats_for_image_format(&*self.gl, texture.format);
@@ -1368,27 +1369,27 @@ impl Device {
             self.bound_pbo = pbo_id;
 
             self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, pbo_id.0);
         }
     }
 
     pub fn update_pbo_data<T>(&mut self, data: &[T]) {
         debug_assert!(self.inside_frame);
-        debug_assert!(self.bound_pbo.0 != 0);
+        debug_assert_ne!(self.bound_pbo, PBOId(0));
 
         gl::buffer_data(&*self.gl,
                         gl::PIXEL_UNPACK_BUFFER,
                         data,
                         gl::STREAM_DRAW);
     }
 
     pub fn orphan_pbo(&mut self, new_size: usize) {
         debug_assert!(self.inside_frame);
-        debug_assert!(self.bound_pbo.0 != 0);
+        debug_assert_ne!(self.bound_pbo, PBOId(0));
 
         self.gl.buffer_data_untyped(gl::PIXEL_UNPACK_BUFFER,
                                     new_size as isize,
                                     ptr::null(),
                                     gl::STREAM_DRAW);
     }
 
     pub fn update_texture_from_pbo(&mut self,
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -90,30 +90,32 @@ impl NestedDisplayListInfo {
         }
         self.convert_id_to_nested(id)
     }
 }
 
 struct FlattenContext<'a> {
     scene: &'a Scene,
     builder: &'a mut FrameBuilder,
+    resource_cache: &'a ResourceCache,
     tiled_image_map: TiledImageMap,
     replacements: Vec<(ClipId, ClipId)>,
     nested_display_list_info: Vec<NestedDisplayListInfo>,
     current_nested_display_list_index: u64,
 }
 
 impl<'a> FlattenContext<'a> {
     fn new(scene: &'a Scene,
            builder: &'a mut FrameBuilder,
-           resource_cache: &ResourceCache)
+           resource_cache: &'a ResourceCache)
            -> FlattenContext<'a> {
         FlattenContext {
             scene,
             builder,
+            resource_cache,
             tiled_image_map: resource_cache.get_tiled_image_map(),
             replacements: Vec::new(),
             nested_display_list_info: Vec::new(),
             current_nested_display_list_index: 0,
         }
     }
 
     fn push_nested_display_list_ids(&mut self, info: ClipAndScrollInfo) {
@@ -560,22 +562,22 @@ impl Frame {
                 context.builder.add_yuv_image(clip_and_scroll,
                                               item_rect_with_offset,
                                               &clip_with_offset,
                                               info.yuv_data,
                                               info.color_space,
                                               info.image_rendering);
             }
             SpecificDisplayItem::Text(ref text_info) => {
+                let instance = context.resource_cache.get_font_instance(text_info.font_key).unwrap();
                 context.builder.add_text(clip_and_scroll,
                                          reference_frame_relative_offset,
                                          item_rect_with_offset,
                                          &clip_with_offset,
-                                         text_info.font_key,
-                                         text_info.size,
+                                         instance,
                                          &text_info.color,
                                          item.glyphs(),
                                          item.display_list().get(item.glyphs()).count(),
                                          text_info.glyph_options);
             }
             SpecificDisplayItem::Rectangle(ref info) => {
                 if !self.try_to_add_rectangle_splitting_on_clip(context,
                                                                 &item_rect_with_offset,
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipAndScrollInfo, ClipId, ColorF};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
-use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
+use api::{ExtendMode, FontInstance, FontRenderMode};
+use api::{GlyphInstance, GlyphOptions, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
 use api::{LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation, LineStyle};
 use api::{LocalClip, PipelineId, RepeatMode, ScrollSensitivity, SubpixelDirection, TextShadow};
 use api::{TileOffset, TransformStyle, WorldPixel, YuvColorSpace, YuvData};
 use app_units::Au;
 use frame::FrameId;
 use gpu_cache::GpuCache;
 use internal_types::{FastHashMap, HardwareCompositeOp};
@@ -870,34 +871,47 @@ impl FrameBuilder {
                            PrimitiveContainer::RadialGradient(radial_gradient_cpu));
     }
 
     pub fn add_text(&mut self,
                     clip_and_scroll: ClipAndScrollInfo,
                     run_offset: LayoutVector2D,
                     rect: LayerRect,
                     local_clip: &LocalClip,
-                    font_key: FontKey,
-                    size: Au,
+                    font: &FontInstance,
                     color: &ColorF,
                     glyph_range: ItemRange<GlyphInstance>,
                     glyph_count: usize,
                     glyph_options: Option<GlyphOptions>) {
         // Trivial early out checks
-        if size.0 <= 0 {
+        if font.size.0 <= 0 {
+            return
+        }
+
+        // Sanity check - anything with glyphs bigger than this
+        // is probably going to consume too much memory to render
+        // efficiently anyway. This is specifically to work around
+        // the font_advance.html reftest, which creates a very large
+        // font as a crash test - the rendering is also ignored
+        // by the azure renderer.
+        if font.size >= Au::from_px(4096) {
             return
         }
 
         // TODO(gw): Use a proper algorithm to select
         // whether this item should be rendered with
         // subpixel AA!
-        let mut normal_render_mode = self.config.default_font_render_mode;
+        let mut default_render_mode = self.config.default_font_render_mode.limit_by(font.render_mode);
+        if let Some(options) = glyph_options {
+            default_render_mode = default_render_mode.limit_by(options.render_mode);
+        }
 
         // There are some conditions under which we can't use
         // subpixel text rendering, even if enabled.
+        let mut normal_render_mode = default_render_mode;
         if normal_render_mode == FontRenderMode::Subpixel {
             if color.a != 1.0 {
                 normal_render_mode = FontRenderMode::Alpha;
             }
 
             // text on a stacking context that has filters
             // (e.g. opacity) can't use sub-pixel.
             // TODO(gw): It's possible we can relax this in
@@ -908,56 +922,59 @@ impl FrameBuilder {
                 if stacking_context.composite_ops.count() > 0 {
                     normal_render_mode = FontRenderMode::Alpha;
                 }
             }
         }
 
         // Shadows never use subpixel AA, but need to respect the alpha/mono flag
         // for reftests.
-        let (shadow_render_mode, subpx_dir) = match self.config.default_font_render_mode {
+        let (shadow_render_mode, subpx_dir) = match default_render_mode {
             FontRenderMode::Subpixel | FontRenderMode::Alpha => {
                 // TODO(gw): Expose subpixel direction in API once WR supports
                 //           vertical text runs.
-                (FontRenderMode::Alpha, SubpixelDirection::Horizontal)
+                (FontRenderMode::Alpha, font.subpx_dir)
             }
             FontRenderMode::Mono => {
                 (FontRenderMode::Mono, SubpixelDirection::None)
             }
         };
 
+        let prim_font = FontInstance::new(font.font_key,
+                                          font.size,
+                                          *color,
+                                          normal_render_mode,
+                                          subpx_dir,
+                                          font.platform_options);
         let prim = TextRunPrimitiveCpu {
-            font_key,
-            logical_font_size: size,
+            font: prim_font,
             glyph_range,
             glyph_count,
             glyph_gpu_blocks: Vec::new(),
             glyph_keys: Vec::new(),
-            glyph_options,
-            normal_render_mode,
             shadow_render_mode,
             offset: run_offset,
             color: *color,
-            subpx_dir,
         };
 
         // Text shadows that have a blur radius of 0 need to be rendered as normal
         // text elements to get pixel perfect results for reftests. It's also a big
         // performance win to avoid blurs and render target allocations where
         // possible. For any text shadows that have zero blur, create a normal text
         // primitive with the shadow's color and offset. These need to be added
         // *before* the visual text primitive in order to get the correct paint
         // order. Store them in a Vec first to work around borrowck issues.
         // TODO(gw): Refactor to avoid having to store them in a Vec first.
         let mut fast_text_shadow_prims = Vec::new();
         for shadow_prim_index in &self.shadow_prim_stack {
             let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
             let shadow_prim = &self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0];
             if shadow_prim.shadow.blur_radius == 0.0 {
                 let mut text_prim = prim.clone();
+                text_prim.font.color = shadow_prim.shadow.color.into();
                 text_prim.color = shadow_prim.shadow.color;
                 text_prim.offset += shadow_prim.shadow.offset;
                 fast_text_shadow_prims.push(text_prim);
             }
         }
         for text_prim in fast_text_shadow_prims {
             self.add_primitive(clip_and_scroll,
                                &rect.translate(&text_prim.offset),
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -373,24 +373,22 @@ fn raterize_200_glyphs() {
 
     let mut font_file = File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file");
     let mut font_data = vec![];
     font_file.read_to_end(&mut font_data).expect("failed to read font file");
 
     let font_key = FontKey::new(IdNamespace(0), 0);
     glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
 
-    let font = FontInstance {
-        font_key,
-        color: ColorF::new(0.0, 0.0, 0.0, 1.0).into(),
-        size: Au::from_px(32),
-        render_mode: FontRenderMode::Subpixel,
-        glyph_options: None,
-        subpx_dir: SubpixelDirection::Horizontal,
-    };
+    let font = FontInstance::new(font_key,
+                                 Au::from_px(32),
+                                 ColorF::new(0.0, 0.0, 0.0, 1.0),
+                                 FontRenderMode::Subpixel,
+                                 SubpixelDirection::Horizontal,
+                                 None);
 
     let mut glyph_keys = Vec::with_capacity(200);
     for i in 0..200 {
         glyph_keys.push(GlyphKey::new(i, LayoutPoint::zero(), font.render_mode, font.subpx_dir));
     }
 
     for i in 0..4 {
         glyph_rasterizer.request_glyphs(
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -64,17 +64,17 @@ impl BatchTextures {
             colors: [SourceTexture::Invalid; 3],
         }
     }
 
     pub fn render_target_cache() -> Self {
         BatchTextures {
             colors: [
                 SourceTexture::CacheRGBA8,
-                SourceTexture::Invalid,
+                SourceTexture::CacheA8,
                 SourceTexture::Invalid,
             ]
         }
     }
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum RenderTargetMode {
@@ -151,18 +151,23 @@ impl RendererFrame {
         RendererFrame {
             pipeline_epoch_map,
             layers_bouncing_back,
             frame,
         }
     }
 }
 
+pub enum DebugOutput {
+    FetchDocuments(String),
+}
+
 pub enum ResultMsg {
     DebugCommand(DebugCommand),
+    DebugOutput(DebugOutput),
     RefreshShader(PathBuf),
     NewFrame(DocumentId, RendererFrame, TextureUpdateList, BackendProfileCounters),
     UpdateResources { updates: TextureUpdateList, cancel_rendering: bool },
 }
 
 #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
 pub struct StackingContextIndex(pub usize);
 
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{FontKey, FontRenderMode, GlyphDimensions};
-use api::{FontInstance, GlyphKey, GlyphOptions, SubpixelDirection};
+use api::{FontInstance, FontInstancePlatformOptions, FontKey, FontRenderMode};
+use api::{GlyphDimensions, GlyphKey, GlyphOptions, SubpixelDirection};
 use gamma_lut::{GammaLut, Color as ColorLut};
 use internal_types::FastHashMap;
 
 use dwrote;
 use std::sync::Arc;
 
 lazy_static! {
     static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
@@ -41,36 +41,36 @@ fn dwrite_texture_type(render_mode: Font
                        dwrote::DWRITE_TEXTURE_TYPE {
     match render_mode {
         FontRenderMode::Mono => dwrote::DWRITE_TEXTURE_ALIASED_1x1 ,
         FontRenderMode::Alpha |
         FontRenderMode::Subpixel => dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1,
     }
 }
 
-fn dwrite_measure_mode(render_mode: FontRenderMode, options: Option<GlyphOptions>) ->
+fn dwrite_measure_mode(render_mode: FontRenderMode, options: Option<FontInstancePlatformOptions>) ->
                        dwrote::DWRITE_MEASURING_MODE {
-    if let Some(GlyphOptions{ force_gdi_rendering: true, .. }) = options {
+    if let Some(FontInstancePlatformOptions{ force_gdi_rendering: true, .. }) = options {
         return dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC;
     }
 
     match render_mode {
         FontRenderMode::Mono => dwrote::DWRITE_MEASURING_MODE_GDI_NATURAL,
         FontRenderMode::Alpha |
         FontRenderMode::Subpixel => dwrote::DWRITE_MEASURING_MODE_NATURAL,
     }
 }
 
 fn dwrite_render_mode(font_face: &dwrote::FontFace,
                       render_mode: FontRenderMode,
                       em_size: f32,
                       measure_mode: dwrote::DWRITE_MEASURING_MODE,
-                      options: Option<GlyphOptions>) ->
+                      options: Option<FontInstancePlatformOptions>) ->
                       dwrote::DWRITE_RENDERING_MODE {
-    if let Some(GlyphOptions{ force_gdi_rendering: true, .. }) = options {
+    if let Some(FontInstancePlatformOptions{ force_gdi_rendering: true, .. }) = options {
         return dwrote::DWRITE_RENDERING_MODE_GDI_CLASSIC;
     }
 
     let dwrite_render_mode = match render_mode {
         FontRenderMode::Mono => dwrote::DWRITE_RENDERING_MODE_ALIASED,
         FontRenderMode::Alpha |
         FontRenderMode::Subpixel => {
             font_face.get_recommended_rendering_mode_default_params(em_size,
@@ -170,22 +170,22 @@ impl FontContext {
             glyphIndices: &glyph,
             glyphAdvances: &advance,
             glyphOffsets: &offset,
             isSideways: 0,
             bidiLevel: 0,
         };
 
         let dwrite_measure_mode = dwrite_measure_mode(font.render_mode,
-                                                      font.glyph_options);
+                                                      font.platform_options);
         let dwrite_render_mode = dwrite_render_mode(face,
                                                     font.render_mode,
                                                     font.size.to_f32_px(),
                                                     dwrite_measure_mode,
-                                                    font.glyph_options);
+                                                    font.platform_options);
 
         let (x_offset, y_offset) = font.get_subpx_offset(key);
         let transform = Some(
                         dwrote::DWRITE_MATRIX { m11: 1.0, m12: 0.0, m21: 0.0, m22: 1.0,
                                                 dx: x_offset as f32, dy: y_offset as f32 }
                         );
 
         dwrote::GlyphRunAnalysis::create(&glyph_run, 1.0, transform,
@@ -297,17 +297,17 @@ impl FontContext {
         // Such as for spaces
         if width == 0 || height == 0 {
             return None;
         }
 
         let mut pixels = analysis.create_alpha_texture(texture_type, bounds);
 
         if font.render_mode != FontRenderMode::Mono {
-            let lut_correction = match font.glyph_options {
+            let lut_correction = match font.platform_options {
                 Some(option) => {
                     if option.force_gdi_rendering {
                         &self.gdi_gamma_lut
                     } else {
                         &self.gamma_lut
                     }
                 },
                 None => &self.gamma_lut
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint};
-use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
+use api::{ExtendMode, FontRenderMode, GlyphInstance, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
 use api::{GlyphKey, LayerToWorldTransform, TileOffset, YuvColorSpace, YuvFormat};
-use api::{device_length, FontInstance, LayerVector2D, LineOrientation, LineStyle, SubpixelDirection};
+use api::{device_length, FontInstance, LayerVector2D, LineOrientation, LineStyle};
 use app_units::Au;
 use border::BorderCornerInstance;
 use euclid::{Size2D};
 use gpu_cache::{GpuCacheAddress, GpuBlockData, GpuCache, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 use render_task::{RenderTask, RenderTaskId, RenderTaskTree};
 use resource_cache::{ImageProperties, ResourceCache};
@@ -499,55 +499,51 @@ pub struct TextDecoration {
 #[derive(Debug, Clone)]
 pub struct TextShadowPrimitiveCpu {
     pub shadow: TextShadow,
     pub primitives: Vec<PrimitiveIndex>,
 }
 
 #[derive(Debug, Clone)]
 pub struct TextRunPrimitiveCpu {
-    pub font_key: FontKey,
+    pub font: FontInstance,
     pub offset: LayerVector2D,
-    pub logical_font_size: Au,
     pub glyph_range: ItemRange<GlyphInstance>,
     pub glyph_count: usize,
     pub glyph_keys: Vec<GlyphKey>,
     pub glyph_gpu_blocks: Vec<GpuBlockData>,
-    pub glyph_options: Option<GlyphOptions>,
-    pub normal_render_mode: FontRenderMode,
     pub shadow_render_mode: FontRenderMode,
     pub color: ColorF,
-    pub subpx_dir: SubpixelDirection,
 }
 
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
 pub enum TextRunMode {
     Normal,
     Shadow,
 }
 
 impl TextRunPrimitiveCpu {
     fn prepare_for_render(&mut self,
                           resource_cache: &mut ResourceCache,
                           device_pixel_ratio: f32,
                           display_list: &BuiltDisplayList,
                           run_mode: TextRunMode,
                           gpu_cache: &mut GpuCache) {
-        let font_size_dp = self.logical_font_size.scale_by(device_pixel_ratio);
-        let render_mode = match run_mode {
-            TextRunMode::Normal => self.normal_render_mode,
-            TextRunMode::Shadow => self.shadow_render_mode,
-        };
+        let mut font = self.font.clone();
+        font.size = font.size.scale_by(device_pixel_ratio);
+        match run_mode {
+            TextRunMode::Shadow => {
+                font.render_mode = self.shadow_render_mode;
+            }
+            TextRunMode::Normal => {}
+        }
 
-        let font = FontInstance::new(self.font_key,
-                                     font_size_dp,
-                                     self.color,
-                                     render_mode,
-                                     self.glyph_options,
-                                     self.subpx_dir);
+        if run_mode == TextRunMode::Shadow {
+            font.render_mode = self.shadow_render_mode;
+        }
 
         // Cache the glyph positions, if not in the cache already.
         // TODO(gw): In the future, remove `glyph_instances`
         //           completely, and just reference the glyphs
         //           directly from the display list.
         if self.glyph_keys.is_empty() {
             let src_glyphs = display_list.get(self.glyph_range);
 
@@ -585,17 +581,17 @@ impl TextRunPrimitiveCpu {
         resource_cache.request_glyphs(font, &self.glyph_keys, gpu_cache);
     }
 
     fn write_gpu_blocks(&self,
                         request: &mut GpuDataRequest) {
         request.push(self.color);
         request.push([self.offset.x,
                       self.offset.y,
-                      self.subpx_dir as u32 as f32,
+                      self.font.subpx_dir as u32 as f32,
                       0.0]);
         request.extend_from_slice(&self.glyph_gpu_blocks);
 
         assert!(request.current_used_block_num() <= MAX_VERTEX_TEXTURE_WIDTH);
     }
 }
 
 #[derive(Debug, Clone)]
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -126,16 +126,21 @@ impl ResourceProfileCounter {
         self.size = 0;
     }
 
     #[inline(always)]
     pub fn inc(&mut self, size: usize) {
         self.value += 1;
         self.size += size;
     }
+
+    pub fn set(&mut self, count: usize, size: usize) {
+        self.value = count;
+        self.size = size;
+    }
 }
 
 impl ProfileCounter for ResourceProfileCounter {
     fn description(&self) -> &'static str {
         self.description
     }
 
     fn value(&self) -> String {
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -1,32 +1,38 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#[cfg(feature = "debugger")]
+use debug_server;
 use frame::Frame;
 use frame_builder::FrameBuilderConfig;
 use gpu_cache::GpuCache;
-use internal_types::{FastHashMap, ResultMsg, RendererFrame};
+use internal_types::{DebugOutput, FastHashMap, ResultMsg, RendererFrame};
 use profiler::{BackendProfileCounters, ResourceProfileCounters};
 use record::ApiRecordingReceiver;
 use resource_cache::ResourceCache;
 use scene::Scene;
+#[cfg(feature = "debugger")]
+use serde_json;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::Sender;
 use std::u32;
 use texture_cache::TextureCache;
 use time::precise_time_ns;
 use thread_profiler::register_thread_with_profiler;
 use rayon::ThreadPool;
 use api::channel::{MsgReceiver, PayloadReceiver, PayloadReceiverHelperMethods};
 use api::channel::{PayloadSender, PayloadSenderHelperMethods};
-use api::{ApiMsg, BlobImageRenderer, BuiltDisplayList, DeviceIntPoint};
+use api::{ApiMsg, DebugCommand, BlobImageRenderer, BuiltDisplayList, DeviceIntPoint};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentId, DocumentMsg};
 use api::{IdNamespace, LayerPoint, RenderNotifier};
+#[cfg(feature = "debugger")]
+use api::{BuiltDisplayListIter, SpecificDisplayItem};
 
 struct Document {
     scene: Scene,
     frame: Frame,
     window_size: DeviceUintSize,
     inner_rect: DeviceUintRect,
     pan: DeviceIntPoint,
     page_zoom_factor: f32,
@@ -270,16 +276,22 @@ impl RenderBackend {
                 if doc.scene.display_lists.get(&pipeline_id).is_some() {
                     let _timer = profile_counters.total_time.timer();
                     doc.build_scene(&mut self.resource_cache, self.hidpi_factor);
                     DocumentOp::Built
                 } else {
                     DocumentOp::Nop
                 }
             }
+            DocumentMsg::RemovePipeline(pipeline_id) => {
+                profile_scope!("RemovePipeline");
+
+                doc.scene.remove_pipeline(pipeline_id);
+                DocumentOp::Nop
+            }
             DocumentMsg::Scroll(delta, cursor, move_phase) => {
                 profile_scope!("Scroll");
                 let _timer = profile_counters.total_time.timer();
 
                 if doc.frame.scroll(delta, cursor, move_phase) && doc.render_on_scroll == Some(true) {
                     let frame = doc.render(&mut self.resource_cache,
                                            &mut self.gpu_cache,
                                            &mut profile_counters.resources,
@@ -457,17 +469,25 @@ impl RenderBackend {
                     let msg = ResultMsg::UpdateResources { updates: pending_update, cancel_rendering: true };
                     self.result_tx.send(msg).unwrap();
                     // We use new_frame_ready to wake up the renderer and get the
                     // resource updates processed, but the UpdateResources message
                     // will cancel rendering the frame.
                     self.notifier.lock().unwrap().as_mut().unwrap().new_frame_ready();
                 }
                 ApiMsg::DebugCommand(option) => {
-                    let msg = ResultMsg::DebugCommand(option);
+                    let msg = match option {
+                        DebugCommand::FetchDocuments => {
+                            let json = self.get_docs_for_debugger();
+                            ResultMsg::DebugOutput(DebugOutput::FetchDocuments(json))
+                        }
+                        _ => {
+                            ResultMsg::DebugCommand(option)
+                        }
+                    };
                     self.result_tx.send(msg).unwrap();
                     let notifier = self.notifier.lock();
                     notifier.unwrap()
                             .as_mut()
                             .unwrap()
                             .new_frame_ready();
                 }
                 ApiMsg::ShutDown => {
@@ -509,9 +529,144 @@ impl RenderBackend {
     fn notify_compositor_of_new_scroll_frame(&mut self, composite_needed: bool) {
         // TODO(gw): This is kindof bogus to have to lock the notifier
         //           each time it's used. This is due to some nastiness
         //           in initialization order for Servo. Perhaps find a
         //           cleaner way to do this, or use the OnceMutex on crates.io?
         let mut notifier = self.notifier.lock();
         notifier.as_mut().unwrap().as_mut().unwrap().new_scroll_frame_ready(composite_needed);
     }
+
+
+    #[cfg(not(feature = "debugger"))]
+    fn get_docs_for_debugger(&self) -> String {
+        String::new()
+    }
+
+    #[cfg(feature = "debugger")]
+    fn traverse_items<'a>(&self,
+                          traversal: &mut BuiltDisplayListIter<'a>,
+                          node: &mut debug_server::TreeNode) {
+        loop {
+            let subtraversal = {
+                let item = match traversal.next() {
+                    Some(item) => item,
+                    None => break,
+                };
+
+                match *item.item() {
+                    display_item @ SpecificDisplayItem::PushStackingContext(..) => {
+                        let mut subtraversal = item.sub_iter();
+                        let mut child_node = debug_server::TreeNode::new(&display_item.debug_string());
+                        self.traverse_items(&mut subtraversal, &mut child_node);
+                        node.add_child(child_node);
+                        Some(subtraversal)
+                    }
+                    SpecificDisplayItem::PopStackingContext => {
+                        return;
+                    }
+                    display_item => {
+                        node.add_item(&display_item.debug_string());
+                        None
+                    }
+                }
+            };
+
+            // If flatten_item created a sub-traversal, we need `traversal` to have the
+            // same state as the completed subtraversal, so we reinitialize it here.
+            if let Some(subtraversal) = subtraversal {
+                *traversal = subtraversal;
+            }
+        }
+    }
+
+    #[cfg(feature = "debugger")]
+    fn get_docs_for_debugger(&self) -> String {
+        let mut docs = debug_server::DocumentList::new();
+
+        for (_, doc) in &self.documents {
+            let mut debug_doc = debug_server::TreeNode::new("document");
+
+            for (_, display_list) in &doc.scene.display_lists {
+                let mut debug_dl = debug_server::TreeNode::new("display_list");
+                self.traverse_items(&mut display_list.iter(), &mut debug_dl);
+                debug_doc.add_child(debug_dl);
+            }
+
+            docs.add(debug_doc);
+        }
+
+        serde_json::to_string(&docs).unwrap()
+    }
 }
+
+#[cfg(feature = "debugger")]
+trait ToDebugString {
+    fn debug_string(&self) -> String;
+}
+
+#[cfg(feature = "debugger")]
+impl ToDebugString for SpecificDisplayItem {
+    fn debug_string(&self) -> String {
+        match *self {
+            SpecificDisplayItem::Image(..) => {
+                String::from("image")
+            }
+            SpecificDisplayItem::YuvImage(..) => {
+                String::from("yuv_image")
+            }
+            SpecificDisplayItem::Text(..) => {
+                String::from("text")
+            }
+            SpecificDisplayItem::Rectangle(..) => {
+                String::from("rectangle")
+            }
+            SpecificDisplayItem::Line(..) => {
+                String::from("line")
+            }
+            SpecificDisplayItem::Gradient(..) => {
+                String::from("gradient")
+            }
+            SpecificDisplayItem::RadialGradient(..) => {
+                String::from("radial_gradient")
+            }
+            SpecificDisplayItem::BoxShadow(..) => {
+                String::from("box_shadow")
+            }
+            SpecificDisplayItem::Border(..) => {
+                String::from("border")
+            }
+            SpecificDisplayItem::PushStackingContext(..) => {
+                String::from("push_stacking_context")
+            }
+            SpecificDisplayItem::Iframe(..) => {
+                String::from("iframe")
+            }
+            SpecificDisplayItem::Clip(..) => {
+                String::from("clip")
+            }
+            SpecificDisplayItem::ScrollFrame(..) => {
+                String::from("scroll_frame")
+            }
+            SpecificDisplayItem::StickyFrame(..) => {
+                String::from("sticky_frame")
+            }
+            SpecificDisplayItem::PushNestedDisplayList => {
+                String::from("push_nested_display_list")
+            }
+            SpecificDisplayItem::PopNestedDisplayList => {
+                String::from("pop_nested_display_list")
+            }
+            SpecificDisplayItem::SetGradientStops => {
+                String::from("set_gradient_stops")
+            }
+            SpecificDisplayItem::PopStackingContext => {
+                String::from("pop_stacking_context")
+            }
+            SpecificDisplayItem::PushTextShadow(..) => {
+                String::from("push_text_shadow")
+            }
+            SpecificDisplayItem::PopTextShadow => {
+                String::from("pop_text_shadow")
+            }
+        }
+    }
+}
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -161,16 +161,17 @@ pub struct CacheMaskTask {
 pub struct RenderTaskData {
     pub data: [f32; FLOATS_PER_RENDER_TASK_INFO],
 }
 
 #[derive(Debug, Clone)]
 pub enum RenderTaskKind {
     Alpha(AlphaRenderTask),
     CachePrimitive(PrimitiveIndex),
+    BoxShadow(PrimitiveIndex),
     CacheMask(CacheMaskTask),
     VerticalBlur(DeviceIntLength),
     HorizontalBlur(DeviceIntLength),
     Readback(DeviceIntRect),
     Alias(RenderTaskId),
 }
 
 #[derive(Debug, Clone)]
@@ -212,17 +213,17 @@ impl RenderTask {
 
     pub fn new_box_shadow(key: BoxShadowPrimitiveCacheKey,
                           size: DeviceIntSize,
                           prim_index: PrimitiveIndex) -> RenderTask {
         RenderTask {
             cache_key: Some(RenderTaskKey::BoxShadow(key)),
             children: Vec::new(),
             location: RenderTaskLocation::Dynamic(None, size),
-            kind: RenderTaskKind::CachePrimitive(prim_index),
+            kind: RenderTaskKind::BoxShadow(prim_index),
         }
     }
 
     pub fn new_readback(screen_rect: DeviceIntRect) -> RenderTask {
         RenderTask {
             cache_key: None,
             children: Vec::new(),
             location: RenderTaskLocation::Dynamic(None, screen_rect.size),
@@ -339,28 +340,30 @@ impl RenderTask {
 
         blur_task_h
     }
 
     pub fn as_alpha_batch_mut<'a>(&'a mut self) -> &'a mut AlphaRenderTask {
         match self.kind {
             RenderTaskKind::Alpha(ref mut task) => task,
             RenderTaskKind::CachePrimitive(..) |
+            RenderTaskKind::BoxShadow(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::HorizontalBlur(..) |
             RenderTaskKind::Alias(..) => unreachable!(),
         }
     }
 
     pub fn as_alpha_batch<'a>(&'a self) -> &'a AlphaRenderTask {
         match self.kind {
             RenderTaskKind::Alpha(ref task) => task,
             RenderTaskKind::CachePrimitive(..) |
+            RenderTaskKind::BoxShadow(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::HorizontalBlur(..) |
             RenderTaskKind::Alias(..) => unreachable!(),
         }
     }
 
@@ -391,17 +394,18 @@ impl RenderTask {
                         0.0,
                         0.0,
                         0.0,
                         0.0,
                         0.0,
                     ],
                 }
             }
-            RenderTaskKind::CachePrimitive(..) => {
+            RenderTaskKind::CachePrimitive(..) |
+            RenderTaskKind::BoxShadow(..) => {
                 let (target_rect, target_index) = self.get_target_rect();
                 RenderTaskData {
                     data: [
                         target_rect.origin.x as f32,
                         target_rect.origin.y as f32,
                         target_rect.size.width as f32,
                         target_rect.size.height as f32,
                         target_index.0 as f32,
@@ -495,17 +499,20 @@ impl RenderTask {
 
     pub fn target_kind(&self) -> RenderTargetKind {
         match self.kind {
             RenderTaskKind::Alpha(..) |
             RenderTaskKind::CachePrimitive(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::HorizontalBlur(..) => RenderTargetKind::Color,
-            RenderTaskKind::CacheMask(..) => RenderTargetKind::Alpha,
+
+            RenderTaskKind::CacheMask(..) |
+            RenderTaskKind::BoxShadow(..) => RenderTargetKind::Alpha,
+
             RenderTaskKind::Alias(..) => {
                 panic!("BUG: target_kind() called on invalidated task");
             }
         }
     }
 
     pub fn set_alias(&mut self, id: RenderTaskId) {
         debug_assert!(self.cache_key.is_some());
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -12,26 +12,26 @@
 #[cfg(not(feature = "debugger"))]
 use api::ApiMsg;
 #[cfg(not(feature = "debugger"))]
 use api::channel::MsgSender;
 use api::DebugCommand;
 use debug_colors;
 use debug_render::DebugRenderer;
 #[cfg(feature = "debugger")]
-use debug_server::{self, DebugMsg, DebugServer};
+use debug_server::{self, DebugServer};
 use device::{DepthFunction, Device, FrameId, Program, Texture, VertexDescriptor, GpuMarker, GpuProfiler, PBOId};
 use device::{GpuTimer, TextureFilter, VAO, VertexUsageHint, FileWatcherHandler, TextureTarget, ShaderError};
 use device::{ExternalTexture, get_gl_format_bgra, TextureSlot, VertexAttribute, VertexAttributeKind};
 use euclid::{Transform3D, rect};
 use frame_builder::FrameBuilderConfig;
 use gleam::gl;
 use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
 use internal_types::{FastHashMap, CacheTextureId, RendererFrame, ResultMsg, TextureUpdateOp};
-use internal_types::{TextureUpdateList, RenderTargetMode, TextureUpdateSource};
+use internal_types::{DebugOutput, TextureUpdateList, RenderTargetMode, TextureUpdateSource};
 use internal_types::{BatchTextures, ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, SourceTexture};
 use profiler::{Profiler, BackendProfileCounters};
 use profiler::{GpuProfileTag, RendererProfileTimers, RendererProfileCounters};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use render_task::RenderTaskTree;
 #[cfg(feature = "debugger")]
 use serde_json;
@@ -1582,90 +1582,91 @@ impl Renderer {
                     // pressure event.
                     if cancel_rendering {
                         self.current_frame = None;
                     }
                 }
                 ResultMsg::RefreshShader(path) => {
                     self.pending_shader_updates.push(path);
                 }
+                ResultMsg::DebugOutput(output) => {
+                    match output {
+                        DebugOutput::FetchDocuments(string) => {
+                            self.debug_server.send(string);
+                        }
+                    }
+                }
                 ResultMsg::DebugCommand(command) => {
                     self.handle_debug_command(command);
                 }
             }
         }
     }
 
     #[cfg(not(feature = "debugger"))]
-    fn update_debug_server(&self) {
+    fn get_passes_for_debugger(&self) -> String {
         // Avoid unused param warning.
         let _ = &self.debug_server;
+        String::new()
     }
 
     #[cfg(feature = "debugger")]
-    fn update_debug_server(&self) {
-        while let Ok(msg) = self.debug_server.debug_rx.try_recv() {
-            match msg {
-                DebugMsg::FetchPasses(sender) => {
-                    let mut debug_passes = debug_server::PassList::new();
+    fn get_passes_for_debugger(&self) -> String {
+        let mut debug_passes = debug_server::PassList::new();
 
-                    if let Some(frame) = self.current_frame.as_ref().and_then(|frame| frame.frame.as_ref()) {
-                        for pass in &frame.passes {
-                            let mut debug_pass = debug_server::Pass::new();
-
-                            for target in &pass.alpha_targets.targets {
-                                let mut debug_target = debug_server::Target::new("A8");
+        if let Some(frame) = self.current_frame.as_ref().and_then(|frame| frame.frame.as_ref()) {
+            for pass in &frame.passes {
+                let mut debug_pass = debug_server::Pass::new();
 
-                                debug_target.add(debug_server::BatchKind::Clip, "Clear", target.clip_batcher.border_clears.len());
-                                debug_target.add(debug_server::BatchKind::Clip, "Borders", target.clip_batcher.borders.len());
-                                debug_target.add(debug_server::BatchKind::Clip, "Rectangles", target.clip_batcher.rectangles.len());
-                                for (_, items) in target.clip_batcher.images.iter() {
-                                    debug_target.add(debug_server::BatchKind::Clip, "Image mask", items.len());
-                                }
+                for target in &pass.alpha_targets.targets {
+                    let mut debug_target = debug_server::Target::new("A8");
 
-                                debug_pass.add(debug_target);
-                            }
-
-                            for target in &pass.color_targets.targets {
-                                let mut debug_target = debug_server::Target::new("RGBA8");
+                    debug_target.add(debug_server::BatchKind::Clip, "Clear", target.clip_batcher.border_clears.len());
+                    debug_target.add(debug_server::BatchKind::Clip, "Borders", target.clip_batcher.borders.len());
+                    debug_target.add(debug_server::BatchKind::Clip, "Rectangles", target.clip_batcher.rectangles.len());
+                    for (_, items) in target.clip_batcher.images.iter() {
+                        debug_target.add(debug_server::BatchKind::Clip, "Image mask", items.len());
+                    }
+                    debug_target.add(debug_server::BatchKind::Cache, "Box Shadow", target.box_shadow_cache_prims.len());
 
-                                debug_target.add(debug_server::BatchKind::Cache, "Vertical Blur", target.vertical_blurs.len());
-                                debug_target.add(debug_server::BatchKind::Cache, "Horizontal Blur", target.horizontal_blurs.len());
-                                debug_target.add(debug_server::BatchKind::Cache, "Box Shadow", target.box_shadow_cache_prims.len());
-                                debug_target.add(debug_server::BatchKind::Cache, "Text Shadow", target.text_run_cache_prims.len());
-                                debug_target.add(debug_server::BatchKind::Cache, "Lines", target.line_cache_prims.len());
+                    debug_pass.add(debug_target);
+                }
+
+                for target in &pass.color_targets.targets {
+                    let mut debug_target = debug_server::Target::new("RGBA8");
 
-                                for batch in target.alpha_batcher
-                                                   .batch_list
-                                                   .opaque_batch_list
-                                                   .batches
-                                                   .iter()
-                                                   .rev() {
-                                    debug_target.add(debug_server::BatchKind::Opaque, batch.key.kind.debug_name(), batch.instances.len());
-                                }
+                    debug_target.add(debug_server::BatchKind::Cache, "Vertical Blur", target.vertical_blurs.len());
+                    debug_target.add(debug_server::BatchKind::Cache, "Horizontal Blur", target.horizontal_blurs.len());
+                    debug_target.add(debug_server::BatchKind::Cache, "Text Shadow", target.text_run_cache_prims.len());
+                    debug_target.add(debug_server::BatchKind::Cache, "Lines", target.line_cache_prims.len());
 
-                                for batch in &target.alpha_batcher
-                                                    .batch_list
-                                                    .alpha_batch_list
-                                                    .batches {
-                                    debug_target.add(debug_server::BatchKind::Alpha, batch.key.kind.debug_name(), batch.instances.len());
-                                }
-
-                                debug_pass.add(debug_target);
-                            }
-
-                            debug_passes.add(debug_pass);
-                        }
+                    for batch in target.alpha_batcher
+                                       .batch_list
+                                       .opaque_batch_list
+                                       .batches
+                                       .iter()
+                                       .rev() {
+                        debug_target.add(debug_server::BatchKind::Opaque, batch.key.kind.debug_name(), batch.instances.len());
                     }
 
-                    let json = serde_json::to_string(&debug_passes).unwrap();
-                    sender.send(json).ok();
+                    for batch in &target.alpha_batcher
+                                        .batch_list
+                                        .alpha_batch_list
+                                        .batches {
+                        debug_target.add(debug_server::BatchKind::Alpha, batch.key.kind.debug_name(), batch.instances.len());
+                    }
+
+                    debug_pass.add(debug_target);
                 }
+
+                debug_passes.add(debug_pass);
             }
         }
+
+        serde_json::to_string(&debug_passes).unwrap()
     }
 
     fn handle_debug_command(&mut self, command: DebugCommand) {
         match command {
             DebugCommand::EnableProfiler(enable) => {
                 if enable {
                     self.debug_flags.insert(PROFILER_DBG);
                 } else {
@@ -1681,18 +1682,20 @@ impl Renderer {
             }
             DebugCommand::EnableRenderTargetDebug(enable) => {
                 if enable {
                     self.debug_flags.insert(RENDER_TARGET_DBG);
                 } else {
                     self.debug_flags.remove(RENDER_TARGET_DBG);
                 }
             }
-            DebugCommand::Flush => {
-                self.update_debug_server();
+            DebugCommand::FetchDocuments => {}
+            DebugCommand::FetchPasses => {
+                let json = self.get_passes_for_debugger();
+                self.debug_server.send(json);
             }
         }
     }
 
     /// Set a callback for handling external images.
     pub fn set_external_image_handler(&mut self, handler: Box<ExternalImageHandler>) {
         self.external_image_handler = Some(handler);
     }
@@ -1834,17 +1837,16 @@ impl Renderer {
                             // Create a new native texture, as requested by the texture cache.
                             let texture = self.device.create_texture(TextureTarget::Array);
                             self.texture_resolver.cache_texture_map.push(texture);
                         }
                         let texture = &mut self.texture_resolver.cache_texture_map[cache_texture_index];
 
                         // Ensure no PBO is bound when creating the texture storage,
                         // or GL will attempt to read data from there.
-                        self.device.bind_pbo(None);
                         self.device.init_texture(texture,
                                                  width,
                                                  height,
                                                  format,
                                                  filter,
                                                  mode,
                                                  layer_count,
                                                  None);
@@ -1877,27 +1879,27 @@ impl Renderer {
                         self.device.update_texture_from_pbo(texture,
                                                             rect.origin.x,
                                                             rect.origin.y,
                                                             rect.size.width,
                                                             rect.size.height,
                                                             layer_index,
                                                             stride,
                                                             0);
+
+                        // Ensure that other texture updates won't read from this PBO.
+                        self.device.bind_pbo(None);
                     }
                     TextureUpdateOp::Free => {
                         let texture = &mut self.texture_resolver.cache_texture_map[update.id.0];
                         self.device.free_texture_storage(texture);
                     }
                 }
             }
         }
-
-        // Ensure that other texture updates won't read from this PBO.
-        self.device.bind_pbo(None);
     }
 
     fn draw_instanced_batch<T>(&mut self,
                                data: &[T],
                                vertex_array_kind: VertexArrayKind,
                                textures: &BatchTextures) {
         for i in 0..textures.colors.len() {
             self.texture_resolver.bind(&textures.colors[i], TextureSampler::color(i), &mut self.device);
@@ -2159,26 +2161,16 @@ impl Renderer {
 
             if !target.horizontal_blurs.is_empty() {
                 self.draw_instanced_batch(&target.horizontal_blurs,
                                           VertexArrayKind::Blur,
                                           &BatchTextures::no_texture());
             }
         }
 
-        // Draw any box-shadow caches for this target.
-        if !target.box_shadow_cache_prims.is_empty() {
-            self.device.set_blend(false);
-            let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_BOX_SHADOW);
-            self.cs_box_shadow.bind(&mut self.device, projection);
-            self.draw_instanced_batch(&target.box_shadow_cache_prims,
-                                      VertexArrayKind::CacheBoxShadow,
-                                      &BatchTextures::no_texture());
-        }
-
         // Draw any textrun caches for this target. For now, this
         // is only used to cache text runs that are to be blurred
         // for text-shadow support. In the future it may be worth
         // considering using this for (some) other text runs, since
         // it removes the overhead of submitting many small glyphs
         // to multiple tiles in the normal text run case.
         if !target.text_run_cache_prims.is_empty() {
             self.device.set_blend(true);
@@ -2292,16 +2284,26 @@ impl Renderer {
             // performance penalty on other GPU types - we should test this
             // and consider different code paths.
             let clear_color = [1.0, 1.0, 1.0, 0.0];
             self.device.clear_target_rect(Some(clear_color),
                                           None,
                                           target.used_rect());
         }
 
+        // Draw any box-shadow caches for this target.
+        if !target.box_shadow_cache_prims.is_empty() {
+            self.device.set_blend(false);
+            let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_BOX_SHADOW);
+            self.cs_box_shadow.bind(&mut self.device, projection);
+            self.draw_instanced_batch(&target.box_shadow_cache_prims,
+                                      VertexArrayKind::CacheBoxShadow,
+                                      &BatchTextures::no_texture());
+        }
+
         // Draw the clip items into the tiled alpha mask.
         {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_CLIP);
 
             // If we have border corner clips, the first step is to clear out the
             // area in the clip mask. This allows drawing multiple invididual clip
             // in regions below.
             if !target.clip_batcher.border_clears.is_empty() {
@@ -2858,9 +2860,11 @@ impl Default for RendererOptions {
 #[cfg(not(feature = "debugger"))]
 pub struct DebugServer;
 
 #[cfg(not(feature = "debugger"))]
 impl DebugServer {
     pub fn new(_: MsgSender<ApiMsg>) -> DebugServer {
         DebugServer
     }
+
+    pub fn send(&mut self, _: String) {}
 }
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -1,28 +1,31 @@
 /* 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 app_units::Au;
 use device::TextureFilter;
 use frame::FrameId;
 use glyph_cache::GlyphCache;
 use gpu_cache::{GpuCache, GpuCacheHandle};
 use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList};
 use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::mem;
 use std::sync::Arc;
 use texture_cache::{TextureCache, TextureCacheHandle};
 use api::{BlobImageRenderer, BlobImageDescriptor, BlobImageError, BlobImageRequest};
 use api::{BlobImageResources, BlobImageData, ResourceUpdates, ResourceUpdate, AddFont};
 use api::{DevicePoint, DeviceUintRect, DeviceUintSize};
-use api::{Epoch, FontInstance, FontKey, FontTemplate};
+use api::{Epoch, FontInstance, FontInstanceKey, FontKey, FontTemplate};
+use api::{FontInstanceOptions, FontInstancePlatformOptions};
+use api::{ColorF, FontRenderMode, SubpixelDirection};
 use api::{GlyphDimensions, GlyphKey, IdNamespace};
 use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
 use api::{TileOffset, TileSize};
 use api::{ExternalImageData, ExternalImageType};
 use rayon::ThreadPool;
 use glyph_rasterizer::{GlyphRasterizer, GlyphRequest};
 
 const DEFAULT_TILE_SIZE: TileSize = 512;
@@ -165,16 +168,17 @@ impl Into<BlobImageRequest> for ImageReq
             key: self.key,
             tile: self.tile,
         }
     }
 }
 
 struct Resources {
     font_templates: FastHashMap<FontKey, FontTemplate>,
+    font_instances: FastHashMap<FontInstanceKey, FontInstance>,
     image_templates: ImageTemplates,
 }
 
 impl BlobImageResources for Resources {
     fn get_font_data(&self, key: FontKey) -> &FontTemplate {
         self.font_templates.get(&key).unwrap()
     }
     fn get_image(&self, key: ImageKey) -> Option<(&ImageData, &ImageDescriptor)> {
@@ -208,16 +212,17 @@ impl ResourceCache {
     pub fn new(texture_cache: TextureCache,
                workers: Arc<ThreadPool>,
                blob_image_renderer: Option<Box<BlobImageRenderer>>) -> ResourceCache {
         ResourceCache {
             cached_glyphs: GlyphCache::new(),
             cached_images: ResourceClassCache::new(),
             resources: Resources {
                 font_templates: FastHashMap::default(),
+                font_instances: FastHashMap::default(),
                 image_templates: ImageTemplates::new(),
             },
             cached_glyph_dimensions: FastHashMap::default(),
             texture_cache,
             state: State::Idle,
             current_frame_id: FrameId(0),
             pending_image_requests: FastHashSet::default(),
             glyph_rasterizer: GlyphRasterizer::new(workers),
@@ -274,16 +279,23 @@ impl ResourceCache {
                         AddFont::Native(id, native_font_handle) => {
                             self.add_font_template(id, FontTemplate::Native(native_font_handle));
                         }
                     }
                 }
                 ResourceUpdate::DeleteFont(font) => {
                     self.delete_font_template(font);
                 }
+                ResourceUpdate::AddFontInstance(instance) => {
+                    self.add_font_instance(instance.key, instance.font_key, instance.glyph_size,
+                                           instance.options, instance.platform_options);
+                }
+                ResourceUpdate::DeleteFontInstance(instance) => {
+                    self.delete_font_instance(instance);
+                }
             }
         }
     }
 
     pub fn add_font_template(&mut self, font_key: FontKey, template: FontTemplate) {
         // Push the new font to the font renderer, and also store
         // it locally for glyph metric requests.
         self.glyph_rasterizer.add_font(font_key, template.clone());
@@ -293,16 +305,50 @@ impl ResourceCache {
     pub fn delete_font_template(&mut self, font_key: FontKey) {
         self.glyph_rasterizer.delete_font(font_key);
         self.resources.font_templates.remove(&font_key);
         if let Some(ref mut r) = self.blob_image_renderer {
             r.delete_font(font_key);
         }
     }
 
+    pub fn add_font_instance(&mut self,
+                             instance_key: FontInstanceKey,
+                             font_key: FontKey,
+                             glyph_size: Au,
+                             options: Option<FontInstanceOptions>,
+                             platform_options: Option<FontInstancePlatformOptions>) {
+        let mut render_mode = FontRenderMode::Subpixel;
+        let mut subpx_dir = SubpixelDirection::Horizontal;
+        if let Some(options) = options {
+            render_mode = options.render_mode;
+            if render_mode == FontRenderMode::Mono {
+                subpx_dir = SubpixelDirection::None;
+            }
+        }
+        let instance = FontInstance::new(font_key,
+                                         glyph_size,
+                                         ColorF::new(0.0, 0.0, 0.0, 1.0),
+                                         render_mode,
+                                         subpx_dir,
+                                         platform_options);
+        self.resources.font_instances.insert(instance_key, instance);
+    }
+
+    pub fn delete_font_instance(&mut self, instance_key: FontInstanceKey) {
+        self.resources.font_instances.remove(&instance_key);
+        if let Some(ref mut r) = self.blob_image_renderer {
+            r.delete_font_instance(instance_key);
+        }
+    }
+
+    pub fn get_font_instance(&self, instance_key: FontInstanceKey) -> Option<&FontInstance> {
+        self.resources.font_instances.get(&instance_key)
+    }
+
     pub fn add_image_template(&mut self,
                               image_key: ImageKey,
                               descriptor: ImageDescriptor,
                               mut data: ImageData,
                               mut tiling: Option<TileSize>) {
         if tiling.is_none() && self.should_tile(&descriptor, &data) {
             // We aren't going to be able to upload a texture this big, so tile it, even
             // if tiling was not requested.
@@ -602,23 +648,21 @@ impl ResourceCache {
         self.glyph_rasterizer.resolve_glyphs(
             &mut self.cached_glyphs,
             &mut self.texture_cache,
             gpu_cache,
             texture_cache_profile,
         );
 
         // Apply any updates of new / updated images (incl. blobs) to the texture cache.
-        self.update_texture_cache(gpu_cache, texture_cache_profile);
-        self.texture_cache.end_frame();
+        self.update_texture_cache(gpu_cache);
+        self.texture_cache.end_frame(texture_cache_profile);
     }
 
-    fn update_texture_cache(&mut self,
-                            gpu_cache: &mut GpuCache,
-                            _texture_cache_profile: &mut TextureCacheProfileCounters) {
+    fn update_texture_cache(&mut self, gpu_cache: &mut GpuCache) {
         for request in self.pending_image_requests.drain() {
             let image_template = self.resources.image_templates.get_mut(request.key).unwrap();
             debug_assert!(image_template.data.uses_texture_cache());
 
             let image_data = match image_template.data {
                 ImageData::Raw(..) | ImageData::External(..) => {
                     // Safe to clone here since the Raw image data is an
                     // Arc, and the external image data is small.
@@ -662,17 +706,17 @@ impl ResourceCache {
                 // The tiled image could be stored on the CPU as one large image or be
                 // already broken up into tiles. This affects the way we compute the stride
                 // and offset.
                 let tiled_on_cpu = image_template.data.is_blob();
 
                 let (stride, offset) = if tiled_on_cpu {
                     (image_descriptor.stride, 0)
                 } else {
-                    let bpp = image_descriptor.format.bytes_per_pixel().unwrap();
+                    let bpp = image_descriptor.format.bytes_per_pixel();
                     let stride = image_descriptor.compute_stride();
                     let offset = image_descriptor.offset + tile.y as u32 * tile_size as u32 * stride
                                                          + tile.x as u32 * tile_size as u32 * bpp;
                     (Some(stride), offset)
                 };
 
                 ImageDescriptor {
                     width: actual_width,
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -122,9 +122,18 @@ impl Scene {
             epoch,
             viewport_size,
             content_size,
             background_color,
         };
 
         self.pipeline_map.insert(pipeline_id, new_pipeline);
     }
+
+    pub fn remove_pipeline(&mut self,
+                           pipeline_id: PipelineId) {
+        if self.root_pipeline_id == Some(pipeline_id) {
+            self.root_pipeline_id = None;
+        }
+        self.display_lists.remove(&pipeline_id);
+        self.pipeline_map.remove(&pipeline_id);
+    }
 }
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use device::TextureFilter;
 use frame::FrameId;
 use freelist::{FreeList, FreeListHandle, UpsertResult, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle};
 use internal_types::{SourceTexture, TextureUpdate, TextureUpdateOp};
 use internal_types::{CacheTextureId, RenderTargetMode, TextureUpdateList, TextureUpdateSource};
+use profiler::{ResourceProfileCounter, TextureCacheProfileCounters};
 use resource_cache::CacheItem;
 use std::cmp;
 use std::mem;
 use api::{ExternalImageType, ImageData, ImageFormat};
 use api::{DeviceUintRect, DeviceUintSize, DeviceUintPoint};
 use api::{ImageDescriptor};
 
 // The fixed number of layers for the shared texture cache.
@@ -218,18 +219,23 @@ impl TextureCache {
             shared_entry_handles: Vec::new(),
         }
     }
 
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         self.frame_id = frame_id;
     }
 
-    pub fn end_frame(&mut self) {
+    pub fn end_frame(&mut self, texture_cache_profile: &mut TextureCacheProfileCounters) {
         self.expire_old_standalone_entries();
+
+        self.array_a8.update_profile(&mut texture_cache_profile.pages_a8);
+        self.array_rg8.update_profile(&mut texture_cache_profile.pages_rg8);
+        self.array_rgb8.update_profile(&mut texture_cache_profile.pages_rgb8);
+        self.array_rgba8.update_profile(&mut texture_cache_profile.pages_rgba8);
     }
 
     // Request an item in the texture cache. All images that will
     // be used on a frame *must* have request() called on their
     // handle, to update the last used timestamp and ensure
     // that resources are not flushed from the cache too early.
     //
     // Returns true if the image needs to be uploaded to the
@@ -830,16 +836,28 @@ impl TextureArray {
         TextureArray {
             format,
             is_allocated: false,
             regions: Vec::new(),
             texture_id: None,
         }
     }
 
+    fn update_profile(&self, counter: &mut ResourceProfileCounter) {
+        if self.is_allocated {
+            let size = TEXTURE_ARRAY_LAYERS as u32 *
+                       TEXTURE_LAYER_DIMENSIONS *
+                       TEXTURE_LAYER_DIMENSIONS *
+                       self.format.bytes_per_pixel();
+            counter.set(TEXTURE_ARRAY_LAYERS as usize, size as usize);
+        } else {
+            counter.set(0, 0);
+        }
+    }
+
     // Allocate space in this texture array.
     fn alloc(&mut self,
              width: u32,
              height: u32,
              user_data: [f32; 2],
              frame_id: FrameId) -> Option<CacheEntry> {
         // Lazily allocate the regions if not already created.
         // This means that very rarely used image formats can be
@@ -952,17 +970,17 @@ impl TextureUpdate {
                             id: ext_image.id,
                             channel_index: ext_image.channel_index,
                         }
                     }
                 }
             }
             ImageData::Raw(bytes) => {
                 let finish = descriptor.offset +
-                             descriptor.width * descriptor.format.bytes_per_pixel().unwrap_or(0) +
+                             descriptor.width * descriptor.format.bytes_per_pixel() +
                              (descriptor.height-1) * descriptor.compute_stride();
                 assert!(bytes.len() >= finish as usize);
 
                 TextureUpdateSource::Bytes {
                     data: bytes
                 }
             }
         };
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -17,17 +17,17 @@ use render_task::{RenderTaskAddress, Ren
 use render_task::{RenderTaskLocation, RenderTaskTree};
 use renderer::BlendMode;
 use renderer::ImageBufferKind;
 use resource_cache::ResourceCache;
 use std::{f32, i32, usize};
 use texture_allocator::GuillotineAllocator;
 use util::{TransformedRect, TransformedRectKind};
 use api::{BuiltDisplayList, ClipAndScrollInfo, ClipId, ColorF, DeviceIntPoint, ImageKey};
-use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize, FontInstance};
+use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize};
 use api::{ExternalImageType, FilterOp, FontRenderMode, ImageRendering, LayerRect};
 use api::{LayerToWorldTransform, MixBlendMode, PipelineId, PropertyBinding, TransformStyle};
 use api::{TileOffset, WorldToLayerTransform, YuvColorSpace, YuvFormat, LayerVector2D};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(i32::MAX as u32);
 
@@ -39,17 +39,17 @@ trait AlphaBatchHelpers {
                       metadata: &PrimitiveMetadata) -> BlendMode;
 }
 
 impl AlphaBatchHelpers for PrimitiveStore {
     fn get_blend_mode(&self, needs_blending: bool, metadata: &PrimitiveMetadata) -> BlendMode {
         match metadata.prim_kind {
             PrimitiveKind::TextRun => {
                 let text_run_cpu = &self.cpu_text_runs[metadata.cpu_prim_index.0];
-                match text_run_cpu.normal_render_mode {
+                match text_run_cpu.font.render_mode {
                     FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color),
                     FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha,
                 }
             }
             PrimitiveKind::Image |
             PrimitiveKind::AlignedGradient |
             PrimitiveKind::AngleGradient |
             PrimitiveKind::RadialGradient => {
@@ -448,27 +448,22 @@ impl AlphaRenderItem {
                         };
 
                         let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         batch.push(base_instance.build(uv_address.as_int(gpu_cache), 0, 0));
                     }
                     PrimitiveKind::TextRun => {
                         let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
-                        let font_size_dp = text_cpu.logical_font_size.scale_by(ctx.device_pixel_ratio);
 
                         // TODO(gw): avoid / recycle this allocation in the future.
                         let mut instances = Vec::new();
 
-                        let font = FontInstance::new(text_cpu.font_key,
-                                                     font_size_dp,
-                                                     text_cpu.color,
-                                                     text_cpu.normal_render_mode,
-                                                     text_cpu.glyph_options,
-                                                     text_cpu.subpx_dir);
+                        let mut font = text_cpu.font.clone();
+                        font.size = font.size.scale_by(ctx.device_pixel_ratio);
 
                         let texture_id = ctx.resource_cache.get_glyphs(font,
                                                                        &text_cpu.glyph_keys,
                                                                        |index, handle| {
                             let uv_address = handle.as_int(gpu_cache);
                             instances.push(base_instance.build(index as i32, uv_address, 0));
                         });
 
@@ -571,18 +566,19 @@ impl AlphaRenderItem {
                         batch.push(base_instance.build(uv_rect_addresses[0],
                                                        uv_rect_addresses[1],
                                                        uv_rect_addresses[2]));
                     }
                     PrimitiveKind::BoxShadow => {
                         let box_shadow = &ctx.prim_store.cpu_box_shadows[prim_metadata.cpu_prim_index.0];
                         let cache_task_id = prim_metadata.render_task_id.unwrap();
                         let cache_task_address = render_tasks.get_task_address(cache_task_id);
+                        let textures = BatchTextures::render_target_cache();
 
-                        let key = AlphaBatchKey::new(AlphaBatchKind::BoxShadow, flags, blend_mode, no_textures);
+                        let key = AlphaBatchKey::new(AlphaBatchKind::BoxShadow, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
 
                         for rect_index in 0..box_shadow.rects.len() {
                             batch.push(base_instance.build(rect_index as i32,
                                                            cache_task_address.0 as i32, 0));
                         }
                     }
                 }
@@ -901,17 +897,16 @@ impl<T: RenderTarget> RenderTargetList<T
 
         (origin, RenderTargetIndex(self.targets.len() - 1))
     }
 }
 
 /// A render target represents a number of rendering operations on a surface.
 pub struct ColorRenderTarget {
     pub alpha_batcher: AlphaBatcher,
-    pub box_shadow_cache_prims: Vec<BoxShadowCacheInstance>,
     // List of text runs to be cached to this render target.
     // TODO(gw): For now, assume that these all come from
     //           the same source texture id. This is almost
     //           always true except for pathological test
     //           cases with more than 4k x 4k of unique
     //           glyphs visible. Once the future glyph / texture
     //           cache changes land, this restriction will
     //           be removed anyway.
@@ -928,17 +923,16 @@ pub struct ColorRenderTarget {
 impl RenderTarget for ColorRenderTarget {
     fn allocate(&mut self, size: DeviceUintSize) -> Option<DeviceUintPoint> {
         self.allocator.allocate(&size)
     }
 
     fn new(size: DeviceUintSize) -> ColorRenderTarget {
         ColorRenderTarget {
             alpha_batcher: AlphaBatcher::new(),
-            box_shadow_cache_prims: Vec::new(),
             text_run_cache_prims: Vec::new(),
             line_cache_prims: Vec::new(),
             text_run_textures: BatchTextures::no_texture(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
             readbacks: Vec::new(),
             allocator: TextureAllocator::new(size),
         }
@@ -988,26 +982,19 @@ impl RenderTarget for ColorRenderTarget 
                 self.horizontal_blurs.push(BlurCommand {
                     task_id: task_id.0 as i32,
                     src_task_id: task.children[0].0 as i32,
                     blur_direction: BlurDirection::Horizontal as i32,
                 });
             }
             RenderTaskKind::CachePrimitive(prim_index) => {
                 let prim_metadata = ctx.prim_store.get_metadata(prim_index);
-
                 let prim_address = prim_metadata.gpu_location.as_int(gpu_cache);
 
                 match prim_metadata.prim_kind {
-                    PrimitiveKind::BoxShadow => {
-                        self.box_shadow_cache_prims.push(BoxShadowCacheInstance {
-                            prim_address: gpu_cache.get_address(&prim_metadata.gpu_location),
-                            task_index: render_tasks.get_task_address(task_id),
-                        });
-                    }
                     PrimitiveKind::TextShadow => {
                         let prim = &ctx.prim_store.cpu_text_shadows[prim_metadata.cpu_prim_index.0];
 
                         // todo(gw): avoid / recycle this allocation...
                         let mut instances = Vec::new();
 
                         let task_index = render_tasks.get_task_address(task_id);
 
@@ -1021,24 +1008,19 @@ impl RenderTarget for ColorRenderTarget 
                                                                         0);     // z is disabled for rendering cache primitives
 
                             match sub_metadata.prim_kind {
                                 PrimitiveKind::TextRun => {
                                     // Add instances that reference the text run GPU location. Also supply
                                     // the parent text-shadow prim address as a user data field, allowing
                                     // the shader to fetch the text-shadow parameters.
                                     let text = &ctx.prim_store.cpu_text_runs[sub_metadata.cpu_prim_index.0];
-                                    let font_size_dp = text.logical_font_size.scale_by(ctx.device_pixel_ratio);
 
-                                    let font = FontInstance::new(text.font_key,
-                                                                 font_size_dp,
-                                                                 text.color,
-                                                                 text.shadow_render_mode,
-                                                                 text.glyph_options,
-                                                                 text.subpx_dir);
+                                    let mut font = text.font.clone();
+                                    font.size = font.size.scale_by(ctx.device_pixel_ratio);
 
                                     let texture_id = ctx.resource_cache.get_glyphs(font,
                                                                                    &text.glyph_keys,
                                                                                    |index, handle| {
                                         let uv_address = handle.as_int(gpu_cache);
                                         instances.push(instance.build(index as i32,
                                                                       uv_address,
                                                                       prim_address));
@@ -1068,39 +1050,42 @@ impl RenderTarget for ColorRenderTarget 
                         }
                     }
                     _ => {
                         // No other primitives make use of primitive caching yet!
                         unreachable!()
                     }
                 }
             }
-            RenderTaskKind::CacheMask(..) => {
+            RenderTaskKind::CacheMask(..) |
+            RenderTaskKind::BoxShadow(..) => {
                 panic!("Should not be added to c