Merge m-c to inbound, a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 25 Sep 2017 16:43:51 -0700
changeset 433615 be79515b9f2afc703b5bcfc14546d9dad7692437
parent 433614 1861531b8dc468415313e0429a567f6ff3317902 (current diff)
parent 433573 e6b3498a39b94616ba36798fe0b71a3090b1b14c (diff)
child 433616 6602b0670805a8303607ea45bebcbb15a59ee222
push id8114
push userjlorenzo@mozilla.com
push dateThu, 02 Nov 2017 16:33:21 +0000
treeherdermozilla-beta@73e0d89a540f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound, a=merge MozReview-Commit-ID: 8wTTCUkrY2i
devtools/client/framework/test/browser_devtools_shim.js
devtools/client/webide/content/simulator.js
devtools/client/webide/content/simulator.xhtml
devtools/client/webide/modules/simulator-process.js
devtools/client/webide/modules/simulators.js
devtools/client/webide/test/addons/fxos_1_0_simulator-linux.xpi
devtools/client/webide/test/addons/fxos_1_0_simulator-linux64.xpi
devtools/client/webide/test/addons/fxos_1_0_simulator-mac64.xpi
devtools/client/webide/test/addons/fxos_1_0_simulator-win32.xpi
devtools/client/webide/test/addons/fxos_2_0_simulator-linux.xpi
devtools/client/webide/test/addons/fxos_2_0_simulator-linux64.xpi
devtools/client/webide/test/addons/fxos_2_0_simulator-mac64.xpi
devtools/client/webide/test/addons/fxos_2_0_simulator-win32.xpi
devtools/client/webide/test/addons/fxos_3_0_simulator-linux.xpi
devtools/client/webide/test/addons/fxos_3_0_simulator-linux64.xpi
devtools/client/webide/test/addons/fxos_3_0_simulator-mac64.xpi
devtools/client/webide/test/addons/fxos_3_0_simulator-win32.xpi
devtools/client/webide/test/addons/fxos_3_0_tv_simulator-linux.xpi
devtools/client/webide/test/addons/fxos_3_0_tv_simulator-linux64.xpi
devtools/client/webide/test/addons/fxos_3_0_tv_simulator-mac64.xpi
devtools/client/webide/test/addons/fxos_3_0_tv_simulator-win32.xpi
devtools/client/webide/test/addons/simulators.json
devtools/client/webide/test/test_simulators.html
devtools/client/webide/themes/simulator.css
devtools/shared/apps/Simulator.jsm
dom/base/Element.h
dom/base/nsDocument.cpp
dom/bindings/BindingUtils.cpp
python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
testing/web-platform/meta/navigation-timing/nav2_test_document_open.html.ini
testing/web-platform/meta/navigation-timing/nav2_test_document_replaced.html.ini
testing/web-platform/meta/navigation-timing/nav2_test_frame_removed.html.ini
testing/web-platform/meta/navigation-timing/nav2_test_instance_accessible_from_the_start.html.ini
testing/web-platform/meta/navigation-timing/nav2_test_navigate_within_document.html.ini
testing/web-platform/meta/navigation-timing/nav2_test_navigation_type_backforward.html.ini
testing/web-platform/meta/navigation-timing/nav2_test_navigation_type_reload.html.ini
testing/web-platform/meta/navigation-timing/nav2_test_redirect_server.html.ini
testing/web-platform/meta/navigation-timing/nav2_test_redirect_xserver.html.ini
testing/web-platform/meta/navigation-timing/nav2_test_unloadEvents_no_previous_document.html.ini
testing/web-platform/meta/navigation-timing/nav2_test_unloadEvents_previous_document_cross_origin.sub.html.ini
testing/web-platform/meta/navigation-timing/nav2_test_unloadEvents_with_previous_document.html.ini
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -257,38 +257,48 @@ var BrowserPageActions = {
 
     if (iframeNode) {
       action.onIframeShown(iframeNode, panelNode);
     }
 
     return panelNode;
   },
 
+  /**
+   * Returns the node in the urlbar to which popups for the given action should
+   * be anchored.  If the action is null, a sensible anchor is returned.
+   *
+   * @param  action (PageActions.Action, optional)
+   *         The action you want to anchor.
+   * @return (DOM node, nonnull) The node to which the action should be
+   *         anchored.
+   */
   panelAnchorNodeForAction(action) {
     // Try each of the following nodes in order, using the first that's visible.
     let potentialAnchorNodeIDs = [
-      action.anchorIDOverride || null,
-      this._urlbarButtonNodeIDForActionID(action.id),
+      action && action.anchorIDOverride,
+      action && this._urlbarButtonNodeIDForActionID(action.id),
       this.mainButtonNode.id,
       "identity-icon",
     ];
     let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIDOMWindowUtils);
     for (let id of potentialAnchorNodeIDs) {
       if (id) {
         let node = document.getElementById(id);
         if (node && !node.hidden) {
           let bounds = dwu.getBoundsWithoutFlushing(node);
           if (bounds.height > 0 && bounds.width > 0) {
             return node;
           }
         }
       }
     }
-    throw new Error(`PageActions: No anchor node for '${action.id}'`);
+    let id = action ? action.id : "<no action>";
+    throw new Error(`PageActions: No anchor node for ${id}`);
   },
 
   get activatedActionPanelNode() {
     return document.getElementById(this._activatedActionPanelID);
   },
 
   get _activatedActionPanelID() {
     return "pageActionActivatedActionPanel";
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -297,31 +297,23 @@ var StarUI = {
     let forms = gNavigatorBundle.getString("editBookmark.removeBookmarks.label");
     let bookmarksCount = this._itemGuids.length;
     let label = PluralForm.get(bookmarksCount, forms)
                           .replace("#1", bookmarksCount);
     this._element("editBookmarkPanelRemoveButton").label = label;
 
     this.beginBatch();
 
-    if (aAnchorElement) {
-      // Set the open=true attribute if the anchor is a
-      // descendent of a toolbarbutton.
-      let parent = aAnchorElement.parentNode;
-      while (parent) {
-        if (parent.localName == "toolbarbutton") {
-          break;
-        }
-        parent = parent.parentNode;
-      }
-      if (parent) {
-        this._anchorToolbarButton = parent;
-        parent.setAttribute("open", "true");
-      }
+    if (aAnchorElement && aAnchorElement.closest("#urlbar")) {
+      this._anchorToolbarButton = aAnchorElement;
+      aAnchorElement.setAttribute("open", "true");
+    } else {
+      this._anchorToolbarButton = null;
     }
+
     let onPanelReady = fn => {
       let target = this.panel;
       if (target.parentNode) {
         // By targeting the panel's parent and using a capturing listener, we
         // can have our listener called before others waiting for the panel to
         // be shown (which probably expect the panel to be fully initialized)
         target = target.parentNode;
       }
@@ -329,23 +321,16 @@ var StarUI = {
         fn();
       }, {"capture": true, "once": true});
     };
     gEditItemOverlay.initPanel({ node: aNode,
                                  onPanelReady,
                                  hiddenRows: ["description", "location",
                                               "loadInSidebar", "keyword"],
                                  focusedElement: "preferred"});
-
-    if (aAnchorElement && aAnchorElement.id == BookmarkingUI.STAR_BOX_ID) {
-      aAnchorElement.setAttribute("open", "true");
-      this.panel.addEventListener("popuphiding", () => {
-        aAnchorElement.removeAttribute("open");
-      });
-    }
     this.panel.openPopup(aAnchorElement, aPosition);
   },
 
   panelShown:
   function SU_panelShown(aEvent) {
     if (aEvent.target == this.panel) {
       if (this._element("editBookmarkPanelContent").hidden) {
         // Note this isn't actually used anymore, we should remove this
@@ -1366,28 +1351,18 @@ var BookmarkingUI = {
   },
 
   get starBox() {
     delete this.starBox;
     return this.starBox = document.getElementById(this.STAR_BOX_ID);
   },
 
   get anchor() {
-    // Try to anchor the panel to:
-    // 1. The bookmarks star box (using the star itself is trickier because it
-    //    can be hidden while the star animation is visible)
-    // 2. The identity icon
-    if (this.starBox && isVisible(this.starBox)) {
-      return this.starBox;
-    }
-    let identityIcon = document.getElementById("identity-icon");
-    if (identityIcon && isVisible(identityIcon)) {
-      return identityIcon;
-    }
-    return null;
+    let action = PageActions.actionForID(PageActions.ACTION_ID_BOOKMARK);
+    return BrowserPageActions.panelAnchorNodeForAction(action);
   },
 
   get notifier() {
     delete this.notifier;
     return this.notifier = document.getElementById("bookmarked-notification-anchor");
   },
 
   get dropmarkerNotifier() {
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -1,18 +1,18 @@
 /* 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/. */
 
 /* import-globals-from preferences.js */
 /* import-globals-from ../../../../toolkit/mozapps/preferences/fontbuilder.js */
 /* import-globals-from ../../../base/content/aboutDialog-appUpdater.js */
 
-XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPreferencesManager",
-                                  "resource://gre/modules/ExtensionPreferencesManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSettingsStore",
+                                  "resource://gre/modules/ExtensionSettingsStore.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/Downloads.jsm");
 Components.utils.import("resource://gre/modules/FileUtils.jsm");
 Components.utils.import("resource:///modules/ShellService.jsm");
 Components.utils.import("resource:///modules/TransientPrefs.jsm");
@@ -211,16 +211,27 @@ var gMainPane = {
     this.updatePerformanceSettingsBox({ duringChangeEvent: false });
 
     // set up the "use current page" label-changing listener
     this._updateUseCurrentButton();
     window.addEventListener("focus", this._updateUseCurrentButton.bind(this));
 
     this.updateBrowserStartupLastSession();
 
+    handleControllingExtension("url_overrides", "newTabURL");
+    let newTabObserver = {
+      observe(subject, topic, data) {
+          handleControllingExtension("url_overrides", "newTabURL");
+      },
+    };
+    Services.obs.addObserver(newTabObserver, "newtab-url-changed");
+    window.addEventListener("unload", () => {
+      Services.obs.removeObserver(newTabObserver, "newtab-url-changed");
+    });
+
     if (AppConstants.platform == "win") {
       // Functionality for "Show tabs in taskbar" on Windows 7 and up.
       try {
         let sysInfo = Cc["@mozilla.org/system-info;1"].
           getService(Ci.nsIPropertyBag2);
         let ver = parseFloat(sysInfo.getProperty("version"));
         let showTabsInTaskbar = document.getElementById("showTabsInTaskbar");
         showTabsInTaskbar.hidden = ver < 6.1;
@@ -244,19 +255,21 @@ var gMainPane = {
     }
     setEventListener("useCurrent", "command",
       gMainPane.setHomePageToCurrent);
     setEventListener("useBookmark", "command",
       gMainPane.setHomePageToBookmark);
     setEventListener("restoreDefaultHomePage", "command",
       gMainPane.restoreDefaultHomePage);
     setEventListener("disableHomePageExtension", "command",
-                     gMainPane.makeDisableControllingExtension("homepage_override"));
+                     gMainPane.makeDisableControllingExtension("prefs", "homepage_override"));
     setEventListener("disableContainersExtension", "command",
-                     gMainPane.makeDisableControllingExtension("privacy.containers"));
+                     gMainPane.makeDisableControllingExtension("prefs", "privacy.containers"));
+    setEventListener("disableNewTabExtension", "command",
+                     gMainPane.makeDisableControllingExtension("url_overrides", "newTabURL"));
     setEventListener("chooseLanguage", "command",
       gMainPane.showLanguages);
     setEventListener("translationAttributionImage", "click",
       gMainPane.openTranslationProviderAttribution);
     setEventListener("translateButton", "command",
       gMainPane.showTranslationExceptions);
     setEventListener("font.language.group", "change",
       gMainPane._rebuildFonts);
@@ -490,17 +503,17 @@ var gMainPane = {
   readBrowserContainersCheckbox() {
     const pref = document.getElementById("privacy.userContext.enabled");
     const settings = document.getElementById("browserContainersSettings");
 
     settings.disabled = !pref.value;
     const containersEnabled = Services.prefs.getBoolPref("privacy.userContext.enabled");
     const containersCheckbox = document.getElementById("browserContainersCheckbox");
     containersCheckbox.checked = containersEnabled;
-    handleControllingExtension("privacy.containers")
+    handleControllingExtension("prefs", "privacy.containers")
       .then((isControlled) => {
         containersCheckbox.disabled = isControlled;
       });
   },
 
   /**
    * Show the Containers UI depending on the privacy.userContext.ui.enabled pref.
    */
@@ -662,17 +675,17 @@ var gMainPane = {
 
   syncFromHomePref() {
     let homePref = document.getElementById("browser.startup.homepage");
 
     // Set the "Use Current Page(s)" button's text and enabled state.
     this._updateUseCurrentButton();
 
     // This is an async task.
-    handleControllingExtension("homepage_override")
+    handleControllingExtension("prefs", "homepage_override")
       .then((isControlled) => {
         // Disable or enable the inputs based on if this is controlled by an extension.
         document.querySelectorAll("#browserHomePage, .homepage-button")
           .forEach((button) => {
             button.disabled = isControlled;
           });
       });
 
@@ -756,17 +769,17 @@ var gMainPane = {
     let tabs = this._getTabsForHomePage();
 
     if (tabs.length > 1)
       useCurrent.label = useCurrent.getAttribute("label2");
     else
       useCurrent.label = useCurrent.getAttribute("label1");
 
     // If the homepage is controlled by an extension then you can't use this.
-    if (await getControllingExtensionId("homepage_override")) {
+    if (await getControllingExtensionId("prefs", "homepage_override")) {
       useCurrent.disabled = true;
       return;
     }
 
     // In this case, the button's disabled state is set by preferences.xml.
     let prefName = "pref.browser.homepage.disable_button.current_page";
     if (document.getElementById(prefName).locked)
       return;
@@ -804,19 +817,19 @@ var gMainPane = {
   /**
    * Restores the default home page as the user's home page.
    */
   restoreDefaultHomePage() {
     var homePage = document.getElementById("browser.startup.homepage");
     homePage.value = homePage.defaultValue;
   },
 
-  makeDisableControllingExtension(pref) {
+  makeDisableControllingExtension(type, settingName) {
     return async function disableExtension() {
-      let id = await getControllingExtensionId(pref);
+      let id = await getControllingExtensionId(type, settingName);
       let addon = await AddonManager.getAddonByID(id);
       addon.userDisabled = true;
     };
   },
 
   /**
    * Utility function to enable/disable the button specified by aButtonID based
    * on the value of the Boolean preference specified by aPreferenceID.
@@ -2648,36 +2661,38 @@ function getLocalHandlerApp(aFile) {
   localHandlerApp.executable = aFile;
 
   return localHandlerApp;
 }
 
 let extensionControlledContentIds = {
   "privacy.containers": "browserContainersExtensionContent",
   "homepage_override": "browserHomePageExtensionContent",
+  "newTabURL": "browserNewTabExtensionContent",
 };
 
 /**
   * Check if a pref is being managed by an extension.
   */
-function getControllingExtensionId(settingName) {
-  return ExtensionPreferencesManager.getControllingExtensionId(settingName);
+async function getControllingExtensionId(type, settingName) {
+  await ExtensionSettingsStore.initialize();
+  return ExtensionSettingsStore.getTopExtensionId(type, settingName);
 }
 
 function getControllingExtensionEl(settingName) {
   return document.getElementById(extensionControlledContentIds[settingName]);
 }
 
-async function handleControllingExtension(prefName) {
-  let controllingExtensionId = await getControllingExtensionId(prefName);
+async function handleControllingExtension(type, settingName) {
+  let controllingExtensionId = await getControllingExtensionId(type, settingName);
 
   if (controllingExtensionId) {
-    showControllingExtension(prefName, controllingExtensionId);
+    showControllingExtension(settingName, controllingExtensionId);
   } else {
-    hideControllingExtension(prefName);
+    hideControllingExtension(settingName);
   }
 
   return !!controllingExtensionId;
 }
 
 async function showControllingExtension(settingName, extensionId) {
   let extensionControlledContent = getControllingExtensionEl(settingName);
   // Tell the user what extension is controlling the setting.
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -334,26 +334,32 @@
              id="browserStartupHomePage"/>
       <radio label="&startupBlankPage.label;"
              value="0"
              id="browserStartupBlank"/>
       <radio label="&startupPrevSession.label;"
              value="3"
              id="browserStartupLastSession"/>
     </radiogroup>
+    <hbox id="browserNewTabExtensionContent" align="center" hidden="true">
+      <description control="disableNewTabExtension" flex="1" />
+      <button id="disableNewTabExtension"
+              class="extension-controlled-button accessory-button"
+              label="&disableExtension.label;" />
+    </hbox>
   </vbox>
 </groupbox>
 
 <!-- Home Page -->
 <groupbox id="homepageGroup"
           data-category="paneGeneral"
           hidden="true">
   <caption><label>&homepage2.label;</label></caption>
 
-  <hbox id="browserHomePageExtensionContent" align="center">
+  <hbox id="browserHomePageExtensionContent" align="center" hidden="true">
     <description control="disableHomePageExtension" flex="1" />
     <button id="disableHomePageExtension"
             class="extension-controlled-button accessory-button"
             label="&disableExtension.label;" />
   </hbox>
 
   <vbox>
     <textbox id="browserHomePage"
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f11db0b6a8625b2587eb33bd272f35180690a771
GIT binary patch
literal 5210
zc$|ee1yodR(|74k3CX2PknYq)VqsZaP(Vav=|;L6mIe_i2}N3v5D}?GML>j=?h>S1
zI{)Q;&wsx6^*`VFpL?D=cjlRyJ9pxqGXsa>;*kIVKm>pSaG?nlK@|#=03c0yMF9Xi
zq>HnNtFsMAnzCy<1`g98Ct<qcCfCx0+{HrGt4&0Jy_ea!O=AI`jhe0+08}TDo;}9L
z;$WoaU0ne1;{||_2mm<2l0udNz*7_eR?PuGJ_7(4Twi?DSHvC=SU@!(z~$AI-&~xE
zr4V{(>8caX;WL3G*j7YrZ2^EnUJIgT;4{9F73^v{<k_LJ)N<%`U#LsSxW>`4!jie;
zInU%Q#{evpIOkTX<c1Co0w;pKFh0&nlUQ_9ho*{$xe=s;gByXPg~+nIWh?Q_lIGJa
zIzmT@V=g^d>p9&SRH*xD@VDpp`Zrs=^)5%#gpqc2PlF>%TP04A`<n`fL9JVp{usH#
zPxz*2{IlSw1^>~pK~$NiXb~(~h>A(=7Jp9uTf)~5@R1JL6TRKDhtfk_cosYX#GOmj
z)&OqFd;Sw?`$Hn$65}CpF%XT=nm#k5+vjwW$;8eX95*y;pCje63G;Gm+-%2h4{@4@
zLJT1$NLFuZ&b(XIu`LcmGnNp?CRH)o9`cehhcCy35H?`-j^aTmUYQD^SyfA`eth-L
zP{llHvaLNHKV3>8Y7%#F;kw4#G7*Wy-}C6hQdW%!CCcx`TR%WCJtZwL2pcAb<VWc0
zdDryqn;P0klS6nAwR{$n+rxA;fegv+TUF!S#heo5(&buk;Lc(NpOPeBxgRm=87f5z
z_2zoCqG~2eF<(uzTXT90VuRrN%6O&L43f*D#+FZ?!dGBLH!=aN7>#9&Jg06WxVWLl
z#)uUn=X8L4XUga93+P(L;m%?zy{J_ux<Oz}5W!DI8q(dxrE2jDr;{v3wwGLPcc?(J
zoIa&^GTA>I@4D6-N5)1CYE==;{zIZ$XmK_A4&_Tb8ZJC(K1Y(%pu;vr$R<G(4mqwY
zQ6W-ka$*prFBK8%%nJ?jV|!r8x%=AZWp+g!uMBGkRVWQQ6D_M0(U{bHlI?2&3%G$>
z<*lyd!-n=mPZBfYFC(lJ{8qyg>Bx^V(IxZKv7PmycKD;&QJwXyBh6`O8KiHR;D+i9
zUWYuxoV?qMxjGS{5KmLdQ~^xI0d0FU`rbJTCuZ@E>!29Np%<jZ5PY1%89+MH$XWn<
zb!@2zHw>w0XyGGjxP#kj^aSCxz-dJMJvRbqRNoN0&Y@~yg}ZuM+179yG7O?it$M)0
zxrVnyQ5`xl7VwD{PkoRL*GuoyCQI@>`)5_Ji#ScxCx(`k3{vay?Zhm1;gJv177#Z4
zBG{)z(IU=W{;v<;+?#q_Z%B7dA|Xcae#qVA<!v9`Ue1!Dke6DFj;cl!@=AS^jC3h-
z7T?2B0jWFnCAZ*Fou4gU5UN|kY}}CsKUO$z5Hj&Te*37eZBAefho3~d9_46#)_hsu
zTB8tcD(o7ueqq-A<=*lR)$IkfS?OfFTXJxfF6H+n_Vo*jU?ElB1Y(B$@;bek&f<_V
zVy69&NSowsTi4D?{Z!-PsOm?Cd2TE=qNy-Yg8;nofD%tlm1^0~U<>rV&6athgPz3k
z_I~-jjT10YMxC|7c8aOF%K%P1E2nF$4c8e@Cl5b`EzTSmj{AfwHQRp)rvja%rl2pV
z_LB%yMK>gq18(TfB9gf8q;L^-&AtI?rIh#}V*EQN#0*E-tz|3-FIaV-0QsG;g&p4b
zv1)zr+8{~E!>K51g6b|Lb13;z_6GlGera2R^foE43I_;)$CXghZohCRQWIAdeVNY7
z8^U}``<p{(cdyKZzH4TLwnIwHf*XENm(ru`aN3wKdonJ{@X0PnyTU2lAP+J0Hm0)=
zje(~>So!@DOatR4vE{kLhHHGLp~<F7WqjkSB7w<@Po(cHGs@9{!4NJ2bsM5xMoyXY
zl&9>(q$jV+L>OKxR6YNwZy)-(6Z#dU=AOm`X*m-E=go6Jq8HN>Fkns%?ku<;dH*}E
z3|W~}1I=*VQuYw{j!Kzd5;&(!;Ai=`p51KX!E;#-_=ALuBnjUT2X%<S#uVqv%08wS
zKiPA768KHj-#ebW@v6k%jP0B+x#yX97pZ7R;$aQdtG?mekn;(bjg*DwLBa13Mk=Mx
z5P~OxmLdJ7?2(mPBqw2I(}Yhv7RJzSVKGGngC--g&a|A5inmSuRhHfPRq*{ldK@C3
zmQ-6br2}&u7fR3~vvdZJ5Sp6zLd0iDQ8!+04=MrYyrHcmVnID+>%Fc4<WyoPq;jT&
z3IB0H$#fp9IC#M?mm-ghCu^~O41AR4#);vYBz(dQmsHygM^hT?yA`Z>(P_;0jNPa#
zD5ajkG_mjAnxH<WFm_?2r0Aq!Ii#Cyo(oQN<p6PqX;O23wyN2QZz&tT_`<Bl25zxe
zsG|%lDRI8<u%*7wY&lgAXTd@yyu;YlgUs^rx}swU$%Do+e9XQ<#X0e|y&3egB1H8y
z5s!FHzndwbULEvNoyDd3z7*lss|MM^G!z4G5_l_at%zGQY23S~j2^;xcTFW>)dXw^
z9A7JQ(7&`3BTMkCS;MpY;)y_?BB|+b#v@E(y!%X>%H&Y<6(isQ@tTO>0Fp1+^-qn?
zyUengjGPf&0x9E>_o0fhx;)ig1$bRtqZeGG1}dw?Z6=+CW=}g1_E`_KBxR1u4z5%G
z<^$I+EWN#~7`375NRmm)8Mc-TFQA6^6a{|;I}_20v^{$^e=Qvui_CBp%VIJwL3T4|
zF&T-Ny{@<ZRfNC?G}#q&xbIa<xSL}hT73UKhkdhL`!CscD<6|Mg6od<fP!oM>?r7L
zdQ-eUfMBKaY>9TbUbZqD;>-EKj%nYKDxdThaT|9y8sjh^tKYOzyOZS6F<En679l9s
z$H>cqb=c7vm4Z*a!K+`wm+0I^9odf(f$KlIyzX3lII70$@N)Sgkky~NGS++}%{Fj?
zB1blmrlUJntRm5MgD1oG&87@Am^Yr*dAP!ktC9AYIrPz@-3fHayx>*5HzZN4scz54
z;#IZr-1wa)yIR-S?wW<@NcL4OtS*H>^Fwjkoc`%nq(bco&sTcDysb5KLjffqGVy$@
z)xqv#?S_&r`IECf8JUhj0`u^}nNoX>{ei4SdQmgrgkX%6j6E`GmDyqvoVUgvEjUsx
zD6Lsq$M;sB)7CzDZ~FbA_^G$BXKeTTV+~5N%g8o_Crgjl+1%79S5Ah}P>~0w*Q&JH
z=>gV>$&+D>v+SGN<Ca@%r^633=?cL7XbQ00x6j;FwIMltr=I6zSh`m7df=4GWEv!#
z);W&}TvzV|E%NUW=aUXhRhrbA;%dB8H=j79cGlOtezFQ~PYB+DeLVeamBnUirTvXQ
zR#snOF!;yTMv(Nf!VxKL;$7@-&q0_=`G?W&HPhI)4<=w1k!?i;K`EQ#7s;%$9|s}Y
zf`;LsiJ8Xst*Ma(!;w~rcN-&vN^U+n3iEetH@r#me0uwLFhhkj0;RO`uy#G~Rr4p5
ziL`^t@9XnH-jx}Y&O|>ZR9i5mZTA2_!cNNVfk8#dASps~%&xuUHY>uwgwMI;PDQX$
z^vU61<=W~*9ru?POA@{_DmLvs>up6ay7?xe0PA?`x5y1$qG$&aW5Qmb*Dti7zz%ft
zQVA@6JKmVwG^hA@h_Tir?RvILI0SKS%rG18gWfzXFEu_UnbNyJHX}@St`jq)k1kNp
z@cJV{ICXH3S*lPwzvF=;U{7imh|Ty}QzancEtR%Tc|y^w?A{*dq_Ei-)u88*AoH&F
zCc0vOds{fJDyWD<vGyW*`rSupJ2~|pyR~gc<5<a#2f=a_$~53QObdUtxQk`9R;;dm
z!`iKBxVYsMl;rVZ`bf{8<g7b^9J2T@5n+13Q!gG=8uOLvX4nI1TyJ|%4&tFvc)Rf1
zufmN)JDeG|WLM`{zQR|>>A%wynD|^yb%?=?MP`?hKcmVLm<j4a!o++GYg6D6U96Hq
zG_kX~dAid52Jkqy&nV_Bx&4!3Ia-OMclhvU=a`3a)=}@r4en~(iHB*)IjaZMoVf}8
zl;!!whfgJ)W{`b-yyo)Q!QCRy2K2K>__e>+&qLNAfvNMkZneu4+LT1brZQtq*>7JA
z;M^?j<yrU^iK&z)Ny(cR+rRR+wxKJzIbZJA!O)A#vRorZ&L^Ifh|1iQJ^w?OAA7Uy
zKh;-tUvrmNF{lmP#ygMBKy^ozEzateD_u-%ZE)n+9@`b!jHqP7ZD$sXgwK}A;?Ls=
zZsq7EjOvsP$LRX=n%2&`G<}mrwNbEG<vZbKY198G<eam3wSV9Il^7hlXddRV^!|Q0
z@-#h$d8I^P+uDyqwjdPFI}p9|iB)Z;tr5{E)BJqXUIUjMaqV+Ciovm?9EFUvpgMZp
zIIguZy_YrPy)EL##hUpYBIqzb|0d#7Oc8#*yvF2qt=Z6eD@g})-*+lpr%Q!I2D0FD
zA?lmA%4#EZFqQj9rG=>VH`m?je(B6%r3C4zz4_D@D|Il5GSOa5q?|r<d^2cTIVp)f
zL3`DRm#kY)SEhG()fPqQY?gdRcKG6IpObk81+p<wgsv)5`%Vkw_@yC~X@egr;Mqk5
z@MP-a@gGD~36;=(YP4l*OA!fcx$RQAnnSb!<GsB<elWl`AV&w(ct^D}8q}FVTKLSC
zV?zzlZ@tKN7ked7Q|YNclziD9Yv52;YD85n<!9~@M7HBN*$eLs6}L|1Fn|CW%3jQ!
zaA{krIseNB-(^gmuT8Rt>yxyK&hWQqcF&lZKDVkH$BR`+Z_qw+Kj{1{rx`u(@P3{z
zX}0`b6sxg(UFTVlHiQb4dP*DmG#S3$wRM{U8vW*-U)OXk?BWFxAi9=9QKX*Mqja(8
zu;>GIc8ou(l(wd-!5M6ipKcP}ooLNvHtGL3<6IdhU|5{JNQZLwo6-&bfZ7UZO}c4H
zm)rcfKKSq}+huKK+asf27iZt(w;C_&l*in*o{BF#r|cce=<N<vIZslDugbpXdKlA|
zmD$S?@Mh9=6ZHs<Z@Ckk&^Ye>CdlFv+gS2=s2h1$KK8JZM?SH_0zh0;Oh#B#N?2Ul
zKvY6rN=#l<N=Q^(UQ{%2#TfN30VfwrJ8R!R1w`c~B>onl5cn{F6#%r<VUWsO=3#Jb
zgMIC<23z9YO{n<=&qz5IlLBFzXn+N9dhBdxZRPGEV(;$aY-m6X;6BZ6GQQfLKBNE$
zZy(F|m$+e455*@iRm4^x$)~ZwJVwgg07@iLf1UFUmB;#&x-(Oe(oW4(2icic%R(Hl
zhbY)pwF1F3etejpRb62N^ghXWT5?Ccf$5QJRPvJY)#*lCnPN_DO=pVYXx`|X<sSM3
z%F#78zIvOh=N}qD;?%dZj%er|3XO0faS%k@T7~NG_~|ge4w&yWdkPZTQA&7oI&tJA
z(~)oKj_pN@hrG2zl&7mxwc+i3T=>663LWHz9I4o^*vA0?Vl4Hpx*9}RUBt<<@UE`I
zq$pMM+I_MWp2+>xTqv;<ai}z2|6M<mcf+H1x$NSto2K_hycfLE$rpxGQ}(T=)2BfN
z>o`^~CQ)AlNK+n{?UDM2=Ei<7Vq{rQ6~b)e9_A&5MRL)(P9w&ar-o==f%hs|SRS>h
z(wW&-!d*d%FeBtwPSx->>2>7wHabc=cg6SNc#h@uUo#F`f9$p~hJeL)66%DgT_nyE
z1sa5h`YcvjRE(1dbzb8+CLC+IrvkSJ=}ro9hi`we!mUp_oGo1<k9Xhe6!Oh4q_u!^
zJyKDcG~LY^X;JfC3oN!+x1w>3H-SMgvzAh|vuv1pR<?(^Pj?gb2lpTedm`EyhqERh
z-q`JzfGY0V4D-%P?rD#Gpm6g4CVgr#Q*_LXPneZTkdmP)bS=GKD~l6fIjDh<Lz@|{
zKi*KZj{_Qz{7%AMQtOrd>R`D4r!4)v*`bQTw>9mwRA$Y-qhPghg=>8b)re-pA6xWN
zO{aVfP9lh?wSyK@;Vn$dM`{sKzUWmzo<Kp4_+Dpjfp{?RpImju=IXU7y*&*q1_S=i
zRg1?+2b(7@p3auoZ2c=&ffr52SNo4#g$jPVc@rkwFQg?b(kBG#f$r!Ei@<dBhTz|z
z!jxJ%B;>pL{rZ|=WrBh|LS5f=hoE}GAnlN$XWYCiR?ij|`R(jY+(E#<^C(r>nNJ#v
zNn-aE73t#aVdZ>PIM7~<P%lPnsJlyV1TF{@j=^+dbcHo_^bCI(YGTi{dW7`(VItpd
z@auPW>FH?c>i54Rz-kBKTm%05=<DC>e+OY#Qf#>Yt)3bG;>A7!uR|YW_54xY_<vXT
zAKv=YkUtCNKMO(ikMjAaCI2^C|Fndh2KX~}_<zUl|FPsxm;YyF)BH04a47!O2oS)>
NUQn#EW11`Me*k&^|EvH2
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 support-files =
   head.js
   privacypane_tests_perwindow.js
   site_data_test.html
   addons/set_homepage.xpi
+  addons/set_newtab.xpi
   offline/offline.html
   offline/manifest.appcache
 
 [browser_applications_selection.js]
 skip-if = os == 'linux' # bug 1382057
 [browser_advanced_update.js]
 skip-if = !updater
 [browser_basic_rebuild_fonts_test.js]
@@ -40,16 +41,17 @@ skip-if = os != "win" || (os == "win" &&
 [browser_checkspelling.js]
 [browser_connection.js]
 [browser_connection_bug388287.js]
 [browser_cookies_exceptions.js]
 [browser_defaultbrowser_alwayscheck.js]
 [browser_healthreport.js]
 skip-if = true || !healthreport # Bug 1185403 for the "true"
 [browser_homepages_filter_aboutpreferences.js]
+[browser_extension_controlled.js]
 [browser_layersacceleration.js]
 [browser_masterpassword.js]
 [browser_notifications_do_not_disturb.js]
 [browser_password_management.js]
 [browser_performance.js]
 skip-if = !e10s
 [browser_performance_e10srollout.js]
 skip-if = !e10s
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_extension_controlled.js
@@ -0,0 +1,131 @@
+XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
+                                   "@mozilla.org/browser/aboutnewtab-service;1",
+                                   "nsIAboutNewTabService");
+
+const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
+const CHROME_URL_ROOT = TEST_DIR + "/";
+
+function getSupportsFile(path) {
+  let cr = Cc["@mozilla.org/chrome/chrome-registry;1"]
+    .getService(Ci.nsIChromeRegistry);
+  let uri = Services.io.newURI(CHROME_URL_ROOT + path);
+  let fileurl = cr.convertChromeURL(uri);
+  return fileurl.QueryInterface(Ci.nsIFileURL);
+}
+
+function installAddon(xpiName) {
+  let filePath = getSupportsFile(`addons/${xpiName}`).file;
+  return new Promise((resolve, reject) => {
+    AddonManager.getInstallForFile(filePath, install => {
+      if (!install) {
+        throw new Error(`An install was not created for ${filePath}`);
+      }
+      install.addListener({
+        onDownloadFailed: reject,
+        onDownloadCancelled: reject,
+        onInstallFailed: reject,
+        onInstallCancelled: reject,
+        onInstallEnded: resolve
+      });
+      install.install();
+    });
+  });
+}
+
+function waitForMessageChange(messageId, cb) {
+  return new Promise((resolve) => {
+    let target = gBrowser.contentDocument.getElementById(messageId);
+    let observer = new MutationObserver(() => {
+      if (cb(target)) {
+        observer.disconnect();
+        resolve();
+      }
+    });
+    observer.observe(target, { attributes: true, attributeFilter: ["hidden"] });
+  });
+}
+
+function waitForMessageHidden(messageId) {
+  return waitForMessageChange(messageId, target => target.hidden);
+}
+
+function waitForMessageShown(messageId) {
+  return waitForMessageChange(messageId, target => !target.hidden);
+}
+
+add_task(async function testExtensionControlledHomepage() {
+  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
+  let doc = gBrowser.contentDocument;
+  is(gBrowser.currentURI.spec, "about:preferences#general",
+     "#general should be in the URI for about:preferences");
+  let homepagePref = () => Services.prefs.getCharPref("browser.startup.homepage");
+  let originalHomepagePref = homepagePref();
+  let extensionHomepage = "https://developer.mozilla.org/";
+  let controlledContent = doc.getElementById("browserHomePageExtensionContent");
+
+  // The homepage is set to the default and editable.
+  ok(originalHomepagePref != extensionHomepage, "homepage is empty by default");
+  is(doc.getElementById("browserHomePage").disabled, false, "The homepage input is enabled");
+  is(controlledContent.hidden, true, "The extension controlled row is hidden");
+
+  // Install an extension that will set the homepage.
+  await installAddon("set_homepage.xpi");
+  await waitForMessageShown("browserHomePageExtensionContent");
+
+  // The homepage has been set by the extension, the user is notified and it isn't editable.
+  let controlledLabel = controlledContent.querySelector("description");
+  is(homepagePref(), extensionHomepage, "homepage is set by extension");
+  // There are two spaces before "set_homepage" because it's " <image /> set_homepage".
+  is(controlledLabel.textContent, "An extension,  set_homepage, controls your home page.",
+     "The user is notified that an extension is controlling the homepage");
+  is(controlledContent.hidden, false, "The extension controlled row is hidden");
+  is(doc.getElementById("browserHomePage").disabled, true, "The homepage input is disabled");
+
+  // Disable the extension.
+  doc.getElementById("disableHomePageExtension").click();
+
+  await waitForMessageHidden("browserHomePageExtensionContent");
+
+  is(homepagePref(), originalHomepagePref, "homepage is set back to default");
+  is(doc.getElementById("browserHomePage").disabled, false, "The homepage input is enabled");
+  is(controlledContent.hidden, true, "The extension controlled row is hidden");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function testExtensionControlledNewTab() {
+  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
+  let doc = gBrowser.contentDocument;
+  is(gBrowser.currentURI.spec, "about:preferences#general",
+     "#general should be in the URI for about:preferences");
+
+  let controlledContent = doc.getElementById("browserNewTabExtensionContent");
+
+  // The new tab is set to the default and message is hidden.
+  ok(!aboutNewTabService.newTabURL.startsWith("moz-extension:"), "new tab is not set");
+  is(controlledContent.hidden, true, "The extension controlled row is hidden");
+
+  // Install an extension that will set the new tab page.
+  await installAddon("set_newtab.xpi");
+
+  await waitForMessageShown("browserNewTabExtensionContent");
+
+  // The new tab page has been set by the extension and the user is notified.
+  let controlledLabel = controlledContent.querySelector("description");
+  ok(aboutNewTabService.newTabURL.startsWith("moz-extension:"), "new tab url is set by extension");
+  // There are two spaces before "set_newtab" because it's " <image /> set_newtab".
+  is(controlledLabel.textContent, "An extension,  set_newtab, controls your New Tab page.",
+     "The user is notified that an extension is controlling the new tab page");
+  is(controlledContent.hidden, false, "The extension controlled row is hidden");
+
+  // Disable the extension.
+  doc.getElementById("disableNewTabExtension").click();
+
+  await waitForMessageHidden("browserNewTabExtensionContent");
+
+  ok(!aboutNewTabService.newTabURL.startsWith("moz-extension:"), "new tab page is set back to default");
+  is(controlledContent.hidden, true, "The extension controlled row is hidden");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
--- a/browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js
+++ b/browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js
@@ -13,99 +13,8 @@ add_task(async function testSetHomepageU
   is(gBrowser.tabs.length, 3, "Three tabs should be open");
   is(Services.prefs.getCharPref("browser.startup.homepage"), "about:blank|about:home",
      "about:blank and about:home should be the only homepages set");
 
   Services.prefs.setCharPref("browser.startup.homepage", oldHomepagePref);
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
-
-const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
-const CHROME_URL_ROOT = TEST_DIR + "/";
-
-function getSupportsFile(path) {
-  let cr = Cc["@mozilla.org/chrome/chrome-registry;1"]
-    .getService(Ci.nsIChromeRegistry);
-  let uri = Services.io.newURI(CHROME_URL_ROOT + path);
-  let fileurl = cr.convertChromeURL(uri);
-  return fileurl.QueryInterface(Ci.nsIFileURL);
-}
-
-function installAddon() {
-  let filePath = getSupportsFile("addons/set_homepage.xpi").file;
-  return new Promise((resolve, reject) => {
-    AddonManager.getInstallForFile(filePath, install => {
-      if (!install) {
-        throw new Error(`An install was not created for ${filePath}`);
-      }
-      install.addListener({
-        onDownloadFailed: reject,
-        onDownloadCancelled: reject,
-        onInstallFailed: reject,
-        onInstallCancelled: reject,
-        onInstallEnded: resolve
-      });
-      install.install();
-    });
-  });
-}
-
-function waitForMessageChange(cb) {
-  return new Promise((resolve) => {
-    let target = gBrowser.contentDocument.getElementById("browserHomePageExtensionContent");
-    let observer = new MutationObserver(() => {
-      if (cb(target)) {
-        observer.disconnect();
-        resolve();
-      }
-    });
-    observer.observe(target, { attributes: true, attributeFilter: ["hidden"] });
-  });
-}
-
-function waitForMessageHidden() {
-  return waitForMessageChange(target => target.hidden);
-}
-
-function waitForMessageShown() {
-  return waitForMessageChange(target => !target.hidden);
-}
-
-add_task(async function testExtensionControlledHomepage() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  let doc = gBrowser.contentDocument;
-  is(gBrowser.currentURI.spec, "about:preferences#general",
-     "#general should be in the URI for about:preferences");
-  let homepagePref = () => Services.prefs.getCharPref("browser.startup.homepage");
-  let originalHomepagePref = homepagePref();
-  let extensionHomepage = "https://developer.mozilla.org/";
-  let controlledContent = doc.getElementById("browserHomePageExtensionContent");
-
-  // The homepage is set to the default and editable.
-  ok(originalHomepagePref != extensionHomepage, "homepage is empty by default");
-  is(doc.getElementById("browserHomePage").disabled, false, "The homepage input is enabled");
-  is(controlledContent.hidden, true, "The extension controlled row is hidden");
-
-  // Install an extension that will set the homepage.
-  await installAddon();
-  await waitForMessageShown();
-
-  // The homepage has been set by the extension, the user is notified and it isn't editable.
-  let controlledLabel = controlledContent.querySelector("description");
-  is(homepagePref(), extensionHomepage, "homepage is set by extension");
-  // There are two spaces before "set_homepage" because it's " <image /> set_homepage".
-  is(controlledLabel.textContent, "An extension,  set_homepage, controls your home page.",
-     "The user is notified that an extension is controlling the homepage");
-  is(controlledContent.hidden, false, "The extension controlled row is hidden");
-  is(doc.getElementById("browserHomePage").disabled, true, "The homepage input is disabled");
-
-  // Disable the extension.
-  doc.getElementById("disableHomePageExtension").click();
-
-  await waitForMessageHidden();
-
-  is(homepagePref(), originalHomepagePref, "homepage is set back to default");
-  is(doc.getElementById("browserHomePage").disabled, false, "The homepage input is enabled");
-  is(controlledContent.hidden, true, "The extension controlled row is hidden");
-
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
--- a/browser/components/preferences/sitePermissions.css
+++ b/browser/components/preferences/sitePermissions.css
@@ -11,8 +11,13 @@
   min-height: 18em;
 }
 
 #siteCol,
 #statusCol,
 #permissionsBox > richlistitem {
   min-height: 35px;
 }
+
+.website-status {
+  margin: 1px;
+  margin-inline-end: 5px;
+}
--- a/browser/components/preferences/sitePermissions.js
+++ b/browser/components/preferences/sitePermissions.js
@@ -148,16 +148,17 @@ var gSitePermissionsManager = {
     hbox.setAttribute("class", "website-name");
     hbox.setAttribute("flex", "3");
     hbox.appendChild(website);
 
     let menulist = document.createElement("menulist");
     let menupopup = document.createElement("menupopup");
     menulist.setAttribute("flex", "1");
     menulist.setAttribute("width", "50");
+    menulist.setAttribute("class", "website-status");
     menulist.appendChild(menupopup);
     let states = SitePermissions.getAvailableStates(permission.type);
     for (let state of states) {
       if (state == SitePermissions.UNKNOWN)
         continue;
       let m = document.createElement("menuitem");
       m.setAttribute("label", this._getCapabilityString(state));
       m.setAttribute("value", state);
--- a/browser/components/preferences/sitePermissions.xul
+++ b/browser/components/preferences/sitePermissions.xul
@@ -39,17 +39,16 @@
     <richlistbox id="permissionsBox" selected="false"
                  hidecolumnpicker="true" flex="1"
                  onkeypress="gSitePermissionsManager.onPermissionKeyPress(event);"
                  onselect="gSitePermissionsManager.onPermissionSelect();">
       <listheader>
         <treecol id="siteCol" label="&treehead.sitename2.label;" flex="3"
                  persist="width" width="50"
                  onclick="gSitePermissionsManager.buildPermissionsList(event.target)"/>
-        <splitter class="tree-splitter"/>
         <treecol id="statusCol" label="&treehead.status.label;" flex="1"
                  persist="width" width="50" data-isCurrentSortCol="true"
                  onclick="gSitePermissionsManager.buildPermissionsList(event.target);"/>
       </listheader>
     </richlistbox>
   </vbox>
   <vbox>
     <hbox class="actionButtons" align="left" flex="1">
--- a/browser/extensions/formautofill/FormAutofillHeuristics.jsm
+++ b/browser/extensions/formautofill/FormAutofillHeuristics.jsm
@@ -471,16 +471,33 @@ this.FormAutofillHeuristics = {
       "cc-name",
       "cc-number",
       "cc-exp-month",
       "cc-exp-year",
       "cc-exp",
     ];
     let regexps = isAutoCompleteOff ? FIELDNAMES_IGNORING_AUTOCOMPLETE_OFF : Object.keys(this.RULES);
 
+    if (!FormAutofillUtils.isAutofillCreditCardsAvailable) {
+      if (isAutoCompleteOff) {
+        if (!this._regexpListOf_CcUnavailable_AcOff) {
+          this._regexpListOf_CcUnavailable_AcOff = regexps.filter(name => !FormAutofillUtils.isCreditCardField(name));
+        }
+        regexps = this._regexpListOf_CcUnavailable_AcOff;
+      } else {
+        if (!this._regexpListOf_CcUnavailable_AcOn) {
+          this._regexpListOf_CcUnavailable_AcOn = regexps.filter(name => !FormAutofillUtils.isCreditCardField(name));
+        }
+        regexps = this._regexpListOf_CcUnavailable_AcOn;
+      }
+    }
+    if (regexps.length == 0) {
+      return null;
+    }
+
     let labelStrings;
     let getElementStrings = {};
     getElementStrings[Symbol.iterator] = function* () {
       yield element.id;
       yield element.name;
       if (!labelStrings) {
         labelStrings = [];
         let labels = LabelUtils.findLabelElements(element);
--- a/browser/extensions/formautofill/test/unit/head.js
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -191,18 +191,20 @@ function objectMatches(object, fields) {
     }
     actual[key] = object[key];
   }
   return ObjectUtils.deepEqual(actual, fields);
 }
 
 add_task(async function head_initialize() {
   Services.prefs.setStringPref("extensions.formautofill.available", "on");
+  Services.prefs.setBoolPref("extensions.formautofill.creditCards.available", true);
   Services.prefs.setBoolPref("extensions.formautofill.heuristics.enabled", true);
   Services.prefs.setBoolPref("dom.forms.autocomplete.formautofill", true);
 
   // Clean up after every test.
   do_register_cleanup(function head_cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.available");
+    Services.prefs.clearUserPref("extensions.formautofill.creditCards.available");
     Services.prefs.clearUserPref("extensions.formautofill.heuristics.enabled");
     Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill");
   });
 });
--- a/browser/installer/allowed-dupes.mn
+++ b/browser/installer/allowed-dupes.mn
@@ -64,20 +64,18 @@ browser/chrome/devtools/skin/images/secu
 browser/chrome/devtools/skin/images/tabs-icon.svg
 browser/chrome/devtools/skin/images/tool-scratchpad.svg
 browser/chrome/devtools/skin/images/tool-storage.svg
 browser/chrome/devtools/skin/images/tool-styleeditor.svg
 browser/chrome/devtools/skin/promisedebugger.css
 browser/chrome/devtools/skin/variables.css
 modules/devtools/Console.jsm
 modules/devtools/Loader.jsm
-modules/devtools/Simulator.jsm
 modules/devtools/shared/Console.jsm
 modules/devtools/shared/Loader.jsm
-modules/devtools/shared/apps/Simulator.jsm
 browser/modules/devtools/client/framework/gDevTools.jsm
 browser/modules/devtools/gDevTools.jsm
 browser/chrome/icons/default/default16.png
 browser/chrome/icons/default/default32.png
 browser/chrome/icons/default/default48.png
 browser/chrome/pdfjs/content/web/images/findbarButton-next-rtl.png
 browser/chrome/pdfjs/content/web/images/findbarButton-next-rtl@2x.png
 browser/chrome/pdfjs/content/web/images/findbarButton-next.png
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -277,12 +277,16 @@ searchResults.needHelp2=Need help? Visit
 
 # LOCALIZATION NOTE %S is the default value of the `dom.ipc.processCount` pref.
 defaultContentProcessCount=%S (default)
 
 # LOCALIZATION NOTE (extensionControlled.homepage_override):
 # This string is shown to notify the user that their home page is being controlled by an extension.
 extensionControlled.homepage_override = An extension, %S, controls your home page.
 
+# LOCALIZATION NOTE (extensionControlled.newTabURL):
+# This string is shown to notify the user that their new tab page is being controlled by an extension.
+extensionControlled.newTabURL = An extension, %S, controls your New Tab page.
+
 # LOCALIZATION NOTE (extensionControlled.privacy.containers):
 # This string is shown to notify the user that Container Tabs are being enabled by an extension
 # %S is the container addon controlling it
-extensionControlled.privacy.containers = An extension, %S, requires Container Tabs.
\ No newline at end of file
+extensionControlled.privacy.containers = An extension, %S, requires Container Tabs.
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -31,17 +31,17 @@
 
 #navigator-toolbox::after {
   content: "";
   display: -moz-box;
   border-bottom: 1px solid var(--toolbox-border-bottom-color);
 }
 
 :root[customizing] #navigator-toolbox::after {
-  display: none;
+  border-bottom-style: none;
 }
 
 /* Bookmark toolbar */
 
 #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar) {
   overflow: -moz-hidden-unscrollable;
   max-height: 4em;
   transition: min-height 170ms ease-out, max-height 170ms ease-out;
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -1364,23 +1364,23 @@ photonpanelmultiview .PanelUI-subView .t
   min-width: auto;
   padding: 4px;
 }
 
 #appMenu-zoom-controls > .subviewbutton {
   margin-inline-start: 10px;
 }
 
-/* An em-based minimum height works better for a text-only button. */
+/* Unset the min-height constraint, because that works better for a text-only button. */
 #appMenu-zoomReset-button {
-  min-height: 2em;
+  min-height: unset;
 }
 
 #appMenu-zoomReset-button > .toolbarbutton-text {
-  min-width: 3em;
+  min-width: calc(3ch + 8px);
   text-align: center;
 }
 
 .toolbaritem-combined-buttons > toolbarseparator[orient="vertical"] + .subviewbutton,
 #appMenu-zoom-controls > toolbarseparator[orient="vertical"] + .subviewbutton {
   margin-inline-start: 0;
 }
 
--- a/browser/themes/shared/incontentprefs/search.css
+++ b/browser/themes/shared/incontentprefs/search.css
@@ -16,16 +16,20 @@
   /* Allow a little visual space to separate the radio from the image above it. */
   margin-top: 10px;
 }
 
 .searchBarShownImage  {
   list-style-image: url("chrome://browser/skin/preferences/in-content/search-bar.svg");
 }
 
+.searchBarImage:-moz-locale-dir(rtl) {
+  transform: scaleX(-1);
+}
+
 #defaultEngine {
  margin-inline-start: 0;
 }
 
 #defaultEngine > .menulist-label-box > .menulist-icon {
   height: 16px;
 }
 
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -222,17 +222,16 @@ def old_configure_options(*options):
     '--enable-thread-sanitizer',
     '--enable-trace-logging',
     '--enable-ubsan-int-overflow',
     '--enable-ui-locale',
     '--enable-universalchardet',
     '--enable-updater',
     '--enable-valgrind',
     '--enable-verify-mar',
-    '--enable-webrtc',
     '--enable-xul',
     '--enable-zipwriter',
     '--includedir',
     '--libdir',
     '--no-create',
     '--prefix',
     '--with-android-distribution-directory',
     '--with-android-max-sdk',
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -369,23 +369,34 @@ def check_compiler(compiler, language, t
     # example)
     if info.language == 'C' and info.language_version != 199901:
         if info.type in ('clang-cl', 'clang', 'gcc'):
             append_flag('-std=gnu99')
 
     # Note: MSVC, while supporting C++11, still reports 199711L for __cplusplus.
     # Note: this is a strict version check because we used to always add
     # -std=gnu++11.
+    draft_cxx14_version = 201300
+    cxx14_version = 201402
     if info.language == 'C++':
-        if info.type in ('clang', 'gcc') and info.language_version != 201103:
-            append_flag('-std=gnu++11')
-        # MSVC 2015 headers include C++14 features, but don't guard them
-        # with appropriate checks.
-        if info.type == 'clang-cl' and info.language_version != 201402:
-            append_flag('-std=c++14')
+        if target.kernel != 'WINNT':
+            if info.type in ('clang', 'gcc') and info.language_version != 201103:
+                append_flag('-std=gnu++11')
+        else:
+            if info.type == 'clang' and info.language_version != 201103:
+                append_flag('-std=gnu++11')
+            # MSVC 2015 headers include C++14 features, but don't guard them
+            # with appropriate checks.
+            if info.type == 'clang-cl' and info.language_version != cxx14_version:
+                append_flag('-std=c++14')
+            # GCC 4.9 indicates that it implements draft C++14 features
+            # instead of the full language.
+            elif info.type == 'gcc' and not \
+                (info.language_version == draft_cxx14_version or info.language_version == cxx14_version):
+                append_flag('-std=gnu++14')
 
     # We force clang-cl to emulate Visual C++ 2015 Update 3.
     if info.type == 'clang-cl' and info.version != '19.00.24213':
         # This flag is a direct clang-cl flag that doesn't need -Xclang,
         # add it directly.
         flags.append('-fms-compatibility-version=19.00.24213')
 
     # Check compiler target
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
@@ -110,17 +110,17 @@ add_task(function* testWebExtensionsTool
           // Wait the initial frame update (which list the background page).
           waitForFrameListUpdate,
           // Wait the new frame update (once the extension popup has been opened).
           popupFramePromise,
         ]);
 
         dump(`Clicking the frame list button\n`);
         let btn = toolbox.doc.getElementById("command-button-frames");
-        let frameMenu = toolbox.showFramesMenu({target: btn});
+        let frameMenu = await toolbox.showFramesMenu({target: btn});
         dump(`Clicked the frame list button\n`);
 
         await frameMenu.once("open");
 
         let frames = frameMenu.items;
 
         if (frames.length != 2) {
           throw Error(`Number of frames found is wrong: ${frames.length} != 2`);
--- a/devtools/client/animationinspector/components/animation-time-block.js
+++ b/devtools/client/animationinspector/components/animation-time-block.js
@@ -271,16 +271,24 @@ AnimationTimeBlock.prototype = {
 
     // Adding the playback rate if it's different than 1.
     if (state.playbackRate !== 1) {
       text += L10N.getStr("player.animationRateLabel") + " ";
       text += state.playbackRate;
       text += "\n";
     }
 
+    // Adding the animation-timing-function
+    // if it is not "ease" which is default value for CSS Animations.
+    if (state.animationTimingFunction && state.animationTimingFunction !== "ease") {
+      text += L10N.getStr("player.animationTimingFunctionLabel") + " ";
+      text += state.animationTimingFunction;
+      text += "\n";
+    }
+
     // Adding a note that the animation is running on the compositor thread if
     // needed.
     if (state.propertyState) {
       if (state.propertyState
                .every(propState => propState.runningOnCompositor)) {
         text += L10N.getStr("player.allPropertiesOnCompositorTooltip");
       } else if (state.propertyState
                       .some(propState => propState.runningOnCompositor)) {
@@ -457,33 +465,33 @@ function renderGraph(parentEl, state, to
  * Render delay section.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Object} graphHelper - SummaryGraphHelper.
  */
 function renderDelay(parentEl, state, graphHelper) {
   const startSegment = graphHelper.getSegment(0);
   const endSegment = { x: state.delay, y: startSegment.y };
-  graphHelper.appendPathElement(parentEl, [startSegment, endSegment], "delay-path");
+  graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "delay-path");
 }
 
 /**
  * Render first iteration section.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Number} mainIterationStartTime - Starting time of main iteration.
  * @param {Number} firstSectionCount - Iteration count of first section.
  * @param {Object} graphHelper - SummaryGraphHelper.
  */
 function renderFirstIteration(parentEl, state, mainIterationStartTime,
                               firstSectionCount, graphHelper) {
   const startTime = mainIterationStartTime;
   const endTime = startTime + firstSectionCount * state.duration;
   const segments = graphHelper.createPathSegments(startTime, endTime);
-  graphHelper.appendPathElement(parentEl, segments, "iteration-path");
+  graphHelper.appendShapePath(parentEl, segments, "iteration-path");
 }
 
 /**
  * Render middle iterations section.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Number} mainIterationStartTime - Starting time of main iteration.
  * @param {Number} firstSectionCount - Iteration count of first section.
@@ -494,17 +502,17 @@ function renderMiddleIterations(parentEl
                                 firstSectionCount, middleSectionCount,
                                 graphHelper) {
   const offset = mainIterationStartTime + firstSectionCount * state.duration;
   for (let i = 0; i < middleSectionCount; i++) {
     // Get the path segments of each iteration.
     const startTime = offset + i * state.duration;
     const endTime = startTime + state.duration;
     const segments = graphHelper.createPathSegments(startTime, endTime);
-    graphHelper.appendPathElement(parentEl, segments, "iteration-path");
+    graphHelper.appendShapePath(parentEl, segments, "iteration-path");
   }
 }
 
 /**
  * Render last iteration section.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Number} mainIterationStartTime - Starting time of main iteration.
@@ -515,17 +523,17 @@ function renderMiddleIterations(parentEl
  */
 function renderLastIteration(parentEl, state, mainIterationStartTime,
                              firstSectionCount, middleSectionCount,
                              lastSectionCount, graphHelper) {
   const startTime = mainIterationStartTime +
                       (firstSectionCount + middleSectionCount) * state.duration;
   const endTime = startTime + lastSectionCount * state.duration;
   const segments = graphHelper.createPathSegments(startTime, endTime);
-  graphHelper.appendPathElement(parentEl, segments, "iteration-path");
+  graphHelper.appendShapePath(parentEl, segments, "iteration-path");
 }
 
 /**
  * Render Infinity iterations.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Number} mainIterationStartTime - Starting time of main iteration.
  * @param {Number} firstSectionCount - Iteration count of first section.
@@ -547,17 +555,17 @@ function renderInfinity(parentEl, state,
              Math.ceil(uncappedInfinityIterationCount));
 
   // Append first full iteration path.
   const firstStartTime =
     mainIterationStartTime + firstSectionCount * state.duration;
   const firstEndTime = firstStartTime + state.duration;
   const firstSegments =
     graphHelper.createPathSegments(firstStartTime, firstEndTime);
-  graphHelper.appendPathElement(parentEl, firstSegments, "iteration-path infinity");
+  graphHelper.appendShapePath(parentEl, firstSegments, "iteration-path infinity");
 
   // Append other iterations. We can copy first segments.
   const isAlternate = state.direction.match(/alternate/);
   for (let i = 1; i < infinityIterationCount; i++) {
     const startTime = firstStartTime + i * state.duration;
     let segments;
     if (isAlternate && i % 2) {
       // Copy as reverse.
@@ -565,34 +573,34 @@ function renderInfinity(parentEl, state,
         return { x: firstEndTime - segment.x + startTime, y: segment.y };
       });
     } else {
       // Copy as is.
       segments = firstSegments.map(segment => {
         return { x: segment.x - firstStartTime + startTime, y: segment.y };
       });
     }
-    graphHelper.appendPathElement(parentEl, segments, "iteration-path infinity copied");
+    graphHelper.appendShapePath(parentEl, segments, "iteration-path infinity copied");
   }
 }
 
 /**
  * Render endDelay section.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Number} mainIterationStartTime - Starting time of main iteration.
  * @param {Number} iterationCount - Whole iteration count.
  * @param {Object} graphHelper - SummaryGraphHelper.
  */
 function renderEndDelay(parentEl, state,
                         mainIterationStartTime, iterationCount, graphHelper) {
   const startTime = mainIterationStartTime + iterationCount * state.duration;
   const startSegment = graphHelper.getSegment(startTime);
   const endSegment = { x: startTime + state.endDelay, y: startSegment.y };
-  graphHelper.appendPathElement(parentEl, [startSegment, endSegment], "enddelay-path");
+  graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "enddelay-path");
 }
 
 /**
  * Render forwards fill section.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Number} mainIterationStartTime - Starting time of main iteration.
  * @param {Number} iterationCount - Whole iteration count.
@@ -600,45 +608,44 @@ function renderEndDelay(parentEl, state,
  * @param {Object} graphHelper - SummaryGraphHelper.
  */
 function renderForwardsFill(parentEl, state, mainIterationStartTime,
                             iterationCount, totalDuration, graphHelper) {
   const startTime = mainIterationStartTime + iterationCount * state.duration +
                       (state.endDelay > 0 ? state.endDelay : 0);
   const startSegment = graphHelper.getSegment(startTime);
   const endSegment = { x: totalDuration, y: startSegment.y };
-  graphHelper.appendPathElement(parentEl, [startSegment, endSegment],
-                                "fill-forwards-path");
+  graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "fill-forwards-path");
 }
 
 /**
  * Render hidden progress of negative delay.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Object} graphHelper - SummaryGraphHelper.
  */
 function renderNegativeDelayHiddenProgress(parentEl, state, graphHelper) {
   const startTime = state.delay;
   const endTime = 0;
   const segments =
     graphHelper.createPathSegments(startTime, endTime);
-  graphHelper.appendPathElement(parentEl, segments, "delay-path negative");
+  graphHelper.appendShapePath(parentEl, segments, "delay-path negative");
 }
 
 /**
  * Render hidden progress of negative endDelay.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Object} graphHelper - SummaryGraphHelper.
  */
 function renderNegativeEndDelayHiddenProgress(parentEl, state, graphHelper) {
   const endTime = state.delay + state.iterationCount * state.duration;
   const startTime = endTime + state.endDelay;
   const segments = graphHelper.createPathSegments(startTime, endTime);
-  graphHelper.appendPathElement(parentEl, segments, "enddelay-path negative");
+  graphHelper.appendShapePath(parentEl, segments, "enddelay-path negative");
 }
 
 /**
  * Create new keyframes object which has only offset and easing.
  * Also, the returned value has no duplication.
  * @param {Object} tracks - The value of AnimationsTimeline.getTracks().
  * @return {Array} keyframes list.
  */
--- a/devtools/client/animationinspector/components/keyframes.js
+++ b/devtools/client/animationinspector/components/keyframes.js
@@ -4,17 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {createNode, createSVGNode} =
   require("devtools/client/animationinspector/utils");
 const {ProgressGraphHelper, getPreferredKeyframesProgressThreshold} =
-         require("devtools/client/animationinspector/graph-helper.js");
+  require("devtools/client/animationinspector/graph-helper.js");
 
 // Counter for linearGradient ID.
 let LINEAR_GRADIENT_ID_COUNTER = 0;
 
 /**
  * UI component responsible for displaying a list of keyframes.
  * Also, shows a graphical graph for the animation progress of one iteration.
  */
@@ -50,39 +50,44 @@ Keyframes.prototype = {
         "preserveAspectRatio": "none"
       }
     });
 
     // This visual is only one iteration,
     // so we use animation.state.duration as total duration.
     const totalDuration = animation.state.duration;
 
-    // Calculate stroke height in viewBox to display stroke of path.
-    const strokeHeightForViewBox = 0.5 / this.containerEl.clientHeight;
     // Minimum segment duration is the duration of one pixel.
     const minSegmentDuration =
       totalDuration / this.containerEl.clientWidth;
 
-    // Set viewBox.
-    graphEl.setAttribute("viewBox",
-                         `0 -${ 1 + strokeHeightForViewBox }
-                          ${ totalDuration }
-                          ${ 1 + strokeHeightForViewBox * 2 }`);
-
     // Create graph helper to render the animation property graph.
+    const win = this.containerEl.ownerGlobal;
     const graphHelper =
-      new ProgressGraphHelper(this.containerEl.ownerDocument.defaultView,
-                              propertyName, animationType, keyframes, totalDuration);
+      new ProgressGraphHelper(win, propertyName, animationType, keyframes, totalDuration);
 
     renderPropertyGraph(graphEl, totalDuration, minSegmentDuration,
                         getPreferredKeyframesProgressThreshold(keyframes), graphHelper);
 
     // Destroy ProgressGraphHelper resources.
     graphHelper.destroy();
 
+    // Set viewBox which includes invisible stroke width.
+    // At first, calculate invisible stroke width from maximum width.
+    // The reason why divide by 2 is that half of stroke width will be invisible
+    // if we use 0 or 1 for y axis.
+    const maxStrokeWidth =
+      win.getComputedStyle(graphEl.querySelector(".keyframes svg .hint")).strokeWidth;
+    const invisibleStrokeWidthInViewBox =
+      maxStrokeWidth / 2 / this.containerEl.clientHeight;
+    graphEl.setAttribute("viewBox",
+                         `0 -${ 1 + invisibleStrokeWidthInViewBox }
+                          ${ totalDuration }
+                          ${ 1 + invisibleStrokeWidthInViewBox * 2 }`);
+
     // Append elements to display keyframe values.
     this.keyframesEl.classList.add(animation.state.type);
     for (let frame of this.keyframes) {
       createNode({
         parent: this.keyframesEl,
         attributes: {
           "class": "frame",
           "style": `left:${frame.offset * 100}%;`,
@@ -105,25 +110,26 @@ Keyframes.prototype = {
  */
 function renderPropertyGraph(parentEl, duration, minSegmentDuration,
                              minProgressThreshold, graphHelper) {
   const segments = graphHelper.createPathSegments(0, duration, minSegmentDuration,
                                                   minProgressThreshold);
 
   const graphType = graphHelper.getGraphType();
   if (graphType !== "color") {
-    graphHelper.appendPathElement(parentEl, segments, graphType);
+    graphHelper.appendShapePath(parentEl, segments, graphType);
+    renderEasingHint(parentEl, segments, graphHelper);
     return;
   }
 
   // Append the color to the path.
   segments.forEach(segment => {
     segment.y = 1;
   });
-  const path = graphHelper.appendPathElement(parentEl, segments, graphType);
+  const path = graphHelper.appendShapePath(parentEl, segments, graphType);
   const defEl = createSVGNode({
     parent: parentEl,
     nodeType: "def"
   });
   const id = `color-property-${ LINEAR_GRADIENT_ID_COUNTER++ }`;
   const linearGradientEl = createSVGNode({
     parent: defEl,
     nodeType: "linearGradient",
@@ -137,9 +143,110 @@ function renderPropertyGraph(parentEl, d
       nodeType: "stop",
       attributes: {
         "stop-color": segment.style,
         "offset": segment.x / duration
       }
     });
   });
   path.style.fill = `url(#${ id })`;
+
+  renderEasingHintForColor(parentEl, graphHelper);
 }
+
+/**
+ * Renders the easing hint.
+ * This method renders an emphasized path over the easing path for a keyframe.
+ * It appears when hovering over the easing.
+ * It also renders a tooltip that appears when hovering.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Array} path segments - [{x: {Number} time, y: {Number} progress}, ...]
+ * @param {ProgressGraphHelper} graphHelper - The object of ProgressGraphHelper.
+ */
+function renderEasingHint(parentEl, segments, helper) {
+  const keyframes = helper.getKeyframes();
+  const duration = helper.getDuration();
+
+  // Split segments for each keyframe.
+  for (let i = 0, indexOfSegments = 0; i < keyframes.length - 1; i++) {
+    const startKeyframe = keyframes[i];
+    const startTime = startKeyframe.offset * duration;
+    const endKeyframe = keyframes[i + 1];
+    const endTime = endKeyframe.offset * duration;
+
+    const keyframeSegments = [];
+    for (; indexOfSegments < segments.length; indexOfSegments++) {
+      const segment = segments[indexOfSegments];
+      if (segment.x < startTime) {
+        // If previous easings were linear, we need to increment the indexOfSegments.
+        continue;
+      }
+      if (segment.x > endTime) {
+        indexOfSegments -= 1;
+        break;
+      }
+      keyframeSegments.push(segment);
+    }
+
+    // If keyframeSegments does not have segment which is at startTime,
+    // get and set the segment.
+    if (keyframeSegments[0].x !== startTime) {
+      keyframeSegments.unshift(helper.getSegment(startTime));
+    }
+    // Also, endTime.
+    if (keyframeSegments[keyframeSegments.length - 1].x !== endTime) {
+      keyframeSegments.push(helper.getSegment(endTime));
+    }
+
+    // Append easing hint as text and emphasis path.
+    const gEl = createSVGNode({
+      parent: parentEl,
+      nodeType: "g"
+    });
+    createSVGNode({
+      parent: gEl,
+      nodeType: "title",
+      textContent: startKeyframe.easing
+    });
+    helper.appendLinePath(gEl, keyframeSegments, `${helper.getGraphType()} hint`);
+  }
+}
+
+/**
+ * Render easing hint for properties that are represented by color.
+ * This method render as text only.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {ProgressGraphHelper} graphHelper - The object of ProgressGraphHalper.
+ */
+function renderEasingHintForColor(parentEl, helper) {
+  const keyframes = helper.getKeyframes();
+  const duration = helper.getDuration();
+
+  // Split segments for each keyframe.
+  for (let i = 0; i < keyframes.length - 1; i++) {
+    const startKeyframe = keyframes[i];
+    const startTime = startKeyframe.offset * duration;
+    const endKeyframe = keyframes[i + 1];
+    const endTime = endKeyframe.offset * duration;
+
+    // Append easing hint.
+    const gEl = createSVGNode({
+      parent: parentEl,
+      nodeType: "g"
+    });
+    createSVGNode({
+      parent: gEl,
+      nodeType: "title",
+      textContent: startKeyframe.easing
+    });
+    createSVGNode({
+      parent: gEl,
+      nodeType: "rect",
+      attributes: {
+        x: startTime,
+        y: -1,
+        width: endTime - startTime,
+        height: 1,
+        class: "hint",
+      }
+    });
+  }
+}
--- a/devtools/client/animationinspector/graph-helper.js
+++ b/devtools/client/animationinspector/graph-helper.js
@@ -81,16 +81,32 @@ ProgressGraphHelper.prototype = {
     this.propertyCSSName = null;
     this.propertyJSName = null;
     this.animationType = null;
     this.keyframes = null;
     this.win = null;
   },
 
   /**
+   * Return animation duration.
+   * @return {Number} duration
+   */
+  getDuration: function () {
+    return this.animation.effect.timing.duration;
+  },
+
+  /**
+   * Return animation's keyframe.
+   * @return {Object} keyframe
+   */
+  getKeyframes: function () {
+    return this.keyframes;
+  },
+
+  /**
    * Return graph type.
    * @return {String} if property is 'opacity' or 'transform', return that value.
    *                  Otherwise, return given animation type in constructor.
    */
   getGraphType: function () {
     return (this.propertyJSName === "opacity" || this.propertyJSName === "transform")
            ? this.propertyJSName : this.animationType;
   },
@@ -243,40 +259,53 @@ ProgressGraphHelper.prototype = {
                                 minSegmentDuration, minProgressThreshold) {
     return !this.valueHelperFunction
            ? createKeyframesPathSegments(endTime - startTime, this.devtoolsKeyframes)
            : createPathSegments(startTime, endTime,
                                 minSegmentDuration, minProgressThreshold, this);
   },
 
   /**
-   * Append path element.
+   * Append path element as shape. Also, this method appends two segment
+   * that are {start x, 0} and {end x, 0} to make shape.
    * @param {Element} parentEl - Parent element of this appended path element.
    * @param {Array} pathSegments - Path segments. Please see createPathSegments.
    * @param {String} cls - Class name.
    * @return {Element} path element.
    */
-  appendPathElement: function (parentEl, pathSegments, cls) {
-    return appendPathElement(parentEl, pathSegments, cls);
+  appendShapePath: function (parentEl, pathSegments, cls) {
+    return appendShapePath(parentEl, pathSegments, cls);
+  },
+
+  /**
+   * Append path element as line.
+   * @param {Element} parentEl - Parent element of this appended path element.
+   * @param {Array} pathSegments - Path segments. Please see createPathSegments.
+   * @param {String} cls - Class name.
+   * @return {Element} path element.
+   */
+  appendLinePath: function (parentEl, pathSegments, cls) {
+    const isClosePathNeeded = false;
+    return appendPathElement(parentEl, pathSegments, cls, isClosePathNeeded);
   },
 };
 
 exports.ProgressGraphHelper = ProgressGraphHelper;
 
 /**
  * This class is used for creating the summary graph in animation-timeline.
  * The shape of the graph can be changed by using the following methods:
  * setKeyframes:
  *   If null, the shape is by computed timing progress.
  *   Otherwise, by computed style of 'opacity' to combine effect easing and
  *   keyframe's easing.
  * setFillMode:
  *   Animation fill-mode (e.g. "none", "backwards", "forwards" or "both")
  * setClosePathNeeded:
- *   If true, appendPathElement make the last segment of <path> element to
+ *   If true, appendShapePath make the last segment of <path> element to
  *   "close" segment("Z").
  *   Therefore, if don't need under-line of graph, please set false.
  * setOriginalBehavior:
  *   In Animation::SetCurrentTime spec, even if current time of animation is over
  *   the endTime, the progress is changed. Likewise, in case the time is less than 0.
  *   If set true, prevent the time to make the same animation behavior as the original.
  * setMinProgressThreshold:
  *   SummaryGraphHelper searches and creates the summary graph until the progress
@@ -355,17 +384,17 @@ SummaryGraphHelper.prototype = {
    * Set animation fill mode.
    * @param {String} fill - "both", "forwards", "backwards" or "both"
    */
   setFillMode: function (fill) {
     this.animation.effect.timing.fill = fill;
   },
 
   /**
-   * Set true if need to close path in appendPathElement.
+   * Set true if need to close path in appendShapePath.
    * @param {bool} isClosePathNeeded - true: close, false: open.
    */
   setClosePathNeeded: function (isClosePathNeeded) {
     this.isClosePathNeeded = isClosePathNeeded;
   },
 
   /**
    * SummaryGraphHelper searches and creates the summary graph untill the progress
@@ -406,24 +435,25 @@ SummaryGraphHelper.prototype = {
    *                 [{x: {Number} time, y: {Number} progress}, ...]
    */
   createPathSegments: function (startTime, endTime) {
     return createPathSegments(startTime, endTime,
                               this.minSegmentDuration, this.minProgressThreshold, this);
   },
 
   /**
-   * Append path element.
+   * Append path element as shape. Also, this method appends two segment
+   * that are {start x, 0} and {end x, 0} to make shape.
    * @param {Element} parentEl - Parent element of this appended path element.
    * @param {Array} pathSegments - Path segments. Please see createPathSegments.
    * @param {String} cls - Class name.
    * @return {Element} path element.
    */
-  appendPathElement: function (parentEl, pathSegments, cls) {
-    return appendPathElement(parentEl, pathSegments, cls, this.isClosePathNeeded);
+  appendShapePath: function (parentEl, pathSegments, cls) {
+    return appendShapePath(parentEl, pathSegments, cls, this.isClosePathNeeded);
   },
 
   /**
    * Return current computed timing progress of the animation.
    * @return {float} computed timing progress as float value of Y axis.
    */
   getProgressValue: function () {
     return Math.max(this.animation.effect.getComputedTiming().progress, 0);
@@ -493,50 +523,60 @@ function createPathSegments(startTime, e
     pathSegments.push(currentSegment);
     previousSegment = currentSegment;
   }
 
   return pathSegments;
 }
 
 /**
- * Append path element.
+ * Append path element as shape. Also, this method appends two segment
+ * that are {start x, 0} and {end x, 0} to make shape.
+ * But does not affect given pathSegments.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Array} pathSegments - Path segments. Please see createPathSegments.
  * @param {String} cls - Class name.
  * @param {bool} isClosePathNeeded - Set true if need to close the path. (default true)
  * @return {Element} path element.
  */
-function appendPathElement(parentEl, pathSegments, cls, isClosePathNeeded = true) {
+function appendShapePath(parentEl, pathSegments, cls, isClosePathNeeded = true) {
+  const segments = [
+    { x: pathSegments[0].x, y: 0 },
+    ...pathSegments,
+    { x: pathSegments[pathSegments.length - 1].x, y: 0 }
+  ];
+  return appendPathElement(parentEl, segments, cls, isClosePathNeeded);
+}
+
+/**
+ * Append path element.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Array} pathSegments - Path segments. Please see createPathSegments.
+ * @param {String} cls - Class name.
+ * @param {bool} isClosePathNeeded - Set true if need to close the path.
+ * @return {Element} path element.
+ */
+function appendPathElement(parentEl, pathSegments, cls, isClosePathNeeded) {
   // Create path string.
-  let path = `M${ pathSegments[0].x },0`;
-  for (let i = 0; i < pathSegments.length; i++) {
-    const pathSegment = pathSegments[i];
-    if (!pathSegment.easing || pathSegment.easing === "linear") {
-      path += createLinePathString(pathSegment);
-      continue;
-    }
-
-    if (i + 1 === pathSegments.length) {
-      // We already create steps or cubic-bezier path string in previous.
-      break;
+  let currentSegment = pathSegments[0];
+  let path = `M${ currentSegment.x },${ currentSegment.y }`;
+  for (let i = 1; i < pathSegments.length; i++) {
+    const currentEasing = currentSegment.easing ? currentSegment.easing : "linear";
+    const nextSegment = pathSegments[i];
+    if (currentEasing === "linear") {
+      path += createLinePathString(nextSegment);
+    } else if (currentEasing.startsWith("steps")) {
+      path += createStepsPathString(currentSegment, nextSegment);
+    } else if (currentEasing.startsWith("frames")) {
+      path += createFramesPathString(currentSegment, nextSegment);
+    } else {
+      path += createCubicBezierPathString(currentSegment, nextSegment);
     }
-
-    const nextPathSegment = pathSegments[i + 1];
-    let createPathFunction;
-    if (pathSegment.easing.startsWith("steps")) {
-      createPathFunction = createStepsPathString;
-    } else if (pathSegment.easing.startsWith("frames")) {
-      createPathFunction = createFramesPathString;
-    } else {
-      createPathFunction = createCubicBezierPathString;
-    }
-    path += createPathFunction(pathSegment, nextPathSegment);
+    currentSegment = nextSegment;
   }
-  path += ` L${ pathSegments[pathSegments.length - 1].x },0`;
   if (isClosePathNeeded) {
     path += " Z";
   }
   // Append and return the path element.
   return createSVGNode({
     parent: parentEl,
     nodeType: "path",
     attributes: {
--- a/devtools/client/animationinspector/test/browser.ini
+++ b/devtools/client/animationinspector/test/browser.ini
@@ -29,16 +29,17 @@ support-files =
 [browser_animation_animated_properties_displayed.js]
 [browser_animation_animated_properties_for_delayed_starttime_animations.js]
 [browser_animation_animated_properties_path.js]
 [browser_animation_animated_properties_progress_indicator.js]
 [browser_animation_click_selects_animation.js]
 [browser_animation_controller_exposes_document_currentTime.js]
 [browser_animation_detail_displayed.js]
 skip-if = os == "linux" && !debug # Bug 1234567
+[browser_animation_detail_easings.js]
 [browser_animation_empty_on_invalid_nodes.js]
 [browser_animation_keyframe_markers.js]
 [browser_animation_mutations_with_same_names.js]
 [browser_animation_panel_exists.js]
 [browser_animation_participate_in_inspector_update.js]
 [browser_animation_playerFronts_are_refreshed.js]
 [browser_animation_playerWidgets_appear_on_panel_init.js]
 [browser_animation_playerWidgets_target_nodes.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/browser_animation_detail_easings.js
@@ -0,0 +1,116 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(5);
+
+// This is a test for displaying the easing of keyframes.
+// Checks easing text which is displayed as popup and
+// the path which emphasises by mouseover.
+
+const TEST_CASES = {
+  "no-easing": {
+    opacity: {
+      expectedValues: ["linear"],
+    }
+  },
+  "effect-easing": {
+    opacity: {
+      expectedValues: ["linear"],
+    }
+  },
+  "keyframe-easing": {
+    opacity: {
+      expectedValues: ["steps(2)"],
+    }
+  },
+  "both-easing": {
+    opacity: {
+      expectedValues: ["steps(2)"],
+    }
+  },
+  "many-keyframes": {
+    backgroundColor: {
+      selector: "rect",
+      expectedValues: ["steps(2)", "ease-out"],
+      noEmphasisPath: true,
+    },
+    opacity: {
+      expectedValues: ["steps(2)", "ease-in", "linear", "ease-out"],
+    }
+  },
+  "css-animations": {
+    opacity: {
+      expectedValues: ["ease", "ease"],
+    }
+  },
+};
+
+add_task(function* () {
+  yield addTab(URL_ROOT + "doc_multiple_easings.html");
+  const { panel } = yield openAnimationInspector();
+
+  const timelineComponent = panel.animationsTimelineComponent;
+  const timeBlocks = getAnimationTimeBlocks(panel);
+  for (let i = 0; i < timeBlocks.length; i++) {
+    yield clickOnAnimation(panel, i);
+
+    const detailComponent = timelineComponent.details;
+    const detailEl = detailComponent.containerEl;
+    const state = detailComponent.animation.state;
+
+    const testcase = TEST_CASES[state.name];
+    if (!testcase) {
+      continue;
+    }
+
+    for (let testProperty in testcase) {
+      const testIdentity = `"${ testProperty }" of "${ state.name }"`;
+      info(`Test for ${ testIdentity }`);
+
+      const testdata = testcase[testProperty];
+      const selector = testdata.selector ? testdata.selector : `.${testProperty}`;
+      const hintEls = detailEl.querySelectorAll(`${ selector }.hint`);
+      const expectedValues = testdata.expectedValues;
+      is(hintEls.length, expectedValues.length,
+         `Length of hints for ${ testIdentity } should be ${expectedValues.length}`);
+
+      for (let j = 0; j < hintEls.length; j++) {
+        const hintEl = hintEls[j];
+        const expectedValue = expectedValues[j];
+
+        info("Test easing text");
+        const gEl = hintEl.closest("g");
+        ok(gEl, `<g> element for ${ testIdentity } should exists`);
+        const titleEl = gEl.querySelector("title");
+        ok(titleEl, `<title> element for ${ testIdentity } should exists`);
+        is(titleEl.textContent, expectedValue,
+           `textContent of <title> for ${ testIdentity } should be ${ expectedValue }`);
+
+        info("Test emphasis path");
+        // Scroll to show the hintEl since the element may be out of displayed area.
+        hintEl.scrollIntoView(false);
+
+        const win = hintEl.ownerDocument.defaultView;
+        // Mouse out once from hintEl.
+        EventUtils.synthesizeMouse(hintEl, -1, -1, {type: "mouseout"}, win);
+        is(win.getComputedStyle(hintEl).strokeOpacity, 0,
+           `stroke-opacity of hintEl for ${ testIdentity } should be 0 ` +
+           `while mouse is out from the element`);
+        // Mouse is over the hintEl.
+        EventUtils.synthesizeMouseAtCenter(hintEl, {type: "mouseover"}, win);
+        if (testdata.noEmphasisPath) {
+          is(win.getComputedStyle(hintEl).strokeOpacity, 0,
+             `stroke-opacity of hintEl for ${ testIdentity } should be 0 ` +
+             `even while mouse is over the element`);
+        } else {
+          is(win.getComputedStyle(hintEl).strokeOpacity, 1,
+             `stroke-opacity of hintEl for ${ testIdentity } should be 1 ` +
+             `while mouse is over the element`);
+        }
+      }
+    }
+  }
+});
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js
@@ -38,16 +38,26 @@ add_task(function* () {
       ok(!title.match(/Repeats: /), "The tooltip doesn't show the iterations");
     }
     if (state.easing && state.easing !== "linear") {
       ok(title.match(/Overall easing: /), "The tooltip shows the easing");
     } else {
       ok(!title.match(/Overall easing: /),
          "The tooltip doesn't show the easing if it is 'linear'");
     }
+    if (state.animationTimingFunction && state.animationTimingFunction !== "ease") {
+      is(state.type, "cssanimation",
+         "The animation type should be CSS Animations if has animation-timing-function");
+      ok(title.match(/Animation timing function: /),
+         "The tooltip shows animation-timing-function");
+    } else {
+      ok(!title.match(/Animation timing function: /),
+         "The tooltip doesn't show the animation-timing-function if it is 'ease'"
+         + " or not CSS Animations");
+    }
     if (state.fill) {
       ok(title.match(/Fill: /), "The tooltip shows the fill");
     }
     if (state.direction) {
       if (state.direction === "normal") {
         ok(!title.match(/Direction: /),
           "The tooltip doesn't show the direction if it is 'normal'");
       } else {
--- a/devtools/client/animationinspector/test/doc_multiple_easings.html
+++ b/devtools/client/animationinspector/test/doc_multiple_easings.html
@@ -1,20 +1,37 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
   <meta charset="UTF-8">
   <style>
+  @keyframes css-animations {
+    from {
+      opacity: 1;
+    }
+    50% {
+      opacity: 0.5;
+    }
+    to {
+      opacity: 0;
+    }
+  }
+
+  #css-animations {
+    animation: css-animations 100s;
+  }
+
   div {
     background-color: lime;
     height: 50px;
   }
   </style>
 </head>
 <body>
+  <div id="css-animations"></div>
   <script>
   "use strict";
   const DURATION = 100 * 1000;
   [
     {
       id: "no-easing",
       frames: { opacity: [1, 0] },
       timing: {
@@ -67,16 +84,55 @@
           marginTop: "100px"
         },
       ],
       timing: {
         easing: "steps(10)",
         duration: DURATION,
       }
     },
+    {
+      id: "many-keyframes",
+      frames: [
+        {
+          offset: 0,
+          easing: "steps(2)",
+          opacity: 1,
+          backgroundColor: "red",
+        },
+        {
+          offset: 0.25,
+          easing: "ease-in",
+          opacity: 0.25,
+        },
+        {
+          offset: 0.3,
+          easing: "ease-out",
+          backgroundColor: "blue",
+        },
+        {
+          offset: 0.5,
+          easing: "linear",
+          opacity: 0.5,
+        },
+        {
+          offset: 0.75,
+          easing: "ease-out",
+          opacity: 0.75,
+        },
+        {
+          offset: 1,
+          opacity: 0,
+          backgroundColor: "lime",
+        },
+      ],
+      timing: {
+        duration: DURATION,
+      }
+    },
   ].forEach(({ id, frames, timing }) => {
     const target = document.createElement("div");
     document.body.appendChild(target);
     const effect = new KeyframeEffect(target, frames, timing);
     const animation = new Animation(effect, document.timeline);
     animation.id = id;
     animation.play();
   });
--- a/devtools/client/framework/devtools.js
+++ b/devtools/client/framework/devtools.js
@@ -13,21 +13,16 @@ const {DevToolsShim} = Cu.import("chrome
 loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
 loader.lazyRequireGetter(this, "TabTarget", "devtools/client/framework/target", true);
 loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
 loader.lazyRequireGetter(this, "ToolboxHostManager", "devtools/client/framework/toolbox-host-manager", true);
 loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
 loader.lazyRequireGetter(this, "HUDService", "devtools/client/webconsole/hudservice", true);
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 
-// Dependencies required for addon sdk compatibility layer.
-loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
-loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
-loader.lazyImporter(this, "BrowserToolboxProcess", "resource://devtools/client/framework/ToolboxProcess.jsm");
-
 loader.lazyRequireGetter(this, "WebExtensionInspectedWindowFront",
       "devtools/shared/fronts/webextension-inspected-window", true);
 
 const {defaultTools: DefaultTools, defaultThemes: DefaultThemes} =
   require("devtools/client/definitions");
 const EventEmitter = require("devtools/shared/old-event-emitter");
 const AboutDevTools = require("devtools/client/framework/about-devtools-toolbox");
 const {Task} = require("devtools/shared/task");
@@ -543,52 +538,16 @@ DevTools.prototype = {
    *
    * @return {TabTarget} A target object
    */
   getTargetForTab: function (tab) {
     return TargetFactory.forTab(tab);
   },
 
   /**
-   * Compatibility layer for addon-sdk. Remove when Firefox 57 hits release.
-   * Initialize the debugger server if needed and and create a connection.
-   *
-   * @return {DebuggerTransport} a client-side DebuggerTransport for communicating with
-   *         the created connection.
-   */
-  connectDebuggerServer: function () {
-    if (!DebuggerServer.initialized) {
-      DebuggerServer.init();
-      DebuggerServer.addBrowserActors();
-    }
-
-    return DebuggerServer.connectPipe();
-  },
-
-  /**
-   * Compatibility layer for addon-sdk. Remove when Firefox 57 hits release.
-   *
-   * Create a connection to the debugger server and return a debugger client for this
-   * new connection.
-   */
-  createDebuggerClient: function () {
-    let transport = this.connectDebuggerServer();
-    return new DebuggerClient(transport);
-  },
-
-  /**
-   * Compatibility layer for addon-sdk. Remove when Firefox 57 hits release.
-   *
-   * Create a BrowserToolbox process linked to the provided addon id.
-   */
-  initBrowserToolboxProcessForAddon: function (addonID) {
-    BrowserToolboxProcess.init({ addonID });
-  },
-
-  /**
    * Compatibility layer for web-extensions. Used by DevToolsShim for
    * browser/components/extensions/ext-devtools.js
    *
    * web-extensions need to use dedicated instances of TabTarget and cannot reuse the
    * cached instances managed by DevTools target factory.
    */
   createTargetForTab: function (tab) {
     return new TabTarget(tab);
--- a/devtools/client/framework/menu-item.js
+++ b/devtools/client/framework/menu-item.js
@@ -40,26 +40,28 @@
  *    Boolean visible
  *      If false, the menu item will be entirely hidden.
  */
 function MenuItem({
     accesskey = null,
     checked = false,
     click = () => {},
     disabled = false,
+    hover = () => {},
+    id = null,
     label = "",
-    id = null,
     submenu = null,
     type = "normal",
     visible = true,
 } = { }) {
   this.accesskey = accesskey;
   this.checked = checked;
   this.click = click;
   this.disabled = disabled;
+  this.hover = hover;
   this.id = id;
   this.label = label;
   this.submenu = submenu;
   this.type = type;
   this.visible = visible;
 }
 
 module.exports = MenuItem;
--- a/devtools/client/framework/menu.js
+++ b/devtools/client/framework/menu.js
@@ -128,16 +128,19 @@ Menu.prototype._createMenuItems = functi
       let menusep = doc.createElement("menuseparator");
       parent.appendChild(menusep);
     } else {
       let menuitem = doc.createElement("menuitem");
       menuitem.setAttribute("label", item.label);
       menuitem.addEventListener("command", () => {
         item.click();
       });
+      menuitem.addEventListener("DOMMenuItemActive", () => {
+        item.hover();
+      });
 
       if (item.type === "checkbox") {
         menuitem.setAttribute("type", "checkbox");
       }
       if (item.type === "radio") {
         menuitem.setAttribute("type", "radio");
       }
       if (item.disabled) {
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -47,17 +47,16 @@ support-files =
   test_browser_toolbox_debugger.js
   !/devtools/client/debugger/new/test/mochitest/head.js
 
 [browser_browser_toolbox.js]
 [browser_browser_toolbox_debugger.js]
 skip-if = debug # Bug 1282269
 [browser_devtools_api.js]
 [browser_devtools_api_destroy.js]
-[browser_devtools_shim.js]
 [browser_dynamic_tool_enabling.js]
 [browser_ignore_toolbox_network_requests.js]
 [browser_keybindings_01.js]
 [browser_keybindings_02.js]
 [browser_keybindings_03.js]
 [browser_menu_api.js]
 [browser_new_activation_workflow.js]
 [browser_source_map-01.js]
deleted file mode 100644
--- a/devtools/client/framework/test/browser_devtools_shim.js
+++ /dev/null
@@ -1,97 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Tests DevToolsShim API works as expected when DevTools are available.
-
-const TOOL_ID = "test-tool";
-
-const { DevToolsShim } =
-    Components.utils.import("chrome://devtools-shim/content/DevToolsShim.jsm", {});
-
-add_task(function* () {
-  yield addTab("about:blank");
-
-  yield testThemeRegistrationWithShim();
-  yield testToolRegistrationWithShim();
-
-  gBrowser.removeCurrentTab();
-});
-
-// Test that theme registration works with the DevToolsShim.
-function* testThemeRegistrationWithShim() {
-  let themeId = yield new Promise(resolve => {
-    DevToolsShim.on("theme-registered", function onThemeRegistered(e, id) {
-      resolve(id);
-    });
-
-    DevToolsShim.registerTheme({
-      id: "test-theme",
-      label: "Test theme",
-      stylesheets: [CHROME_URL_ROOT + "doc_theme.css"],
-      classList: ["theme-test"],
-    });
-  });
-
-  is(themeId, "test-theme", "theme-registered event handler sent theme id");
-
-  ok(gDevTools.getThemeDefinitionMap().has(themeId), "theme added to map");
-  DevToolsShim.unregisterTheme("test-theme");
-  ok(!gDevTools.getThemeDefinitionMap().has(themeId), "theme removed");
-}
-
-// Test that tool registration works with the DevToolsShim.
-function* testToolRegistrationWithShim() {
-  let toolDefinition = {
-    id: TOOL_ID,
-    isTargetSupported: () => true,
-    visibilityswitch: "devtools.test-tool.enabled",
-    url: "about:blank",
-    label: "someLabel",
-    build: function (iframeWindow, toolbox) {
-      let panel = createTestPanel(iframeWindow, toolbox);
-      return panel.open();
-    },
-  };
-
-  // Check that tool registration works when using the DevToolsShim.
-  ok(!gDevTools.getToolDefinitionMap().has(TOOL_ID), "The tool is not registered");
-  DevToolsShim.registerTool(toolDefinition);
-  ok(gDevTools.getToolDefinitionMap().has(TOOL_ID), "The tool is registered");
-
-  let events = {};
-
-  // Check that events can be listened to on the shim in the same way as on gDevTools
-  DevToolsShim.on(TOOL_ID + "-init", function onTool1Init(event, toolbox, iframe) {
-    DevToolsShim.off(TOOL_ID + "-init", onTool1Init);
-    ok(toolbox, "toolbox argument available");
-    ok(iframe, "iframe argument available");
-    events.init = true;
-  });
-
-  DevToolsShim.on(TOOL_ID + "-ready", function onToolReady(event, toolbox, iframe) {
-    DevToolsShim.off(TOOL_ID + "-ready", onToolReady);
-    ok(toolbox, "toolbox argument available");
-    ok(iframe, "iframe argument available");
-    events.ready = true;
-  });
-
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-  let toolbox = yield gDevTools.showToolbox(target, TOOL_ID);
-
-  // init & ready events should have been fired when opening the toolbox.
-  ok(events.init, "init event fired");
-  ok(events.ready, "ready event fired");
-
-  ok(gDevTools.getToolDefinitionMap().has(TOOL_ID), "The tool is still registered");
-  DevToolsShim.unregisterTool(TOOL_ID);
-  ok(!gDevTools.getToolDefinitionMap().has(TOOL_ID), "The tool is no longer registered");
-
-  // Wait for toolbox select event after unregistering the currently selected tool.
-  yield toolbox.once("select");
-
-  yield toolbox.destroy();
-}
--- a/devtools/client/framework/test/browser_toolbox_window_title_frame_select.js
+++ b/devtools/client/framework/test/browser_toolbox_window_title_frame_select.js
@@ -45,17 +45,17 @@ add_task(function* () {
 
   let btn = toolbox.doc.getElementById("command-button-frames");
 
   yield testShortcutToOpenFrames(btn, toolbox);
 
   // Open frame menu and wait till it's available on the screen.
   // Also check 'open' attribute on the command button.
   ok(!btn.classList.contains("checked"), "The checked class must not be present");
-  let menu = toolbox.showFramesMenu({target: btn});
+  let menu = yield toolbox.showFramesMenu({target: btn});
   yield once(menu, "open");
 
   ok(btn.classList.contains("checked"), "The checked class must be set");
 
   // Verify that the frame list menu is populated
   let frames = menu.items;
   is(frames.length, 2, "We have both frames in the list");
 
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -2050,20 +2050,29 @@ Toolbox.prototype = {
     return this._target.client.request(packet, resp => {
       this._updateFrames(null, { frames: resp.frames });
     });
   },
 
   /**
    * Show a drop down menu that allows the user to switch frames.
    */
-  showFramesMenu: function (event) {
+  showFramesMenu: async function (event) {
     let menu = new Menu();
     let target = event.target;
 
+    // Need to initInspector to check presence of getNodeActorFromWindowID
+    // and use the highlighter later
+    await this.initInspector();
+    if (!("_supportsFrameHighlight" in this)) {
+    // Only works with FF58+ targets
+      this._supportsFrameHighlight =
+        await this.target.actorHasMethod("domwalker", "getNodeActorFromWindowID");
+    }
+
     // Generate list of menu items from the list of frames.
     this.frameMap.forEach(frame => {
       // A frame is checked if it's the selected one.
       let checked = frame.id == this.selectedFrameId;
 
       let label = frame.url;
 
       if (this.target.isWebExtension) {
@@ -2073,26 +2082,30 @@ Toolbox.prototype = {
 
       // Create menu item.
       menu.append(new MenuItem({
         label,
         type: "radio",
         checked,
         click: () => {
           this.onSelectFrame(frame.id);
+        },
+        hover: () => {
+          this.onHightlightFrame(frame.id);
         }
       }));
     });
 
     menu.once("open").then(() => {
       this.frameButton.isChecked = true;
     });
 
     menu.once("close").then(() => {
       this.frameButton.isChecked = false;
+      this.highlighterUtils.unhighlight();
     });
 
     // Show a drop down menu with frames.
     // XXX Missing menu API for specifying target (anchor)
     // and relative position to it. See also:
     // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Method/openPopup
     // https://bugzilla.mozilla.org/show_bug.cgi?id=1274551
     let rect = target.getBoundingClientRect();
@@ -2130,16 +2143,28 @@ Toolbox.prototype = {
       to: this._target.form.actor,
       type: "switchToFrame",
       windowId: frameId
     };
     this._target.client.request(packet);
   },
 
   /**
+   * Highlight a frame in the page
+   */
+  onHightlightFrame: async function (frameId) {
+    // Only enable frame highlighting when the top level document is targeted
+    if (this._supportsFrameHighlight &&
+        this.frameMap.get(this.selectedFrameId).parentID === undefined) {
+      let frameActor = await this.walker.getNodeActorFromWindowID(frameId);
+      this.highlighterUtils.highlightNodeFront(frameActor);
+    }
+  },
+
+  /**
    * A handler for 'frameUpdate' packets received from the backend.
    * Following properties might be set on the packet:
    *
    * destroyAll {Boolean}: All frames have been destroyed.
    * selected {Number}: A frame has been selected
    * frames {Array}: list of frames. Every frame can have:
    *                 id {Number}: frame ID
    *                 url {String}: frame URL
--- a/devtools/client/inspector/test/browser_inspector_highlighter-iframes_02.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-iframes_02.js
@@ -39,17 +39,17 @@ add_task(function* () {
 /**
  * Helper designed to switch context to another frame at the provided index.
  * Returns a promise that will resolve when the navigation is complete.
  * @return {Promise}
  */
 function* switchToFrameContext(frameIndex, toolbox, inspector) {
   // Open frame menu and wait till it's available on the screen.
   let btn = toolbox.doc.getElementById("command-button-frames");
-  let menu = toolbox.showFramesMenu({target: btn});
+  let menu = yield toolbox.showFramesMenu({target: btn});
   yield once(menu, "open");
 
   info("Select the iframe in the frame list.");
   let newRoot = inspector.once("new-root");
 
   menu.items[frameIndex].click();
 
   yield newRoot;
--- a/devtools/client/inspector/test/browser_inspector_select-docshell.js
+++ b/devtools/client/inspector/test/browser_inspector_select-docshell.js
@@ -24,17 +24,17 @@ add_task(function* () {
 
   assertMarkupViewIsLoaded(inspector);
 
   // Verify that the frame map button is empty at the moment.
   let btn = toolbox.doc.getElementById("command-button-frames");
   ok(!btn.firstChild, "The frame list button doesn't have any children");
 
   // Open frame menu and wait till it's available on the screen.
-  let menu = toolbox.showFramesMenu({target: btn});
+  let menu = yield toolbox.showFramesMenu({target: btn});
   yield once(menu, "open");
 
   // Verify that the menu is popuplated.
   let frames = menu.items.slice();
   is(frames.length, 2, "We have both frames in the menu");
 
   frames.sort(function (a, b) {
     return a.label.localeCompare(b.label);
--- a/devtools/client/locales/en-US/animationinspector.properties
+++ b/devtools/client/locales/en-US/animationinspector.properties
@@ -72,16 +72,22 @@ player.animationIterationStartLabel=Iter
 
 # LOCALIZATION NOTE (player.animationOverallEasingLabel):
 # This string is displayed in a tooltip that appears when hovering over
 # animations in the timeline. It is the label displayed before the easing
 # that applies to a whole iteration of an animation as opposed to the
 # easing that applies between animation keyframes.
 player.animationOverallEasingLabel=Overall easing:
 
+# LOCALIZATION NOTE (player.animationTimingFunctionLabel):
+# This string is displayed in a tooltip that appears when hovering over
+# animations in the timeline. It is the label displayed before the
+# animation-timing-function for CSS Animations.
+player.animationTimingFunctionLabel=Animation timing function:
+
 # LOCALIZATION NOTE (player.animationFillLabel):
 # This string is displayed in a tooltip that appears when hovering over
 # animations in the timeline. It is the label displayed before the animation
 # fill mode value.
 player.animationFillLabel=Fill:
 
 # LOCALIZATION NOTE (player.animationDirectionLabel):
 # This string is displayed in a tooltip that appears when hovering over
--- a/devtools/client/locales/en-US/webide.dtd
+++ b/devtools/client/locales/en-US/webide.dtd
@@ -70,19 +70,17 @@
 <!ENTITY key_zoomout "-">
 <!ENTITY key_resetzoom "0">
 
 <!ENTITY projectPanel_myProjects "My Projects">
 <!ENTITY projectPanel_runtimeApps "Runtime Apps">
 <!ENTITY projectPanel_tabs "Tabs">
 <!ENTITY runtimePanel_usb "USB Devices">
 <!ENTITY runtimePanel_wifi "Wi-Fi Devices">
-<!ENTITY runtimePanel_simulator "Simulators">
 <!ENTITY runtimePanel_other "Other">
-<!ENTITY runtimePanel_installsimulator "Install Simulator">
 <!ENTITY runtimePanel_noadbhelper "Install ADB Helper">
 <!ENTITY runtimePanel_nousbdevice "Can’t see your device?">
 <!ENTITY runtimePanel_refreshDevices_label "Refresh Devices">
 
 <!-- Lense -->
 <!ENTITY details_valid_header "valid">
 <!ENTITY details_warning_header "warnings">
 <!ENTITY details_error_header "errors">
@@ -169,24 +167,8 @@
 <!ENTITY wifi_auth_yes_scanner "Have a QR scanner prompt?">
 <!-- LOCALIZATION NOTE (wifi_auth_token_request): Instructions requesting the
      user to transfer authentication info by transferring a token. -->
 <!ENTITY wifi_auth_token_request "If your other device asks for a token instead of scanning a QR code, please copy the value below to the other device:">
 <!ENTITY wifi_auth_qr_size_note "If the QR code appears too small for the connection to be successfully established, try zooming or enlarging the window.">
 
 <!-- Logs panel -->
 <!ENTITY logs_title "Pre-packaging Command Logs">
-
-<!-- Simulator Options -->
-<!ENTITY simulator_title "Simulator Options">
-<!ENTITY simulator_remove "Delete Simulator">
-<!ENTITY simulator_reset "Restore Defaults">
-<!ENTITY simulator_name "Name">
-<!ENTITY simulator_software "Software">
-<!ENTITY simulator_version "Version">
-<!ENTITY simulator_profile "Profile">
-<!ENTITY simulator_hardware "Hardware">
-<!ENTITY simulator_device "Device">
-<!ENTITY simulator_screenSize "Screen">
-<!ENTITY simulator_pixelRatio "Pixel Ratio">
-<!ENTITY simulator_tv_data "TV Simulation">
-<!ENTITY simulator_tv_data_open "Config Data">
-<!ENTITY simulator_tv_data_open_button "Open Config Directory…">
--- a/devtools/client/locales/en-US/webide.properties
+++ b/devtools/client/locales/en-US/webide.properties
@@ -37,32 +37,24 @@ error_listRunningApps=Can’t get app list from device
 
 # Variable: name of the operation (in english)
 error_operationTimeout=Operation timed out: %1$S
 error_operationFail=Operation failed: %1$S
 
 # Variable: app name
 error_cantConnectToApp=Can’t connect to app: %1$S
 
-# Variable: error message (in english)
-error_cantFetchAddonsJSON=Can’t fetch the add-on list: %S
-
 error_appProjectsLoadFailed=Unable to load project list. This can occur if you’ve used this profile with a newer version of Firefox.
 error_folderCreationFailed=Unable to create project folder in the selected directory.
 
 # Variable: runtime app build ID (looks like this %Y%M%D format) and firefox build ID (same format)
 error_runtimeVersionTooRecent=The connected runtime has a more recent build date (%1$S) than your desktop Firefox (%2$S) does. This is an unsupported setup and may cause DevTools to fail. Please update Firefox.
 
 addons_stable=stable
 addons_unstable=unstable
-# LOCALIZATION NOTE (addons_simulator_label): This label is shown as the name of
-# a given simulator version in the "Manage Simulators" pane.  %1$S: Firefox OS
-# version in the simulator, ex. 1.3.  %2$S: Simulator stability label, ex.
-# "stable" or "unstable".
-addons_simulator_label=Firefox OS %1$S Simulator (%2$S)
 addons_install_button=install
 addons_uninstall_button=uninstall
 addons_adb_label=ADB Helper Add-on
 addons_adb_warning=USB devices won’t be detected without this add-on
 addons_status_unknown=?
 addons_status_installed=Installed
 addons_status_uninstalled=Not Installed
 addons_status_preparing=preparing
@@ -78,14 +70,8 @@ runtimedetails_notUSBDevice=Not a USB de
 status_tooltip=Validation status: %1$S
 status_valid=VALID
 status_warning=WARNINGS
 status_error=ERRORS
 status_unknown=UNKNOWN
 
 # Device preferences and settings
 device_reset_default=Reset to default
-
-# Simulator options
-simulator_custom_device=Custom
-simulator_custom_binary=Custom B2G binary…
-simulator_custom_profile=Custom Gaia profile…
-simulator_default_profile=Use default
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -692,16 +692,34 @@ body {
   stroke: var(--transform-border-color);
 }
 
 .keyframes svg path.color {
   stroke: none;
   height: 100%;
 }
 
+.keyframes svg .hint {
+  stroke-opacity: 0;
+  stroke-linecap: round;
+  stroke-width: 5;
+}
+
+.keyframes svg path.hint {
+  fill: none;
+}
+
+.keyframes svg path.hint:hover {
+  stroke-opacity: 1;
+}
+
+.keyframes svg rect.hint {
+  fill-opacity: .1;
+}
+
 .animation-detail {
   position: relative;
   width: 100%;
   background-color: var(--theme-body-background);
   z-index: 5;
 }
 
 .animation-detail .animation-detail-header {
--- a/devtools/client/webaudioeditor/test/browser_wa_reset-04.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_reset-04.js
@@ -17,17 +17,17 @@ add_task(function* () {
     "The 'waiting for an audio context' notice should initially be hidden.");
   is($("#content").hidden, true,
     "The tool's content should initially be hidden.");
 
   let btn = toolbox.doc.getElementById("command-button-frames");
   ok(!btn.firstChild, "The frame list button has no children");
 
   // Open frame menu and wait till it's available on the screen.
-  let menu = toolbox.showFramesMenu({target: btn});
+  let menu = yield toolbox.showFramesMenu({target: btn});
   yield once(menu, "open");
 
   let frames = menu.items;
   is(frames.length, 2, "We have both frames in the list");
 
   // Select the iframe
   frames[1].click();
 
--- a/devtools/client/webide/content/addons.js
+++ b/devtools/client/webide/content/addons.js
@@ -12,35 +12,29 @@ const Strings = Services.strings.createB
 window.addEventListener("load", function () {
   document.querySelector("#aboutaddons").onclick = function () {
     let browserWin = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
     if (browserWin && browserWin.BrowserOpenAddonsMgr) {
       browserWin.BrowserOpenAddonsMgr("addons://list/extension");
     }
   };
   document.querySelector("#close").onclick = CloseUI;
-  GetAvailableAddons().then(BuildUI, (e) => {
-    console.error(e);
-    window.alert(Strings.formatStringFromName("error_cantFetchAddonsJSON", [e], 1));
-  });
+  BuildUI(GetAvailableAddons());
 }, {capture: true, once: true});
 
 window.addEventListener("unload", function () {
   ForgetAddonsList();
 }, {capture: true, once: true});
 
 function CloseUI() {
   window.parent.UI.openProject();
 }
 
 function BuildUI(addons) {
   BuildItem(addons.adb, "adb");
-  for (let addon of addons.simulators) {
-    BuildItem(addon, "simulator");
-  }
 }
 
 function BuildItem(addon, type) {
 
   function onAddonUpdate(event, arg) {
     switch (event) {
       case "update":
         progress.removeAttribute("value");
@@ -76,21 +70,16 @@ function BuildItem(addon, type) {
   let name = document.createElement("span");
   name.className = "name";
 
   switch (type) {
     case "adb":
       li.setAttribute("addon", type);
       name.textContent = Strings.GetStringFromName("addons_adb_label");
       break;
-    case "simulator":
-      li.setAttribute("addon", "simulator-" + addon.version);
-      let stability = Strings.GetStringFromName("addons_" + addon.stability);
-      name.textContent = Strings.formatStringFromName("addons_simulator_label", [addon.version, stability], 2);
-      break;
   }
 
   li.appendChild(name);
 
   let status = document.createElement("span");
   status.className = "status";
   status.textContent = Strings.GetStringFromName("addons_status_" + addon.status);
   li.appendChild(status);
--- a/devtools/client/webide/content/jar.mn
+++ b/devtools/client/webide/content/jar.mn
@@ -23,10 +23,8 @@ webide.jar:
     content/wifi-auth.js              (wifi-auth.js)
     content/wifi-auth.xhtml           (wifi-auth.xhtml)
     content/project-listing.xhtml     (project-listing.xhtml)
     content/project-listing.js        (project-listing.js)
     content/project-panel.js          (project-panel.js)
     content/runtime-panel.js          (runtime-panel.js)
     content/runtime-listing.xhtml     (runtime-listing.xhtml)
     content/runtime-listing.js        (runtime-listing.js)
-    content/simulator.js              (simulator.js)
-    content/simulator.xhtml           (simulator.xhtml)
--- a/devtools/client/webide/content/monitor.js
+++ b/devtools/client/webide/content/monitor.js
@@ -21,17 +21,17 @@ window.addEventListener("load", function
   Monitor.load();
 }, {once: true});
 
 
 /**
  * The Monitor is a WebIDE tool used to display any kind of time-based data in
  * the form of graphs.
  *
- * The data can come from a Firefox OS device, simulator, or from a WebSockets
+ * The data can come from a Firefox OS device, or from a WebSockets
  * server running locally.
  *
  * The format of a data update is typically an object like:
  *
  *     { graph: 'mygraph', curve: 'mycurve', value: 42, time: 1234 }
  *
  * or an array of such objects. For more details on the data format, see the
  * `Graph.update(data)` method.
--- a/devtools/client/webide/content/runtime-listing.js
+++ b/devtools/client/webide/content/runtime-listing.js
@@ -9,17 +9,16 @@ const RuntimeList = require("devtools/cl
 var runtimeList = new RuntimeList(window, window.parent);
 
 window.addEventListener("load", function () {
   document.getElementById("runtime-screenshot").onclick = TakeScreenshot;
   document.getElementById("runtime-details").onclick = ShowRuntimeDetails;
   document.getElementById("runtime-disconnect").onclick = DisconnectRuntime;
   document.getElementById("runtime-preferences").onclick = ShowDevicePreferences;
   document.getElementById("runtime-settings").onclick = ShowSettings;
-  document.getElementById("runtime-panel-installsimulator").onclick = ShowAddons;
   document.getElementById("runtime-panel-noadbhelper").onclick = ShowAddons;
   document.getElementById("runtime-panel-nousbdevice").onclick = ShowTroubleShooting;
   document.getElementById("refresh-devices").onclick = RefreshScanners;
   runtimeList.update();
   runtimeList.updateCommands();
 }, {capture: true, once: true});
 
 window.addEventListener("unload", function () {
--- a/devtools/client/webide/content/runtime-listing.xhtml
+++ b/devtools/client/webide/content/runtime-listing.xhtml
@@ -21,19 +21,16 @@
         <label class="panel-header">&runtimePanel_usb;
           <button class="runtime-panel-item-refreshdevices refresh-icon" id="refresh-devices" title="&runtimePanel_refreshDevices_label;"></button>
         </label>
         <button class="panel-item" id="runtime-panel-nousbdevice">&runtimePanel_nousbdevice;</button>
         <button class="panel-item" id="runtime-panel-noadbhelper">&runtimePanel_noadbhelper;</button>
         <div id="runtime-panel-usb"></div>
         <label class="panel-header" id="runtime-header-wifi">&runtimePanel_wifi;</label>
         <div id="runtime-panel-wifi"></div>
-        <label class="panel-header">&runtimePanel_simulator;</label>
-        <div id="runtime-panel-simulator"></div>
-        <button class="panel-item" id="runtime-panel-installsimulator">&runtimePanel_installsimulator;</button>
         <label class="panel-header">&runtimePanel_other;</label>
         <div id="runtime-panel-other"></div>
         <div id="runtime-actions">
           <button class="panel-item" id="runtime-details">&runtimeMenu_showDetails_label;</button>
           <button class="panel-item" id="runtime-preferences">&runtimeMenu_showDevicePrefs_label;</button>
           <button class="panel-item" id="runtime-settings">&runtimeMenu_showSettings_label;</button>
           <button class="panel-item" id="runtime-screenshot">&runtimeMenu_takeScreenshot_label;</button>
           <button class="panel-item" id="runtime-disconnect">&runtimeMenu_disconnect_label;</button>
deleted file mode 100644
--- a/devtools/client/webide/content/simulator.js
+++ /dev/null
@@ -1,354 +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/. */
-
-var Cu = Components.utils;
-var Ci = Components.interfaces;
-
-const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const { getDevices, getDeviceString } = require("devtools/client/shared/devices");
-const { Simulators, Simulator } = require("devtools/client/webide/modules/simulators");
-const Services = require("Services");
-const EventEmitter = require("devtools/shared/old-event-emitter");
-const promise = require("promise");
-const utils = require("devtools/client/webide/modules/utils");
-
-const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
-
-var SimulatorEditor = {
-
-  // Available Firefox OS Simulator addons (key: `addon.id`).
-  _addons: {},
-
-  // Available device simulation profiles (key: `device.name`).
-  _devices: {},
-
-  // The names of supported simulation options.
-  _deviceOptions: [],
-
-  // The <form> element used to edit Simulator options.
-  _form: null,
-
-  // The Simulator object being edited.
-  _simulator: null,
-
-  // Generate the dynamic form elements.
-  init() {
-    let promises = [];
-
-    // Grab the <form> element.
-    let form = this._form;
-    if (!form) {
-      // This is the first time we run `init()`, bootstrap some things.
-      form = this._form = document.querySelector("#simulator-editor");
-      form.addEventListener("change", this.update.bind(this));
-      Simulators.on("configure", (e, simulator) => { this.edit(simulator); });
-      // Extract the list of device simulation options we'll support.
-      let deviceFields = form.querySelectorAll("*[data-device]");
-      this._deviceOptions = Array.map(deviceFields, field => field.name);
-    }
-
-    // Append a new <option> to a <select> (or <optgroup>) element.
-    function opt(select, value, text) {
-      let option = document.createElement("option");
-      option.value = value;
-      option.textContent = text;
-      select.appendChild(option);
-    }
-
-    // Generate B2G version selector.
-    promises.push(Simulators.findSimulatorAddons().then(addons => {
-      this._addons = {};
-      form.version.innerHTML = "";
-      form.version.classList.remove("custom");
-      addons.forEach(addon => {
-        this._addons[addon.id] = addon;
-        opt(form.version, addon.id, addon.name);
-      });
-      opt(form.version, "custom", "");
-      opt(form.version, "pick", Strings.GetStringFromName("simulator_custom_binary"));
-    }));
-
-    // Generate profile selector.
-    form.profile.innerHTML = "";
-    form.profile.classList.remove("custom");
-    opt(form.profile, "default", Strings.GetStringFromName("simulator_default_profile"));
-    opt(form.profile, "custom", "");
-    opt(form.profile, "pick", Strings.GetStringFromName("simulator_custom_profile"));
-
-    // Generate example devices list.
-    form.device.innerHTML = "";
-    form.device.classList.remove("custom");
-    opt(form.device, "custom", Strings.GetStringFromName("simulator_custom_device"));
-    promises.push(getDevices().then(devices => {
-      devices.TYPES.forEach(type => {
-        let b2gDevices = devices[type].filter(d => d.firefoxOS);
-        if (b2gDevices.length < 1) {
-          return;
-        }
-        let optgroup = document.createElement("optgroup");
-        optgroup.label = getDeviceString(type);
-        b2gDevices.forEach(device => {
-          this._devices[device.name] = device;
-          opt(optgroup, device.name, device.name);
-        });
-        form.device.appendChild(optgroup);
-      });
-    }));
-
-    return promise.all(promises);
-  },
-
-  // Edit the configuration of an existing Simulator, or create a new one.
-  edit(simulator) {
-    // If no Simulator was given to edit, we're creating a new one.
-    if (!simulator) {
-      simulator = new Simulator(); // Default options.
-      Simulators.add(simulator);
-    }
-
-    this._simulator = null;
-
-    return this.init().then(() => {
-      this._simulator = simulator;
-
-      // Update the form fields.
-      this._form.name.value = simulator.name;
-
-      this.updateVersionSelector();
-      this.updateProfileSelector();
-      this.updateDeviceSelector();
-      this.updateDeviceFields();
-
-      // Change visibility of 'TV Simulator Menu'.
-      let tvSimMenu = document.querySelector("#tv_simulator_menu");
-      tvSimMenu.style.visibility = (this._simulator.type === "television") ?
-                                   "visible" : "hidden";
-
-      // Trigger any listener waiting for this update
-      let change = document.createEvent("HTMLEvents");
-      change.initEvent("change", true, true);
-      this._form.dispatchEvent(change);
-    });
-  },
-
-  // Open the directory of TV Simulator config.
-  showTVConfigDirectory() {
-    let profD = Services.dirsvc.get("ProfD", Ci.nsIFile);
-    profD.append("extensions");
-    profD.append(this._simulator.addon.id);
-    profD.append("profile");
-    profD.append("dummy");
-    let profileDir = profD.path;
-
-    // Show the profile directory.
-    let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
-                                           "nsIFile", "initWithPath");
-    new nsLocalFile(profileDir).reveal();
-  },
-
-  // Close the configuration panel.
-  close() {
-    this._simulator = null;
-    window.parent.UI.openProject();
-  },
-
-  // Restore the simulator to its default configuration.
-  restoreDefaults() {
-    let simulator = this._simulator;
-    this.version = simulator.addon.id;
-    this.profile = "default";
-    simulator.restoreDefaults();
-    Simulators.emitUpdated();
-    return this.edit(simulator);
-  },
-
-  // Delete this simulator.
-  deleteSimulator() {
-    Simulators.remove(this._simulator);
-    this.close();
-  },
-
-  // Select an available option, or set the "custom" option.
-  updateSelector(selector, value) {
-    selector.value = value;
-    if (selector.selectedIndex == -1) {
-      selector.value = "custom";
-      selector.classList.add("custom");
-      selector[selector.selectedIndex].textContent = value;
-    }
-  },
-
-  // VERSION: Can be an installed `addon.id` or a custom binary path.
-
-  get version() {
-    return this._simulator.options.b2gBinary || this._simulator.addon.id;
-  },
-
-  set version(value) {
-    let form = this._form;
-    let simulator = this._simulator;
-    let oldVer = simulator.version;
-    if (this._addons[value]) {
-      // `value` is a simulator addon ID.
-      simulator.addon = this._addons[value];
-      simulator.options.b2gBinary = null;
-    } else {
-      // `value` is a custom binary path.
-      simulator.options.b2gBinary = value;
-      // TODO (Bug 1146531) Indicate that a custom profile is now required.
-    }
-    // If `form.name` contains the old version, update its last occurrence.
-    if (form.name.value.includes(oldVer) && simulator.version !== oldVer) {
-      let regex = new RegExp("(.*)" + oldVer);
-      let name = form.name.value.replace(regex, "$1" + simulator.version);
-      simulator.options.name = form.name.value = Simulators.uniqueName(name);
-    }
-  },
-
-  updateVersionSelector() {
-    this.updateSelector(this._form.version, this.version);
-  },
-
-  // PROFILE. Can be "default" or a custom profile directory path.
-
-  get profile() {
-    return this._simulator.options.gaiaProfile || "default";
-  },
-
-  set profile(value) {
-    this._simulator.options.gaiaProfile = (value == "default" ? null : value);
-  },
-
-  updateProfileSelector() {
-    this.updateSelector(this._form.profile, this.profile);
-  },
-
-  // DEVICE. Can be an existing `device.name` or "custom".
-
-  get device() {
-    let devices = this._devices;
-    let simulator = this._simulator;
-
-    // Search for the name of a device matching current simulator options.
-    for (let name in devices) {
-      let match = true;
-      for (let option of this._deviceOptions) {
-        if (simulator.options[option] === devices[name][option]) {
-          continue;
-        }
-        match = false;
-        break;
-      }
-      if (match) {
-        return name;
-      }
-    }
-    return "custom";
-  },
-
-  set device(name) {
-    let device = this._devices[name];
-    if (!device) {
-      return;
-    }
-    let form = this._form;
-    let simulator = this._simulator;
-    this._deviceOptions.forEach(option => {
-      simulator.options[option] = form[option].value = device[option] || null;
-    });
-    // TODO (Bug 1146531) Indicate when a custom profile is required (e.g. for
-    // tablet, TV…).
-  },
-
-  updateDeviceSelector() {
-    this.updateSelector(this._form.device, this.device);
-  },
-
-  // Erase any current values, trust only the `simulator.options`.
-  updateDeviceFields() {
-    let form = this._form;
-    let simulator = this._simulator;
-    this._deviceOptions.forEach(option => {
-      form[option].value = simulator.options[option];
-    });
-  },
-
-  // Handle a change in our form's fields.
-  update(event) {
-    let simulator = this._simulator;
-    if (!simulator) {
-      return;
-    }
-    let form = this._form;
-    let input = event.target;
-    switch (input.name) {
-      case "name":
-        simulator.options.name = input.value;
-        break;
-      case "version":
-        switch (input.value) {
-          case "pick":
-            utils.getCustomBinary(window).then(file => {
-              if (file) {
-                this.version = file.path;
-              }
-              // Whatever happens, don't stay on the "pick" option.
-              this.updateVersionSelector();
-            });
-            break;
-          case "custom":
-            this.version = input[input.selectedIndex].textContent;
-            break;
-          default:
-            this.version = input.value;
-        }
-        break;
-      case "profile":
-        switch (input.value) {
-          case "pick":
-            utils.getCustomProfile(window).then(directory => {
-              if (directory) {
-                this.profile = directory.path;
-              }
-              // Whatever happens, don't stay on the "pick" option.
-              this.updateProfileSelector();
-            });
-            break;
-          case "custom":
-            this.profile = input[input.selectedIndex].textContent;
-            break;
-          default:
-            this.profile = input.value;
-        }
-        break;
-      case "device":
-        this.device = input.value;
-        break;
-      default:
-        simulator.options[input.name] = input.value || null;
-        this.updateDeviceSelector();
-    }
-    Simulators.emitUpdated();
-  },
-};
-
-window.addEventListener("load", function onLoad() {
-  document.querySelector("#close").onclick = e => {
-    SimulatorEditor.close();
-  };
-  document.querySelector("#reset").onclick = e => {
-    SimulatorEditor.restoreDefaults();
-  };
-  document.querySelector("#remove").onclick = e => {
-    SimulatorEditor.deleteSimulator();
-  };
-
-  // We just loaded, so we probably missed the first configure request.
-  SimulatorEditor.edit(Simulators._lastConfiguredSimulator);
-
-  document.querySelector("#open-tv-dummy-directory").onclick = e => {
-    SimulatorEditor.showTVConfigDirectory();
-    e.preventDefault();
-  };
-});
deleted file mode 100644
--- a/devtools/client/webide/content/simulator.xhtml
+++ /dev/null
@@ -1,99 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!-- 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/. -->
-
-<!DOCTYPE html [
-  <!ENTITY % webideDTD SYSTEM "chrome://devtools/locale/webide.dtd" >
-  %webideDTD;
-]>
-
-<html xmlns="http://www.w3.org/1999/xhtml">
-  <head>
-    <meta charset="utf8"/>
-    <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
-    <link rel="stylesheet" href="chrome://webide/skin/simulator.css" type="text/css"/>
-    <script type="application/javascript" src="chrome://webide/content/simulator.js"></script>
-  </head>
-  <body>
-
-    <div id="controls">
-      <a id="remove" class="hidden">&simulator_remove;</a>
-      <a id="reset">&simulator_reset;</a>
-      <a id="close">&deck_close;</a>
-    </div>
-
-    <form id="simulator-editor">
-
-      <h1>&simulator_title;</h1>
-
-      <h2>&simulator_software;</h2>
-
-      <ul>
-        <li>
-          <label>
-            <span class="label">&simulator_name;</span>
-            <input type="text" name="name"/>
-          </label>
-        </li>
-        <li>
-          <label>
-            <span class="label">&simulator_version;</span>
-            <select name="version"/>
-          </label>
-        </li>
-        <li>
-          <label>
-            <span class="label">&simulator_profile;</span>
-            <select name="profile"/>
-          </label>
-        </li>
-      </ul>
-
-      <h2>&simulator_hardware;</h2>
-
-      <ul>
-        <li>
-          <label>
-            <span class="label">&simulator_device;</span>
-            <select name="device"/>
-          </label>
-        </li>
-        <li>
-          <label>
-            <span class="label">&simulator_screenSize;</span>
-            <input name="width" data-device="" type="number"/>
-            <span>×</span>
-            <input name="height" data-device="" type="number"/>
-          </label>
-        </li>
-        <li class="hidden">
-          <label>
-            <span class="label">&simulator_pixelRatio;</span>
-            <input name="pixelRatio" data-device="" type="number" step="0.05"/>
-          </label>
-        </li>
-      </ul>
-
-      <!-- This menu is shown when simulator type is television-->
-      <p id="tv_simulator_menu" style="visibility:hidden;">
-        <h2>&simulator_tv_data;</h2>
-
-        <ul>
-          <li>
-            <label>
-              <span class="label">&simulator_tv_data_open;</span>
-              <button id="open-tv-dummy-directory">
-                &simulator_tv_data_open_button;
-              </button>
-            </label>
-          </li>
-        </ul>
-
-      </p>
-
-    </form>
-
-  </body>
-</html>
--- a/devtools/client/webide/content/webide.js
+++ b/devtools/client/webide/content/webide.js
@@ -16,17 +16,16 @@ const {AppManager} = require("devtools/c
 const EventEmitter = require("devtools/shared/old-event-emitter");
 const promise = require("promise");
 const {GetAvailableAddons} = require("devtools/client/webide/modules/addons");
 const {getJSON} = require("devtools/client/shared/getjson");
 const utils = require("devtools/client/webide/modules/utils");
 const Telemetry = require("devtools/client/shared/telemetry");
 const {RuntimeScanners} = require("devtools/client/webide/modules/runtimes");
 const {showDoorhanger} = require("devtools/client/shared/doorhanger");
-const {Simulators} = require("devtools/client/webide/modules/simulators");
 const {Task} = require("devtools/shared/task");
 
 const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
 
 const HTML = "http://www.w3.org/1999/xhtml";
 const HELP_URL = "https://developer.mozilla.org/docs/Tools/WebIDE/Troubleshooting";
 
 const MAX_ZOOM = 1.4;
@@ -40,17 +39,16 @@ const MS_PER_DAY = 86400000;
    Object.defineProperty(this, key, {
      value: value,
      enumerable: true,
      writable: false
    });
  });
 
 // Download remote resources early
-getJSON("devtools.webide.addonsURL");
 getJSON("devtools.webide.templatesURL");
 getJSON("devtools.devices.url");
 
 // See bug 989619
 console.log = console.log.bind(console);
 console.warn = console.warn.bind(console);
 console.error = console.error.bind(console);
 
@@ -86,42 +84,37 @@ var UI = {
       console.error(e);
       this.reportError("error_appProjectsLoadFailed");
     });
 
     // Auto install the ADB Addon Helper. Only once.
     // If the user decides to uninstall any of this addon, we won't install it again.
     let autoinstallADBHelper = Services.prefs.getBoolPref("devtools.webide.autoinstallADBHelper");
     if (autoinstallADBHelper) {
-      GetAvailableAddons().then(addons => {
-        addons.adb.install();
-      }, console.error);
+      let addons = GetAvailableAddons();
+      addons.adb.install();
     }
 
     Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
 
     this.setupDeck();
 
     this.contentViewer = window.QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIWebNavigation)
                                .QueryInterface(Ci.nsIDocShell)
                                .contentViewer;
     this.contentViewer.fullZoom = Services.prefs.getCharPref("devtools.webide.zoom");
 
     gDevToolsBrowser.isWebIDEInitialized.resolve();
-
-    this.configureSimulator = this.configureSimulator.bind(this);
-    Simulators.on("configure", this.configureSimulator);
   },
 
   destroy: function () {
     window.removeEventListener("focus", this.onfocus, true);
     AppManager.off("app-manager-update", this.appManagerUpdate);
     AppManager.destroy();
-    Simulators.off("configure", this.configureSimulator);
     this.updateConnectionTelemetry();
     this._telemetry.toolClosed("webide");
     this._telemetry.destroy();
   },
 
   onfocus: function () {
     // Because we can't track the activity in the folder project,
     // we need to validate the project regularly. Let's assume that
@@ -194,20 +187,16 @@ var UI = {
         break;
       case "runtime-targets":
         this.autoSelectProject();
         break;
     }
     this._updatePromise = promise.resolve();
   },
 
-  configureSimulator: function (event, simulator) {
-    UI.selectDeckPanel("simulator");
-  },
-
   openInBrowser: function (url) {
     // Open a URL in a Firefox window
     let mainWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
     if (mainWindow) {
       mainWindow.openUILinkIn(url, "tab");
       mainWindow.focus()
     } else {
       window.open(url);
--- a/devtools/client/webide/content/webide.xul
+++ b/devtools/client/webide/content/webide.xul
@@ -10,17 +10,17 @@
 ]>
 
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 
 <?xml-stylesheet href="chrome://global/skin/global.css"?>
 <?xml-stylesheet href="resource://devtools/client/themes/common.css"?>
 <?xml-stylesheet href="chrome://webide/skin/webide.css"?>
 
-<window id="webide" onclose="return UI.canCloseProject();"
+<window id="webide" 
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml"
         title="&windowTitle;"
         windowtype="devtools:webide"
         macanimationtype="document"
         fullscreenbutton="true"
         screenX="4" screenY="4"
         width="800" height="600"
@@ -148,17 +148,16 @@
       <splitter class="devtools-side-splitter" id="project-listing-splitter"/>
       <deck flex="1" id="deck" selectedIndex="-1">
         <iframe id="deck-panel-details" flex="1" src="details.xhtml"/>
         <iframe id="deck-panel-addons" flex="1" src="addons.xhtml"/>
         <iframe id="deck-panel-prefs" flex="1" src="prefs.xhtml"/>
         <iframe id="deck-panel-runtimedetails" flex="1" lazysrc="runtimedetails.xhtml"/>
         <iframe id="deck-panel-monitor" flex="1" lazysrc="monitor.xhtml"/>
         <iframe id="deck-panel-devicepreferences" flex="1" lazysrc="devicepreferences.xhtml"/>
-        <iframe id="deck-panel-simulator" flex="1" lazysrc="simulator.xhtml"/>
       </deck>
       <splitter class="devtools-side-splitter" id="runtime-listing-splitter"/>
       <vbox id="runtime-listing-panel" class="runtime-listing panel-list" flex="1">
         <div id="runtime-listing-wrapper" class="panel-list-wrapper">
           <iframe id="runtime-listing-panel-details" flex="1" src="runtime-listing.xhtml" tooltip="aHTMLTooltip"/>
         </div>
       </vbox>
     </div>
--- a/devtools/client/webide/modules/addons.js
+++ b/devtools/client/webide/modules/addons.js
@@ -4,21 +4,17 @@
 
 "use strict";
 
 const {AddonManager} = require("resource://gre/modules/AddonManager.jsm");
 const Services = require("Services");
 const {getJSON} = require("devtools/client/shared/getjson");
 const EventEmitter = require("devtools/shared/old-event-emitter");
 
-const ADDONS_URL = "devtools.webide.addonsURL";
-
-var SIMULATOR_LINK = Services.prefs.getCharPref("devtools.webide.simulatorAddonsURL");
 var ADB_LINK = Services.prefs.getCharPref("devtools.webide.adbAddonURL");
-var SIMULATOR_ADDON_ID = Services.prefs.getCharPref("devtools.webide.simulatorAddonID");
 var ADB_ADDON_ID = Services.prefs.getCharPref("devtools.webide.adbAddonID");
 
 var platform = Services.appShell.hiddenDOMWindow.navigator.platform;
 var OS = "";
 if (platform.indexOf("Win") != -1) {
   OS = "win32";
 } else if (platform.indexOf("Mac") != -1) {
   OS = "mac64";
@@ -30,53 +26,33 @@ if (platform.indexOf("Win") != -1) {
   }
 }
 
 var addonsListener = {};
 addonsListener.onEnabled =
 addonsListener.onDisabled =
 addonsListener.onInstalled =
 addonsListener.onUninstalled = (updatedAddon) => {
-  GetAvailableAddons().then(addons => {
-    for (let a of [...addons.simulators, addons.adb]) {
-      if (a.addonID == updatedAddon.id) {
-        a.updateInstallStatus();
-      }
-    }
-  });
+  let addons = GetAvailableAddons();
+  addons.adb.updateInstallStatus();
 };
 AddonManager.addAddonListener(addonsListener);
 
-var GetAvailableAddons_promise = null;
+var AvailableAddons = null;
 var GetAvailableAddons = exports.GetAvailableAddons = function () {
-  if (!GetAvailableAddons_promise) {
-    GetAvailableAddons_promise = new Promise((resolve, reject) => {
-      let addons = {
-        simulators: [],
-        adb: null
-      };
-      getJSON(ADDONS_URL).then(json => {
-        for (let stability in json) {
-          for (let version of json[stability]) {
-            addons.simulators.push(new SimulatorAddon(stability, version));
-          }
-        }
-        addons.adb = new ADBAddon();
-        resolve(addons);
-      }, e => {
-        GetAvailableAddons_promise = null;
-        reject(e);
-      });
-    });
+  if (!AvailableAddons) {
+    AvailableAddons = {
+      adb: new ADBAddon()
+    };
   }
-  return GetAvailableAddons_promise;
+  return AvailableAddons;
 };
 
 exports.ForgetAddonsList = function () {
-  GetAvailableAddons_promise = null;
+  AvailableAddons = null;
 };
 
 function Addon() {}
 Addon.prototype = {
   _status: "unknown",
   set status(value) {
     if (this._status != value) {
       this._status = value;
@@ -155,30 +131,16 @@ Addon.prototype = {
   onInstallCancelled: function (install) {
     this.installFailureHandler(install, "Install cancelled");
   },
   onInstallFailed: function (install) {
     this.installFailureHandler(install, "Install failed");
   },
 };
 
-function SimulatorAddon(stability, version) {
-  EventEmitter.decorate(this);
-  this.stability = stability;
-  this.version = version;
-  // This addon uses the string "linux" for "linux32"
-  let fixedOS = OS == "linux32" ? "linux" : OS;
-  this.xpiLink = SIMULATOR_LINK.replace(/#OS#/g, fixedOS)
-                               .replace(/#VERSION#/g, version)
-                               .replace(/#SLASHED_VERSION#/g, version.replace(/\./g, "_"));
-  this.addonID = SIMULATOR_ADDON_ID.replace(/#SLASHED_VERSION#/g, version.replace(/\./g, "_"));
-  this.updateInstallStatus();
-}
-SimulatorAddon.prototype = Object.create(Addon.prototype);
-
 function ADBAddon() {
   EventEmitter.decorate(this);
   // This addon uses the string "linux" for "linux32"
   let fixedOS = OS == "linux32" ? "linux" : OS;
   this.xpiLink = ADB_LINK.replace(/#OS#/g, fixedOS);
   this.addonID = ADB_ADDON_ID;
   this.updateInstallStatus();
 }
--- a/devtools/client/webide/modules/app-manager.js
+++ b/devtools/client/webide/modules/app-manager.js
@@ -770,17 +770,16 @@ var AppManager = exports.AppManager = {
   },
 
   /* RUNTIME LIST */
 
   _clearRuntimeList: function () {
     this.runtimeList = {
       usb: [],
       wifi: [],
-      simulator: [],
       other: []
     };
   },
 
   _rebuildRuntimeList: function () {
     let runtimes = RuntimeScanners.listRuntimes();
     this._clearRuntimeList();
 
@@ -788,19 +787,16 @@ var AppManager = exports.AppManager = {
     for (let runtime of runtimes) {
       switch (runtime.type) {
         case RuntimeTypes.USB:
           this.runtimeList.usb.push(runtime);
           break;
         case RuntimeTypes.WIFI:
           this.runtimeList.wifi.push(runtime);
           break;
-        case RuntimeTypes.SIMULATOR:
-          this.runtimeList.simulator.push(runtime);
-          break;
         default:
           this.runtimeList.other.push(runtime);
       }
     }
 
     this.update("runtime-details");
     this.update("runtime-list");
   },
--- a/devtools/client/webide/modules/moz.build
+++ b/devtools/client/webide/modules/moz.build
@@ -8,13 +8,11 @@ DevToolsModules(
     'addons.js',
     'app-manager.js',
     'app-projects.js',
     'app-validator.js',
     'config-view.js',
     'project-list.js',
     'runtime-list.js',
     'runtimes.js',
-    'simulator-process.js',
-    'simulators.js',
     'tab-store.js',
     'utils.js'
 )
--- a/devtools/client/webide/modules/runtime-list.js
+++ b/devtools/client/webide/modules/runtime-list.js
@@ -121,17 +121,16 @@ RuntimeList.prototype = {
     if (WiFiScanner.allowed) {
       wifiHeaderNode.removeAttribute("hidden");
     } else {
       wifiHeaderNode.setAttribute("hidden", "true");
     }
 
     let usbListNode = doc.querySelector("#runtime-panel-usb");
     let wifiListNode = doc.querySelector("#runtime-panel-wifi");
-    let simulatorListNode = doc.querySelector("#runtime-panel-simulator");
     let otherListNode = doc.querySelector("#runtime-panel-other");
     let noHelperNode = doc.querySelector("#runtime-panel-noadbhelper");
     let noUSBNode = doc.querySelector("#runtime-panel-nousbdevice");
 
     if (Devices.helperAddonInstalled) {
       noHelperNode.setAttribute("hidden", "true");
     } else {
       noHelperNode.removeAttribute("hidden");
@@ -147,17 +146,16 @@ RuntimeList.prototype = {
       noUSBNode.removeAttribute("hidden");
     } else {
       noUSBNode.setAttribute("hidden", "true");
     }
 
     for (let [type, parent] of [
       ["usb", usbListNode],
       ["wifi", wifiListNode],
-      ["simulator", simulatorListNode],
       ["other", otherListNode],
     ]) {
       while (parent.hasChildNodes()) {
         parent.firstChild.remove();
       }
       for (let runtime of runtimeList[type]) {
         let r = runtime;
         let panelItemNode = doc.createElement(this._panelBoxEl);
--- a/devtools/client/webide/modules/runtimes.js
+++ b/devtools/client/webide/modules/runtimes.js
@@ -4,17 +4,16 @@
 
 "use strict";
 
 const {Ci} = require("chrome");
 const Services = require("Services");
 const {Devices} = require("resource://devtools/shared/apps/Devices.jsm");
 const {Connection} = require("devtools/shared/client/connection-manager");
 const {DebuggerServer} = require("devtools/server/main");
-const {Simulators} = require("devtools/client/webide/modules/simulators");
 const discovery = require("devtools/shared/discovery/discovery");
 const EventEmitter = require("devtools/shared/old-event-emitter");
 const promise = require("promise");
 loader.lazyRequireGetter(this, "AuthenticationResult",
   "devtools/shared/security/auth", true);
 loader.lazyRequireGetter(this, "DevToolsUtils",
   "devtools/shared/DevToolsUtils");
 
@@ -190,57 +189,16 @@ var RuntimeScanners = {
 };
 
 EventEmitter.decorate(RuntimeScanners);
 
 exports.RuntimeScanners = RuntimeScanners;
 
 /* SCANNERS */
 
-var SimulatorScanner = {
-
-  _runtimes: [],
-
-  enable() {
-    this._updateRuntimes = this._updateRuntimes.bind(this);
-    Simulators.on("updated", this._updateRuntimes);
-    this._updateRuntimes();
-  },
-
-  disable() {
-    Simulators.off("updated", this._updateRuntimes);
-  },
-
-  _emitUpdated() {
-    this.emit("runtime-list-updated");
-  },
-
-  _updateRuntimes() {
-    Simulators.findSimulators().then(simulators => {
-      this._runtimes = [];
-      for (let simulator of simulators) {
-        this._runtimes.push(new SimulatorRuntime(simulator));
-      }
-      this._emitUpdated();
-    });
-  },
-
-  scan() {
-    return promise.resolve();
-  },
-
-  listRuntimes: function () {
-    return this._runtimes;
-  }
-
-};
-
-EventEmitter.decorate(SimulatorScanner);
-RuntimeScanners.add(SimulatorScanner);
-
 /**
  * This is a lazy ADB scanner shim which only tells the ADB Helper to start and
  * stop as needed.  The real scanner that lists devices lives in ADB Helper.
  * ADB Helper 0.8.0 and later wait until these signals are received before
  * starting ADB polling.  For earlier versions, they have no effect.
  */
 var LazyAdbScanner = {
 
@@ -356,17 +314,16 @@ RuntimeScanners.add(StaticScanner);
 
 /* RUNTIMES */
 
 // These type strings are used for logging events to Telemetry.
 // You must update Histograms.json if new types are added.
 var RuntimeTypes = exports.RuntimeTypes = {
   USB: "USB",
   WIFI: "WIFI",
-  SIMULATOR: "SIMULATOR",
   REMOTE: "REMOTE",
   LOCAL: "LOCAL",
   OTHER: "OTHER"
 };
 
 function WiFiRuntime(deviceName) {
   this.deviceName = deviceName;
 }
@@ -472,45 +429,16 @@ WiFiRuntime.prototype = {
       }
     };
   }
 };
 
 // For testing use only
 exports._WiFiRuntime = WiFiRuntime;
 
-function SimulatorRuntime(simulator) {
-  this.simulator = simulator;
-}
-
-SimulatorRuntime.prototype = {
-  type: RuntimeTypes.SIMULATOR,
-  connect: function (connection) {
-    return this.simulator.launch().then(port => {
-      connection.host = "localhost";
-      connection.port = port;
-      connection.keepConnecting = true;
-      connection.once(Connection.Events.DISCONNECTED, e => this.simulator.kill());
-      connection.connect();
-    });
-  },
-  configure() {
-    Simulators.emit("configure", this.simulator);
-  },
-  get id() {
-    return this.simulator.id;
-  },
-  get name() {
-    return this.simulator.name;
-  },
-};
-
-// For testing use only
-exports._SimulatorRuntime = SimulatorRuntime;
-
 var gLocalRuntime = {
   type: RuntimeTypes.LOCAL,
   connect: function (connection) {
     if (!DebuggerServer.initialized) {
       DebuggerServer.init();
       DebuggerServer.addBrowserActors();
     }
     DebuggerServer.allowChromeProcess = true;
deleted file mode 100644
--- a/devtools/client/webide/modules/simulator-process.js
+++ /dev/null
@@ -1,336 +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/.
- */
-
-"use strict";
-
-const { Cc, Ci, Cu } = require("chrome");
-
-const Environment = Cc["@mozilla.org/process/environment;1"]
-                      .getService(Ci.nsIEnvironment);
-const EventEmitter = require("devtools/shared/old-event-emitter");
-const Services = require("Services");
-
-const {Subprocess} = Cu.import("resource://gre/modules/Subprocess.jsm", {});
-
-loader.lazyGetter(this, "OS", () => {
-  switch (Services.appinfo.OS) {
-    case "Darwin":
-      return "mac64";
-    case "Linux":
-      if (Services.appinfo.XPCOMABI.indexOf("x86_64") === 0) {
-        return "linux64";
-      } else {
-        return "linux32";
-      }
-    case "WINNT":
-      return "win32";
-    default:
-      return "";
-  }
-});
-
-function SimulatorProcess() {}
-SimulatorProcess.prototype = {
-
-  // Check if B2G is running.
-  get isRunning() {
-    return !!this.process;
-  },
-
-  // Start the process and connect the debugger client.
-  run() {
-
-    // Resolve B2G binary.
-    let b2g = this.b2gBinary;
-    if (!b2g || !b2g.exists()) {
-      throw Error("B2G executable not found.");
-    }
-
-    // Ensure Gaia profile exists.
-    let gaia = this.gaiaProfile;
-    if (!gaia || !gaia.exists()) {
-      throw Error("Gaia profile directory not found.");
-    }
-
-    this.once("stdout", function () {
-      if (OS == "mac64") {
-        console.debug("WORKAROUND run osascript to show b2g-desktop window on OS=='mac64'");
-        // Escape double quotes and escape characters for use in AppleScript.
-        let path = b2g.path.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
-
-        Subprocess.call({
-          command: "/usr/bin/osascript",
-          arguments: ["-e", 'tell application "' + path + '" to activate'],
-        });
-      }
-    });
-
-    let logHandler = (e, data) => this.log(e, data.trim());
-    this.on("stdout", logHandler);
-    this.on("stderr", logHandler);
-    this.once("exit", () => {
-      this.off("stdout", logHandler);
-      this.off("stderr", logHandler);
-    });
-
-    let environment;
-    if (OS.indexOf("linux") > -1) {
-      environment = ["TMPDIR=" + Services.dirsvc.get("TmpD", Ci.nsIFile).path];
-      ["DISPLAY", "XAUTHORITY"]
-        .filter(key => Environment.exists(key))
-        .forEach(key => {
-          environment.push(key + "=" + Environment.get(key));
-        });
-    }
-
-    // Spawn a B2G instance.
-    Subprocess.call({
-      command: b2g.path,
-      arguments: this.args,
-      environmentAppend: true,
-      environment: environment,
-      stderr: "pipe",
-    }).then(process => {
-      this.process = process;
-      let dumpPipe = async (pipe, type) => {
-        let data = await pipe.readString();
-        while (data) {
-          this.emit(type, data);
-          data = await pipe.readString();
-        }
-      };
-      dumpPipe(process.stdout, "stdout");
-      dumpPipe(process.stderr, "stderr");
-
-      // On B2G instance exit, reset tracked process, remote debugger port and
-      // shuttingDown flag, then finally emit an exit event.
-      process.wait().then(result => {
-        this.process = null;
-        this.emit("exit", result.exitCode);
-      });
-    });
-  },
-
-  // Request a B2G instance kill.
-  kill() {
-    return new Promise(resolve => {
-      if (this.process) {
-        this.once("exit", (e, exitCode) => {
-          this.shuttingDown = false;
-          resolve(exitCode);
-        });
-        if (!this.shuttingDown) {
-          this.shuttingDown = true;
-          this.emit("kill", null);
-          this.process.kill();
-        }
-      } else {
-        return resolve(undefined);
-      }
-    });
-  },
-
-  // Maybe log output messages.
-  log(level, message) {
-    if (!Services.prefs.getBoolPref("devtools.webide.logSimulatorOutput")) {
-      return;
-    }
-    if (level === "stderr" || level === "error") {
-      console.error(message);
-      return;
-    }
-    console.log(message);
-  },
-
-  // Compute B2G CLI arguments.
-  get args() {
-    let args = [];
-
-    // Gaia profile.
-    args.push("-profile", this.gaiaProfile.path);
-
-    // Debugger server port.
-    let port = parseInt(this.options.port);
-    args.push("-start-debugger-server", "" + port);
-
-    // Screen size.
-    let width = parseInt(this.options.width);
-    let height = parseInt(this.options.height);
-    if (width && height) {
-      args.push("-screen", width + "x" + height);
-    }
-
-    // Ignore eventual zombie instances of b2g that are left over.
-    args.push("-no-remote");
-
-    // If we are running a simulator based on Mulet,
-    // we have to override the default chrome URL
-    // in order to prevent the Browser UI to appear.
-    if (this.b2gBinary.leafName.includes("firefox")) {
-      args.push("-chrome", "chrome://b2g/content/shell.html");
-    }
-
-    return args;
-  },
-};
-
-EventEmitter.decorate(SimulatorProcess.prototype);
-
-
-function CustomSimulatorProcess(options) {
-  this.options = options;
-}
-
-var CSPp = CustomSimulatorProcess.prototype = Object.create(SimulatorProcess.prototype);
-
-// Compute B2G binary file handle.
-Object.defineProperty(CSPp, "b2gBinary", {
-  get: function () {
-    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
-    file.initWithPath(this.options.b2gBinary);
-    return file;
-  }
-});
-
-// Compute Gaia profile file handle.
-Object.defineProperty(CSPp, "gaiaProfile", {
-  get: function () {
-    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
-    file.initWithPath(this.options.gaiaProfile);
-    return file;
-  }
-});
-
-exports.CustomSimulatorProcess = CustomSimulatorProcess;
-
-
-function AddonSimulatorProcess(addon, options) {
-  this.addon = addon;
-  this.options = options;
-}
-
-var ASPp = AddonSimulatorProcess.prototype = Object.create(SimulatorProcess.prototype);
-
-// Compute B2G binary file handle.
-Object.defineProperty(ASPp, "b2gBinary", {
-  get: function () {
-    let file;
-    try {
-      let pref = "extensions." + this.addon.id + ".customRuntime";
-      file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
-    } catch (e) {}
-
-    if (!file) {
-      file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
-      file.append("b2g");
-      let binaries = {
-        win32: "b2g-bin.exe",
-        mac64: "B2G.app/Contents/MacOS/b2g-bin",
-        linux32: "b2g-bin",
-        linux64: "b2g-bin",
-      };
-      binaries[OS].split("/").forEach(node => file.append(node));
-    }
-    // If the binary doesn't exists, it may be because of a simulator
-    // based on mulet, which has a different binary name.
-    if (!file.exists()) {
-      file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
-      file.append("firefox");
-      let binaries = {
-        win32: "firefox.exe",
-        mac64: "FirefoxNightly.app/Contents/MacOS/firefox-bin",
-        linux32: "firefox-bin",
-        linux64: "firefox-bin",
-      };
-      binaries[OS].split("/").forEach(node => file.append(node));
-    }
-    return file;
-  }
-});
-
-// Compute Gaia profile file handle.
-Object.defineProperty(ASPp, "gaiaProfile", {
-  get: function () {
-    let file;
-
-    // Custom profile from simulator configuration.
-    if (this.options.gaiaProfile) {
-      file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
-      file.initWithPath(this.options.gaiaProfile);
-      return file;
-    }
-
-    // Custom profile from addon prefs.
-    try {
-      let pref = "extensions." + this.addon.id + ".gaiaProfile";
-      file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
-      return file;
-    } catch (e) {}
-
-    // Default profile from addon.
-    file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
-    file.append("profile");
-    return file;
-  }
-});
-
-exports.AddonSimulatorProcess = AddonSimulatorProcess;
-
-
-function OldAddonSimulatorProcess(addon, options) {
-  this.addon = addon;
-  this.options = options;
-}
-
-var OASPp = OldAddonSimulatorProcess.prototype = Object.create(AddonSimulatorProcess.prototype);
-
-// Compute B2G binary file handle.
-Object.defineProperty(OASPp, "b2gBinary", {
-  get: function () {
-    let file;
-    try {
-      let pref = "extensions." + this.addon.id + ".customRuntime";
-      file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
-    } catch (e) {}
-
-    if (!file) {
-      file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
-      let version = this.addon.name.match(/\d+\.\d+/)[0].replace(/\./, "_");
-      file.append("resources");
-      file.append("fxos_" + version + "_simulator");
-      file.append("data");
-      file.append(OS == "linux32" ? "linux" : OS);
-      let binaries = {
-        win32: "b2g/b2g-bin.exe",
-        mac64: "B2G.app/Contents/MacOS/b2g-bin",
-        linux32: "b2g/b2g-bin",
-        linux64: "b2g/b2g-bin",
-      };
-      binaries[OS].split("/").forEach(node => file.append(node));
-    }
-    return file;
-  }
-});
-
-// Compute B2G CLI arguments.
-Object.defineProperty(OASPp, "args", {
-  get: function () {
-    let args = [];
-
-    // Gaia profile.
-    args.push("-profile", this.gaiaProfile.path);
-
-    // Debugger server port.
-    let port = parseInt(this.options.port);
-    args.push("-dbgport", "" + port);
-
-    // Ignore eventual zombie instances of b2g that are left over.
-    args.push("-no-remote");
-
-    return args;
-  }
-});
-
-exports.OldAddonSimulatorProcess = OldAddonSimulatorProcess;
deleted file mode 100644
--- a/devtools/client/webide/modules/simulators.js
+++ /dev/null
@@ -1,368 +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/. */
-
-"use strict";
-
-const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
-const { Task } = require("devtools/shared/task");
-loader.lazyRequireGetter(this, "ConnectionManager", "devtools/shared/client/connection-manager", true);
-loader.lazyRequireGetter(this, "AddonSimulatorProcess", "devtools/client/webide/modules/simulator-process", true);
-loader.lazyRequireGetter(this, "OldAddonSimulatorProcess", "devtools/client/webide/modules/simulator-process", true);
-loader.lazyRequireGetter(this, "CustomSimulatorProcess", "devtools/client/webide/modules/simulator-process", true);
-const asyncStorage = require("devtools/shared/async-storage");
-const EventEmitter = require("devtools/shared/old-event-emitter");
-const Services = require("Services");
-
-const SimulatorRegExp = new RegExp(Services.prefs.getCharPref("devtools.webide.simulatorAddonRegExp"));
-const LocaleCompare = (a, b) => {
-  return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
-};
-
-var Simulators = {
-
-  // The list of simulator configurations.
-  _simulators: [],
-
-  /**
-   * Load a previously saved list of configurations (only once).
-   *
-   * @return Promise.
-   */
-  _load() {
-    if (this._loadingPromise) {
-      return this._loadingPromise;
-    }
-
-    this._loadingPromise = Task.spawn(function* () {
-      let jobs = [];
-
-      let value = yield asyncStorage.getItem("simulators");
-      if (Array.isArray(value)) {
-        value.forEach(options => {
-          let simulator = new Simulator(options);
-          Simulators.add(simulator, true);
-
-          // If the simulator had a reference to an addon, fix it.
-          if (options.addonID) {
-            let deferred = new Promise(resolve => {
-              AddonManager.getAddonByID(options.addonID, addon => {
-                simulator.addon = addon;
-                delete simulator.options.addonID;
-                resolve();
-              });
-            });
-            jobs.push(deferred);
-          }
-        });
-      }
-
-      yield Promise.all(jobs);
-      yield Simulators._addUnusedAddons();
-      Simulators.emitUpdated();
-      return Simulators._simulators;
-    });
-
-    return this._loadingPromise;
-  },
-
-  /**
-   * Add default simulators to the list for each new (unused) addon.
-   *
-   * @return Promise.
-   */
-  _addUnusedAddons: Task.async(function* () {
-    let jobs = [];
-
-    let addons = yield Simulators.findSimulatorAddons();
-    addons.forEach(addon => {
-      jobs.push(Simulators.addIfUnusedAddon(addon, true));
-    });
-
-    yield Promise.all(jobs);
-  }),
-
-  /**
-   * Save the current list of configurations.
-   *
-   * @return Promise.
-   */
-  _save: Task.async(function* () {
-    yield this._load();
-
-    let value = Simulators._simulators.map(simulator => {
-      let options = JSON.parse(JSON.stringify(simulator.options));
-      if (simulator.addon != null) {
-        options.addonID = simulator.addon.id;
-      }
-      return options;
-    });
-
-    yield asyncStorage.setItem("simulators", value);
-  }),
-
-  /**
-   * List all available simulators.
-   *
-   * @return Promised simulator list.
-   */
-  findSimulators: Task.async(function* () {
-    yield this._load();
-    return Simulators._simulators;
-  }),
-
-  /**
-   * List all installed simulator addons.
-   *
-   * @return Promised addon list.
-   */
-  findSimulatorAddons() {
-    return new Promise(resolve => {
-      AddonManager.getAllAddons(all => {
-        let addons = [];
-        for (let addon of all) {
-          if (Simulators.isSimulatorAddon(addon)) {
-            addons.push(addon);
-          }
-        }
-        // Sort simulator addons by name.
-        addons.sort(LocaleCompare);
-        resolve(addons);
-      });
-    });
-  },
-
-  /**
-   * Add a new simulator for `addon` if no other simulator uses it.
-   */
-  addIfUnusedAddon(addon, silently = false) {
-    let simulators = this._simulators;
-    let matching = simulators.filter(s => s.addon && s.addon.id == addon.id);
-    if (matching.length > 0) {
-      return Promise.resolve();
-    }
-    let options = {};
-    options.name = addon.name.replace(" Simulator", "");
-    // Some addons specify a simulator type at the end of their version string,
-    // e.g. "2_5_tv".
-    let type = this.simulatorAddonVersion(addon).split("_")[2];
-    if (type) {
-      // "tv" is shorthand for type "television".
-      options.type = (type === "tv" ? "television" : type);
-    }
-    return this.add(new Simulator(options, addon), silently);
-  },
-
-  // TODO (Bug 1146521) Maybe find a better way to deal with removed addons?
-  removeIfUsingAddon(addon) {
-    let simulators = this._simulators;
-    let remaining = simulators.filter(s => !s.addon || s.addon.id != addon.id);
-    this._simulators = remaining;
-    if (remaining.length !== simulators.length) {
-      this.emitUpdated();
-    }
-  },
-
-  /**
-   * Add a new simulator to the list. Caution: `simulator.name` may be modified.
-   *
-   * @return Promise to added simulator.
-   */
-  add(simulator, silently = false) {
-    let simulators = this._simulators;
-    let uniqueName = this.uniqueName(simulator.options.name);
-    simulator.options.name = uniqueName;
-    simulators.push(simulator);
-    if (!silently) {
-      this.emitUpdated();
-    }
-    return Promise.resolve(simulator);
-  },
-
-  /**
-   * Remove a simulator from the list.
-   */
-  remove(simulator) {
-    let simulators = this._simulators;
-    let remaining = simulators.filter(s => s !== simulator);
-    this._simulators = remaining;
-    if (remaining.length !== simulators.length) {
-      this.emitUpdated();
-    }
-  },
-
-  /**
-   * Get a unique name for a simulator (may add a suffix, e.g. "MyName (1)").
-   */
-  uniqueName(name) {
-    let simulators = this._simulators;
-
-    let names = {};
-    simulators.forEach(simulator => names[simulator.name] = true);
-
-    // Strip any previous suffix, add a new suffix if necessary.
-    let stripped = name.replace(/ \(\d+\)$/, "");
-    let unique = stripped;
-    for (let i = 1; names[unique]; i++) {
-      unique = stripped + " (" + i + ")";
-    }
-    return unique;
-  },
-
-  /**
-   * Compare an addon's ID against the expected form of a simulator addon ID,
-   * and try to extract its version if there is a match.
-   *
-   * Note: If a simulator addon is recognized, but no version can be extracted
-   * (e.g. custom RegExp pref value), we return "Unknown" to keep the returned
-   * value 'truthy'.
-   */
-  simulatorAddonVersion(addon) {
-    let match = SimulatorRegExp.exec(addon.id);
-    if (!match) {
-      return null;
-    }
-    let version = match[1];
-    return version || "Unknown";
-  },
-
-  /**
-   * Detect simulator addons, including "unofficial" ones.
-   */
-  isSimulatorAddon(addon) {
-    return !!this.simulatorAddonVersion(addon);
-  },
-
-  emitUpdated() {
-    this.emit("updated", { length: this._simulators.length });
-    this._simulators.sort(LocaleCompare);
-    this._save();
-  },
-
-  onConfigure(e, simulator) {
-    this._lastConfiguredSimulator = simulator;
-  },
-
-  onInstalled(addon) {
-    if (this.isSimulatorAddon(addon)) {
-      this.addIfUnusedAddon(addon);
-    }
-  },
-
-  onEnabled(addon) {
-    if (this.isSimulatorAddon(addon)) {
-      this.addIfUnusedAddon(addon);
-    }
-  },
-
-  onDisabled(addon) {
-    if (this.isSimulatorAddon(addon)) {
-      this.removeIfUsingAddon(addon);
-    }
-  },
-
-  onUninstalled(addon) {
-    if (this.isSimulatorAddon(addon)) {
-      this.removeIfUsingAddon(addon);
-    }
-  },
-};
-exports.Simulators = Simulators;
-AddonManager.addAddonListener(Simulators);
-EventEmitter.decorate(Simulators);
-Simulators.on("configure", Simulators.onConfigure.bind(Simulators));
-
-function Simulator(options = {}, addon = null) {
-  this.addon = addon;
-  this.options = options;
-
-  // Fill `this.options` with default values where needed.
-  let defaults = this.defaults;
-  for (let option in defaults) {
-    if (this.options[option] == null) {
-      this.options[option] = defaults[option];
-    }
-  }
-}
-Simulator.prototype = {
-
-  // Default simulation options.
-  _defaults: {
-    // Based on the Firefox OS Flame.
-    phone: {
-      width: 320,
-      height: 570,
-      pixelRatio: 1.5
-    },
-    // Based on a 720p HD TV.
-    television: {
-      width: 1280,
-      height: 720,
-      pixelRatio: 1,
-    }
-  },
-  _defaultType: "phone",
-
-  restoreDefaults() {
-    let defaults = this.defaults;
-    let options = this.options;
-    for (let option in defaults) {
-      options[option] = defaults[option];
-    }
-  },
-
-  launch() {
-    // Close already opened simulation.
-    if (this.process) {
-      return this.kill().then(this.launch.bind(this));
-    }
-
-    this.options.port = ConnectionManager.getFreeTCPPort();
-
-    // Choose simulator process type.
-    if (this.options.b2gBinary) {
-      // Custom binary.
-      this.process = new CustomSimulatorProcess(this.options);
-    } else if (this.version > "1.3") {
-      // Recent simulator addon.
-      this.process = new AddonSimulatorProcess(this.addon, this.options);
-    } else {
-      // Old simulator addon.
-      this.process = new OldAddonSimulatorProcess(this.addon, this.options);
-    }
-    this.process.run();
-
-    return Promise.resolve(this.options.port);
-  },
-
-  kill() {
-    let process = this.process;
-    if (!process) {
-      return Promise.resolve();
-    }
-    this.process = null;
-    return process.kill();
-  },
-
-  get defaults() {
-    let defaults = this._defaults;
-    return defaults[this.type] || defaults[this._defaultType];
-  },
-
-  get id() {
-    return this.name;
-  },
-
-  get name() {
-    return this.options.name;
-  },
-
-  get type() {
-    return this.options.type || this._defaultType;
-  },
-
-  get version() {
-    return this.options.b2gBinary ? "Custom" : this.addon.name.match(/\d+\.\d+/)[0];
-  },
-};
-exports.Simulator = Simulator;
deleted file mode 100644
index 238c9756257ea0d9d8dc5f7e639d7ab6716b6d95..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 2f86c4d4d5f68a212acf5e59cdb9d5f7a5cb60a5..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 6da2fcbad77aeb90b39898a5cb005d6f2f8d6f93..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 546deacaf81c59cf28a7bbfdfbffd6b7838cd024..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index e2335e3a0462f1344d412552263b43345bda9d4b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 75fe209eac3f6c16523e57976464e1db50d9b521..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 58749f72421d1fc04c91a08672c8d907eb314d48..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 60cffd46eb560f796e2c83165a62e44057105c15..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index c54cae3aa703eaff030fdedced22c6cd0f30a16b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 9a650a888211adf65c511e1fee30d072e857b882..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index d13dd78de9cc8087db058c850f5c814b988515c4..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 92d5cc39427c419b6b2e824e73d49a761b881e0b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 7a2a432ff4459e665d0b1cdf7887c81f56bd9fbe..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index d389321956e2b75e0a20e2fcb24ff0ebf71491d3..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 48e271d54a2e8c5d0b32b4dcdec9b6f0ca880953..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 4c8bb2f10010dfe9ce2b994af313e46a7a6ebce2..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/devtools/client/webide/test/addons/simulators.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "stable": ["1.0", "2.0"],
-  "unstable": ["3.0", "3.0_tv"]
-}
--- a/devtools/client/webide/test/browser.ini
+++ b/devtools/client/webide/test/browser.ini
@@ -1,12 +1,11 @@
 [DEFAULT]
 tags = devtools
 skip-if = asan || debug # bug 1078284 too many intermittents for these tests
 subsuite = devtools
 support-files =
-  addons/simulators.json
   doc_tabs.html
   head.js
   templates.json
 
 [browser_tabs.js]
 skip-if = e10s # Bug 1072167 - browser_tabs.js test fails under e10s
--- a/devtools/client/webide/test/chrome.ini
+++ b/devtools/client/webide/test/chrome.ini
@@ -1,32 +1,15 @@
 [DEFAULT]
 tags = devtools
 skip-if = asan || debug # bug 1078284 too many intermittents for these tests
 support-files =
   app/index.html
   app/manifest.webapp
   app.zip
-  addons/simulators.json
-  addons/fxos_1_0_simulator-linux.xpi
-  addons/fxos_1_0_simulator-linux64.xpi
-  addons/fxos_1_0_simulator-win32.xpi
-  addons/fxos_1_0_simulator-mac64.xpi
-  addons/fxos_2_0_simulator-linux.xpi
-  addons/fxos_2_0_simulator-linux64.xpi
-  addons/fxos_2_0_simulator-win32.xpi
-  addons/fxos_2_0_simulator-mac64.xpi
-  addons/fxos_3_0_simulator-linux.xpi
-  addons/fxos_3_0_simulator-linux64.xpi
-  addons/fxos_3_0_simulator-win32.xpi
-  addons/fxos_3_0_simulator-mac64.xpi
-  addons/fxos_3_0_tv_simulator-linux.xpi
-  addons/fxos_3_0_tv_simulator-linux64.xpi
-  addons/fxos_3_0_tv_simulator-win32.xpi
-  addons/fxos_3_0_tv_simulator-mac64.xpi
   addons/adbhelper-linux.xpi
   addons/adbhelper-linux64.xpi
   addons/adbhelper-win32.xpi
   addons/adbhelper-mac64.xpi
   build_app1/package.json
   build_app2/manifest.webapp
   build_app2/package.json
   build_app2/stage/empty-directory
@@ -52,12 +35,10 @@ skip-if = (os == "linux") # Bug 1024734
 [test_addons.html]
 skip-if = true # Bug 1201392 - Update add-ons after migration
 [test_device_runtime.html]
 [test_autoconnect_runtime.html]
 [test_autoselect_project.html]
 [test_device_preferences.html]
 [test_fullscreenToolbox.html]
 [test_zoom.html]
-[test_simulators.html]
-skip-if = true # Bug 1281138 - intermittent failures
 [test_toolbox.html]
 [test_app_validator.html]
--- a/devtools/client/webide/test/head.js
+++ b/devtools/client/webide/test/head.js
@@ -21,18 +21,16 @@ if (window.location === "chrome://browse
   TEST_BASE = "chrome://mochitests/content/browser/devtools/client/webide/test/";
 } else {
   TEST_BASE = "chrome://mochitests/content/chrome/devtools/client/webide/test/";
 }
 
 Services.prefs.setBoolPref("devtools.webide.enabled", true);
 Services.prefs.setBoolPref("devtools.webide.enableLocalRuntime", true);
 
-Services.prefs.setCharPref("devtools.webide.addonsURL", TEST_BASE + "addons/simulators.json");
-Services.prefs.setCharPref("devtools.webide.simulatorAddonsURL", TEST_BASE + "addons/fxos_#SLASHED_VERSION#_simulator-#OS#.xpi");
 Services.prefs.setCharPref("devtools.webide.adbAddonURL", TEST_BASE + "addons/adbhelper-#OS#.xpi");
 Services.prefs.setCharPref("devtools.webide.templatesURL", TEST_BASE + "templates.json");
 Services.prefs.setCharPref("devtools.devices.url", TEST_BASE + "browser_devices.json");
 
 var registerCleanupFunction = registerCleanupFunction ||
                               SimpleTest.registerCleanupFunction;
 registerCleanupFunction(() => {
   flags.testing = false;
--- a/devtools/client/webide/test/test_addons.html
+++ b/devtools/client/webide/test/test_addons.html
@@ -13,66 +13,20 @@
   </head>
 
   <body>
 
     <script type="application/javascript">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
-        const {GetAvailableAddons} = require("devtools/client/webide/modules/addons");
         const {Devices} = Cu.import("resource://devtools/shared/apps/Devices.jsm");
-        const {Simulators} = require("devtools/client/webide/modules/simulators");
 
         let adbAddonsInstalled;
 
-        function getVersion(name) {
-          return name.match(/(\d+\.\d+)/)[0];
-        }
-
-        function onSimulatorInstalled(name) {
-          return new Promise(resolve => {
-            Simulators.on("updated", function onUpdate() {
-              Simulators.findSimulatorAddons().then(addons => {
-                for (let addon of addons) {
-                  if (name == addon.name.replace(" Simulator", "")) {
-                    Simulators.off("updated", onUpdate);
-                    nextTick().then(resolve);
-                    return;
-                  }
-                }
-              });
-            });
-          });
-        }
-
-        function installSimulatorFromUI(doc, name) {
-          let li = doc.querySelector('[addon="simulator-' + getVersion(name) + '"]');
-          li.querySelector(".install-button").click();
-          return onSimulatorInstalled(name);
-        }
-
-        function uninstallSimulatorFromUI(doc, name) {
-          return new Promise((resolve, reject) => {
-            Simulators.on("updated", function onUpdate() {
-              nextTick().then(() => {
-                let li = doc.querySelector('[status="uninstalled"][addon="simulator-' + getVersion(name) + '"]');
-                if (li) {
-                  Simulators.off("updated", onUpdate);
-                  resolve();
-                } else {
-                  reject("Can't find item");
-                }
-              });
-            });
-            let li = doc.querySelector('[status="installed"][addon="simulator-' + getVersion(name) + '"]');
-            li.querySelector(".uninstall-button").click();
-          });
-        }
-
         function uninstallADBFromUI(doc) {
           return new Promise((resolve, reject) => {
             Devices.on("addon-status-updated", function onUpdate() {
               nextTick().then(() => {
                 let li = doc.querySelector('[status="uninstalled"][addon="adb"]');
                 if (li) {
                   Devices.off("addon-status-updated", onUpdate);
                   resolve();
@@ -88,77 +42,20 @@
 
         Task.spawn(function*() {
 
           ok(!Devices.helperAddonInstalled, "Helper not installed");
 
           let win = yield openWebIDE(true);
           let docRuntime = getRuntimeDocument(win);
 
-          adbAddonsInstalled = new Promise(resolve => {
-            Devices.on("addon-status-updated", function onUpdate1() {
-              Devices.off("addon-status-updated", onUpdate1);
-              resolve();
-            });
-          });
-
           ok(Devices.helperAddonInstalled, "Helper has been auto-installed");
 
           yield nextTick();
 
-          let addons = yield GetAvailableAddons();
-
-          is(addons.simulators.length, 3, "3 simulator addons to install");
-
-          let sim10 = addons.simulators.filter(a => a.version == "1.0")[0];
-          sim10.install();
-
-          yield onSimulatorInstalled("Firefox OS 1.0");
-
-          win.Cmds.showAddons();
-
-          let frame = win.document.querySelector("#deck-panel-addons");
-          let addonDoc = frame.contentWindow.document;
-          let lis;
-
-          lis = addonDoc.querySelectorAll("li");
-          is(lis.length, 5, "5 addons listed");
-
-          lis = addonDoc.querySelectorAll('li[status="installed"]');
-          is(lis.length, 3, "3 addons installed");
-
-          lis = addonDoc.querySelectorAll('li[status="uninstalled"]');
-          is(lis.length, 2, "2 addons uninstalled");
-
-          info("Uninstalling Simulator 2.0");
-
-          yield installSimulatorFromUI(addonDoc, "Firefox OS 2.0");
-
-          info("Uninstalling Simulator 3.0");
-
-          yield installSimulatorFromUI(addonDoc, "Firefox OS 3.0");
-
-          yield nextTick();
-
-          let panelNode = docRuntime.querySelector("#runtime-panel");
-          let items;
-
-          items = panelNode.querySelectorAll(".runtime-panel-item-usb");
-          is(items.length, 1, "Found one runtime button");
-
-          items = panelNode.querySelectorAll(".runtime-panel-item-simulator");
-          is(items.length, 3, "Found 3 simulators button");
-
-          yield uninstallSimulatorFromUI(addonDoc, "Firefox OS 1.0");
-          yield uninstallSimulatorFromUI(addonDoc, "Firefox OS 2.0");
-          yield uninstallSimulatorFromUI(addonDoc, "Firefox OS 3.0");
-
-          items = panelNode.querySelectorAll(".runtime-panel-item-simulator");
-          is(items.length, 0, "No simulator listed");
-
           let w = addonDoc.querySelector(".warning");
           let display = addonDoc.defaultView.getComputedStyle(w).display
           is(display, "none", "Warning about missing ADB hidden");
 
           yield uninstallADBFromUI(addonDoc, "adb");
 
           items = panelNode.querySelectorAll(".runtime-panel-item-usb");
           is(items.length, 0, "No usb runtime listed");
deleted file mode 100644
--- a/devtools/client/webide/test/test_simulators.html
+++ /dev/null
@@ -1,427 +0,0 @@
-<!DOCTYPE html>
-
-<html>
-
-  <head>
-    <meta charset="utf8">
-    <title></title>
-
-    <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-    <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
-    <script type="application/javascript" src="head.js"></script>
-    <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
-  </head>
-
-  <body>
-
-    <script type="application/javascript">
-      window.onload = function() {
-        SimpleTest.waitForExplicitFinish();
-
-        const asyncStorage = require("devtools/shared/async-storage");
-        const EventEmitter = require("devtools/shared/old-event-emitter");
-        const { GetAvailableAddons } = require("devtools/client/webide/modules/addons");
-        const { getDevices } = require("devtools/client/shared/devices");
-        const { Simulator, Simulators } = require("devtools/client/webide/modules/simulators");
-        const { AddonSimulatorProcess,
-                OldAddonSimulatorProcess,
-                CustomSimulatorProcess } = require("devtools/client/webide/modules/simulator-process");
-
-        function addonStatus(addon, status) {
-          if (addon.status == status) {
-            return Promise.resolve();
-          }
-          return new Promise(resolve => {
-            addon.on("update", function onUpdate() {
-              if (addon.status == status) {
-                addon.off("update", onUpdate);
-                nextTick().then(() => resolve());
-              }
-            });
-          });
-        }
-
-        function waitForUpdate(length) {
-          info(`Wait for update with length ${length}`);
-          return new Promise(resolve => {
-            let handler = (_, data) => {
-              if (data.length != length) {
-                return;
-              }
-              info(`Got update with length ${length}`);
-              Simulators.off("updated", handler);
-              resolve();
-            };
-            Simulators.on("updated", handler);
-          });
-        }
-
-        Task.spawn(function* () {
-          let win = yield openWebIDE(false);
-
-          yield Simulators._load();
-
-          let docRuntime = getRuntimeDocument(win);
-          let find = win.document.querySelector.bind(docRuntime);
-          let findAll = win.document.querySelectorAll.bind(docRuntime);
-
-          let simulatorList = find("#runtime-panel-simulator");
-          let simulatorPanel = win.document.querySelector("#deck-panel-simulator");
-
-          // Hack SimulatorProcesses to spy on simulation parameters.
-
-          let resolver;
-	  function fakeRun() {
-            resolver({
-              path: this.b2gBinary.path,
-              args: this.args
-            });
-            // Don't actually try to connect to the fake simulator.
-            throw new Error("Aborting on purpose before connection.");
-          }
-
-          AddonSimulatorProcess.prototype.run = fakeRun;
-          OldAddonSimulatorProcess.prototype.run = fakeRun;
-          CustomSimulatorProcess.prototype.run = fakeRun;
-
-          function runSimulator(i) {
-            return new Promise(resolve => {
-              resolver = resolve;
-              findAll(".runtime-panel-item-simulator")[i].click();
-            });
-          }
-
-          // Install fake "Firefox OS 1.0" simulator addon.
-
-          let addons = yield GetAvailableAddons();
-
-          let sim10 = addons.simulators.filter(a => a.version == "1.0")[0];
-
-          sim10.install();
-
-          let updated = waitForUpdate(1);
-          yield addonStatus(sim10, "installed");
-          yield updated;
-          // Wait for next tick to ensure UI elements are updated
-          yield nextTick();
-
-          is(findAll(".runtime-panel-item-simulator").length, 1, "One simulator in runtime panel");
-
-          // Install fake "Firefox OS 2.0" simulator addon.
-
-          let sim20 = addons.simulators.filter(a => a.version == "2.0")[0];
-
-          sim20.install();
-
-          updated = waitForUpdate(2);
-          yield addonStatus(sim20, "installed");
-          yield updated;
-          // Wait for next tick to ensure UI elements are updated
-          yield nextTick();
-
-          is(findAll(".runtime-panel-item-simulator").length, 2, "Two simulators in runtime panel");
-
-          // Dry run a simulator to verify that its parameters look right.
-
-          let params = yield runSimulator(0);
-
-          ok(params.path.includes(sim10.addonID) && params.path.includes("b2g-bin"), "Simulator binary path looks right");
-
-          let pid = params.args.indexOf("-profile");
-          ok(pid > -1, "Simulator process arguments have --profile");
-
-          let profilePath = params.args[pid + 1];
-          ok(profilePath.includes(sim10.addonID) && profilePath.includes("profile"), "Simulator profile path looks right");
-
-          ok(params.args.indexOf("-dbgport") > -1 || params.args.indexOf("-start-debugger-server") > -1, "Simulator process arguments have a debugger port");
-
-          ok(params.args.indexOf("-no-remote") > -1, "Simulator process arguments have --no-remote");
-
-          // Wait for next tick to ensure UI elements are updated
-          yield nextTick();
-
-          // Configure the fake 1.0 simulator.
-
-          simulatorList.querySelectorAll(".configure-button")[0].click();
-          is(win.document.querySelector("#deck").selectedPanel, simulatorPanel, "Simulator deck panel is selected");
-
-          yield lazyIframeIsLoaded(simulatorPanel);
-
-          let doc = simulatorPanel.contentWindow.document;
-          let form = doc.querySelector("#simulator-editor");
-
-          let formReady = new Promise((resolve, reject) => {
-            form.addEventListener("change", () => {
-              resolve();
-            });
-          });
-
-          let change = doc.createEvent("HTMLEvents");
-          change.initEvent("change", true, true);
-
-          function set(input, value) {
-            input.value = value;
-            input.dispatchEvent(change);
-            return nextTick();
-          }
-
-          let MockFilePicker = SpecialPowers.MockFilePicker;
-          MockFilePicker.init(simulatorPanel.contentWindow);
-
-          yield formReady;
-
-          // Test `name`.
-
-          is(form.name.value, find(".runtime-panel-item-simulator").textContent, "Original simulator name");
-
-          let customName = "CustomFox ";
-          yield set(form.name, customName + "1.0");
-
-          is(find(".runtime-panel-item-simulator").textContent, form.name.value, "Updated simulator name");
-
-          // Test `version`.
-
-          is(form.version.value, sim10.addonID, "Original simulator version");
-          ok(!form.version.classList.contains("custom"), "Version selector is not customized");
-
-          yield set(form.version, sim20.addonID);
-
-          ok(!form.version.classList.contains("custom"), "Version selector is not customized after addon change");
-          is(form.name.value, customName + "2.0", "Simulator name was updated to new version");
-
-          // Pick custom binary, but act like the user aborted the file picker.
-
-          MockFilePicker.setFiles([]);
-          yield set(form.version, "pick");
-
-          is(form.version.value, sim20.addonID, "Version selector reverted to last valid choice after customization abort");
-          ok(!form.version.classList.contains("custom"), "Version selector is not customized after customization abort");
-
-          // Pick custom binary, and actually follow through. (success, verify value = "custom" and textContent = custom path)
-
-          yield MockFilePicker.useAnyFile();
-          yield set(form.version, "pick");
-
-          let fakeBinary = MockFilePicker.file;
-
-          ok(form.version.value == "custom", "Version selector was set to a new custom binary");
-          ok(form.version.classList.contains("custom"), "Version selector is now customized");
-          is(form.version.selectedOptions[0].textContent, fakeBinary.path, "Custom option textContent is correct");
-
-          yield set(form.version, sim10.addonID);
-
-          ok(form.version.classList.contains("custom"), "Version selector remains customized after change back to addon");
-          is(form.name.value, customName + "1.0", "Simulator name was updated to new version");
-
-          yield set(form.version, "custom");
-
-          ok(form.version.value == "custom", "Version selector is back to custom");
-
-          // Test `profile`.
-
-          is(form.profile.value, "default", "Default simulator profile");
-          ok(!form.profile.classList.contains("custom"), "Profile selector is not customized");
-
-          MockFilePicker.setFiles([]);
-          yield set(form.profile, "pick");
-
-          is(form.profile.value, "default", "Profile selector reverted to last valid choice after customization abort");
-          ok(!form.profile.classList.contains("custom"), "Profile selector is not customized after customization abort");
-
-          let fakeProfile = FileUtils.getDir("TmpD", []);
-
-          MockFilePicker.setFiles([ fakeProfile ]);
-          yield set(form.profile, "pick");
-
-          ok(form.profile.value == "custom", "Profile selector was set to a new custom directory");
-          ok(form.profile.classList.contains("custom"), "Profile selector is now customized");
-          is(form.profile.selectedOptions[0].textContent, fakeProfile.path, "Custom option textContent is correct");
-
-          yield set(form.profile, "default");
-
-          is(form.profile.value, "default", "Profile selector back to default");
-          ok(form.profile.classList.contains("custom"), "Profile selector remains customized after change back to default");
-
-          yield set(form.profile, "custom");
-
-          is(form.profile.value, "custom", "Profile selector back to custom");
-
-          params = yield runSimulator(0);
-
-          is(params.path, fakeBinary.path, "Simulator process uses custom binary path");
-
-          pid = params.args.indexOf("-profile");
-          is(params.args[pid + 1], fakeProfile.path, "Simulator process uses custom profile directory");
-
-          yield set(form.version, sim10.addonID);
-
-          is(form.name.value, customName + "1.0", "Simulator restored to 1.0");
-
-          params = yield runSimulator(0);
-
-          pid = params.args.indexOf("-profile");
-          is(params.args[pid + 1], fakeProfile.path, "Simulator process still uses custom profile directory");
-
-          yield set(form.version, "custom");
-
-          // Test `device`.
-
-          let defaults = Simulator.prototype._defaults;
-
-          for (let param in defaults.phone) {
-            is(form[param].value, String(defaults.phone[param]), "Default phone value for device " + param);
-          }
-
-          let width = 5000, height = 4000;
-          yield set(form.width, width);
-          yield set(form.height, height);
-
-          is(form.device.value, "custom", "Device selector is custom");
-
-          params = yield runSimulator(0);
-
-          let sid = params.args.indexOf("-screen");
-          ok(sid > -1, "Simulator process arguments have --screen");
-          ok(params.args[sid + 1].includes(width + "x" + height), "Simulator screen resolution looks right");
-
-          yield set(form.version, sim10.addonID);
-
-          // Configure the fake 2.0 simulator.
-
-          simulatorList.querySelectorAll(".configure-button")[1].click();
-          // Wait for next tick to ensure UI elements are updated
-          yield nextTick();
-
-          // Test `name`.
-
-          is(form.name.value, findAll(".runtime-panel-item-simulator")[1].textContent, "Original simulator name");
-
-          yield set(form.name, customName + "2.0");
-
-          is(findAll(".runtime-panel-item-simulator")[1].textContent, form.name.value, "Updated simulator name");
-
-          yield set(form.version, sim10.addonID);
-
-          ok(form.name.value !== customName + "1.0", "Conflicting simulator name was deduplicated");
-
-          is(form.name.value, findAll(".runtime-panel-item-simulator")[1].textContent, "Deduplicated simulator name stayed consistent");
-
-          yield set(form.version, sim20.addonID);
-
-          is(form.name.value, customName + "2.0", "Name deduplication was undone when possible");
-
-          // Test `device`.
-
-          for (let param in defaults.phone) {
-            is(form[param].value, String(defaults.phone[param]), "Default phone value for device " + param);
-          }
-
-          let devices = yield getDevices();
-          devices = devices[devices.TYPES[0]];
-          let device = devices[devices.length - 1];
-
-          yield set(form.device, device.name);
-
-          is(form.device.value, device.name, "Device selector was changed");
-          is(form.width.value, String(device.width), "New device width is correct");
-          is(form.height.value, String(device.height), "New device height is correct");
-
-          params = yield runSimulator(1);
-
-          sid = params.args.indexOf("-screen");
-          ok(params.args[sid + 1].includes(device.width + "x" + device.height), "Simulator screen resolution looks right");
-
-          // Test Simulator Menu.
-          is(doc.querySelector("#tv_simulator_menu").style.visibility, "hidden", "OpenTVDummyDirectory Button is not hidden");
-
-          // Restore default simulator options.
-
-          doc.querySelector("#reset").click();
-          // Wait for next tick to ensure UI elements are updated
-          yield nextTick();
-
-          for (let param in defaults.phone) {
-            is(form[param].value, String(defaults.phone[param]), "Default phone value for device " + param);
-          }
-
-          // Install and configure the fake "Firefox OS 3.0 TV" simulator addon.
-
-          let sim30tv = addons.simulators.filter(a => a.version == "3.0_tv")[0];
-
-          sim30tv.install();
-
-          updated = waitForUpdate(3);
-          yield addonStatus(sim30tv, "installed");
-          yield updated;
-          // Wait for next tick to ensure UI elements are updated
-          yield nextTick();
-
-          is(findAll(".runtime-panel-item-simulator").length, 3, "Three simulators in runtime panel");
-
-          simulatorList.querySelectorAll(".configure-button")[2].click();
-          // Wait for next tick to ensure UI elements are updated
-          yield nextTick();
-
-          for (let param in defaults.television) {
-            is(form[param].value, String(defaults.television[param]), "Default TV value for device " + param);
-          }
-
-          // Test Simulator Menu
-          is(doc.querySelector("#tv_simulator_menu").style.visibility, "visible", "OpenTVDummyDirectory Button is not visible");
-
-          // Force reload the list of simulators.
-
-          Simulators._loadingPromise = null;
-          Simulators._simulators = [];
-          yield Simulators._load();
-          // Wait for next tick to ensure UI elements are updated
-          yield nextTick();
-
-          is(findAll(".runtime-panel-item-simulator").length, 3, "Three simulators saved and reloaded " + Simulators._simulators.map(s => s.name).join(','));
-
-          // Uninstall the 3.0 TV and 2.0 addons, and watch their Simulator objects disappear.
-
-          sim30tv.uninstall();
-
-          yield addonStatus(sim30tv, "uninstalled");
-
-          is(findAll(".runtime-panel-item-simulator").length, 2, "Two simulators left in runtime panel");
-
-          sim20.uninstall();
-
-          yield addonStatus(sim20, "uninstalled");
-
-          is(findAll(".runtime-panel-item-simulator").length, 1, "One simulator left in runtime panel");
-
-          // Remove 1.0 simulator.
-
-          simulatorList.querySelectorAll(".configure-button")[0].click();
-          // Wait for next tick to ensure UI elements are updated
-          yield nextTick();
-
-          doc.querySelector("#remove").click();
-          // Wait for next tick to ensure UI elements are updated
-          yield nextTick();
-
-          is(findAll(".runtime-panel-item-simulator").length, 0, "Last simulator was removed");
-
-          yield asyncStorage.removeItem("simulators");
-
-          sim10.uninstall();
-
-          MockFilePicker.cleanup();
-
-          doc.querySelector("#close").click();
-
-          ok(!win.document.querySelector("#deck").selectedPanel, "No panel selected");
-
-          yield closeWebIDE(win);
-
-          SimpleTest.finish();
-
-        });
-      }
-
-    </script>
-  </body>
-</html>
--- a/devtools/client/webide/themes/jar.mn
+++ b/devtools/client/webide/themes/jar.mn
@@ -11,12 +11,11 @@ webide.jar:
   skin/throbber.svg            (throbber.svg)
   skin/deck.css                (deck.css)
   skin/addons.css              (addons.css)
   skin/runtimedetails.css      (runtimedetails.css)
   skin/monitor.css             (monitor.css)
   skin/config-view.css         (config-view.css)
   skin/wifi-auth.css           (wifi-auth.css)
   skin/panel-listing.css       (panel-listing.css)
-  skin/simulator.css           (simulator.css)
   skin/rocket.svg              (rocket.svg)
   skin/noise.png               (noise.png)
   skin/default-app-icon.png    (default-app-icon.png)
--- a/devtools/client/webide/themes/panel-listing.css
+++ b/devtools/client/webide/themes/panel-listing.css
@@ -42,17 +42,16 @@ label,
 .panel-header:first-child {
   margin-top: 0;
 }
 
 .panel-header[hidden], .panel-item[hidden] {
   display: none;
 }
 
-#runtime-panel-simulator,
 .panel-item-complex {
   clear: both;
   position: relative;
 }
 
 .panel-item span {
   display: block;
   float: left;
@@ -123,27 +122,25 @@ button.panel-item:not(:disabled):hover {
   border: 0;
 }
 
 .configure-button:hover {
   cursor: pointer;
 }
 
 .project-panel-item-openpackaged  { background-image: -moz-image-rect(url("icons.png"), 260, 438, 286, 412); }
-.runtime-panel-item-simulator     { background-image: -moz-image-rect(url("icons.png"), 0, 438, 26, 412); }
 .runtime-panel-item-other         { background-image: -moz-image-rect(url("icons.png"), 26, 438, 52, 412); }
 #runtime-screenshot               { background-image: -moz-image-rect(url("icons.png"), 131, 438, 156, 412); }
 
 #runtime-preferences,
 #runtime-settings                 { background-image: -moz-image-rect(url("icons.png"), 105, 464, 131, 438); }
 
 #runtime-panel-nousbdevice,
 #runtime-details                  { background-image: -moz-image-rect(url("icons.png"), 156, 438, 182, 412);  }
 
 .runtime-panel-item-usb,
 #runtime-disconnect               { background-image: -moz-image-rect(url("icons.png"), 52, 438, 78, 412); }
 
 .runtime-panel-item-wifi,
 .project-panel-item-openhosted    { background-image: -moz-image-rect(url("icons.png"), 208, 438, 234, 412); }
 
 .project-panel-item-newapp,
-#runtime-panel-noadbhelper,
-#runtime-panel-installsimulator   { background-image: -moz-image-rect(url("icons.png"), 234, 438, 260, 412); }
+#runtime-panel-noadbhelper        { background-image: -moz-image-rect(url("icons.png"), 234, 438, 260, 412); }
deleted file mode 100644
--- a/devtools/client/webide/themes/simulator.css
+++ /dev/null
@@ -1,41 +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/. */
-
-select:not(.custom) > option[value="custom"] {
-  display: none;
-}
-
-select, input[type="text"] {
-  width: 13rem;
-}
-
-input[name="name"] {
-  height: 1.8rem;
-}
-
-input[type="number"] {
-  width: 6rem;
-}
-
-input[type="text"], input[type="number"] {
-  padding-left: 0.2rem;
-}
-
-li > label:hover {
-  background-color: transparent;
-}
-
-ul {
-  padding-left: 0;
-}
-
-.label {
-  width: 6rem;
-  padding: 0.2rem;
-  text-align: right;
-}
-
-.hidden {
-  display: none;
-}
--- a/devtools/client/webide/webide-prefs.js
+++ b/devtools/client/webide/webide-prefs.js
@@ -2,20 +2,15 @@
    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/. */
 
 pref("devtools.webide.templatesURL", "https://code.cdn.mozilla.net/templates/list.json");
 pref("devtools.webide.autoinstallADBHelper", true);
 pref("devtools.webide.autoConnectRuntime", true);
 pref("devtools.webide.restoreLastProject", true);
 pref("devtools.webide.enableLocalRuntime", false);
-pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
-pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/#VERSION#/#OS#/fxos_#SLASHED_VERSION#_simulator-#OS#-latest.xpi");
-pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozilla.org");
-pref("devtools.webide.simulatorAddonRegExp", "fxos_(.*)_simulator@mozilla\\.org$");
 pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
 pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");
 pref("devtools.webide.monitorWebSocketURL", "ws://localhost:9000");
 pref("devtools.webide.lastConnectedRuntime", "");
 pref("devtools.webide.lastSelectedProject", "");
-pref("devtools.webide.logSimulatorOutput", false);
 pref("devtools.webide.zoom", "1");
 pref("devtools.webide.busyTimeout", 10000);
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -257,16 +257,35 @@ var AnimationPlayerActor = protocol.Acto
   /**
    * Get the animation direction from this player.
    * @return {String}
    */
   getDirection: function () {
     return this.player.effect.getComputedTiming().direction;
   },
 
+  /**
+   * Get animation-timing-function from animated element if CSS Animations.
+   * @return {String}
+   */
+  getAnimationTimingFunction: function () {
+    if (!this.isCssAnimation()) {
+      return null;
+    }
+
+    let pseudo = null;
+    let target = this.player.effect.target;
+    if (target.type) {
+      // Animated element is a pseudo element.
+      pseudo = target.type;
+      target = target.parentElement;
+    }
+    return this.window.getComputedStyle(target, pseudo).animationTimingFunction;
+  },
+
   getPropertiesCompositorStatus: function () {
     let properties = this.player.effect.getProperties();
     return properties.map(prop => {
       return {
         property: prop.property,
         runningOnCompositor: prop.runningOnCompositor,
         warning: prop.warning
       };
@@ -302,16 +321,17 @@ var AnimationPlayerActor = protocol.Acto
       duration: this.getDuration(),
       delay: this.getDelay(),
       endDelay: this.getEndDelay(),
       iterationCount: this.getIterationCount(),
       iterationStart: this.getIterationStart(),
       fill: this.getFill(),
       easing: this.getEasing(),
       direction: this.getDirection(),
+      animationTimingFunction: this.getAnimationTimingFunction(),
       // animation is hitting the fast path or not. Returns false whenever the
       // animation is paused as it is taken off the compositor then.
       isRunningOnCompositor:
         this.getPropertiesCompositorStatus()
             .some(propState => propState.runningOnCompositor),
       propertyState: this.getPropertiesCompositorStatus(),
       // The document timeline's currentTime is being sent along too. This is
       // not strictly related to the node's animationPlayer, but is useful to
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -2612,16 +2612,41 @@ var WalkerActor = protocol.ActorClassWit
     if (rawNode.defaultView && rawNode === rawNode.defaultView.document) {
       rawNode = rawNode.documentElement;
     }
 
     return this.attachElement(rawNode);
   },
 
   /**
+   * Given a windowID return the NodeActor for the corresponding frameElement,
+   * unless it's the root window
+   */
+  getNodeActorFromWindowID: function (windowID) {
+    let win;
+
+    try {
+      win = Services.wm.getOuterWindowWithId(windowID);
+    } catch (e) {
+      // ignore
+    }
+
+    if (!win) {
+      return { error: "noWindow",
+               message: "The related docshell is destroyed or not found" };
+    } else if (!win.frameElement) {
+      // the frame element of the root document is privileged & thus
+      // inaccessible, so return the document body/element instead
+      return this.attachElement(win.document.body || win.document.documentElement);
+    }
+
+    return this.attachElement(win.frameElement);
+  },
+
+  /**
    * Given a StyleSheetActor (identified by its ID), commonly used in the
    * style-editor, get its ownerNode and return the corresponding walker's
    * NodeActor.
    * Note that getNodeFromActor was added later and can now be used instead.
    */
   getStyleSheetOwnerNode: function (styleSheetActorID) {
     return this.getNodeFromActor(styleSheetActorID, ["ownerNode"]);
   },
--- a/devtools/server/actors/tab.js
+++ b/devtools/server/actors/tab.js
@@ -527,16 +527,17 @@ TabActor.prototype = {
    */
   _shouldAddNewGlobalAsDebuggee(wrappedGlobal) {
     if (wrappedGlobal.hostAnnotations &&
         wrappedGlobal.hostAnnotations.type == "document" &&
         wrappedGlobal.hostAnnotations.element === this.window) {
       return true;
     }
 
+    // Otherwise, check if it is a WebExtension content script sandbox
     let global = unwrapDebuggerObjectGlobal(wrappedGlobal);
     if (!global) {
       return false;
     }
 
     // Check if the global is a sdk page-mod sandbox.
     let metadata = {};
     let id = "";
deleted file mode 100644
--- a/devtools/shared/apps/Simulator.jsm
+++ /dev/null
@@ -1,45 +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/. */
-
-"use strict";
-
-const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
-const EventEmitter = require("devtools/shared/old-event-emitter");
-
-/**
- * TODO (Bug 1132453) The `Simulator` module is deprecated, and should be
- * removed once all simulator addons stop using it (see bug 1132452).
- *
- * If you want to register, unregister, or otherwise deal with installed
- * simulators, please use the `Simulators` module defined in:
- *
- *   devtools/client/webide/modules/simulators.js
- */
-
-this.EXPORTED_SYMBOLS = ["Simulator"];
-
-let Simulator = this.Simulator = {
-  _simulators: {},
-
-  register: function (name, simulator) {
-    // simulators register themselves as "Firefox OS X.Y"
-    this._simulators[name] = simulator;
-    this.emit("register", name);
-  },
-
-  unregister: function (name) {
-    delete this._simulators[name];
-    this.emit("unregister", name);
-  },
-
-  availableNames: function () {
-    return Object.keys(this._simulators).sort();
-  },
-
-  getByName: function (name) {
-    return this._simulators[name];
-  },
-};
-
-EventEmitter.decorate(Simulator);
--- a/devtools/shared/fronts/animation.js
+++ b/devtools/shared/fronts/animation.js
@@ -63,16 +63,17 @@ const AnimationPlayerFront = FrontClassW
       duration: this._form.duration,
       delay: this._form.delay,
       endDelay: this._form.endDelay,
       iterationCount: this._form.iterationCount,
       iterationStart: this._form.iterationStart,
       easing: this._form.easing,
       fill: this._form.fill,
       direction: this._form.direction,
+      animationTimingFunction: this._form.animationTimingFunction,
       isRunningOnCompositor: this._form.isRunningOnCompositor,
       propertyState: this._form.propertyState,
       documentCurrentTime: this._form.documentCurrentTime
     };
   },
 
   /**
    * Executed when the AnimationPlayerActor emits a "changed" event. Used to
--- a/devtools/shared/fronts/inspector.js
+++ b/devtools/shared/fronts/inspector.js
@@ -623,16 +623,24 @@ const WalkerFront = FrontClassWithSpec(w
   getNodeActorFromObjectActor: custom(function (objectActorID) {
     return this._getNodeActorFromObjectActor(objectActorID).then(response => {
       return response ? response.node : null;
     });
   }, {
     impl: "_getNodeActorFromObjectActor"
   }),
 
+  getNodeActorFromWindowID: custom(function (windowID) {
+    return this._getNodeActorFromWindowID(windowID).then(response => {
+      return response ? response.node : null;
+    });
+  }, {
+    impl: "_getNodeActorFromWindowID"
+  }),
+
   getStyleSheetOwnerNode: custom(function (styleSheetActorID) {
     return this._getStyleSheetOwnerNode(styleSheetActorID).then(response => {
       return response ? response.node : null;
     });
   }, {
     impl: "_getStyleSheetOwnerNode"
   }),
 
--- a/devtools/shared/specs/inspector.js
+++ b/devtools/shared/specs/inspector.js
@@ -344,16 +344,24 @@ const walkerSpec = generateActorSpec({
     getNodeActorFromObjectActor: {
       request: {
         objectActorID: Arg(0, "string")
       },
       response: {
         nodeFront: RetVal("nullable:disconnectedNode")
       }
     },
+    getNodeActorFromWindowID: {
+      request: {
+        windowID: Arg(0, "string")
+      },
+      response: {
+        nodeFront: RetVal("nullable:disconnectedNode")
+      }
+    },
     getStyleSheetOwnerNode: {
       request: {
         styleSheetActorID: Arg(0, "string")
       },
       response: {
         ownerNode: RetVal("nullable:disconnectedNode")
       }
     },
--- a/devtools/shim/DevToolsShim.jsm
+++ b/devtools/shim/DevToolsShim.jsm
@@ -28,28 +28,25 @@ function removeItem(array, callback) {
 /**
  * The DevToolsShim is a part of the DevTools go faster project, which moves the Firefox
  * DevTools outside of mozilla-central to an add-on. It aims to bridge the gap for
  * existing mozilla-central code that still needs to interact with DevTools (such as
  * web-extensions).
  *
  * DevToolsShim is a singleton that provides a set of helpers to interact with DevTools,
  * that work whether the DevTools addon is installed or not. It can be used to start
- * listening to events, register tools, themes. As soon as a DevTools addon is installed
- * the DevToolsShim will forward all the requests received until then to the real DevTools
- * instance.
+ * listening to events. As soon as a DevTools addon is installed the DevToolsShim will
+ * forward all the requests received until then to the real DevTools instance.
  *
  * DevToolsShim.isInstalled() can also be used to know if DevTools are currently
  * installed.
  */
 this.DevToolsShim = {
   _gDevTools: null,
   listeners: [],
-  tools: [],
-  themes: [],
 
   /**
    * Lazy getter for the `gDevTools` instance. Should only be called when users interacts
    * with DevTools as it will force loading them.
    *
    * @return {DevTools} a devtools instance (from client/framework/devtools)
    */
   get gDevTools() {
@@ -105,20 +102,16 @@ this.DevToolsShim = {
       this._gDevTools = null;
     }
   },
 
   /**
    * The following methods can be called before DevTools are initialized:
    * - on
    * - off
-   * - registerTool
-   * - unregisterTool
-   * - registerTheme
-   * - unregisterTheme
    *
    * If DevTools are not initialized when calling the method, DevToolsShim will call the
    * appropriate method as soon as a gDevTools instance is registered.
    */
 
   /**
    * This method is used by browser/components/extensions/ext-devtools.js for the events:
    * - toolbox-created
@@ -140,64 +133,16 @@ this.DevToolsShim = {
     if (this.isInitialized()) {
       this._gDevTools.off(event, listener);
     } else {
       removeItem(this.listeners, ([e, l]) => e === event && l === listener);
     }
   },
 
   /**
-   * This method is only used by the addon-sdk and should be removed when Firefox 56 is
-   * no longer supported.
-   */
-  registerTool: function (tool) {
-    if (this.isInitialized()) {
-      this._gDevTools.registerTool(tool);
-    } else {
-      this.tools.push(tool);
-    }
-  },
-
-  /**
-   * This method is only used by the addon-sdk and should be removed when Firefox 56 is
-   * no longer supported.
-   */
-  unregisterTool: function (tool) {
-    if (this.isInitialized()) {
-      this._gDevTools.unregisterTool(tool);
-    } else {
-      removeItem(this.tools, t => t === tool);
-    }
-  },
-
-  /**
-   * This method is only used by the addon-sdk and should be removed when Firefox 56 is
-   * no longer supported.
-   */
-  registerTheme: function (theme) {
-    if (this.isInitialized()) {
-      this._gDevTools.registerTheme(theme);
-    } else {
-      this.themes.push(theme);
-    }
-  },
-
-  /**
-   * This method is only used by the addon-sdk and should be removed when Firefox 56 is
-   * no longer supported.
-   */
-  unregisterTheme: function (theme) {
-    if (this.isInitialized()) {
-      this._gDevTools.unregisterTheme(theme);
-    } else {
-      removeItem(this.themes, t => t === theme);
-    }
-  },
-
-  /**
    * Called from SessionStore.jsm in mozilla-central when saving the current state.
    *
    * @param {Object} state
    *                 A SessionStore state object that gets modified by reference
    */
   saveDevToolsSession: function (state) {
     if (!this.isInitialized()) {
       return;
@@ -256,57 +201,31 @@ this.DevToolsShim = {
   },
 
   _onDevToolsRegistered: function () {
     // Register all pending event listeners on the real gDevTools object.
     for (let [event, listener] of this.listeners) {
       this._gDevTools.on(event, listener);
     }
 
-    for (let tool of this.tools) {
-      this._gDevTools.registerTool(tool);
-    }
-
-    for (let theme of this.themes) {
-      this._gDevTools.registerTheme(theme);
-    }
-
     this.listeners = [];
-    this.tools = [];
-    this.themes = [];
   },
 };
 
 /**
- * Compatibility layer for addon-sdk. Remove when Firefox 57 hits release.
- *
- * The methods below are used by classes and tests from addon-sdk/
- * If DevTools are not installed when calling one of them, the call will throw.
- */
-
-let addonSdkMethods = [
-  "closeToolbox",
-  "connectDebuggerServer",
-  "createDebuggerClient",
-  "getToolbox",
-  "initBrowserToolboxProcessForAddon",
-  "showToolbox",
-];
-
-/**
  * Compatibility layer for webextensions.
  *
  * Those methods are called only after a DevTools webextension was loaded in DevTools,
  * therefore DevTools should always be available when they are called.
  */
 let webExtensionsMethods = [
   "createTargetForTab",
   "createWebExtensionInspectedWindowFront",
   "getTargetForTab",
   "getTheme",
   "openBrowserConsole",
 ];
 
-for (let method of [...addonSdkMethods, ...webExtensionsMethods]) {
+for (let method of webExtensionsMethods) {
   this.DevToolsShim[method] = function () {
     return this.gDevTools[method].apply(this.gDevTools, arguments);
   };
 }
--- a/devtools/shim/tests/unit/test_devtools_shim.js
+++ b/devtools/shim/tests/unit/test_devtools_shim.js
@@ -12,20 +12,16 @@ const { DevToolsShim } =
 /**
  * Create a mocked version of DevTools that records all calls made to methods expected
  * to be called by DevToolsShim.
  */
 function createMockDevTools() {
   let methods = [
     "on",
     "off",
-    "registerTool",
-    "registerTheme",
-    "unregisterTool",
-    "unregisterTheme",
     "emit",
     "saveDevToolsSession",
     "restoreDevToolsSession",
   ];
 
   let mock = {
     callLog: {}
   };
@@ -134,82 +130,16 @@ function test_off_called_before_with_bad
   // on should still be called
   checkCalls(mock, "on", 1, ["test_event", cb1]);
   // Calls to off should not be held and forwarded.
   checkCalls(mock, "off", 0);
 
   restoreDevToolsInstalled();
 }
 
-function test_registering_tool() {
-  mockDevToolsInstalled(true);
-
-  ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
-
-  let tool1 = {};
-  let tool2 = {};
-  let tool3 = {};
-  let mock = createMockDevTools();
-
-  // Pre-register tool1
-  DevToolsShim.registerTool(tool1);
-
-  // Pre-register tool3, but unregister right after
-  DevToolsShim.registerTool(tool3);
-  DevToolsShim.unregisterTool(tool3);
-
-  DevToolsShim.register(mock);
-  checkCalls(mock, "registerTool", 1, [tool1]);
-
-  DevToolsShim.registerTool(tool2);
-  checkCalls(mock, "registerTool", 2, [tool2]);
-
-  DevToolsShim.unregister();
-
-  // Create a new mock and check the tools are not added once again.
-  mock = createMockDevTools();
-  DevToolsShim.register(mock);
-  checkCalls(mock, "registerTool", 0);
-
-  restoreDevToolsInstalled();
-}
-
-function test_registering_theme() {
-  mockDevToolsInstalled(true);
-
-  ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
-
-  let theme1 = {};
-  let theme2 = {};
-  let theme3 = {};
-  let mock = createMockDevTools();
-
-  // Pre-register theme1
-  DevToolsShim.registerTheme(theme1);
-
-  // Pre-register theme3, but unregister right after
-  DevToolsShim.registerTheme(theme3);
-  DevToolsShim.unregisterTheme(theme3);
-
-  DevToolsShim.register(mock);
-  checkCalls(mock, "registerTheme", 1, [theme1]);
-
-  DevToolsShim.registerTheme(theme2);
-  checkCalls(mock, "registerTheme", 2, [theme2]);
-
-  DevToolsShim.unregister();
-
-  // Create a new mock and check the themes are not added once again.
-  mock = createMockDevTools();
-  DevToolsShim.register(mock);
-  checkCalls(mock, "registerTheme", 0);
-
-  restoreDevToolsInstalled();
-}
-
 function test_events() {
   mockDevToolsInstalled(true);
 
   ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
 
   let mock = createMockDevTools();
   // Check emit was not called.
   checkCalls(mock, "emit", 0);
@@ -278,19 +208,13 @@ function run_test() {
   DevToolsShim.unregister();
 
   test_off_called_before_registering_devtools();
   DevToolsShim.unregister();
 
   test_off_called_before_with_bad_callback();
   DevToolsShim.unregister();
 
-  test_registering_tool();
-  DevToolsShim.unregister();
-
-  test_registering_theme();
-  DevToolsShim.unregister();
-
   test_scratchpad_apis();
   DevToolsShim.unregister();
 
   test_events();
 }
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -564,16 +564,30 @@ EffectCompositor::HasThrottledStyleUpdat
         return true;
       }
     }
   }
 
   return false;
 }
 
+bool
+EffectCompositor::HasPendingStyleUpdatesFor(Element* aElement) const
+{
+  for (auto& elementSet : mElementsToRestyle) {
+    for (auto iter = elementSet.ConstIter(); !iter.Done(); iter.Next()) {
+      if (iter.Key().mElement->Contains(aElement)) {
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
 void
 EffectCompositor::AddStyleUpdatesTo(RestyleTracker& aTracker)
 {
   if (!mPresContext) {
     return;
   }
 
   for (size_t i = 0; i < kCascadeLevelCount; i++) {
--- a/dom/animation/EffectCompositor.h
+++ b/dom/animation/EffectCompositor.h
@@ -163,16 +163,17 @@ public:
   bool GetServoAnimationRule(
     const dom::Element* aElement,
     CSSPseudoElementType aPseudoType,
     CascadeLevel aCascadeLevel,
     RawServoAnimationValueMapBorrowedMut aAnimationValues);
 
   bool HasPendingStyleUpdates() const;
   bool HasThrottledStyleUpdates() const;
+  bool HasPendingStyleUpdatesFor(dom::Element* aElement) const;
 
   // Tell the restyle tracker about all the animated styles that have
   // pending updates so that it can update the animation rule for these
   // elements.
   void AddStyleUpdatesTo(RestyleTracker& aTracker);
 
   nsIStyleRuleProcessor* RuleProcessor(CascadeLevel aCascadeLevel) const
   {
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -134,33 +134,36 @@ enum {
   // is, has an animation-only style change pending _and_ that style
   // change will attempt to restyle descendants).
   ELEMENT_IS_POTENTIAL_ANIMATION_ONLY_RESTYLE_ROOT = ELEMENT_SHARED_RESTYLE_BIT_4,
 
   // Set if this element has a pending restyle with an eRestyle_SomeDescendants
   // restyle hint.
   ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR = ELEMENT_FLAG_BIT(4),
 
+  // Set if a child element has later-sibling restyle hint. This is needed for
+  // nsComputedDOMStyle to decide when should we need to flush style (only used
+  // in Gecko).
+  ELEMENT_HAS_CHILD_WITH_LATER_SIBLINGS_HINT = ELEMENT_FLAG_BIT(5),
+
   // Just the HAS_PENDING bits, for convenience
   ELEMENT_PENDING_RESTYLE_FLAGS =
     ELEMENT_HAS_PENDING_RESTYLE |
     ELEMENT_HAS_PENDING_ANIMATION_ONLY_RESTYLE,
 
   // Just the IS_POTENTIAL bits, for convenience
   ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS =
     ELEMENT_IS_POTENTIAL_RESTYLE_ROOT |
     ELEMENT_IS_POTENTIAL_ANIMATION_ONLY_RESTYLE_ROOT,
 
   // All of the restyle bits together, for convenience.
   ELEMENT_ALL_RESTYLE_FLAGS = ELEMENT_PENDING_RESTYLE_FLAGS |
                               ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS |
                               ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR,
 
-  // ELEMENT_FLAG_BIT(5) is currently unused
-
   // Remaining bits are for subclasses
   ELEMENT_TYPE_SPECIFIC_BITS_OFFSET = NODE_TYPE_SPECIFIC_BITS_OFFSET + 6
 };
 
 #undef ELEMENT_FLAG_BIT
 
 // Make sure we have space for our bits
 ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET);
@@ -1826,17 +1829,18 @@ inline const mozilla::dom::Element* nsIN
 {
   MOZ_ASSERT(IsElement());
   return static_cast<const mozilla::dom::Element*>(this);
 }
 
 inline void nsINode::UnsetRestyleFlagsIfGecko()
 {
   if (IsElement() && !AsElement()->IsStyledByServo()) {
-    UnsetFlags(ELEMENT_ALL_RESTYLE_FLAGS);
+    UnsetFlags(ELEMENT_ALL_RESTYLE_FLAGS |
+               ELEMENT_HAS_CHILD_WITH_LATER_SIBLINGS_HINT);
   }
 }
 
 /**
  * Macros to implement Clone(). _elementName is the class for which to implement
  * Clone.
  */
 #define NS_IMPL_ELEMENT_CLONE(_elementName)                                 \
--- a/dom/base/FlushType.h
+++ b/dom/base/FlushType.h
@@ -33,16 +33,25 @@ enum class FlushType : uint8_t {
                               but allow it to be interrupted (so
                               an incomplete layout may result) */
   Layout           = 7, /* As above, but layout must run to
                            completion */
   Display          = 8, /* As above, plus flush painting */
   Count
 };
 
+/**
+ * This is the enum used by nsIDocument::FlushPendingNotifications to decide
+ * whether the current document is skippable.
+ */
+enum class FlushTarget : uint8_t {
+  Normal = 0,     /* Flush current and parent documents. */
+  ParentOnly = 1  /* Skip current document, only flush its parent. */
+};
+
 struct ChangesToFlush {
   ChangesToFlush(FlushType aFlushType, bool aFlushAnimations)
     : mFlushType(aFlushType)
     , mFlushAnimations(aFlushAnimations)
   {}
 
   FlushType mFlushType;
   bool mFlushAnimations;
new file mode 100644
--- /dev/null
+++ b/dom/base/crashtests/1352453.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      tr {
+        font: medium / 1000 cursive;
+      }
+
+      :only-of-type {
+        border-image: repeating-linear-gradient(45deg, blue, red) space 1% / 1pt auto;
+      }
+    </style>
+    <script>
+      o1 = document.createElement('tr');
+      o2 = document.createElement('th');
+      o3 = document.createElement('rt');
+      document.documentElement.appendChild(o1);
+      o1.appendChild(o2);
+      o2.appendChild(o3);
+    </script>
+  </head>
+</html>
--- a/dom/base/crashtests/crashtests.list
+++ b/dom/base/crashtests/crashtests.list
@@ -204,16 +204,17 @@ load 1181619.html
 load 1230422.html
 load 1251361.html
 load 1304437.html
 pref(dom.IntersectionObserver.enabled,true) load 1324209.html
 pref(dom.IntersectionObserver.enabled,true) load 1326194-1.html
 pref(dom.IntersectionObserver.enabled,true) load 1326194-2.html
 pref(dom.IntersectionObserver.enabled,true) load 1332939.html
 pref(dom.webcomponents.enabled,true) load 1341693.html
+load 1352453.html
 pref(dom.IntersectionObserver.enabled,true) load 1353529.xul
 load 1368327.html
 pref(dom.IntersectionObserver.enabled,true) load 1369363.xul
 load 1370072.html
 pref(clipboard.autocopy,true) load 1370737.html
 pref(dom.IntersectionObserver.enabled,true) load 1370968.html
 load 1377826.html
 skip-if(stylo&&isDebugBuild&&winWidget) load structured_clone_container_throws.html # Bug 1383845
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -8342,17 +8342,17 @@ nsIDocument::CreateEvent(const nsAString
   }
   WidgetEvent* e = ev->WidgetEventPtr();
   e->mFlags.mBubbles = false;
   e->mFlags.mCancelable = false;
   return ev.forget();
 }
 
 void
-nsDocument::FlushPendingNotifications(FlushType aType)
+nsDocument::FlushPendingNotifications(FlushType aType, FlushTarget aTarget)
 {
   nsDocumentOnStack dos(this);
 
   // We need to flush the sink for non-HTML documents (because the XML
   // parser still does insertion with deferred notifications).  We
   // also need to flush the sink if this is a layout-related flush, to
   // make sure that layout is started as needed.  But we can skip that
   // part if we have no presshell or if it's already done an initial
@@ -8392,21 +8392,23 @@ nsDocument::FlushPendingNotifications(Fl
   // Since media queries mean that a size change of our container can
   // affect style, we need to promote a style flush on ourself to a
   // layout flush on our parent, since we need our container to be the
   // correct size to determine the correct style.
   if (mParentDocument && IsSafeToFlush()) {
     FlushType parentType = aType;
     if (aType >= FlushType::Style)
       parentType = std::max(FlushType::Layout, aType);
-    mParentDocument->FlushPendingNotifications(parentType);
-  }
-
-  if (nsIPresShell* shell = GetShell()) {
-    shell->FlushPendingNotifications(aType);
+    mParentDocument->FlushPendingNotifications(parentType, FlushTarget::Normal);
+  }
+
+  if (aTarget == FlushTarget::Normal) {
+    if (nsIPresShell* shell = GetShell()) {
+      shell->FlushPendingNotifications(aType);
+    }
   }
 }
 
 static bool
 Copy(nsIDocument* aDocument, void* aData)
 {
   nsTArray<nsCOMPtr<nsIDocument> >* resources =
     static_cast<nsTArray<nsCOMPtr<nsIDocument> >* >(aData);
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -568,17 +568,19 @@ public:
 
   virtual void StyleRuleChanged(mozilla::StyleSheet* aStyleSheet,
                                 mozilla::css::Rule* aStyleRule) override;
   virtual void StyleRuleAdded(mozilla::StyleSheet* aStyleSheet,
                               mozilla::css::Rule* aStyleRule) override;
   virtual void StyleRuleRemoved(mozilla::StyleSheet* aStyleSheet,
                                 mozilla::css::Rule* aStyleRule) override;
 
-  virtual void FlushPendingNotifications(mozilla::FlushType aType) override;
+  virtual void FlushPendingNotifications(mozilla::FlushType aType,
+                                         mozilla::FlushTarget aTarget
+                                           = mozilla::FlushTarget::Normal) override;
   virtual void FlushExternalResources(mozilla::FlushType aType) override;
   virtual void SetXMLDeclaration(const char16_t *aVersion,
                                  const char16_t *aEncoding,
                                  const int32_t aStandalone) override;
   virtual void GetXMLDeclaration(nsAString& aVersion,
                                  nsAString& aEncoding,
                                  nsAString& Standalone) override;
   virtual bool IsScriptEnabled() override;
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -1634,17 +1634,19 @@ public:
                               mozilla::css::Rule* aStyleRule) = 0;
   virtual void StyleRuleRemoved(mozilla::StyleSheet* aStyleSheet,
                                 mozilla::css::Rule* aStyleRule) = 0;
 
   /**
    * Flush notifications for this document and its parent documents
    * (since those may affect the layout of this one).
    */
-  virtual void FlushPendingNotifications(mozilla::FlushType aType) = 0;
+  virtual void FlushPendingNotifications(mozilla::FlushType aType,
+                                         mozilla::FlushTarget aTarget
+                                           = mozilla::FlushTarget::Normal) = 0;
 
   /**
    * Calls FlushPendingNotifications on any external resources this document
    * has. If this document has no external resources or is an external resource
    * itself this does nothing. This should only be called with
    * aType >= FlushType::Style.
    */
   virtual void FlushExternalResources(mozilla::FlushType aType) = 0;
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1042,17 +1042,17 @@ NativeInterface2JSObjectAndThrowIfFailed
       if (obj) {
         aRetval.setObject(*obj);
         return true;
       }
   }
 
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!XPCConvert::NativeInterface2JSObject(aRetval, nullptr, aHelper, aIID,
+  if (!XPCConvert::NativeInterface2JSObject(aRetval, aHelper, aIID,
                                             aAllowNativeWrapper, &rv)) {
     // I can't tell if NativeInterface2JSObject throws JS exceptions
     // or not.  This is a sloppy stab at the right semantics; the
     // method really ought to be fixed to behave consistently.
     if (!JS_IsExceptionPending(aCx)) {
       Throw(aCx, NS_FAILED(rv) ? rv : NS_ERROR_UNEXPECTED);
     }
     return false;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -14,16 +14,17 @@
 #include "TabChild.h"
 #include "HandlerServiceChild.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
 #include "mozilla/Unused.h"
+#include "mozilla/TelemetryIPC.h"
 #include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h"
 #include "mozilla/docshell/OfflineCacheUpdateChild.h"
 #include "mozilla/dom/ContentBridgeChild.h"
 #include "mozilla/dom/ContentBridgeParent.h"
 #include "mozilla/dom/VideoDecoderManagerChild.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/DocGroup.h"
@@ -1225,16 +1226,19 @@ ContentChild::InitXPCOM(const XPCOMInitD
   nsLayoutStylesheetCache::SetUserContentCSSURL(ucsURL);
 
   // This will register cross-process observer.
   mozilla::dom::time::InitializeDateCacheCleaner();
 
   GfxInfoBase::SetFeatureStatus(aXPCOMInit.gfxFeatureStatus());
 
   DataStorage::SetCachedStorageEntries(aXPCOMInit.dataStorage());
+
+  // Set the dynamic scalar definitions for this process.
+  TelemetryIPC::AddDynamicScalarDefinitions(aXPCOMInit.dynamicScalarDefs());
 }
 
 mozilla::ipc::IPCResult
 ContentChild::RecvRequestMemoryReport(const uint32_t& aGeneration,
                                       const bool& aAnonymize,
                                       const bool& aMinimizeMemoryUsage,
                                       const MaybeFileDesc& aDMDFile)
 {
@@ -3639,16 +3643,23 @@ ContentChild::RecvSuspendInputEventQueue
 
 mozilla::ipc::IPCResult
 ContentChild::RecvResumeInputEventQueue()
 {
   nsThreadManager::get().ResumeInputEventPrioritization();
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+ContentChild::RecvAddDynamicScalars(nsTArray<DynamicScalarDefinition>&& aDefs)
+{
+  TelemetryIPC::AddDynamicScalarDefinitions(aDefs);
+  return IPC_OK();
+}
+
 already_AddRefed<nsIEventTarget>
 ContentChild::GetSpecificMessageEventTarget(const Message& aMsg)
 {
   switch(aMsg.type()) {
     // Javascript
     case PJavaScript::Msg_DropTemporaryStrongReferences__ID:
     case PJavaScript::Msg_DropObject__ID:
 
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -628,16 +628,19 @@ public:
   RecvFlushInputEventQueue() override;
 
   virtual mozilla::ipc::IPCResult
   RecvSuspendInputEventQueue() override;
 
   virtual mozilla::ipc::IPCResult
   RecvResumeInputEventQueue() override;
 
+  virtual mozilla::ipc::IPCResult
+  RecvAddDynamicScalars(nsTArray<DynamicScalarDefinition>&& aDefs) override;
+
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   bool
   SendGetA11yContentId();
 #endif // defined(XP_WIN) && defined(ACCESSIBILITY)
 
   // Get a reference to the font family list passed from the chrome process,
   // for use during gfx initialization.
   InfallibleTArray<mozilla::dom::FontFamilyListEntry>&
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2258,16 +2258,19 @@ ContentParent::InitInternal(ProcessPrior
       gfxFeatureStatus.status() = status;
       gfxFeatureStatus.failureId() = failureId;
       xpcomInit.gfxFeatureStatus().AppendElement(gfxFeatureStatus);
     }
   }
 
   DataStorage::GetAllChildProcessData(xpcomInit.dataStorage());
 
+  // Send the dynamic scalar definitions to the new process.
+  TelemetryIPC::GetDynamicScalarDefinitions(xpcomInit.dynamicScalarDefs());
+
   // Must send screen info before send initialData
   ScreenManager& screenManager = ScreenManager::GetSingleton();
   screenManager.CopyScreensToRemote(this);
 
   Unused << SendSetXPCOMProcessAttributes(xpcomInit, initialData, lnfCache,
                                           fontFamilies);
 
   if (aSendRegisteredChrome) {
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -89,16 +89,17 @@ using mozilla::DataStorageType from "ipc
 using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
 using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h";
 using mozilla::layers::CompositorOptions from "mozilla/layers/CompositorOptions.h";
 using struct mozilla::dom::FlyWebPublishOptions from "mozilla/dom/FlyWebPublishOptionsIPCSerializer.h";
 using mozilla::Telemetry::HistogramAccumulation from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::KeyedHistogramAccumulation from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::DynamicScalarDefinition from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h";
 using mozilla::CrossProcessMutexHandle from "mozilla/ipc/CrossProcessMutex.h";
 using mozilla::HangDetails from "mozilla/HangDetails.h";
 
 union ChromeRegistryItem
 {
     ChromePackage;
@@ -259,16 +260,17 @@ struct XPCOMInitData
     OptionalURIParams userContentSheetURL;
     PrefSetting[] prefs;
     GfxVarUpdate[] gfxNonDefaultVarUpdates;
     ContentDeviceData contentDeviceData;
     GfxInfoFeatureStatus[] gfxFeatureStatus;
     DataStorageEntry[] dataStorage;
     nsCString[] appLocales;
     nsCString[] requestedLocales;
+    DynamicScalarDefinition[] dynamicScalarDefs;
 };
 
 /**
  * The PContent protocol is a top-level protocol between the UI process
  * and a content process. There is exactly one PContentParent/PContentChild pair
  * for each content process.
  */
 nested(upto inside_cpow) sync protocol PContent
@@ -660,16 +662,24 @@ child:
     async ResumeInputEventQueue();
 
     /*
      * IPC message to suspend consuming the pending events in the input event
      * queue.
      */
     prio(input) async SuspendInputEventQueue();
 
+    /*
+     * IPC message to propagate dynamic scalar definitions, added after the
+     * content process is spawned, from the parent to the child.
+     * Dynamic scalar definitions added at the process startup are handled
+     * using the |TelemetryIPC::AddDynamicScalarDefinitions| functions.
+     */
+    async AddDynamicScalars(DynamicScalarDefinition[] definitions);
+
 parent:
     async InitBackground(Endpoint<PBackgroundParent> aEndpoint);
 
     sync CreateChildProcess(IPCTabContext context,
                             ProcessPriority priority,
                             TabId openerTabId,
                             TabId tabId)
         returns (ContentParentId cpId, bool isForBrowser);
--- a/dom/media/mediasource/MediaSourceDemuxer.cpp
+++ b/dom/media/mediasource/MediaSourceDemuxer.cpp
@@ -53,17 +53,17 @@ MediaSourceDemuxer::AddSizeOfResources(
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // NB: The track buffers must only be accessed on the TaskQueue.
   RefPtr<MediaSourceDemuxer> self = this;
   RefPtr<MediaSourceDecoder::ResourceSizes> sizes = aSizes;
   nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
     "MediaSourceDemuxer::AddSizeOfResources", [self, sizes]() {
-      for (TrackBuffersManager* manager : self->mSourceBuffers) {
+      for (const RefPtr<TrackBuffersManager>& manager : self->mSourceBuffers) {
         manager->AddSizeOfResources(sizes);
       }
     });
 
   GetTaskQueue()->Dispatch(task.forget());
 }
 
 void MediaSourceDemuxer::NotifyInitDataArrived()
@@ -162,54 +162,57 @@ MediaSourceDemuxer::GetCrypto()
 {
   MonitorAutoLock mon(mMonitor);
   auto crypto = MakeUnique<EncryptionInfo>();
   *crypto = mInfo.mCrypto;
   return crypto;
 }
 
 void
-MediaSourceDemuxer::AttachSourceBuffer(TrackBuffersManager* aSourceBuffer)
+MediaSourceDemuxer::AttachSourceBuffer(
+  RefPtr<TrackBuffersManager>& aSourceBuffer)
 {
-  nsCOMPtr<nsIRunnable> task = NewRunnableMethod<TrackBuffersManager*>(
+  nsCOMPtr<nsIRunnable> task = NewRunnableMethod<RefPtr<TrackBuffersManager>&&>(
     "MediaSourceDemuxer::DoAttachSourceBuffer",
     this,
     &MediaSourceDemuxer::DoAttachSourceBuffer,
     aSourceBuffer);
   GetTaskQueue()->Dispatch(task.forget());
 }
 
 void
-MediaSourceDemuxer::DoAttachSourceBuffer(mozilla::TrackBuffersManager* aSourceBuffer)
+MediaSourceDemuxer::DoAttachSourceBuffer(
+  RefPtr<mozilla::TrackBuffersManager>&& aSourceBuffer)
 {
   MOZ_ASSERT(OnTaskQueue());
-  mSourceBuffers.AppendElement(aSourceBuffer);
+  mSourceBuffers.AppendElement(Move(aSourceBuffer));
   ScanSourceBuffersForContent();
 }
 
 void
-MediaSourceDemuxer::DetachSourceBuffer(TrackBuffersManager* aSourceBuffer)
+MediaSourceDemuxer::DetachSourceBuffer(
+  RefPtr<TrackBuffersManager>& aSourceBuffer)
 {
-  nsCOMPtr<nsIRunnable> task = NewRunnableMethod<TrackBuffersManager*>(
+  nsCOMPtr<nsIRunnable> task = NewRunnableMethod<RefPtr<TrackBuffersManager>&&>(
     "MediaSourceDemuxer::DoDetachSourceBuffer",
     this,
     &MediaSourceDemuxer::DoDetachSourceBuffer,
     aSourceBuffer);
   GetTaskQueue()->Dispatch(task.forget());
 }
 
 void
-MediaSourceDemuxer::DoDetachSourceBuffer(TrackBuffersManager* aSourceBuffer)
+MediaSourceDemuxer::DoDetachSourceBuffer(
+  RefPtr<TrackBuffersManager>&& aSourceBuffer)
 {
   MOZ_ASSERT(OnTaskQueue());
-  for (uint32_t i = 0; i < mSourceBuffers.Length(); i++) {
-    if (mSourceBuffers[i].get() == aSourceBuffer) {
-      mSourceBuffers.RemoveElementAt(i);
-    }
-  }
+  mSourceBuffers.RemoveElementsBy(
+    [&aSourceBuffer](const RefPtr<TrackBuffersManager> aLinkedSourceBuffer) {
+      return aLinkedSourceBuffer == aSourceBuffer;
+    });
   {
     MonitorAutoLock mon(mMonitor);
     if (aSourceBuffer == mAudioTrack) {
       mAudioTrack = nullptr;
     }
     if (aSourceBuffer == mVideoTrack) {
       mVideoTrack = nullptr;
     }
@@ -226,17 +229,17 @@ MediaSourceDemuxer::GetTrackInfo(TrackTy
       return &mInfo.mAudio;
     case TrackType::kVideoTrack:
       return &mInfo.mVideo;
     default:
       return nullptr;
   }
 }
 
-TrackBuffersManager*
+RefPtr<TrackBuffersManager>
 MediaSourceDemuxer::GetManager(TrackType aTrack)
 {
   MonitorAutoLock mon(mMonitor);
   switch (aTrack) {
     case TrackType::kAudioTrack:
       return mAudioTrack;
     case TrackType::kVideoTrack:
       return mVideoTrack;
--- a/dom/media/mediasource/MediaSourceDemuxer.h
+++ b/dom/media/mediasource/MediaSourceDemuxer.h
@@ -39,18 +39,18 @@ public:
 
   bool IsSeekable() const override;
 
   UniquePtr<EncryptionInfo> GetCrypto() override;
 
   bool ShouldComputeStartTime() const override { return false; }
 
   /* interface for TrackBuffersManager */
-  void AttachSourceBuffer(TrackBuffersManager* aSourceBuffer);
-  void DetachSourceBuffer(TrackBuffersManager* aSourceBuffer);
+  void AttachSourceBuffer(RefPtr<TrackBuffersManager>& aSourceBuffer);
+  void DetachSourceBuffer(RefPtr<TrackBuffersManager>& aSourceBuffer);
   AutoTaskQueue* GetTaskQueue() { return mTaskQueue; }
   void NotifyInitDataArrived();
 
   // Returns a string describing the state of the MediaSource internal
   // buffered data. Used for debugging purposes.
   void GetMozDebugReaderData(nsACString& aString);
 
   void AddSizeOfResources(MediaSourceDecoder::ResourceSizes* aSizes);
@@ -61,20 +61,20 @@ public:
   static constexpr media::TimeUnit EOS_FUZZ =
     media::TimeUnit::FromMicroseconds(500000);
 
 private:
   ~MediaSourceDemuxer();
   friend class MediaSourceTrackDemuxer;
   // Scan source buffers and update information.
   bool ScanSourceBuffersForContent();
-  TrackBuffersManager* GetManager(TrackInfo::TrackType aType);
+  RefPtr<TrackBuffersManager> GetManager(TrackInfo::TrackType aType);
   TrackInfo* GetTrackInfo(TrackInfo::TrackType);
-  void DoAttachSourceBuffer(TrackBuffersManager* aSourceBuffer);
-  void DoDetachSourceBuffer(TrackBuffersManager* aSourceBuffer);
+  void DoAttachSourceBuffer(RefPtr<TrackBuffersManager>&& aSourceBuffer);
+  void DoDetachSourceBuffer(RefPtr<TrackBuffersManager>&& aSourceBuffer);
   bool OnTaskQueue()
   {
     return !GetTaskQueue() || GetTaskQueue()->IsCurrentThreadIn();
   }
 
   RefPtr<AutoTaskQueue> mTaskQueue;
   nsTArray<RefPtr<MediaSourceTrackDemuxer>> mDemuxers;
 
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -277,17 +277,17 @@ SourceBuffer::Detach()
   if (!mMediaSource) {
     MSE_DEBUG("Already detached");
     return;
   }
   AbortBufferAppend();
   if (mTrackBuffersManager) {
     mTrackBuffersManager->Detach();
     mMediaSource->GetDecoder()->GetDemuxer()->DetachSourceBuffer(
-      mTrackBuffersManager.get());
+      mTrackBuffersManager);
   }
   mTrackBuffersManager = nullptr;
   mMediaSource = nullptr;
 }
 
 void
 SourceBuffer::Ended()
 {
@@ -319,17 +319,17 @@ SourceBuffer::SourceBuffer(MediaSource* 
 
   ErrorResult dummy;
   if (mCurrentAttributes.mGenerateTimestamps) {
     SetMode(SourceBufferAppendMode::Sequence, dummy);
   } else {
     SetMode(SourceBufferAppendMode::Segments, dummy);
   }
   mMediaSource->GetDecoder()->GetDemuxer()->AttachSourceBuffer(
-    mTrackBuffersManager.get());
+    mTrackBuffersManager);
 }
 
 SourceBuffer::~SourceBuffer()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mMediaSource);
   MSE_DEBUG("");
 }
--- a/dom/performance/Performance.cpp
+++ b/dom/performance/Performance.cpp
@@ -17,16 +17,17 @@
 #include "PerformanceResourceTiming.h"
 #include "PerformanceService.h"
 #include "PerformanceWorker.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/PerformanceBinding.h"
 #include "mozilla/dom/PerformanceEntryEvent.h"
 #include "mozilla/dom/PerformanceNavigationBinding.h"
 #include "mozilla/dom/PerformanceObserverBinding.h"
+#include "mozilla/dom/PerformanceNavigationTiming.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Preferences.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 
 #define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__)
 
 namespace mozilla {
--- a/dom/performance/Performance.h
+++ b/dom/performance/Performance.h
@@ -47,24 +47,24 @@ public:
                       nsITimedChannel* aChannel);
 
   static already_AddRefed<Performance>
   CreateForWorker(workers::WorkerPrivate* aWorkerPrivate);
 
   JSObject* WrapObject(JSContext *cx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
-  void GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval);
+  virtual void GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval);
 
-  void GetEntriesByType(const nsAString& aEntryType,
-                        nsTArray<RefPtr<PerformanceEntry>>& aRetval);
+  virtual void GetEntriesByType(const nsAString& aEntryType,
+                                nsTArray<RefPtr<PerformanceEntry>>& aRetval);
 
-  void GetEntriesByName(const nsAString& aName,
-                        const Optional<nsAString>& aEntryType,
-                        nsTArray<RefPtr<PerformanceEntry>>& aRetval);
+  virtual void GetEntriesByName(const nsAString& aName,
+                                const Optional<nsAString>& aEntryType,
+                                nsTArray<RefPtr<PerformanceEntry>>& aRetval);
 
   virtual void AddEntry(nsIHttpChannel* channel,
                         nsITimedChannel* timedChannel) = 0;
 
   void ClearResourceTimings();
 
   DOMHighResTimeStamp Now() const;
 
@@ -149,17 +149,17 @@ protected:
 
   void RunNotificationObserversTask();
   void QueueEntry(PerformanceEntry* aEntry);
 
   DOMHighResTimeStamp RoundTime(double aTime) const;
 
   nsTObserverArray<PerformanceObserver*> mObservers;
 
-private:
+protected:
   static const uint64_t kDefaultResourceTimingBufferSize = 150;
 
   // When kDefaultResourceTimingBufferSize is increased or removed, these should
   // be changed to use SegmentedVector
   AutoTArray<RefPtr<PerformanceEntry>, kDefaultResourceTimingBufferSize> mUserEntries;
   AutoTArray<RefPtr<PerformanceEntry>, kDefaultResourceTimingBufferSize> mResourceEntries;
 
   uint64_t mResourceTimingBufferSize;
--- a/dom/performance/PerformanceMainThread.cpp
+++ b/dom/performance/PerformanceMainThread.cpp
@@ -11,25 +11,27 @@
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(PerformanceMainThread)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PerformanceMainThread,
                                                 Performance)
 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTiming,
-                                mNavigation)
+                                mNavigation,
+                                mDocEntry)
   tmp->mMozMemory = nullptr;
   mozilla::DropJSObjects(this);
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PerformanceMainThread,
                                                   Performance)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTiming,
-                                    mNavigation)
+                                    mNavigation,
+                                    mDocEntry)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceMainThread,
                                                 Performance)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMozMemory)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_ADDREF_INHERITED(PerformanceMainThread, Performance)
@@ -147,48 +149,17 @@ PerformanceMainThread::AddEntry(nsIHttpC
     // resource timing returns a relative timing (no offset).
     RefPtr<PerformanceTiming> performanceTiming =
         new PerformanceTiming(this, timedChannel, channel,
             0);
 
     // The PerformanceResourceTiming object will use the PerformanceTiming
     // object to get all the required timings.
     RefPtr<PerformanceResourceTiming> performanceEntry =
-      new PerformanceResourceTiming(performanceTiming, this, entryName);
-
-    nsAutoCString protocol;
-    // Can be an empty string.
-    Unused << channel->GetProtocolVersion(protocol);
-
-    // If this is a local fetch, nextHopProtocol should be set to empty string.
-    nsCOMPtr<nsICacheInfoChannel> cachedChannel = do_QueryInterface(channel);
-    if (cachedChannel) {
-      bool isFromCache;
-      if (NS_SUCCEEDED(cachedChannel->IsFromCache(&isFromCache))
-          && isFromCache) {
-        protocol.Truncate();
-      }
-    }
-
-    performanceEntry->SetNextHopProtocol(NS_ConvertUTF8toUTF16(protocol));
-
-    uint64_t encodedBodySize = 0;
-    Unused << channel->GetEncodedBodySize(&encodedBodySize);
-    performanceEntry->SetEncodedBodySize(encodedBodySize);
-
-    uint64_t transferSize = 0;
-    Unused << channel->GetTransferSize(&transferSize);
-    performanceEntry->SetTransferSize(transferSize);
-
-    uint64_t decodedBodySize = 0;
-    Unused << channel->GetDecodedBodySize(&decodedBodySize);
-    if (decodedBodySize == 0) {
-      decodedBodySize = encodedBodySize;
-    }
-    performanceEntry->SetDecodedBodySize(decodedBodySize);
+      new PerformanceResourceTiming(performanceTiming, this, entryName, channel);
 
     // If the initiator type had no valid value, then set it to the default
     // ("other") value.
     if (initiatorType.IsEmpty()) {
       initiatorType = NS_LITERAL_STRING("other");
     }
     performanceEntry->SetInitiatorType(initiatorType);
     InsertResourceEntry(performanceEntry);
@@ -333,10 +304,86 @@ PerformanceMainThread::CreationTimeStamp
 }
 
 DOMHighResTimeStamp
 PerformanceMainThread::CreationTime() const
 {
   return GetDOMTiming()->GetNavigationStart();
 }
 
+void
+PerformanceMainThread::EnsureDocEntry()
+{
+  if (!mDocEntry) {
+    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+    mDocEntry = new PerformanceNavigationTiming(Timing(), this,
+                                                httpChannel);
+  }
+}
+
+
+void
+PerformanceMainThread::GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval)
+{
+  // We return an empty list when 'privacy.resistFingerprinting' is on.
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    aRetval.Clear();
+    return;
+  }
+
+  aRetval = mResourceEntries;
+  aRetval.AppendElements(mUserEntries);
+
+  EnsureDocEntry();
+  if (mDocEntry) {
+    aRetval.AppendElement(mDocEntry);
+  }
+
+  aRetval.Sort(PerformanceEntryComparator());
+}
+
+void
+PerformanceMainThread::GetEntriesByType(const nsAString& aEntryType,
+                                        nsTArray<RefPtr<PerformanceEntry>>& aRetval)
+{
+  // We return an empty list when 'privacy.resistFingerprinting' is on.
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    aRetval.Clear();
+    return;
+  }
+
+  if (aEntryType.EqualsLiteral("navigation")) {
+    aRetval.Clear();
+    EnsureDocEntry();
+    if (mDocEntry) {
+      aRetval.AppendElement(mDocEntry);
+    }
+    return;
+  }
+
+  Performance::GetEntriesByType(aEntryType, aRetval);
+}
+
+void
+PerformanceMainThread::GetEntriesByName(const nsAString& aName,
+                                        const Optional<nsAString>& aEntryType,
+                                        nsTArray<RefPtr<PerformanceEntry>>& aRetval)
+{
+  // We return an empty list when 'privacy.resistFingerprinting' is on.
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    aRetval.Clear();
+    return;
+  }
+
+  if (aName.EqualsLiteral("document")) {
+    aRetval.Clear();
+    EnsureDocEntry();
+    if (mDocEntry) {
+      aRetval.AppendElement(mDocEntry);
+    }
+    return;
+  }
+
+  Performance::GetEntriesByName(aName, aEntryType, aRetval);
+}
+
 } // dom namespace
 } // mozilla namespace
--- a/dom/performance/PerformanceMainThread.h
+++ b/dom/performance/PerformanceMainThread.h
@@ -42,28 +42,39 @@ public:
     return mDOMTiming;
   }
 
   virtual nsITimedChannel* GetChannel() const override
   {
     return mChannel;
   }
 
+  // The GetEntries* methods need to be overriden in order to add the
+  // the document entry of type navigation.
+  virtual void GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval) override;
+  virtual void GetEntriesByType(const nsAString& aEntryType,
+                                nsTArray<RefPtr<PerformanceEntry>>& aRetval) override;
+  virtual void GetEntriesByName(const nsAString& aName,
+                                const Optional<nsAString>& aEntryType,
+                                nsTArray<RefPtr<PerformanceEntry>>& aRetval) override;
+
 protected:
   ~PerformanceMainThread();
 
   void InsertUserEntry(PerformanceEntry* aEntry) override;
 
   bool IsPerformanceTimingAttribute(const nsAString& aName) override;
 
   DOMHighResTimeStamp
   GetPerformanceTimingFromString(const nsAString& aTimingName) override;
 
   void DispatchBufferFullEvent() override;
+  void EnsureDocEntry();
 
+  RefPtr<PerformanceEntry> mDocEntry;
   RefPtr<nsDOMNavigationTiming> mDOMTiming;
   nsCOMPtr<nsITimedChannel> mChannel;
   RefPtr<PerformanceTiming> mTiming;
   RefPtr<PerformanceNavigation> mNavigation;
   JS::Heap<JSObject*> mMozMemory;
 };
 
 } // namespace dom
new file mode 100644
--- /dev/null
+++ b/dom/performance/PerformanceNavigationTiming.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/PerformanceNavigationTiming.h"
+#include "mozilla/dom/PerformanceNavigationTimingBinding.h"
+
+using namespace mozilla::dom;
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceNavigationTiming)
+NS_INTERFACE_MAP_END_INHERITING(PerformanceResourceTiming)
+
+NS_IMPL_ADDREF_INHERITED(PerformanceNavigationTiming, PerformanceResourceTiming)
+NS_IMPL_RELEASE_INHERITED(PerformanceNavigationTiming, PerformanceResourceTiming)
+
+JSObject*
+PerformanceNavigationTiming::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return PerformanceNavigationTimingBinding::Wrap(aCx, this, aGivenProto);
+}
+
+DOMHighResTimeStamp
+PerformanceNavigationTiming::UnloadEventStart()
+{
+  return mTiming->GetDOMTiming()->GetUnloadEventStart();
+}
+
+DOMHighResTimeStamp
+PerformanceNavigationTiming::UnloadEventEnd()
+{
+  return mTiming->GetDOMTiming()->GetUnloadEventEnd();
+}
+
+DOMHighResTimeStamp
+PerformanceNavigationTiming::DomInteractive()
+{
+  return mTiming->GetDOMTiming()->GetDomInteractive();
+}
+
+DOMHighResTimeStamp
+PerformanceNavigationTiming::DomContentLoadedEventStart()
+{
+  return mTiming->GetDOMTiming()->GetDomContentLoadedEventStart();
+}
+
+DOMHighResTimeStamp
+PerformanceNavigationTiming::DomContentLoadedEventEnd()
+{
+  return mTiming->GetDOMTiming()->GetDomContentLoadedEventEnd();
+}
+
+DOMHighResTimeStamp
+PerformanceNavigationTiming::DomComplete()
+{
+  return mTiming->GetDOMTiming()->GetDomComplete();
+}
+
+DOMHighResTimeStamp
+PerformanceNavigationTiming::LoadEventStart()
+{
+  return mTiming->GetDOMTiming()->GetLoadEventStart();
+}
+
+DOMHighResTimeStamp
+PerformanceNavigationTiming::LoadEventEnd() const
+{
+  return mTiming->GetDOMTiming()->GetLoadEventEnd();
+}
+
+NavigationType
+PerformanceNavigationTiming::Type()
+{
+  switch(mTiming->GetDOMTiming()->GetType()) {
+    case nsDOMNavigationTiming::TYPE_NAVIGATE:
+      return NavigationType::Navigate;
+      break;
+    case nsDOMNavigationTiming::TYPE_RELOAD:
+      return NavigationType::Reload;
+      break;
+    case nsDOMNavigationTiming::TYPE_BACK_FORWARD:
+      return NavigationType::Back_forward;
+      break;
+    default:
+      MOZ_CRASH(); // Should not happen
+      return NavigationType::Navigate;
+  }
+}
+
+uint16_t
+PerformanceNavigationTiming::RedirectCount()
+{
+  return mTiming->GetRedirectCount();
+}
new file mode 100644
--- /dev/null
+++ b/dom/performance/PerformanceNavigationTiming.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformanceNavigationTiming_h___
+#define mozilla_dom_PerformanceNavigationTiming_h___
+
+#include "nsCOMPtr.h"
+#include "nsIChannel.h"
+#include "nsITimedChannel.h"
+#include "mozilla/dom/PerformanceResourceTiming.h"
+#include "mozilla/dom/PerformanceNavigationTimingBinding.h"
+#include "nsIHttpChannel.h"
+
+namespace mozilla {
+namespace dom {
+
+// https://www.w3.org/TR/navigation-timing-2/#sec-PerformanceNavigationTiming
+class PerformanceNavigationTiming final
+  : public PerformanceResourceTiming
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+
+  explicit PerformanceNavigationTiming(PerformanceTiming* aPerformanceTiming,
+                                       Performance* aPerformance,
+                                       nsIHttpChannel* aChannel)
+    : PerformanceResourceTiming(aPerformanceTiming, aPerformance,
+                                NS_LITERAL_STRING("document"), aChannel) {
+      SetEntryType(NS_LITERAL_STRING("navigation"));
+      SetInitiatorType(NS_LITERAL_STRING("navigation"));
+    }
+
+  DOMHighResTimeStamp Duration() const override
+  {
+    return LoadEventEnd() - StartTime();
+  }
+
+  DOMHighResTimeStamp StartTime() const override
+  {
+    return 0;
+  }
+
+  JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  DOMHighResTimeStamp UnloadEventStart();
+  DOMHighResTimeStamp UnloadEventEnd();
+
+  DOMHighResTimeStamp DomInteractive();
+  DOMHighResTimeStamp DomContentLoadedEventStart();
+  DOMHighResTimeStamp DomContentLoadedEventEnd();
+  DOMHighResTimeStamp DomComplete();
+  DOMHighResTimeStamp LoadEventStart();
+  DOMHighResTimeStamp LoadEventEnd() const;
+  NavigationType Type();
+  uint16_t RedirectCount();
+
+private:
+  ~PerformanceNavigationTiming() {}
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PerformanceNavigationTiming_h___
--- a/dom/performance/PerformanceResourceTiming.cpp
+++ b/dom/performance/PerformanceResourceTiming.cpp
@@ -20,24 +20,53 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceResourceTiming)
 NS_INTERFACE_MAP_END_INHERITING(PerformanceEntry)
 
 NS_IMPL_ADDREF_INHERITED(PerformanceResourceTiming, PerformanceEntry)
 NS_IMPL_RELEASE_INHERITED(PerformanceResourceTiming, PerformanceEntry)
 
 PerformanceResourceTiming::PerformanceResourceTiming(PerformanceTiming* aPerformanceTiming,
                                                      Performance* aPerformance,
-                                                     const nsAString& aName)
+                                                     const nsAString& aName,
+                                                     nsIHttpChannel* aChannel)
 : PerformanceEntry(aPerformance->GetParentObject(), aName, NS_LITERAL_STRING("resource")),
   mTiming(aPerformanceTiming),
   mEncodedBodySize(0),
   mTransferSize(0),
   mDecodedBodySize(0)
 {
   MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
+  SetPropertiesFromChannel(aChannel);
+}
+
+void
+PerformanceResourceTiming::SetPropertiesFromChannel(nsIHttpChannel* aChannel)
+{
+  if (!aChannel) {
+    return;
+  }
+
+  nsAutoCString protocol;
+  Unused << aChannel->GetProtocolVersion(protocol);
+  SetNextHopProtocol(NS_ConvertUTF8toUTF16(protocol));
+
+  uint64_t encodedBodySize = 0;
+  Unused << aChannel->GetEncodedBodySize(&encodedBodySize);
+  SetEncodedBodySize(encodedBodySize);
+
+  uint64_t transferSize = 0;
+  Unused << aChannel->GetTransferSize(&transferSize);
+  SetTransferSize(transferSize);
+
+  uint64_t decodedBodySize = 0;
+  Unused << aChannel->GetDecodedBodySize(&decodedBodySize);
+  if (decodedBodySize == 0) {
+    decodedBodySize = encodedBodySize;
+  }
+  SetDecodedBodySize(decodedBodySize);
 }
 
 PerformanceResourceTiming::~PerformanceResourceTiming()
 {
 }
 
 DOMHighResTimeStamp
 PerformanceResourceTiming::StartTime() const
--- a/dom/performance/PerformanceResourceTiming.h
+++ b/dom/performance/PerformanceResourceTiming.h
@@ -13,29 +13,30 @@
 #include "Performance.h"
 #include "PerformanceEntry.h"
 #include "PerformanceTiming.h"
 
 namespace mozilla {
 namespace dom {
 
 // http://www.w3.org/TR/resource-timing/#performanceresourcetiming
-class PerformanceResourceTiming final : public PerformanceEntry
+class PerformanceResourceTiming : public PerformanceEntry
 {
 public:
   typedef mozilla::TimeStamp TimeStamp;
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
       PerformanceResourceTiming,
       PerformanceEntry)
 
   PerformanceResourceTiming(PerformanceTiming* aPerformanceTiming,
                             Performance* aPerformance,
-                            const nsAString& aName);
+                            const nsAString& aName,
+                            nsIHttpChannel* aChannel = nullptr);
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
 
   virtual DOMHighResTimeStamp StartTime() const override;
 
   virtual DOMHighResTimeStamp Duration() const override
   {
@@ -168,16 +169,17 @@ public:
     mDecodedBodySize = aDecodedBodySize;
   }
 
   size_t
   SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
 
 protected:
   virtual ~PerformanceResourceTiming();
+  void SetPropertiesFromChannel(nsIHttpChannel* aChannel);
 
   size_t
   SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
 
   nsString mInitiatorType;
   nsString mNextHopProtocol;
   RefPtr<PerformanceTiming> mTiming;
   uint64_t mEncodedBodySize;
--- a/dom/performance/moz.build
+++ b/dom/performance/moz.build
@@ -8,30 +8,32 @@ with Files("**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 EXPORTS.mozilla.dom += [
     'Performance.h',
     'PerformanceEntry.h',
     'PerformanceMark.h',
     'PerformanceMeasure.h',
     'PerformanceNavigation.h',
+    'PerformanceNavigationTiming.h',
     'PerformanceObserver.h',
     'PerformanceObserverEntryList.h',
     'PerformanceResourceTiming.h',
     'PerformanceService.h',
     'PerformanceTiming.h',
 ]
 
 UNIFIED_SOURCES += [
     'Performance.cpp',
     'PerformanceEntry.cpp',
     'PerformanceMainThread.cpp',
     'PerformanceMark.cpp',
     'PerformanceMeasure.cpp',
     'PerformanceNavigation.cpp',
+    'PerformanceNavigationTiming.cpp',
     'PerformanceObserver.cpp',
     'PerformanceObserverEntryList.cpp',
     'PerformanceResourceTiming.cpp',
     'PerformanceService.cpp',
     'PerformanceTiming.cpp',
     'PerformanceWorker.cpp',
 ]
 
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -729,16 +729,18 @@ var interfaceNamesInGlobalScope =
     "PerformanceEntry",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceMark",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceMeasure",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceNavigation",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "PerformanceNavigationTiming",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceObserver",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceObserverEntryList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceResourceTiming",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceTiming",
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/webidl/PerformanceNavigationTiming.webidl
@@ -0,0 +1,33 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://www.w3.org/TR/navigation-timing-2/#sec-PerformanceNavigationTiming
+ *
+ * Copyright © 2016 W3C® (MIT, ERCIM, Keio, Beihang).
+ * W3C liability, trademark and document use rules apply.
+ */
+
+enum NavigationType {
+  "navigate",
+  "reload",
+  "back_forward",
+  "prerender"
+};
+
+interface PerformanceNavigationTiming : PerformanceResourceTiming {
+  readonly        attribute DOMHighResTimeStamp unloadEventStart;
+  readonly        attribute DOMHighResTimeStamp unloadEventEnd;
+  readonly        attribute DOMHighResTimeStamp domInteractive;
+  readonly        attribute DOMHighResTimeStamp domContentLoadedEventStart;
+  readonly        attribute DOMHighResTimeStamp domContentLoadedEventEnd;
+  readonly        attribute DOMHighResTimeStamp domComplete;
+  readonly        attribute DOMHighResTimeStamp loadEventStart;
+  readonly        attribute DOMHighResTimeStamp loadEventEnd;
+  readonly        attribute NavigationType      type;
+  readonly        attribute unsigned short      redirectCount;
+
+  jsonifier;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -734,16 +734,17 @@ WEBIDL_FILES = [
     'PaymentRequest.webidl',
     'PaymentRequestUpdateEvent.webidl',
     'PaymentResponse.webidl',
     'Performance.webidl',
     'PerformanceEntry.webidl',
     'PerformanceMark.webidl',
     'PerformanceMeasure.webidl',
     'PerformanceNavigation.webidl',
+    'PerformanceNavigationTiming.webidl',
     'PerformanceObserver.webidl',
     'PerformanceObserverEntryList.webidl',
     'PerformanceResourceTiming.webidl',
     'PerformanceTiming.webidl',
     'PeriodicWave.webidl',
     'Permissions.webidl',
     'PermissionStatus.webidl',
     'Plugin.webidl',
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -691,16 +691,17 @@ static inline bool XULElementsRulesInMin
   return // scrollbar parts:
          aTag == nsGkAtoms::scrollbar ||
          aTag == nsGkAtoms::scrollbarbutton ||
          aTag == nsGkAtoms::scrollcorner ||
          aTag == nsGkAtoms::slider ||
          aTag == nsGkAtoms::thumb ||
          aTag == nsGkAtoms::scale ||
          // other
+         aTag == nsGkAtoms::datetimebox ||
          aTag == nsGkAtoms::resizer ||
          aTag == nsGkAtoms::label ||
          aTag == nsGkAtoms::videocontrols;
 }
 
 #ifdef DEBUG
 /**
  * Returns true if aElement is a XUL element created by the video controls
@@ -805,19 +806,17 @@ nsXULElement::BindToTree(nsIDocument* aD
       // for HTML we currently should only pull it in if the document contains
       // an <audio> or <video> element. This assertion is here to make sure
       // that we don't fail to notice if a change to bindings causes us to
       // start pulling in xul.css much more frequently. If this assertion
       // fails then we need to figure out why, and how we can continue to avoid
       // pulling in xul.css.
       // Note that add-ons may introduce bindings that cause this assertion to
       // fire.
-      NS_ASSERTION(IsInVideoControls(this) ||
-                   IsInFeedSubscribeLine(this) ||
-                   IsXULElement(nsGkAtoms::datetimebox),
+      NS_ASSERTION(IsInVideoControls(this) || IsInFeedSubscribeLine(this),
                    "Unexpected XUL element in non-XUL doc");
     }
   }
 
   if (aDocument) {
       NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
                    "Missing a script blocker!");
       // We're in a document now.  Kick off the frame load.
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -496,16 +496,17 @@ bool
 Factory::DoesBackendSupportDataDrawtarget(BackendType aType)
 {
   switch (aType) {
   case BackendType::DIRECT2D:
   case BackendType::DIRECT2D1_1:
   case BackendType::RECORDING:
   case BackendType::NONE:
   case BackendType::BACKEND_LAST:
+  case BackendType::WEBRENDER_TEXT:
     return false;
   case BackendType::CAIRO:
   case BackendType::SKIA:
     return true;
   }
 
   return false;
 }
--- a/gfx/2d/Types.h
+++ b/gfx/2d/Types.h
@@ -136,16 +136,17 @@ enum class DrawTargetType : int8_t {
 
 enum class BackendType : int8_t {
   NONE = 0,
   DIRECT2D, // Used for version independent D2D objects.
   CAIRO,
   SKIA,
   RECORDING,
   DIRECT2D1_1,
+  WEBRENDER_TEXT,
 
   // Add new entries above this line.
   BACKEND_LAST
 };
 
 enum class FontType : int8_t {
   DWRITE,
   GDI,
--- a/gfx/layers/AnimationHelper.h
+++ b/gfx/layers/AnimationHelper.h
@@ -68,18 +68,30 @@ struct AnimatedValue {
   }
 
   ~AnimatedValue() {}
 
 private:
   AnimatedValue() = delete;
 };
 
-// CompositorAnimationStorage stores the layer animations and animated value
-// after sampling based on an unique id (CompositorAnimationsId)
+// CompositorAnimationStorage stores the animations and animated values
+// keyed by a CompositorAnimationsId. The "animations" are a representation of
+// an entire animation over time, while the "animated values" are values sampled
+// from the animations at a particular point in time.
+//
+// There is one CompositorAnimationStorage per CompositorBridgeParent (i.e.
+// one per browser window), and the CompositorAnimationsId key is unique within
+// a particular CompositorAnimationStorage instance.
+//
+// Each layer which has animations gets a CompositorAnimationsId key, and reuses
+// that key during its lifetime. Likewise, in layers-free webrender, a display
+// item that is animated (e.g. nsDisplayTransform) gets a CompositorAnimationsId
+// key and reuses that key (it persists the key via the frame user-data
+// mechanism).
 class CompositorAnimationStorage final
 {
   typedef nsClassHashtable<nsUint64HashKey, AnimatedValue> AnimatedValueTable;
   typedef nsClassHashtable<nsUint64HashKey, AnimationArray> AnimationsTable;
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorAnimationStorage)
 public:
 
@@ -157,69 +169,66 @@ public:
   {
     return mAnimations.Count();
   }
 
   /**
    * Clear AnimatedValues and Animations data
    */
   void Clear();
+  void ClearById(const uint64_t& aId);
 
-  void ClearById(const uint64_t& aId);
 private:
   ~CompositorAnimationStorage() { };
 
 private:
   AnimatedValueTable mAnimatedValues;
   AnimationsTable mAnimations;
 };
 
+/**
+ * This utility class allows reusing code between the webrender and
+ * non-webrender compositor-side implementations. It provides
+ * utility functions for sampling animations at particular timestamps.
+ */
 class AnimationHelper
 {
 public:
 
-
-  /*
-   * TODO Bug 1356509 Once we decouple the compositor animations and layers
-   * in parent side, the API will be called inside SampleAnimations.
-   * Before this, we expose this API for AsyncCompositionManager.
-   *
+  /**
    * Sample animations based on a given time stamp for a element(layer) with
    * its animation data.
    * Returns true if there exists compositor animation, and stores corresponding
    * animated value in |aAnimationValue|.
    */
   static bool
   SampleAnimationForEachNode(TimeStamp aTime,
                              AnimationArray& aAnimations,
                              InfallibleTArray<AnimData>& aAnimationData,
                              StyleAnimationValue& aAnimationValue,
                              bool& aHasInEffectAnimations);
-  /*
-   * TODO Bug 1356509 Once we decouple the compositor animations and layers
-   * in parent side, the API will be called inside SampleAnimations.
-   * Before this, we expose this API for AsyncCompositionManager.
-   *
-   * Populates AnimData stuctures into |aAnimData| based on |aAnimations|
+  /**
+   * Populates AnimData stuctures into |aAnimData| and |aBaseAnimationStyle|
+   * based on |aAnimations|.
    */
   static void
   SetAnimations(AnimationArray& aAnimations,
                 InfallibleTArray<AnimData>& aAnimData,
                 StyleAnimationValue& aBaseAnimationStyle);
 
-  /*
+  /**
    * Get a unique id to represent the compositor animation between child
    * and parent side. This id will be used as a key to store animation
    * data in the CompositorAnimationStorage per compositor.
    * Each layer on the content side calls this when it gets new animation
    * data.
    */
   static uint64_t GetNextCompositorAnimationsId();
 
-  /*
+  /**
    * Sample animation based a given time stamp |aTime| and the animation
    * data inside CompositorAnimationStorage |aStorage|. The animated values
    * after sampling will be stored in CompositorAnimationStorage as well.
    */
   static void
   SampleAnimations(CompositorAnimationStorage* aStorage,
                    TimeStamp aTime);
 };
--- a/gfx/layers/AnimationInfo.cpp
+++ b/gfx/layers/AnimationInfo.cpp
@@ -38,21 +38,16 @@ AnimationInfo::AddAnimation()
   // Here generates a new id when the first animation is added and
   // this id is used to represent the animations in this layer.
   EnsureAnimationsId();
 
   MOZ_ASSERT(!mPendingAnimations, "should have called ClearAnimations first");
 
   Animation* anim = mAnimations.AppendElement();
 
-  if (mManager->AsWebRenderLayerManager()) {
-    mManager->AsWebRenderLayerManager()->
-      KeepCompositorAnimationsIdAlive(mCompositorAnimationsId);
-  }
-
   mMutated = true;
 
   return anim;
 }
 
 Animation*
 AnimationInfo::AddAnimationForNextTransaction()
 {
@@ -68,21 +63,16 @@ void
 AnimationInfo::ClearAnimations()
 {
   mPendingAnimations = nullptr;
 
   if (mAnimations.IsEmpty() && mAnimationData.IsEmpty()) {
     return;
   }
 
-  if (mManager->AsWebRenderLayerManager()) {
-    mManager->AsWebRenderLayerManager()->
-      AddCompositorAnimationsIdForDiscard(mCompositorAnimationsId);
-  }
-
   mAnimations.Clear();
   mAnimationData.Clear();
 
   mMutated = true;
 }
 
 void
 AnimationInfo::ClearAnimationsForNextTransaction()
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
@@ -9,17 +9,16 @@
 
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
 
 namespace mozilla {
 namespace layers {
 
 class CompositorOptions;
-class CompositorAnimationStorage;
 
 /**
  * This class handles layer updates pushed directly from child processes to
  * the compositor thread. It's associated with a CompositorBridgeParent on the
  * compositor thread. While it uses the PCompositorBridge protocol to manage
  * these updates, it doesn't actually drive compositing itself. For that it
  * hands off work to the CompositorBridgeParent it's associated with.
  */
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -104,16 +104,22 @@ WebRenderLayerManager::DoDestroy(bool aI
     // Just clear ImageKeys, they are deleted during WebRenderAPI destruction.
     mImageKeysToDeleteLater.Clear();
     mImageKeysToDelete.Clear();
     // CompositorAnimations are cleared by WebRenderBridgeParent.
     mDiscardedCompositorAnimationsIds.Clear();
     WrBridge()->Destroy(aIsSync);
   }
 
+  // Clear this before calling RemoveUnusedAndResetWebRenderUserData(),
+  // otherwise that function might destroy some WebRenderAnimationData instances
+  // which will put stuff back into mDiscardedCompositorAnimationsIds. If
+  // mActiveCompositorAnimationIds is empty that won't happen.
+  mActiveCompositorAnimationIds.clear();
+
   mLastCanvasDatas.Clear();
   RemoveUnusedAndResetWebRenderUserData();
 
   if (mTransactionIdAllocator) {
     // Make sure to notify the refresh driver just in case it's waiting on a
     // pending transaction. Do this at the top of the event loop so we don't
     // cause a paint to occur during compositor shutdown.
     RefPtr<TransactionIdAllocator> allocator = mTransactionIdAllocator;
@@ -986,25 +992,40 @@ WebRenderLayerManager::DiscardImages()
     resources.DeleteImage(key);
   }
   mImageKeysToDeleteLater.Clear();
   mImageKeysToDelete.Clear();
   WrBridge()->UpdateResources(resources);
 }
 
 void
-WebRenderLayerManager::AddCompositorAnimationsIdForDiscard(uint64_t aId)
+WebRenderLayerManager::AddActiveCompositorAnimationId(uint64_t aId)
 {
-  mDiscardedCompositorAnimationsIds.AppendElement(aId);
+  // In layers-free mode we track the active compositor animation ids on the
+  // client side so that we don't try to discard the same animation id multiple
+  // times. We could just ignore the multiple-discard on the parent side, but
+  // checking on the content side reduces IPC traffic.
+  MOZ_ASSERT(IsLayersFreeTransaction());
+  mActiveCompositorAnimationIds.insert(aId);
 }
 
 void
-WebRenderLayerManager::KeepCompositorAnimationsIdAlive(uint64_t aId)
+WebRenderLayerManager::AddCompositorAnimationsIdForDiscard(uint64_t aId)
 {
-  mDiscardedCompositorAnimationsIds.RemoveElement(aId);
+  if (!IsLayersFreeTransaction()) {
+    // For layers-full we don't track the active animation id in
+    // mActiveCompositorAnimationIds, we just call this on layer destruction and
+    // don't need to worry about discarding the same id multiple times.
+    mDiscardedCompositorAnimationsIds.AppendElement(aId);
+  } else if (mActiveCompositorAnimationIds.erase(aId)) {
+    // For layers-free ensure we don't try to discard an animation id that wasn't
+    // active. We also remove it from mActiveCompositorAnimationIds so we don't
+    // discard it again unless it gets re-activated.
+    mDiscardedCompositorAnimationsIds.AppendElement(aId);
+  }
 }
 
 void
 WebRenderLayerManager::DiscardCompositorAnimations()
 {
   if (WrBridge()->IPCOpen() &&
       !mDiscardedCompositorAnimationsIds.IsEmpty()) {
     WrBridge()->
--- a/gfx/layers/wr/WebRenderLayerManager.h
+++ b/gfx/layers/wr/WebRenderLayerManager.h
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_WEBRENDERLAYERMANAGER_H
 #define GFX_WEBRENDERLAYERMANAGER_H
 
+#include <unordered_set>
 #include <vector>
 
 #include "gfxPrefs.h"
 #include "Layers.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/layers/APZTestData.h"
 #include "mozilla/layers/FocusTarget.h"
 #include "mozilla/layers/StackingContextHelper.h"
@@ -166,22 +167,21 @@ public:
   { return mPaintedLayerCallbackData; }
 
   // adds an imagekey to a list of keys that will be discarded on the next
   // transaction or destruction
   void AddImageKeyForDiscard(wr::ImageKey);
   void DiscardImages();
   void DiscardLocalImages();
 
-  // Before destroying a layer with animations, add its compositorAnimationsId
-  // to a list of ids that will be discarded on the next transaction
+  // Methods to manage the compositor animation ids. Active animations are still
+  // going, and when they end we discard them and remove them from the active
+  // list.
+  void AddActiveCompositorAnimationId(uint64_t aId);
   void AddCompositorAnimationsIdForDiscard(uint64_t aId);
-  // If the animations are valid and running on the compositor,
-  // we should keep the compositorAnimationsId alive on the compositor side.
-  void KeepCompositorAnimationsIdAlive(uint64_t aId);
   void DiscardCompositorAnimations();
 
   WebRenderBridgeChild* WrBridge() const { return mWrChild; }
 
   virtual void Mutated(Layer* aLayer) override;
   virtual void MutatedSimple(Layer* aLayer) override;
 
   void Hold(Layer* aLayer);
@@ -293,16 +293,22 @@ private:
 private:
   nsIWidget* MOZ_NON_OWNING_REF mWidget;
   nsTArray<wr::ImageKey> mImageKeysToDelete;
   // TODO - This is needed because we have some code that creates image keys
   // and enqueues them for deletion right away which is bad not only because
   // of poor texture cache usage, but also because images end up deleted before
   // they are used. This should hopfully be temporary.
   nsTArray<wr::ImageKey> mImageKeysToDeleteLater;
+
+  // Set of compositor animation ids for which there are active animations (as
+  // of the last transaction) on the compositor side.
+  std::unordered_set<uint64_t> mActiveCompositorAnimationIds;
+  // Compositor animation ids for animations that are done now and that we want
+  // the compositor to discard information for.
   nsTArray<uint64_t> mDiscardedCompositorAnimationsIds;
 
   /* PaintedLayer callbacks; valid at the end of a transaciton,
    * while rendering */
   DrawPaintedLayerCallback mPaintedLayerCallback;
   void *mPaintedLayerCallbackData;
 
   RefPtr<WebRenderBridgeChild> mWrChild;
--- a/gfx/layers/wr/WebRenderUserData.cpp
+++ b/gfx/layers/wr/WebRenderUserData.cpp
@@ -215,16 +215,28 @@ WebRenderFallbackData::SetGeometry(nsAut
 
 WebRenderAnimationData::WebRenderAnimationData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem,
                                                WebRenderUserDataRefTable* aTable)
   : WebRenderUserData(aWRManager, aItem, aTable)
   , mAnimationInfo(aWRManager)
 {
 }
 
+WebRenderAnimationData::~WebRenderAnimationData()
+{
+  // It may be the case that nsDisplayItem that created this WebRenderUserData
+  // gets destroyed without getting a chance to discard the compositor animation
+  // id, so we should do it as part of cleanup here.
+  uint64_t animationId = mAnimationInfo.GetCompositorAnimationsId();
+  // animationId might be 0 if mAnimationInfo never held any active animations.
+  if (animationId) {
+    mWRManager->AddCompositorAnimationsIdForDiscard(animationId);
+  }
+}
+
 WebRenderCanvasData::WebRenderCanvasData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem,
                                          WebRenderUserDataRefTable* aTable)
   : WebRenderUserData(aWRManager, aItem, aTable)
 {
 }
 
 WebRenderCanvasData::~WebRenderCanvasData()
 {
--- a/gfx/layers/wr/WebRenderUserData.h
+++ b/gfx/layers/wr/WebRenderUserData.h
@@ -135,17 +135,17 @@ protected:
   bool mInvalid;
 };
 
 class WebRenderAnimationData : public WebRenderUserData
 {
 public:
   explicit WebRenderAnimationData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem,
                                   WebRenderUserDataRefTable* aTable);
-  virtual ~WebRenderAnimationData() {}
+  virtual ~WebRenderAnimationData();
 
   virtual UserDataType GetType() override { return UserDataType::eAnimation; }
   static UserDataType Type() { return UserDataType::eAnimation; }
   AnimationInfo& GetAnimationInfo() { return mAnimationInfo; }
 
 protected:
   AnimationInfo mAnimationInfo;
 };
--- a/gfx/thebes/gfxContext.cpp
+++ b/gfx/thebes/gfxContext.cpp
@@ -18,16 +18,17 @@
 #include "gfxPattern.h"
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "GeckoProfiler.h"
 #include "gfx2DGlue.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/gfx/DrawTargetTiled.h"
 #include <algorithm>
+#include "TextDrawTarget.h"
 
 #if XP_WIN
 #include "gfxWindowsPlatform.h"
 #include "mozilla/gfx/DeviceManagerDx.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::gfx;
@@ -119,16 +120,25 @@ gfxContext::~gfxContext()
 {
   for (int i = mStateStack.Length() - 1; i >= 0; i--) {
     for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
       mStateStack[i].drawTarget->PopClip();
     }
   }
 }
 
+mozilla::layout::TextDrawTarget*
+gfxContext::GetTextDrawer()
+{
+  if (mDT->GetBackendType() == BackendType::WEBRENDER_TEXT) {
+    return static_cast<mozilla::layout::TextDrawTarget*>(&*mDT);
+  }
+  return nullptr;
+}
+
 void
 gfxContext::Save()
 {
   CurrentState().transform = mTransform;
   mStateStack.AppendElement(AzureState(CurrentState()));
   CurrentState().pushedClips.Clear();
 #ifdef DEBUG
   CurrentState().mContentChanged = false;
--- a/gfx/thebes/gfxContext.h
+++ b/gfx/thebes/gfxContext.h
@@ -19,16 +19,19 @@
 
 typedef struct _cairo cairo_t;
 class GlyphBufferAzure;
 
 namespace mozilla {
 namespace gfx {
 struct RectCornerRadii;
 } // namespace gfx
+namespace layout {
+class TextDrawTarget;
+} // namespace layout
 } // namespace mozilla
 
 class ClipExporter;
 
 /**
  * This is the main class for doing actual drawing. It is initialized using
  * a surface and can be drawn on. It manages various state information like
  * a current transformation matrix (CTM), a current path, current color,
@@ -75,16 +78,21 @@ public:
      * is responsible for handling this scenario as appropriate.
      */
     static already_AddRefed<gfxContext>
         CreatePreservingTransformOrNull(mozilla::gfx::DrawTarget* aTarget);
 
     mozilla::gfx::DrawTarget *GetDrawTarget() { return mDT; }
 
     /**
+     * Returns the DrawTarget if it's actually a TextDrawTarget.
+     */
+    mozilla::layout::TextDrawTarget* GetTextDrawer();
+
+    /**
      ** State
      **/
     // XXX document exactly what bits are saved
     void Save();
     void Restore();
 
     /**
      ** Paths & Drawing
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -35,16 +35,17 @@
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 #include "gfxMathTable.h"
 #include "gfxSVGGlyphs.h"
 #include "gfx2DGlue.h"
+#include "TextDrawTarget.h"
 
 #include "GreekCasing.h"
 
 #include "cairo.h"
 
 #include "harfbuzz/hb.h"
 #include "harfbuzz/hb-ot.h"
 
@@ -1917,16 +1918,20 @@ gfxFont::DrawGlyphs(const gfxShapedText 
             if (glyphCount > 0) {
                 const gfxShapedText::DetailedGlyph *details =
                     aShapedText->GetDetailedGlyphs(aOffset + i);
                 NS_ASSERTION(details, "detailedGlyph should not be missing!");
                 for (uint32_t j = 0; j < glyphCount; ++j, ++details) {
                     double advance = details->mAdvance;
 
                     if (glyphData->IsMissing()) {
+                        if (auto* textDrawer = aRunParams.context->GetTextDrawer()) {
+                            textDrawer->FoundUnsupportedFeature();
+                            return false;
+                        }
                         // Default-ignorable chars will have zero advance width;
                         // we don't have to draw the hexbox for them.
                         if (aRunParams.drawMode != DrawMode::GLYPH_PATH &&
                             advance > 0) {
                             double glyphX = aPt->x;
                             double glyphY = aPt->y;
                             if (aRunParams.isRTL) {
                                 if (aFontParams.isVerticalFont) {
@@ -2001,17 +2006,16 @@ gfxFont::DrawGlyphs(const gfxShapedText 
 void
 gfxFont::DrawEmphasisMarks(const gfxTextRun* aShapedText, gfxPoint* aPt,
                            uint32_t aOffset, uint32_t aCount,
                            const EmphasisMarkDrawParams& aParams)
 {
     gfxFloat& inlineCoord = aParams.isVertical ? aPt->y : aPt->x;
     gfxTextRun::Range markRange(aParams.mark);
     gfxTextRun::DrawParams params(aParams.context);
-    params.textDrawer = aParams.textDrawer;
 
     gfxFloat clusterStart = -std::numeric_limits<gfxFloat>::infinity();
     bool shouldDrawEmphasisMark = false;
     for (uint32_t i = 0, idx = aOffset; i < aCount; ++i, ++idx) {
         if (aParams.spacing) {
             inlineCoord += aParams.direction * aParams.spacing[i].mBefore;
         }
         if (aShapedText->IsClusterStart(idx) ||
@@ -2057,17 +2061,24 @@ gfxFont::Draw(const gfxTextRun *aTextRun
         fontParams.drawOptions = *aRunParams.drawOpts;
     }
 
     fontParams.scaledFont = GetScaledFont(aRunParams.dt);
     if (!fontParams.scaledFont) {
         return;
     }
 
+    auto* textDrawer = aRunParams.context->GetTextDrawer();
     fontParams.haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this);
+
+    if (fontParams.haveSVGGlyphs && textDrawer) {
+        textDrawer->FoundUnsupportedFeature();
+        return;
+    }
+
     fontParams.haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs();
     fontParams.contextPaint = aRunParams.runContextPaint;
     fontParams.isVerticalFont =
         aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
 
     bool sideways = false;
     gfxContextMatrixAutoSaveRestore matrixRestore;
 
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -70,19 +70,16 @@ class gfxMathTable;
 
 struct gfxTextRunDrawCallbacks;
 
 namespace mozilla {
 class SVGContextPaint;
 namespace gfx {
 class GlyphRenderingOptions;
 } // namespace gfx
-namespace layout {
-class TextDrawTarget;
-} // namespace layout
 } // namespace mozilla
 
 struct gfxFontStyle {
     gfxFontStyle();
     gfxFontStyle(uint8_t aStyle, uint16_t aWeight, int16_t aStretch,
                  gfxFloat aSize, nsIAtom *aLanguage, bool aExplicitLanguage,
                  float aSizeAdjust, bool aSystemFont,
                  bool aPrinterFont,
@@ -2308,17 +2305,16 @@ struct MOZ_STACK_CLASS FontDrawParams {
     mozilla::gfx::DrawOptions drawOptions;
     bool                      isVerticalFont;
     bool                      haveSVGGlyphs;
     bool                      haveColorGlyphs;
 };
 
 struct MOZ_STACK_CLASS EmphasisMarkDrawParams {
     gfxContext* context;
-    mozilla::layout::TextDrawTarget* textDrawer = nullptr;
     gfxFont::Spacing* spacing;
     gfxTextRun* mark;
     gfxFloat advance;
     gfxFloat direction;
     bool isVertical;
 };
 
 #endif
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -2536,16 +2536,23 @@ gfxPlatform::InitWebRenderConfig()
         "ANGLE is disabled",
         NS_LITERAL_CSTRING("FEATURE_FAILURE_ANGLE_DISABLED"));
     } else {
       gfxVars::SetUseWebRenderANGLE(gfxConfig::IsEnabled(Feature::WEBRENDER));
     }
   }
 #endif
 
+#ifdef MOZ_WIDGET_ANDROID
+  featureWebRender.ForceDisable(
+    FeatureStatus::Unavailable,
+    "WebRender not ready for use on Android",
+    NS_LITERAL_CSTRING("FEATURE_FAILURE_ANDROID"));
+#endif
+
   // gfxFeature is not usable in the GPU process, so we use gfxVars to transmit this feature
   if (gfxConfig::IsEnabled(Feature::WEBRENDER)) {
     gfxVars::SetUseWebRender(true);
     reporter.SetSuccessful();
 
     if (XRE_IsParentProcess()) {
       Preferences::RegisterPrefixCallbackAndCall(WebRenderDebugPrefChangeCallback,
                                                  WR_DEBUG_PREF);
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -111,16 +111,18 @@ GetBackendName(mozilla::gfx::BackendType
       case mozilla::gfx::BackendType::CAIRO:
         return "cairo";
       case mozilla::gfx::BackendType::SKIA:
         return "skia";
       case mozilla::gfx::BackendType::RECORDING:
         return "recording";
       case mozilla::gfx::BackendType::DIRECT2D1_1:
         return "direct2d 1.1";
+      case mozilla::gfx::BackendType::WEBRENDER_TEXT:
+        return "webrender text";
       case mozilla::gfx::BackendType::NONE:
         return "none";
       case mozilla::gfx::BackendType::BACKEND_LAST:
         return "invalid";
   }
   MOZ_CRASH("Incomplete switch");
 }
 
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -602,17 +602,17 @@ gfxTextRun::Draw(Range aRange, gfxPoint 
                  "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH");
     NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || !aParams.callbacks,
                  "callback must not be specified unless using GLYPH_PATH");
 
     bool skipDrawing = mSkipDrawing;
     if (aParams.drawMode & DrawMode::GLYPH_FILL) {
         Color currentColor;
         if (aParams.context->GetDeviceColor(currentColor) &&
-            currentColor.a == 0 && !aParams.textDrawer) {
+            currentColor.a == 0 && !aParams.context->GetTextDrawer()) {
             skipDrawing = true;
         }
     }
 
     gfxFloat direction = GetDirection();
 
     if (skipDrawing) {
         // We don't need to draw anything;
@@ -720,26 +720,24 @@ gfxTextRun::Draw(Range aRange, gfxPoint 
     if (aParams.advanceWidth) {
         *aParams.advanceWidth = advance;
     }
 }
 
 // This method is mostly parallel to Draw().
 void
 gfxTextRun::DrawEmphasisMarks(gfxContext *aContext,
-                              mozilla::layout::TextDrawTarget* aTextDrawer,
                               gfxTextRun* aMark,
                               gfxFloat aMarkAdvance, gfxPoint aPt,
                               Range aRange, PropertyProvider* aProvider) const
 {
     MOZ_ASSERT(aRange.end <= GetLength());
 
     EmphasisMarkDrawParams params;
     params.context = aContext;
-    params.textDrawer = aTextDrawer;
     params.mark = aMark;
     params.advance = aMarkAdvance;
     params.direction = GetDirection();
     params.isVertical = IsVertical();
 
     gfxFloat& inlineCoord = params.isVertical ? aPt.y : aPt.x;
     gfxFloat direction = params.direction;
 
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -37,19 +37,16 @@ class gfxUserFontEntry;
 class gfxUserFontSet;
 class nsIAtom;
 class nsLanguageAtomService;
 class gfxMissingFontRecorder;
 
 namespace mozilla {
 class SVGContextPaint;
 enum class StyleHyphens : uint8_t;
-namespace layout {
-class TextDrawTarget;
-};
 };
 
 /**
  * Callback for Draw() to use when drawing text with mode
  * DrawMode::GLYPH_PATH.
  */
 struct gfxTextRunDrawCallbacks {
 
@@ -242,17 +239,16 @@ public:
         // Return the appUnitsPerDevUnit value to be used when measuring.
         // Only called if the hyphen width is requested.
         virtual uint32_t GetAppUnitsPerDevUnit() const = 0;
     };
 
     struct MOZ_STACK_CLASS DrawParams
     {
         gfxContext* context;
-        mozilla::layout::TextDrawTarget* textDrawer = nullptr;
         DrawMode drawMode = DrawMode::GLYPH_FILL;
         nscolor textStrokeColor = 0;
         gfxPattern* textStrokePattern = nullptr;
         const mozilla::gfx::StrokeOptions *strokeOpts = nullptr;
         const mozilla::gfx::DrawOptions *drawOpts = nullptr;
         PropertyProvider* provider = nullptr;
         // If non-null, the advance width of the substring is set.
         gfxFloat* advanceWidth = nullptr;
@@ -284,17 +280,16 @@ public:
     void Draw(Range aRange, gfxPoint aPt, const DrawParams& aParams) const;
 
     /**
      * Draws the emphasis marks for this text run. Uses only GetSpacing
      * from aProvider. The provided point is the baseline origin of the
      * line of emphasis marks.
      */
     void DrawEmphasisMarks(gfxContext* aContext,
-                           mozilla::layout::TextDrawTarget* aTextDrawer,
                            gfxTextRun* aMark,
                            gfxFloat aMarkAdvance, gfxPoint aPt,
                            Range aRange, PropertyProvider* aProvider) const;
 
     /**
      * Computes the ReflowMetrics for a substring.
      * Uses GetSpacing from aBreakProvider.
      * @param aBoundingBoxType which kind of bounding box (loose/tight)
--- a/js/xpconnect/idl/nsIXPConnect.idl
+++ b/js/xpconnect/idl/nsIXPConnect.idl
@@ -306,25 +306,16 @@ interface nsIXPConnect : nsISupports
     */
     JSObjectPtr
     wrapNative(in JSContextPtr aJSContext,
                in JSObjectPtr  aScope,
                in nsISupports  aCOMObj,
                in nsIIDRef     aIID);
 
     /**
-     * Same as wrapNative, but it returns the JSObject in an nsIXPConnectJSObjectHolder.
-     */
-    nsIXPConnectJSObjectHolder
-    wrapNativeHolder(in JSContextPtr aJSContext,
-                     in JSObjectPtr  aScope,
-                     in nsISupports  aCOMObj,
-                     in nsIIDRef     aIID);
-
-    /**
      * Same as wrapNative, but it returns the JSObject in aVal. C++ callers
      * must ensure that aVal is rooted.
      * aIID may be null, it means the same as passing in
      * &NS_GET_IID(nsISupports) but when passing in null certain shortcuts
      * can be taken because we know without comparing IIDs that the caller is
      * asking for an nsISupports wrapper.
      * If aAllowWrapper, then the returned value will be wrapped in the proper
      * type of security wrapper on top of the XPCWrappedNative (if needed).
--- a/js/xpconnect/loader/mozJSComponentLoader.h
+++ b/js/xpconnect/loader/mozJSComponentLoader.h
@@ -18,18 +18,16 @@
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "jsapi.h"
 
 #include "xpcIJSGetFactory.h"
 #include "xpcpublic.h"
 
 class nsIFile;
-class nsIPrincipal;
-class nsIXPConnectJSObjectHolder;
 class ComponentLoaderInfo;
 
 namespace mozilla {
     class ScriptPreloader;
 } // namespace mozilla
 
 
 /* 6bd13476-1dd2-11b2-bbef-f0ccb5fa64b6 (thanks, mozbot) */
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -337,17 +337,17 @@ XPCConvert::NativeData2JS(MutableHandleV
             if (!variant)
                 return false;
 
             return XPCVariant::VariantDataToJS(variant,
                                                pErr, d);
         }
 
         xpcObjectHelper helper(iface);
-        return NativeInterface2JSObject(d, nullptr, helper, iid, true, pErr);
+        return NativeInterface2JSObject(d, helper, iid, true, pErr);
     }
 
     default:
         NS_ERROR("bad type");
         return false;
     }
     return true;
 }
@@ -709,48 +709,29 @@ XPCConvert::JSData2Native(void* d, Handl
     }
     default:
         NS_ERROR("bad type");
         return false;
     }
     return true;
 }
 
-static inline bool
-CreateHolderIfNeeded(JSContext* cx, HandleObject obj, MutableHandleValue d,
-                     nsIXPConnectJSObjectHolder** dest)
-{
-    if (dest) {
-        if (!obj)
-            return false;
-        RefPtr<XPCJSObjectHolder> objHolder = new XPCJSObjectHolder(cx, obj);
-        objHolder.forget(dest);
-    }
-
-    d.setObjectOrNull(obj);
-
-    return true;
-}
-
 /***************************************************************************/
 // static
 bool
 XPCConvert::NativeInterface2JSObject(MutableHandleValue d,
-                                     nsIXPConnectJSObjectHolder** dest,
                                      xpcObjectHelper& aHelper,
                                      const nsID* iid,
                                      bool allowNativeWrapper,
                                      nsresult* pErr)
 {
     if (!iid)
         iid = &NS_GET_IID(nsISupports);
 
     d.setNull();
-    if (dest)
-        *dest = nullptr;
     if (!aHelper.Object())
         return true;
     if (pErr)
         *pErr = NS_ERROR_XPC_BAD_CONVERT_NATIVE;
 
     // We used to have code here that unwrapped and simply exposed the
     // underlying JSObject. That caused anomolies when JSComponents were
     // accessed from other JS code - they didn't act like other xpconnect
@@ -777,29 +758,31 @@ XPCConvert::NativeInterface2JSObject(Mut
         js::AssertSameCompartment(cx, global);
         flat = cache->WrapObject(cx, nullptr);
         if (!flat)
             return false;
     }
     if (flat) {
         if (allowNativeWrapper && !JS_WrapObject(cx, &flat))
             return false;
-        return CreateHolderIfNeeded(cx, flat, d, dest);
+        d.setObjectOrNull(flat);
+        return true;
     }
 
     if (iid->Equals(NS_GET_IID(nsISupports))) {
         // Check for a Promise being returned via nsISupports.  In that
         // situation, we want to dig out its underlying JS object and return
         // that.
         RefPtr<Promise> promise = do_QueryObject(aHelper.Object());
         if (promise) {
             flat = promise->PromiseObj();
             if (!JS_WrapObject(cx, &flat))
                 return false;
-            return CreateHolderIfNeeded(cx, flat, d, dest);
+            d.setObjectOrNull(flat);
+            return true;
         }
     }
 
     // Don't double wrap CPOWs. This is a temporary measure for compatibility
     // with objects that don't provide necessary QIs (such as objects under
     // the new DOM bindings). We expect the other side of the CPOW to have
     // the appropriate wrappers in place.
     RootedObject cpow(cx, UnwrapNativeCPOW(aHelper.Object()));
@@ -826,43 +809,29 @@ XPCConvert::NativeInterface2JSObject(Mut
     if (NS_FAILED(rv) || !wrapper)
         return false;
 
     // If we're not creating security wrappers, we can return the
     // XPCWrappedNative as-is here.
     flat = wrapper->GetFlatJSObject();
     if (!allowNativeWrapper) {
         d.setObjectOrNull(flat);
-        if (dest)
-            wrapper.forget(dest);
         if (pErr)
             *pErr = NS_OK;
         return true;
     }
 
     // The call to wrap here handles both cross-compartment and same-compartment
     // security wrappers.
     RootedObject original(cx, flat);
     if (!JS_WrapObject(cx, &flat))
         return false;
 
     d.setObjectOrNull(flat);
 
-    if (dest) {
-        // The wrapper still holds the original flat object.
-        if (flat == original) {
-            wrapper.forget(dest);
-        } else {
-            if (!flat)
-                return false;
-            RefPtr<XPCJSObjectHolder> objHolder = new XPCJSObjectHolder(cx, flat);
-            objHolder.forget(dest);
-        }
-    }
-
     if (pErr)
         *pErr = NS_OK;
 
     return true;
 }
 
 /***************************************************************************/
 
--- a/js/xpconnect/src/XPCForwards.h
+++ b/js/xpconnect/src/XPCForwards.h
@@ -26,17 +26,16 @@ class XPCNativeMember;
 class XPCNativeInterface;
 class XPCNativeSet;
 
 class XPCWrappedNative;
 class XPCWrappedNativeProto;
 class XPCWrappedNativeTearOff;
 
 class XPCTraceableVariant;
-class XPCJSObjectHolder;
 
 class JSObject2WrappedJSMap;
 class Native2WrappedNativeMap;
 class IID2WrappedJSClassMap;
 class IID2NativeInterfaceMap;
 class ClassInfo2NativeSetMap;
 class ClassInfo2WrappedNativeProtoMap;
 class NativeSetMap;
--- a/js/xpconnect/src/XPCWrappedJSClass.cpp
+++ b/js/xpconnect/src/XPCWrappedJSClass.cpp
@@ -1160,18 +1160,17 @@ nsXPCWrappedJSClass::CallMethod(nsXPCWra
                                                         getter_AddRefs(newThis)))) {
                                 goto pre_call_clean_up;
                             }
                             if (newThis) {
                                 RootedValue v(cx);
                                 xpcObjectHelper helper(newThis);
                                 bool ok =
                                   XPCConvert::NativeInterface2JSObject(
-                                      &v, nullptr, helper, nullptr,
-                                      false, nullptr);
+                                      &v, helper, nullptr, false, nullptr);
                                 if (!ok) {
                                     goto pre_call_clean_up;
                                 }
                                 thisObj = v.toObjectOrNull();
                                 if (!JS_WrapObject(cx, &thisObj))
                                     goto pre_call_clean_up;
                             }
                         }
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -2223,23 +2223,8 @@ static void DEBUG_CheckClassInfoClaims(X
 
         if (className)
             free(className);
         if (contractID)
             free(contractID);
     }
 }
 #endif
-
-NS_IMPL_ISUPPORTS(XPCJSObjectHolder, nsIXPConnectJSObjectHolder)
-
-JSObject*
-XPCJSObjectHolder::GetJSObject()
-{
-    NS_PRECONDITION(mJSObj, "bad object state");
-    return mJSObj;
-}
-
-XPCJSObjectHolder::XPCJSObjectHolder(JSContext* cx, JSObject* obj)
-  : mJSObj(cx, obj)
-{
-    MOZ_ASSERT(obj);
-}
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -195,19 +195,18 @@ XPCWrappedNativeScope::GetComponentsJSOb
         nsIPrincipal* p = GetPrincipal();
         bool system = nsXPConnect::SecurityManager()->IsSystemPrincipal(p);
         mComponents = system ? new nsXPCComponents(this)
                              : new nsXPCComponentsBase(this);
     }
 
     RootedValue val(cx);
     xpcObjectHelper helper(mComponents);
-    bool ok = XPCConvert::NativeInterface2JSObject(&val, nullptr, helper,
-                                                   nullptr, false,
-                                                   nullptr);
+    bool ok = XPCConvert::NativeInterface2JSObject(&val, helper, nullptr,
+                                                   false, nullptr);
     if (NS_WARN_IF(!ok))
         return false;
 
     if (NS_WARN_IF(!val.isObject()))
         return false;
 
     // The call to wrap() here is necessary even though the object is same-
     // compartment, because it applies our security wrapper.
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -602,26 +602,24 @@ InitClassesWithNewWrappedGlobal(JSContex
 } // namespace xpc
 
 static nsresult
 NativeInterface2JSObject(HandleObject aScope,
                          nsISupports* aCOMObj,
                          nsWrapperCache* aCache,
                          const nsIID * aIID,
                          bool aAllowWrapping,
-                         MutableHandleValue aVal,
-                         nsIXPConnectJSObjectHolder** aHolder)
+                         MutableHandleValue aVal)
 {
     AutoJSContext cx;
     JSAutoCompartment ac(cx, aScope);
 
     nsresult rv;
     xpcObjectHelper helper(aCOMObj, aCache);
-    if (!XPCConvert::NativeInterface2JSObject(aVal, aHolder, helper, aIID,
-                                              aAllowWrapping, &rv))
+    if (!XPCConvert::NativeInterface2JSObject(aVal, helper, aIID, aAllowWrapping, &rv))
         return rv;
 
     MOZ_ASSERT(aAllowWrapping || !xpc::WrapperFactory::IsXrayWrapper(&aVal.toObject()),
                "Shouldn't be returning a xray wrapper here");
 
     return NS_OK;
 }
 
@@ -634,61 +632,43 @@ nsXPConnect::WrapNative(JSContext * aJSC
 {
     MOZ_ASSERT(aJSContext, "bad param");
     MOZ_ASSERT(aScopeArg, "bad param");
     MOZ_ASSERT(aCOMObj, "bad param");
 
     RootedObject aScope(aJSContext, aScopeArg);
     RootedValue v(aJSContext);
     nsresult rv = NativeInterface2JSObject(aScope, aCOMObj, nullptr, &aIID,
-                                           true, &v, nullptr);
+                                           true, &v);
     if (NS_FAILED(rv))
         return rv;
 
     if (!v.isObjectOrNull())
         return NS_ERROR_FAILURE;
 
     *aRetVal = v.toObjectOrNull();
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsXPConnect::WrapNativeHolder(JSContext * aJSContext,
-                              JSObject * aScopeArg,
-                              nsISupports* aCOMObj,
-                              const nsIID & aIID,
-                              nsIXPConnectJSObjectHolder **aHolder)
-{
-    MOZ_ASSERT(aHolder, "bad param");
-    MOZ_ASSERT(aJSContext, "bad param");
-    MOZ_ASSERT(aScopeArg, "bad param");
-    MOZ_ASSERT(aCOMObj, "bad param");
-
-    RootedObject aScope(aJSContext, aScopeArg);
-    RootedValue v(aJSContext);
-    return NativeInterface2JSObject(aScope, aCOMObj, nullptr, &aIID,
-                                    true, &v, aHolder);
-}
-
-NS_IMETHODIMP
 nsXPConnect::WrapNativeToJSVal(JSContext* aJSContext,
                                JSObject* aScopeArg,
                                nsISupports* aCOMObj,
                                nsWrapperCache* aCache,
                                const nsIID* aIID,
                                bool aAllowWrapping,
                                MutableHandleValue aVal)
 {
     MOZ_ASSERT(aJSContext, "bad param");
     MOZ_ASSERT(aScopeArg, "bad param");
     MOZ_ASSERT(aCOMObj, "bad param");
 
     RootedObject aScope(aJSContext, aScopeArg);
     return NativeInterface2JSObject(aScope, aCOMObj, aCache, aIID,
-                                    aAllowWrapping, aVal, nullptr);
+                                    aAllowWrapping, aVal);
 }
 
 NS_IMETHODIMP
 nsXPConnect::WrapJS(JSContext * aJSContext,
                     JSObject * aJSObjArg,
                     const nsIID & aIID,
                     void * *result)
 {
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -2029,36 +2029,16 @@ private:
 
     JS::Heap<JSObject*> mJSObj;
     RefPtr<nsXPCWrappedJSClass> mClass;
     nsXPCWrappedJS* mRoot;    // If mRoot != this, it is an owning pointer.
     nsXPCWrappedJS* mNext;
     nsCOMPtr<nsISupports> mOuter;    // only set in root
 };
 
-/***************************************************************************/
-
-class XPCJSObjectHolder final : public nsIXPConnectJSObjectHolder
-{
-public:
-    // all the interface method declarations...
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIXPCONNECTJSOBJECTHOLDER
-
-    // non-interface implementation
-
-public:
-    XPCJSObjectHolder(JSContext* cx, JSObject* obj);
-
-private:
-    virtual ~XPCJSObjectHolder() {}
-    XPCJSObjectHolder() = delete;
-
-    JS::PersistentRooted<JSObject*> mJSObj;
-};
 
 /***************************************************************************
 ****************************************************************************
 *
 * All manner of utility classes follow...
 *
 ****************************************************************************
 ***************************************************************************/
@@ -2115,18 +2095,17 @@ public:
      * @param cache the wrapper cache for src (may be null, in which case src
      *              will be QI'ed to get the cache)
      * @param allowNativeWrapper if true, this method may wrap the resulting
      *        JSObject in an XPCNativeWrapper and return that, as needed.
      * @param pErr [out] relevant error code, if any.
      * @param src_is_identity optional performance hint. Set to true only
      *                        if src is the identity pointer.
      */
-    static bool NativeInterface2JSObject(JS::MutableHandleValue d,
-                                         nsIXPConnectJSObjectHolder** dest,
+    static bool NativeInterface2JSObject(JS::MutableHandleValue dest,
                                          xpcObjectHelper& aHelper,
                                          const nsID* iid,
                                          bool allowNativeWrapper,
                                          nsresult* pErr);
 
     static bool GetNativeInterfaceFromJSObject(void** dest, JSObject* src,
                                                const nsID* iid,
                                                nsresult* pErr);
--- a/layout/base/GeckoRestyleManager.cpp
+++ b/layout/base/GeckoRestyleManager.cpp
@@ -3646,16 +3646,22 @@ GeckoRestyleManager::ComputeAndProcessSt
                     swappedStructOwners);
   r.RestyleChildrenOfDisplayContentsElement(frame, aNewContext, aMinChange,
                                             aRestyleTracker,
                                             aRestyleHint, aRestyleHintData);
   ProcessRestyledFrames(changeList);
   ClearCachedInheritedStyleDataOnDescendants(contextsToClear);
 }
 
+bool
+GeckoRestyleManager::HasPendingRestyles() const
+{
+  return mPendingRestyles.Count() != 0;
+}
+
 nsStyleSet*
 ElementRestyler::StyleSet() const
 {
   MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
              "ElementRestyler should only be used with a Gecko-flavored "
              "style backend");
   return mPresContext->StyleSet()->AsGecko();
 }
--- a/layout/base/GeckoRestyleManager.h
+++ b/layout/base/GeckoRestyleManager.h
@@ -340,16 +340,17 @@ public:
   // environment variable.
   static uint32_t StructsToLog();
 
   static nsCString StructNamesToString(uint32_t aSIDs);
   int32_t& LoggingDepth() { return mLoggingDepth; }
 #endif
 
   bool IsProcessingRestyles() { return mIsProcessingRestyles; }
+  bool HasPendingRestyles() const;
 
 private:
   inline nsStyleSet* StyleSet() const {
     MOZ_ASSERT(PresContext()->StyleSet()->IsGecko(),
                "GeckoRestyleManager should only be used with a Gecko-flavored "
                "style backend");
     return PresContext()->StyleSet()->AsGecko();
   }
--- a/layout/base/RestyleTracker.cpp
+++ b/layout/base/RestyleTracker.cpp
@@ -202,16 +202,20 @@ RestyleTracker::DoProcessRestyles()
           RestyleData* data;
 #ifdef DEBUG
           bool found =
 #endif
             mPendingRestyles.Get(element, &data);
           NS_ASSERTION(found, "Where did our entry go?");
           data->mRestyleHint =
             nsRestyleHint(data->mRestyleHint & ~eRestyle_LaterSiblings);
+
+          if (Element* parent = element->GetFlattenedTreeParentElement()) {
+            parent->UnsetFlags(ELEMENT_HAS_CHILD_WITH_LATER_SIBLINGS_HINT);
+          }
         }
 
         LOG_RESTYLE("%d pending restyles after expanding out "
                     "eRestyle_LaterSiblings", mPendingRestyles.Count());
 
         mHaveLaterSiblingRestyles = false;
       }
 
--- a/layout/base/RestyleTracker.h
+++ b/layout/base/RestyleTracker.h
@@ -351,16 +351,25 @@ RestyleTracker::AddPendingRestyle(Elemen
       // PostRecreateFramesFor, so we need to track it here.
       MOZ_ASSERT(curData, "expected to find a RestyleData for cur");
       if (curData) {
         curData->mDescendants.AppendElement(aElement);
       }
     }
   }
 
+  // If we need to restyle later siblings, we will need a flag on parent to note
+  // that some children need restyle for nsComputedDOMStyle.
+  if (aRestyleHint & eRestyle_LaterSiblings) {
+    nsIContent* parent = aElement->GetFlattenedTreeParent();
+    if (parent && parent->IsElement()) {
+      parent->SetFlags(ELEMENT_HAS_CHILD_WITH_LATER_SIBLINGS_HINT);
+    }
+  }
+
   mHaveLaterSiblingRestyles =
     mHaveLaterSiblingRestyles || (aRestyleHint & eRestyle_LaterSiblings) != 0;
   return hadRestyleLaterSiblings;
 }
 
 } // namespace mozilla
 
 #endif /* mozilla_RestyleTracker_h */
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -1191,20 +1191,25 @@ void
 ServoRestyleManager::ProcessPendingRestyles()
 {
   DoProcessPendingRestyles(ServoTraversalFlags::Empty);
 }
 
 void
 ServoRestyleManager::ProcessAllPendingAttributeAndStateInvalidations()
 {
-  AutoTimelineMarker marker(mPresContext->GetDocShell(),
-                            "ProcessAllPendingAttributeAndStateInvalidations");
+  if (mSnapshots.IsEmpty()) {
+    return;
+  }
   for (auto iter = mSnapshots.Iter(); !iter.Done(); iter.Next()) {
-    Servo_ProcessInvalidations(StyleSet()->RawSet(), iter.Key(), &mSnapshots);
+    // Servo data for the element might have been dropped. (e.g. by removing
+    // from its document)
+    if (iter.Key()->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
+      Servo_ProcessInvalidations(StyleSet()->RawSet(), iter.Key(), &mSnapshots);
+    }
   }
   ClearSnapshots();
 }
 
 bool
 ServoRestyleManager::HasPendingRestyleAncestor(Element* aElement) const
 {
   return Servo_HasPendingRestyleAncestor(aElement);
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -119,16 +119,17 @@
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/StyleSetHandle.h"
 #include "mozilla/StyleSetHandleInlines.h"
 #include "RegionBuilder.h"
 #include "SVGSVGElement.h"
 #include "DisplayItemClip.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "prenv.h"
+#include "TextDrawTarget.h"
 
 #ifdef MOZ_XUL
 #include "nsXULPopupManager.h"
 #endif
 
 #include "GeckoProfiler.h"
 #include "nsAnimationManager.h"
 #include "nsTransitionManager.h"
@@ -6062,28 +6063,47 @@ nsLayoutUtils::PaintTextShadow(const nsI
                          shadowDetails->mYOffset);
     nscoord blurRadius = std::max(shadowDetails->mRadius, 0);
 
     nsRect shadowRect(aTextRect);
     shadowRect.MoveBy(shadowOffset);
 
     nsPresContext* presCtx = aFrame->PresContext();
     nsContextBoxBlur contextBoxBlur;
+
+    nscolor shadowColor;
+    if (shadowDetails->mHasColor)
+      shadowColor = shadowDetails->mColor;
+    else
+      shadowColor = aForegroundColor;
+
+    // Webrender just needs the shadow details
+    if (auto* textDrawer = aContext->GetTextDrawer()) {
+      wr::TextShadow wrShadow;
+
+      wrShadow.offset = {
+        presCtx->AppUnitsToFloatDevPixels(shadowDetails->mXOffset),
+        presCtx->AppUnitsToFloatDevPixels(shadowDetails->mYOffset)
+      };
+
+      wrShadow.blur_radius = presCtx->AppUnitsToFloatDevPixels(shadowDetails->mRadius);
+      wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
+
+      textDrawer->AppendShadow(wrShadow);
+      return;
+    }
+
     gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius,
                                                     presCtx->AppUnitsPerDevPixel(),
                                                     aDestCtx, aDirtyRect, nullptr,
                                                     nsContextBoxBlur::DISABLE_HARDWARE_ACCELERATION_BLUR);
     if (!shadowContext)
       continue;
 
-    nscolor shadowColor;
-    if (shadowDetails->mHasColor)
-      shadowColor = shadowDetails->mColor;
-    else
-      shadowColor = aForegroundColor;
+    
 
     aDestCtx->Save();
     aDestCtx->NewPath();
     aDestCtx->SetColor(Color::FromABGR(shadowColor));
 
     // The callback will draw whatever we want to blur as a shadow.
     aCallback(shadowContext, shadowOffset, shadowColor, aCallbackData);
 
--- a/layout/generic/TextDrawTarget.h
+++ b/layout/generic/TextDrawTarget.h
@@ -2,16 +2,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef TextDrawTarget_h
 #define TextDrawTarget_h
 
 #include "mozilla/gfx/2D.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/layers/StackingContextHelper.h"
 
 namespace mozilla {
 namespace layout {
 
 using namespace gfx;
 
 // This is used by all Advanced Layers users, so we use plain gfx types
 struct TextRunFragment {
@@ -83,32 +87,32 @@ class TextDrawTarget : public DrawTarget
 public:
   // The different phases of drawing the text we're in
   // Each should only happen once, and in the given order.
   enum class Phase : uint8_t {
     eSelection, eUnderline, eOverline, eGlyphs, eEmphasisMarks, eLineThrough
   };
 
   explicit TextDrawTarget()
-  : mCurrentlyDrawing(Phase::eSelection)
+  : mCurrentlyDrawing(Phase::eSelection), mHasUnsupportedFeatures(false)
   {
-    mCurrentTarget = gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, IntSize(1, 1), gfx::SurfaceFormat::B8G8R8A8);
     SetSelectionIndex(0);
   }
 
   // Prevent this from being copied
   TextDrawTarget(const TextDrawTarget& src) = delete;
   TextDrawTarget& operator=(const TextDrawTarget&) = delete;
 
   // Change the phase of text we're drawing.
   void StartDrawing(Phase aPhase) { mCurrentlyDrawing = aPhase; }
+  void FoundUnsupportedFeature() { mHasUnsupportedFeatures = true; }
 
   void SetSelectionIndex(size_t i) {
     // i should only be accessed if i-1 has already been
-    MOZ_ASSERT(mParts.Length() <= i);
+    MOZ_ASSERT(i <= mParts.Length());
 
     if (mParts.Length() == i){
       mParts.AppendElement();
     }
 
     mCurrentPart = &mParts[i];
   }
 
@@ -248,16 +252,20 @@ public:
 
   }
 
   const nsTArray<SelectedTextRunFragment>& GetParts() { return mParts; }
 
   bool
   CanSerializeFonts()
   {
+    if (mHasUnsupportedFeatures) {
+      return false;
+    }
+
     for (const SelectedTextRunFragment& part : GetParts()) {
       for (const TextRunFragment& frag : part.text) {
         if (!frag.font->CanSerialize()) {
           return false;
         }
       }
     }
     return true;
@@ -294,237 +302,310 @@ public:
     // Must have an actual font (i.e. actual text)
     if (!font) {
       return false;
     }
 
     return true;
   }
 
+  void
+  CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
+                          const layers::StackingContextHelper& aSc,
+                          layers::WebRenderLayerManager* aManager,
+                          nsDisplayItem* aItem,
+                          nsRect& aBounds) {
+
+  // Drawing order: selections,
+  //                shadows,
+  //                underline, overline, [grouped in one array]
+  //                text, emphasisText,  [grouped in one array]
+  //                lineThrough
+
+  // Compute clip/bounds
+  auto appUnitsPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+  LayoutDeviceRect layoutBoundsRect = LayoutDeviceRect::FromAppUnits(
+      aBounds, appUnitsPerDevPixel);
+  LayoutDeviceRect layoutClipRect = layoutBoundsRect;
+  auto clip = aItem->GetClip();
+  if (clip.HasClip()) {
+    layoutClipRect = LayoutDeviceRect::FromAppUnits(
+                clip.GetClipRect(), appUnitsPerDevPixel);
+  }
+
+  LayerRect boundsRect = LayerRect::FromUnknownRect(layoutBoundsRect.ToUnknownRect());
+  LayerRect clipRect = LayerRect::FromUnknownRect(layoutClipRect.ToUnknownRect());
+
+  bool backfaceVisible = !aItem->BackfaceIsHidden();
+
+  wr::LayoutRect wrBoundsRect = aSc.ToRelativeLayoutRect(boundsRect);
+  wr::LayoutRect wrClipRect = aSc.ToRelativeLayoutRect(clipRect);
+
+
+  // Create commands
+  for (auto& part : GetParts()) {
+    if (part.selection) {
+      auto selection = part.selection.value();
+      aBuilder.PushRect(selection.rect, wrClipRect, backfaceVisible, selection.color);
+    }
+  }
+
+  for (auto& part : GetParts()) {
+    // WR takes the shadows in CSS-order (reverse of rendering order),
+    // because the drawing of a shadow actually occurs when it's popped.
+    for (const wr::TextShadow& shadow : part.shadows) {
+      aBuilder.PushTextShadow(wrBoundsRect, wrClipRect, backfaceVisible, shadow);
+    }
+
+    for (const wr::Line& decoration : part.beforeDecorations) {
+      aBuilder.PushLine(wrClipRect, backfaceVisible, decoration);
+    }
+
+    for (const mozilla::layout::TextRunFragment& text : part.text) {
+      aManager->WrBridge()->PushGlyphs(aBuilder, text.glyphs, text.font,
+                                       text.color, aSc, boundsRect, clipRect,
+                                       backfaceVisible);
+    }
+
+    for (const wr::Line& decoration : part.afterDecorations) {
+      aBuilder.PushLine(wrClipRect, backfaceVisible, decoration);
+    }
+
+    for (size_t i = 0; i < part.shadows.Length(); ++i) {
+      aBuilder.PopTextShadow();
+    }
+  }
+}
+
 
 private:
   // The part of the text we're currently drawing (glyphs, underlines, etc.)
   Phase mCurrentlyDrawing;
 
   // Which chunk of mParts is actively being populated
   SelectedTextRunFragment* mCurrentPart;
 
   // Chunks of the text, grouped by selection
   nsTArray<SelectedTextRunFragment> mParts;
 
-  // A dummy to handle parts of the DrawTarget impl we don't care for
-  RefPtr<DrawTarget> mCurrentTarget;
+  // Whether Tofu or SVG fonts were encountered
+  bool mHasUnsupportedFeatures;
 
   // The rest of this is dummy implementations of DrawTarget's API
 public:
   DrawTargetType GetType() const override {
-    return mCurrentTarget->GetType();
+    return DrawTargetType::SOFTWARE_RASTER;
   }
 
   BackendType GetBackendType() const override {
-    return mCurrentTarget->GetBackendType();
+    return BackendType::WEBRENDER_TEXT;
   }
 
   bool IsRecording() const override { return true; }
   bool IsCaptureDT() const override { return false; }
 
   already_AddRefed<SourceSurface> Snapshot() override {
-    return mCurrentTarget->Snapshot();
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+    return nullptr;
   }
 
   already_AddRefed<SourceSurface> IntoLuminanceSource(LuminanceType aLuminanceType,
                                                       float aOpacity) override {
-    return mCurrentTarget->IntoLuminanceSource(aLuminanceType, aOpacity);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+    return nullptr;
   }
 
   IntSize GetSize() override {
-    return mCurrentTarget->GetSize();
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+    return IntSize(1, 1);
   }
 
   void Flush() override {
-    mCurrentTarget->Flush();
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void DrawCapturedDT(DrawTargetCapture *aCaptureDT,
                       const Matrix& aTransform) override {
-    mCurrentTarget->DrawCapturedDT(aCaptureDT, aTransform);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void DrawSurface(SourceSurface *aSurface,
                    const Rect &aDest,
                    const Rect &aSource,
                    const DrawSurfaceOptions &aSurfOptions,
                    const DrawOptions &aOptions) override {
-    mCurrentTarget->DrawSurface(aSurface, aDest, aSource, aSurfOptions, aOptions);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void DrawFilter(FilterNode *aNode,
                           const Rect &aSourceRect,
                           const Point &aDestPoint,
                           const DrawOptions &aOptions) override {
-    mCurrentTarget->DrawFilter(aNode, aSourceRect, aDestPoint, aOptions);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void DrawSurfaceWithShadow(SourceSurface *aSurface,
                              const Point &aDest,
                              const Color &aColor,
                              const Point &aOffset,
                              Float aSigma,
                              CompositionOp aOperator) override {
-    mCurrentTarget->DrawSurfaceWithShadow(aSurface, aDest, aColor, aOffset, aSigma, aOperator);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void ClearRect(const Rect &aRect) override {
-    mCurrentTarget->ClearRect(aRect);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void CopySurface(SourceSurface *aSurface,
                    const IntRect &aSourceRect,
                    const IntPoint &aDestination) override {
-    mCurrentTarget->CopySurface(aSurface, aSourceRect, aDestination);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void FillRect(const Rect &aRect,
                 const Pattern &aPattern,
                 const DrawOptions &aOptions = DrawOptions()) override {
-    mCurrentTarget->FillRect(aRect, aPattern, aOptions);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void StrokeRect(const Rect &aRect,
                   const Pattern &aPattern,
                   const StrokeOptions &aStrokeOptions,
                   const DrawOptions &aOptions) override {
-    mCurrentTarget->StrokeRect(aRect, aPattern, aStrokeOptions, aOptions);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void StrokeLine(const Point &aStart,
                   const Point &aEnd,
                   const Pattern &aPattern,
                   const StrokeOptions &aStrokeOptions,
                   const DrawOptions &aOptions) override {
-    mCurrentTarget->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aOptions);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
 
   void Stroke(const Path *aPath,
               const Pattern &aPattern,
               const StrokeOptions &aStrokeOptions,
               const DrawOptions &aOptions) override {
-    mCurrentTarget->Stroke(aPath, aPattern, aStrokeOptions, aOptions);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void Fill(const Path *aPath,
             const Pattern &aPattern,
             const DrawOptions &aOptions) override {
-    mCurrentTarget->Fill(aPath, aPattern, aOptions);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void StrokeGlyphs(ScaledFont* aFont,
                     const GlyphBuffer& aBuffer,
                     const Pattern& aPattern,
                     const StrokeOptions& aStrokeOptions,
                     const DrawOptions& aOptions,
                     const GlyphRenderingOptions* aRenderingOptions) override {
-    MOZ_ASSERT(mCurrentlyDrawing == Phase::eGlyphs);
-    mCurrentTarget->StrokeGlyphs(aFont, aBuffer, aPattern,
-                                 aStrokeOptions, aOptions, aRenderingOptions);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void Mask(const Pattern &aSource,
                     const Pattern &aMask,
                     const DrawOptions &aOptions) override {
-    return mCurrentTarget->Mask(aSource, aMask, aOptions);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void MaskSurface(const Pattern &aSource,
                    SourceSurface *aMask,
                    Point aOffset,
                    const DrawOptions &aOptions) override {
-    return mCurrentTarget->MaskSurface(aSource, aMask, aOffset, aOptions);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   bool Draw3DTransformedSurface(SourceSurface* aSurface,
                                 const Matrix4x4& aMatrix) override {
-    return mCurrentTarget->Draw3DTransformedSurface(aSurface, aMatrix);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void PushClip(const Path *aPath) override {
-    mCurrentTarget->PushClip(aPath);
+    // Fine to pretend we do this
   }
 
   void PushClipRect(const Rect &aRect) override {
-    mCurrentTarget->PushClipRect(aRect);
+    // Fine to pretend we do this
   }
 
   void PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount) override {
-    mCurrentTarget->PushDeviceSpaceClipRects(aRects, aCount);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 
   void PopClip() override {
-    mCurrentTarget->PopClip();
+    // Fine to pretend we do this
   }
 
   void PushLayer(bool aOpaque, Float aOpacity,
                          SourceSurface* aMask,
                          const Matrix& aMaskTransform,
                          const IntRect& aBounds,
                          bool aCopyBackground) override {
-    mCurrentTarget->PushLayer(aOpaque, aOpacity, aMask, aMaskTransform, aBounds, aCopyBackground);
+    // Fine to pretend we do this
   }
 
   void PopLayer() override {
-    mCurrentTarget->PopLayer();
+    // Fine to pretend we do this
   }
 
 
   already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
                                                               const IntSize &aSize,
                                                               int32_t aStride,
                                                               SurfaceFormat aFormat) const override {
-    return mCurrentTarget->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+    return nullptr;
   }
 
   already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override {
-      return mCurrentTarget->OptimizeSourceSurface(aSurface);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+    return nullptr;
   }
 
   already_AddRefed<SourceSurface>
-    CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override {
-      return mCurrentTarget->CreateSourceSurfaceFromNativeSurface(aSurface);
+  CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override {
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+    return nullptr;
   }
 
   already_AddRefed<DrawTarget>
-    CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override {
-      return mCurrentTarget->CreateSimilarDrawTarget(aSize, aFormat);
+  CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override {
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+    return nullptr;
   }
 
   already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule) const override {
-    return mCurrentTarget->CreatePathBuilder(aFillRule);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+    return nullptr;
   }
 
   already_AddRefed<FilterNode> CreateFilter(FilterType aType) override {
-    return mCurrentTarget->CreateFilter(aType);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+    return nullptr;
   }
 
   already_AddRefed<GradientStops>
-    CreateGradientStops(GradientStop *aStops,
-                        uint32_t aNumStops,
-                        ExtendMode aExtendMode) const override {
-      return mCurrentTarget->CreateGradientStops(aStops, aNumStops, aExtendMode);
-  }
-
-  void SetTransform(const Matrix &aTransform) override {
-    mCurrentTarget->SetTransform(aTransform);
-    // Need to do this to make inherited GetTransform to work
-    DrawTarget::SetTransform(aTransform);
+  CreateGradientStops(GradientStop *aStops,
+                      uint32_t aNumStops,
+                      ExtendMode aExtendMode) const override {
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+    return nullptr;
   }
 
   void* GetNativeSurface(NativeSurfaceType aType) override {
-    return mCurrentTarget->GetNativeSurface(aType);
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+    return nullptr;
   }
 
-  void DetachAllSnapshots() override { mCurrentTarget->DetachAllSnapshots(); }
+  void DetachAllSnapshots() override {
+    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+  }
 };
 
 }
 }
 
 #endif
--- a/layout/generic/TextOverflow.cpp
+++ b/layout/generic/TextOverflow.cpp
@@ -19,16 +19,17 @@
 #include "nsLayoutUtils.h"
 #include "nsPresContext.h"
 #include "nsRect.h"
 #include "nsTextFrame.h"
 #include "nsIFrameInlines.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Likely.h"
 #include "nsISelection.h"
+#include "TextDrawTarget.h"
 
 namespace mozilla {
 namespace css {
 
 class LazyReferenceRenderingDrawTargetGetterFromFrame final :
     public gfxFontGroup::LazyReferenceDrawTargetGetter {
 public:
   typedef mozilla::gfx::DrawTarget DrawTarget;
@@ -203,16 +204,23 @@ public:
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
 
   virtual uint32_t GetPerFrameKey() const override {
     return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
   }
   void PaintTextToContext(gfxContext* aCtx,
                           nsPoint aOffsetFromRect);
+
+  virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
+                                       mozilla::wr::IpcResourceUpdateQueue& aResources,
+                                       const StackingContextHelper& aSc,
+                                       layers::WebRenderLayerManager* aManager,
+                                       nsDisplayListBuilder* aDisplayListBuilder) override;
+
   NS_DISPLAY_DECL_NAME("TextOverflow", TYPE_TEXT_OVERFLOW)
 private:
   nsRect          mRect;   // in reference frame coordinates
   const nsStyleTextOverflowSide* mStyle;
   nscoord         mAscent; // baseline for the marker text in mRect
   uint32_t        mIndex;
 };
 
@@ -273,16 +281,53 @@ nsDisplayTextOverflowMarker::PaintTextTo
   } else {
     RefPtr<nsFontMetrics> fm =
       nsLayoutUtils::GetInflatedFontMetricsForFrame(mFrame);
     nsLayoutUtils::DrawString(mFrame, *fm, aCtx, mStyle->mString.get(),
                               mStyle->mString.Length(), pt);
   }
 }
 
+bool
+nsDisplayTextOverflowMarker::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
+                                                     mozilla::wr::IpcResourceUpdateQueue& aResources,
+                                                     const StackingContextHelper& aSc,
+                                                     layers::WebRenderLayerManager* aManager,
+                                                     nsDisplayListBuilder* aDisplayListBuilder)
+{
+  if (!aManager->IsLayersFreeTransaction() ||
+      !gfxPrefs::LayersAllowTextLayers() ||
+      !CanUseAdvancedLayer(aDisplayListBuilder->GetWidgetLayerManager())) {
+      return false;
+  }
+
+  bool snap;
+  nsRect bounds = GetBounds(aDisplayListBuilder, &snap);
+  if (bounds.IsEmpty()) {
+    return true;
+  }
+
+  // Run the rendering algorithm to capture the glyphs and shadows
+  RefPtr<TextDrawTarget> textDrawer = new TextDrawTarget();
+  RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(textDrawer);
+  // TextOverflowMarker only draws glyphs
+  textDrawer->StartDrawing(TextDrawTarget::Phase::eGlyphs);
+  Paint(aDisplayListBuilder, captureCtx);
+
+  if (!textDrawer->CanSerializeFonts()) {
+    return false;
+  }
+
+  textDrawer->CreateWebRenderCommands(aBuilder, aSc, aManager, this, bounds);
+
+
+  return true;
+}
+
+
 TextOverflow::TextOverflow(nsDisplayListBuilder* aBuilder,
                            nsIFrame* aBlockFrame)
   : mContentArea(aBlockFrame->GetWritingMode(),
                  aBlockFrame->GetContentRectRelativeToSelf(),
                  aBlockFrame->GetSize())
   , mBuilder(aBuilder)
   , mBlock(aBlockFrame)
   , mScrollableFrame(nsLayoutUtils::GetScrollableFrameFor(aBlockFrame))
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -4997,17 +4997,17 @@ public:
   }
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
                                          nsRegion *aInvalidRegion) const override;
 
-  void RenderToContext(gfxContext* aCtx, TextDrawTarget* aTextDrawer, nsDisplayListBuilder* aBuilder, bool aIsRecording = false);
+  void RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder, bool aIsRecording = false);
 
   bool CanApplyOpacity() const override
   {
     nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
     if (f->IsSelected()) {
       return false;
     }
 
@@ -5117,38 +5117,41 @@ nsDisplayText::nsDisplayText(nsDisplayLi
   : nsCharClipDisplayItem(aBuilder, aFrame)
   , mOpacity(1.0f)
 {
   MOZ_COUNT_CTOR(nsDisplayText);
   mIsFrameSelected = aIsSelected;
   mBounds = mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
     // Bug 748228
   mBounds.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
-
-  if (gfxPrefs::LayersAllowTextLayers() &&
-      CanUseAdvancedLayer(aBuilder->GetWidgetLayerManager())) {
-    mTextDrawer = new TextDrawTarget();
-    RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(mTextDrawer);
-
-    // TODO: Paint() checks mDisableSubpixelAA, we should too.
-    RenderToContext(captureCtx, mTextDrawer, aBuilder, true);
-
-    if (!mTextDrawer->CanSerializeFonts()) {
-      mTextDrawer = nullptr;
-    }
-  }
 }
 
 LayerState
 nsDisplayText::GetLayerState(nsDisplayListBuilder* aBuilder,
                              LayerManager* aManager,
                              const ContainerLayerParameters& aParameters)
 {
-  // Basic things that all advanced backends need
+
+  // Are we doing text layers or webrender?
+  if (!(gfxPrefs::LayersAllowTextLayers() &&
+      CanUseAdvancedLayer(aBuilder->GetWidgetLayerManager()))) {
+    return mozilla::LAYER_NONE;
+  }
+
+  // If we haven't yet, compute the layout/style of the text by running
+  // the painting algorithm with a TextDrawTarget (doesn't actually paint).
   if (!mTextDrawer) {
+    mTextDrawer = new TextDrawTarget();
+    RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(mTextDrawer);
+
+    // TODO: Paint() checks mDisableSubpixelAA, we should too.
+    RenderToContext(captureCtx, aBuilder, true);
+  }
+
+  if (!mTextDrawer->CanSerializeFonts()) {
     return mozilla::LAYER_NONE;
   }
 
   // If we're using the webrender backend, then we're good to go!
   if (aManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
     return mozilla::LAYER_ACTIVE;
   }
 
@@ -5163,17 +5166,17 @@ nsDisplayText::GetLayerState(nsDisplayLi
 
 void
 nsDisplayText::Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) {
   AUTO_PROFILER_LABEL("nsDisplayText::Paint", GRAPHICS);
 
   DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(),
                                                     mDisableSubpixelAA);
-  RenderToContext(aCtx, nullptr, aBuilder);
+  RenderToContext(aCtx, aBuilder);
 }
 
 bool
 nsDisplayText::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                        mozilla::wr::IpcResourceUpdateQueue& aResources,
                                        const StackingContextHelper& aSc,
                                        WebRenderLayerManager* aManager,
                                        nsDisplayListBuilder* aDisplayListBuilder)
@@ -5184,74 +5187,17 @@ nsDisplayText::CreateWebRenderCommands(m
       return false;
     }
   }
 
   if (mBounds.IsEmpty()) {
     return true;
   }
 
-  auto appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
-  LayoutDeviceRect layoutBoundsRect = LayoutDeviceRect::FromAppUnits(
-      mBounds, appUnitsPerDevPixel);
-  LayoutDeviceRect layoutClipRect = layoutBoundsRect;
-  if (GetClip().HasClip()) {
-    layoutClipRect = LayoutDeviceRect::FromAppUnits(
-                GetClip().GetClipRect(), appUnitsPerDevPixel);
-  }
-
-  LayerRect boundsRect = LayerRect::FromUnknownRect(layoutBoundsRect.ToUnknownRect());
-  LayerRect clipRect = LayerRect::FromUnknownRect(layoutClipRect.ToUnknownRect());
-  wr::LayoutRect wrClipRect = aSc.ToRelativeLayoutRect(clipRect); // wr::ToLayoutRect(clipRect);
-  wr::LayoutRect wrBoundsRect = aSc.ToRelativeLayoutRect(boundsRect); //wr::ToLayoutRect(boundsRect);
-  bool backfaceVisible = !BackfaceIsHidden();
-
-  // Drawing order: selections, shadows,
-  //                underline, overline, [grouped in one array]
-  //                text, emphasisText,  [grouped in one array]
-  //                lineThrough
-
-  for (auto& part : mTextDrawer->GetParts()) {
-    if (part.selection) {
-      auto selection = part.selection.value();
-      aBuilder.PushRect(selection.rect, wrClipRect, backfaceVisible, selection.color);
-    }
-  }
-
-  for (auto& part : mTextDrawer->GetParts()) {
-    // WR takes the shadows in CSS-order (reverse of rendering order),
-    // because the drawing of a shadow actually occurs when it's popped.
-    for (const wr::TextShadow& shadow : part.shadows) {
-      aBuilder.PushTextShadow(wrBoundsRect, wrClipRect, backfaceVisible, shadow);
-    }
-
-    for (const wr::Line& decoration : part.beforeDecorations) {
-      aBuilder.PushLine(wrClipRect, backfaceVisible, decoration);
-    }
-
-    for (const mozilla::layout::TextRunFragment& text : part.text) {
-      // mOpacity is set after we do our analysis, so we need to apply it here.
-      // mOpacity is only non-trivial when we have "pure" text, so we don't
-      // ever need to apply it to shadows or decorations.
-      auto color = text.color;
-      color.a *= mOpacity;
-
-      aManager->WrBridge()->PushGlyphs(aBuilder, text.glyphs, text.font,
-                                       color, aSc, boundsRect, clipRect,
-                                       backfaceVisible);
-    }
-
-    for (const wr::Line& decoration : part.afterDecorations) {
-      aBuilder.PushLine(wrClipRect, backfaceVisible, decoration);
-    }
-
-    for (size_t i = 0; i < part.shadows.Length(); ++i) {
-      aBuilder.PopTextShadow();
-    }
-  }
+  mTextDrawer->CreateWebRenderCommands(aBuilder, aSc, aManager, this, mBounds);
 
   return true;
 }
 
 already_AddRefed<layers::Layer>
 nsDisplayText::BuildLayer(nsDisplayListBuilder* aBuilder,
                           LayerManager* aManager,
                           const ContainerLayerParameters& aContainerParameters)
@@ -5284,21 +5230,17 @@ nsDisplayText::BuildLayer(nsDisplayListB
   for (auto& part : mTextDrawer->GetParts()) {
     for (const mozilla::layout::TextRunFragment& text : part.text) {
       if (!font) {
         font = text.font;
       }
 
       GlyphArray* glyphs = allGlyphs.AppendElement();
       glyphs->glyphs() = text.glyphs;
-
-      // Apply folded alpha (only applies to glyphs)
-      auto color = text.color;
-      color.a *= mOpacity;
-      glyphs->color() = color;
+      glyphs->color() = text.color;
     }
   }
 
   MOZ_ASSERT(font);
 
   layer->SetGlyphs(Move(allGlyphs));
   layer->SetScaledFont(font);
 
@@ -5309,17 +5251,17 @@ nsDisplayText::BuildLayer(nsDisplayListB
   layer->SetBounds(IntRect(destBounds.x, destBounds.y, destBounds.width, destBounds.height));
 
   layer->SetBaseTransform(gfx::Matrix4x4::Translation(aContainerParameters.mOffset.x,
                                                       aContainerParameters.mOffset.y, 0));
   return layer.forget();
 }
 
 void
-nsDisplayText::RenderToContext(gfxContext* aCtx, TextDrawTarget* aTextDrawer, nsDisplayListBuilder* aBuilder, bool aIsRecording)
+nsDisplayText::RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder, bool aIsRecording)
 {
   nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
 
   // Add 1 pixel of dirty area around mVisibleRect to allow us to paint
   // antialiased pixels beyond the measured text extents.
   // This is temporary until we do this in the actual calculation of text extents.
   auto A2D = mFrame->PresContext()->AppUnitsPerDevPixel();
   LayoutDeviceRect extraVisible =
@@ -5357,17 +5299,16 @@ nsDisplayText::RenderToContext(gfxContex
       gfxMatrix mat = aCtx->CurrentMatrix()
         .PreTranslate(pt).PreScale(scaleFactor, 1.0).PreTranslate(-pt);
       aCtx->SetMatrix (mat);
     }
   }
   nsTextFrame::PaintTextParams params(aCtx);
   params.framePt = gfxPoint(framePt.x, framePt.y);
   params.dirtyRect = extraVisible;
-  params.textDrawer = aTextDrawer;
 
   if (aBuilder->IsForGenerateGlyphMask()) {
     MOZ_ASSERT(!aBuilder->IsForPaintingSelectionBG());
     params.state = nsTextFrame::PaintTextParams::GenerateTextMask;
   } else if (aBuilder->IsForPaintingSelectionBG()) {
     params.state = nsTextFrame::PaintTextParams::PaintTextBGColor;
   } else {
     params.state = nsTextFrame::PaintTextParams::PaintText;
@@ -6048,17 +5989,16 @@ nsTextFrame::ComputeSelectionUnderlineHe
 enum class DecorationType
 {
   Normal, Selection
 };
 struct nsTextFrame::PaintDecorationLineParams
   : nsCSSRendering::DecorationRectParams
 {
   gfxContext* context = nullptr;
-  TextDrawTarget* textDrawer = nullptr;
   LayoutDeviceRect dirtyRect;
   Point pt;
   const nscolor* overrideColor = nullptr;
   nscolor color = NS_RGBA(0, 0, 0, 0);
   gfxFloat icoordInFrame = 0.0f;
   DecorationType decorationType = DecorationType::Normal;
   DrawPathCallbacks* callbacks = nullptr;
 };
@@ -6067,17 +6007,16 @@ void
 nsTextFrame::PaintDecorationLine(const PaintDecorationLineParams& aParams)
 {
   nsCSSRendering::PaintDecorationLineParams params;
   static_cast<nsCSSRendering::DecorationRectParams&>(params) = aParams;
   params.dirtyRect = aParams.dirtyRect.ToUnknownRect();
   params.pt = aParams.pt;
   params.color = aParams.overrideColor ? *aParams.overrideColor : aParams.color;
   params.icoordInFrame = Float(aParams.icoordInFrame);
-  params.textDrawer = aParams.textDrawer;
   if (aParams.callbacks) {
     Rect path = nsCSSRendering::DecorationLineToPath(params);
     if (aParams.decorationType == DecorationType::Normal) {
       aParams.callbacks->PaintDecorationLine(path, params.color);
     } else {
       aParams.callbacks->PaintSelectionDecorationLine(path, params.color);
     }
   } else {
@@ -6087,54 +6026,54 @@ nsTextFrame::PaintDecorationLine(const P
 }
 
 /**
  * This, plus kSelectionTypesWithDecorations, encapsulates all knowledge
  * about drawing text decoration for selections.
  */
 void
 nsTextFrame::DrawSelectionDecorations(gfxContext* aContext,
-                                      TextDrawTarget* aTextDrawer,
                                       const LayoutDeviceRect& aDirtyRect,
                                       SelectionType aSelectionType,
                                       nsTextPaintStyle& aTextPaintStyle,
                                       const TextRangeStyle &aRangeStyle,
                                       const Point& aPt,
                                       gfxFloat aICoordInFrame,
                                       gfxFloat aWidth,
                                       gfxFloat aAscent,
                                       const gfxFont::Metrics& aFontMetrics,
                                       DrawPathCallbacks* aCallbacks,
                                       bool aVertical,
                                       gfxFloat aDecorationOffsetDir,
                                       uint8_t aDecoration)
 {
   PaintDecorationLineParams params;
   params.context = aContext;
-  params.textDrawer = aTextDrawer;
   params.dirtyRect = aDirtyRect;
   params.pt = aPt;
   params.lineSize.width = aWidth;
   params.ascent = aAscent;
   params.offset = aDecoration == NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE ?
                   aFontMetrics.underlineOffset : aFontMetrics.maxAscent;
   params.decoration = aDecoration;
   params.decorationType = DecorationType::Selection;
   params.callbacks = aCallbacks;
   params.vertical = aVertical;
   params.descentLimit =
     ComputeDescentLimitForSelectionUnderline(aTextPaintStyle.PresContext(),
                                              aFontMetrics);
 
   float relativeSize;
 
+  auto* textDrawer = aContext->GetTextDrawer();
+
   // Since this happens after text, all we *should* be allowed to do is strikeThrough.
   // If this isn't true, we're at least bug-compatible with gecko!
-  if (aTextDrawer) {
-    aTextDrawer->StartDrawing(TextDrawTarget::Phase::eLineThrough);
+  if (textDrawer) {
+    textDrawer->StartDrawing(TextDrawTarget::Phase::eLineThrough);
   }
 
   switch (aSelectionType) {
     case SelectionType::eIMERawClause:
     case SelectionType::eIMESelectedRawClause:
     case SelectionType::eIMEConvertedClause:
     case SelectionType::eIMESelectedClause:
     case SelectionType::eSpellCheck: {
@@ -6484,41 +6423,41 @@ nsTextFrame::PaintOneShadow(const PaintS
     shadowRect, 0, blurRadius, A2D, aParams.context,
     LayoutDevicePixel::ToAppUnits(aParams.dirtyRect, A2D), nullptr, aBlurFlags);
   if (!shadowContext)
     return;
 
   nscolor shadowColor = aShadowDetails->mHasColor ? aShadowDetails->mColor
                                                   : aParams.foregroundColor;
 
-  if (aParams.textDrawer) {
+  auto* textDrawer = aParams.context->GetTextDrawer();
+  if (textDrawer) {
     wr::TextShadow wrShadow;
 
     wrShadow.offset = {
       PresContext()->AppUnitsToFloatDevPixels(aShadowDetails->mXOffset),
       PresContext()->AppUnitsToFloatDevPixels(aShadowDetails->mYOffset)
     };
 
     wrShadow.blur_radius = PresContext()->AppUnitsToFloatDevPixels(aShadowDetails->mRadius);
     wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
 
-    aParams.textDrawer->AppendShadow(wrShadow);
+    textDrawer->AppendShadow(wrShadow);
     return;
   }
 
   aParams.context->Save();
   aParams.context->SetColor(Color::FromABGR(shadowColor));
 
   // Draw the text onto our alpha-only surface to capture the alpha values.
   // Remember that the box blur context has a device offset on it, so we don't need to
   // translate any coordinates to fit on the surface.
   gfxFloat advanceWidth;
   nsTextPaintStyle textPaintStyle(this);
   DrawTextParams params(shadowContext);
-  params.textDrawer = nullptr; // Don't record anything that happens here
   params.advanceWidth = &advanceWidth;
   params.dirtyRect = aParams.dirtyRect;
   params.framePt = aParams.framePt + shadowOffset;
   params.provider = aParams.provider;
   params.textStyle = &textPaintStyle;
   params.textColor =
     aParams.context == shadowContext ? shadowColor : NS_RGB(0, 0, 0);
   params.clipEdges = aParams.clipEdges;
@@ -6594,27 +6533,30 @@ nsTextFrame::PaintTextWithSelectionColor
   bool vertical = mTextRun->IsVertical();
   const gfxFloat startIOffset = vertical ?
     aParams.textBaselinePt.y - aParams.framePt.y :
     aParams.textBaselinePt.x - aParams.framePt.x;
   gfxFloat iOffset, hyphenWidth;
   Range range; // in transformed string
   TextRangeStyle rangeStyle;
   // Draw background colors
+
+  auto* textDrawer = aParams.context->GetTextDrawer();
+
   if (anyBackgrounds && !aParams.IsGenerateTextMask()) {
     int32_t appUnitsPerDevPixel =
       aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
     SelectionIterator iterator(prevailingSelections, contentRange,
                                *aParams.provider, mTextRun, startIOffset);
     SelectionType selectionType;
     size_t selectionIndex = 0;
     while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
                                    &selectionType, &rangeStyle)) {
-      if (aParams.textDrawer) {
-        aParams.textDrawer->SetSelectionIndex(selectionIndex);
+      if (textDrawer) {
+        textDrawer->SetSelectionIndex(selectionIndex);
       }
 
       nscolor foreground, background;
       GetSelectionTextColors(selectionType, *aParams.textPaintStyle,
                              rangeStyle, &foreground, &background);
       // Draw background color
       gfxFloat advance = hyphenWidth +
         mTextRun->GetAdvanceWidth(range, aParams.provider);
@@ -6627,19 +6569,18 @@ nsTextFrame::PaintTextWithSelectionColor
         } else {
           bgRect = nsRect(aParams.framePt.x + offs, aParams.framePt.y,
                           advance, GetSize().height);
         }
 
         LayoutDeviceRect selectionRect =
           LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel);
 
-        if (aParams.textDrawer) {
-          aParams.textDrawer->SetSelectionRect(selectionRect,
-                                               ToDeviceColor(background));
+        if (textDrawer) {
+          textDrawer->SetSelectionRect(selectionRect, ToDeviceColor(background));
         } else {
           PaintSelectionBackground(
             *aParams.context->GetDrawTarget(), background, aParams.dirtyRect,
             selectionRect, aParams.callbacks);
         }
       }
       iterator.UpdateWithAdvance(advance);
       ++selectionIndex;
@@ -6647,17 +6588,16 @@ nsTextFrame::PaintTextWithSelectionColor
   }
 
   if (aParams.IsPaintBGColor()) {
     return true;
   }
 
   gfxFloat advance;
   DrawTextParams params(aParams.context);
-  params.textDrawer = aParams.textDrawer;
   params.dirtyRect = aParams.dirtyRect;
   params.framePt = aParams.framePt;
   params.provider = aParams.provider;
   params.textStyle = aParams.textPaintStyle;
   params.clipEdges = &aClipEdges;
   params.advanceWidth = &advance;
   params.callbacks = aParams.callbacks;
 
@@ -6669,18 +6609,18 @@ nsTextFrame::PaintTextWithSelectionColor
   const nsStyleText* textStyle = StyleText();
   SelectionIterator iterator(prevailingSelections, contentRange,
                              *aParams.provider, mTextRun, startIOffset);
   SelectionType selectionType;
 
   size_t selectionIndex = 0;
   while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
                                  &selectionType, &rangeStyle)) {
-    if (aParams.textDrawer) {
-      aParams.textDrawer->SetSelectionIndex(selectionIndex);
+    if (textDrawer) {
+      textDrawer->SetSelectionIndex(selectionIndex);
     }
 
     nscolor foreground, background;
     if (aParams.IsGenerateTextMask()) {
       foreground = NS_RGBA(0, 0, 0, 255);
     } else {
       GetSelectionTextColors(selectionType, *aParams.textPaintStyle,
                              rangeStyle, &foreground, &background);
@@ -6787,36 +6727,39 @@ nsTextFrame::PaintTextSelectionDecoratio
   if (verticalRun) {
     pt.x = (aParams.textBaselinePt.x - mAscent) / app;
   } else {
     pt.y = (aParams.textBaselinePt.y - mAscent) / app;
   }
   gfxFloat decorationOffsetDir = mTextRun->IsSidewaysLeft() ? -1.0 : 1.0;
   SelectionType nextSelectionType;
   TextRangeStyle selectedStyle;
+
   size_t selectionIndex = 0;
+  auto* textDrawer = aParams.context->GetTextDrawer();
+
   while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
                                  &nextSelectionType, &selectedStyle)) {
-    if (aParams.textDrawer) {
-      aParams.textDrawer->SetSelectionIndex(selectionIndex);
+    if (textDrawer) {
+      textDrawer->SetSelectionIndex(selectionIndex);
     }
     gfxFloat advance = hyphenWidth +
       mTextRun->GetAdvanceWidth(range, aParams.provider);
     if (nextSelectionType == aSelectionType) {
       if (verticalRun) {
         pt.y = (aParams.framePt.y + iOffset -
                (mTextRun->IsInlineReversed() ? advance : 0)) / app;
       } else {
         pt.x = (aParams.framePt.x + iOffset -
                (mTextRun->IsInlineReversed() ? advance : 0)) / app;
       }
       gfxFloat width = Abs(advance) / app;
       gfxFloat xInFrame = pt.x - (aParams.framePt.x / app);
       DrawSelectionDecorations(
-        aParams.context, aParams.textDrawer, aParams.dirtyRect, aSelectionType,
+        aParams.context, aParams.dirtyRect, aSelectionType,
         *aParams.textPaintStyle, selectedStyle, pt, xInFrame,
         width, mAscent / app, decorationMetrics, aParams.callbacks,
         verticalRun, decorationOffsetDir, kDecoration);
     }
     iterator.UpdateWithAdvance(advance);
     ++selectionIndex;
   }
 }
@@ -6856,17 +6799,16 @@ nsTextFrame::PaintTextWithSelection(
     }
   }
 
   return true;
 }
 
 void
 nsTextFrame::DrawEmphasisMarks(gfxContext* aContext,
-                               TextDrawTarget* aTextDrawer,
                                WritingMode aWM,
                                const gfxPoint& aTextBaselinePt,
                                const gfxPoint& aFramePt, Range aRange,
                                const nscolor* aDecorationOverrideColor,
                                PropertyProvider* aProvider)
 {
   const EmphasisMarkInfo* info = GetProperty(EmphasisMarkProperty());
   if (!info) {
@@ -6894,22 +6836,21 @@ nsTextFrame::DrawEmphasisMarks(gfxContex
   } else {
     if (aWM.IsVerticalRL()) {
       pt.x -= info->baselineOffset;
     } else {
       pt.x += info->baselineOffset;
     }
   }
   if (!isTextCombined) {
-    mTextRun->DrawEmphasisMarks(aContext, aTextDrawer, info->textRun.get(),
+    mTextRun->DrawEmphasisMarks(aContext, info->textRun.get(),
                                 info->advance, pt, aRange, aProvider);
   } else {
     pt.y += (GetSize().height - info->advance) / 2;
     gfxTextRun::DrawParams params(aContext);
-    params.textDrawer = aTextDrawer;
     info->textRun->Draw(Range(info->textRun.get()), pt,
                         params);
   }
 }
 
 nscolor
 nsTextFrame::GetCaretColorAt(int32_t aOffset)
 {
@@ -7272,17 +7213,16 @@ nsTextFrame::PaintText(const PaintTextPa
     shadowParams.provider = &provider;
     shadowParams.foregroundColor = foregroundColor;
     shadowParams.clipEdges = &clipEdges;
     PaintShadows(textStyle->mTextShadow, shadowParams);
   }
 
   gfxFloat advanceWidth;
   DrawTextParams params(aParams.context);
-  params.textDrawer = aParams.textDrawer;
   params.dirtyRect = aParams.dirtyRect;
   params.framePt = aParams.framePt;
   params.provider = &provider;
   params.advanceWidth = &advanceWidth;
   params.textStyle = &textPaintStyle;
   params.textColor = foregroundColor;
   params.textStrokeColor = textStrokeColor;
   params.textStrokeWidth = textPaintStyle.GetWebkitTextStrokeWidth();
@@ -7298,34 +7238,34 @@ nsTextFrame::PaintText(const PaintTextPa
 
 static void
 DrawTextRun(const gfxTextRun* aTextRun,
             const gfxPoint& aTextBaselinePt,
             gfxTextRun::Range aRange,
             const nsTextFrame::DrawTextRunParams& aParams)
 {
   gfxTextRun::DrawParams params(aParams.context);
-  params.textDrawer = aParams.textDrawer;
   params.provider = aParams.provider;
   params.advanceWidth = aParams.advanceWidth;
   params.contextPaint = aParams.contextPaint;
   params.callbacks = aParams.callbacks;
   if (aParams.callbacks) {
     aParams.callbacks->NotifyBeforeText(aParams.textColor);
     params.drawMode = DrawMode::GLYPH_PATH;
     aTextRun->Draw(aRange, aTextBaselinePt, params);
     aParams.callbacks->NotifyAfterText();
   } else {
-    if (NS_GET_A(aParams.textColor) != 0 || aParams.textDrawer) {
+    auto* textDrawer = aParams.context->GetTextDrawer();
+    if (NS_GET_A(aParams.textColor) != 0 || textDrawer) {
       aParams.context->SetColor(Color::FromABGR(aParams.textColor));
     } else {
       params.drawMode = DrawMode::GLYPH_STROKE;
     }
 
-    if ((NS_GET_A(aParams.textStrokeColor) != 0 || aParams.textDrawer) &&
+    if ((NS_GET_A(aParams.textStrokeColor) != 0 || textDrawer) &&
         aParams.textStrokeWidth != 0.0f) {
       StrokeOptions strokeOpts;
       params.drawMode |= DrawMode::GLYPH_STROKE;
       params.textStrokeColor = aParams.textStrokeColor;
       strokeOpts.mLineWidth = aParams.textStrokeWidth;
       params.strokeOpts = &strokeOpts;
       aTextRun->Draw(aRange, aTextBaselinePt, params);
     } else {
@@ -7335,18 +7275,19 @@ DrawTextRun(const gfxTextRun* aTextRun,
 }
 
 void
 nsTextFrame::DrawTextRun(Range aRange, const gfxPoint& aTextBaselinePt,
                          const DrawTextRunParams& aParams)
 {
   MOZ_ASSERT(aParams.advanceWidth, "Must provide advanceWidth");
 
-  if (aParams.textDrawer) {
-    aParams.textDrawer->StartDrawing(TextDrawTarget::Phase::eGlyphs);
+  auto* textDrawer = aParams.context->GetTextDrawer();
+  if (textDrawer) {
+    textDrawer->StartDrawing(TextDrawTarget::Phase::eGlyphs);
   }
 
   ::DrawTextRun(mTextRun, aTextBaselinePt, aRange, aParams);
 
   if (aParams.drawSoftHyphen) {
     // Don't use ctx as the context, because we need a reference context here,
     // ctx may be transformed.
     RefPtr<gfxTextRun> hyphenTextRun =
@@ -7423,17 +7364,16 @@ nsTextFrame::DrawTextRunAndDecorations(R
     // so we will multiply the values from metrics by this factor.
     gfxFloat decorationOffsetDir = mTextRun->IsSidewaysLeft() ? -1.0 : 1.0;
 
     PaintDecorationLineParams params;
     params.context = aParams.context;
     params.dirtyRect = aParams.dirtyRect;
     params.overrideColor = aParams.decorationOverrideColor;
     params.callbacks = aParams.callbacks;
-    params.textDrawer = aParams.textDrawer;
     // pt is the physical point where the decoration is to be drawn,
     // relative to the frame; one of its coordinates will be updated below.
     params.pt = Point(x / app, y / app);
     Float& bCoord = verticalDec ? params.pt.x : params.pt.y;
     params.lineSize = Size(measure / app, 0);
     params.ascent = ascent;
     params.vertical = verticalDec;
 
@@ -7470,30 +7410,32 @@ nsTextFrame::DrawTextRunAndDecorations(R
       bCoord = (frameBStart - dec.mBaselineOffset) / app;
 
       params.color = dec.mColor;
       params.offset = decorationOffsetDir * metrics.*lineOffset;
       params.style = dec.mStyle;
       PaintDecorationLine(params);
     };
 
+    auto* textDrawer = aParams.context->GetTextDrawer();
+
     // Underlines
-    if (aParams.textDrawer && aDecorations.mUnderlines.Length() > 0) {
-      aParams.textDrawer->StartDrawing(TextDrawTarget::Phase::eUnderline);
+    if (textDrawer && aDecorations.mUnderlines.Length() > 0) {
+      textDrawer->StartDrawing(TextDrawTarget::Phase::eUnderline);
     }
     //
     params.decoration = NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
     for (const LineDecoration& dec : Reversed(aDecorations.mUnderlines)) {
       paintDecorationLine(dec, &Metrics::underlineSize,
                           &Metrics::underlineOffset);
     }
 
     // Overlines
-    if (aParams.textDrawer && aDecorations.mOverlines.Length() > 0) {
-      aParams.textDrawer->StartDrawing(TextDrawTarget::Phase::eOverline);
+    if (textDrawer && aDecorations.mOverlines.Length() > 0) {
+      textDrawer->StartDrawing(TextDrawTarget::Phase::eOverline);
     }
     params.decoration = NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
     for (const LineDecoration& dec : Reversed(aDecorations.mOverlines)) {
       paintDecorationLine(dec, &Metrics::underlineSize, &Metrics::maxAscent);
     }
 
     {
       gfxContextMatrixAutoSaveRestore unscaledRestorer;
@@ -7503,26 +7445,26 @@ nsTextFrame::DrawTextRunAndDecorations(R
       }
 
       // CSS 2.1 mandates that text be painted after over/underlines,
       // and *then* line-throughs
       DrawTextRun(aRange, aTextBaselinePt, aParams);
     }
 
     // Emphasis marks
-    if (aParams.textDrawer) {
-      aParams.textDrawer->StartDrawing(TextDrawTarget::Phase::eEmphasisMarks);
-    }
-    DrawEmphasisMarks(aParams.context, aParams.textDrawer, wm,
+    if (textDrawer) {
+      textDrawer->StartDrawing(TextDrawTarget::Phase::eEmphasisMarks);
+    }
+    DrawEmphasisMarks(aParams.context, wm,
                       aTextBaselinePt, aParams.framePt, aRange,
                       aParams.decorationOverrideColor, aParams.provider);
 
     // Line-throughs
-    if (aParams.textDrawer && aDecorations.mStrikes.Length() > 0) {
-      aParams.textDrawer->StartDrawing(TextDrawTarget::Phase::eLineThrough);
+    if (textDrawer && aDecorations.mStrikes.Length() > 0) {
+      textDrawer->StartDrawing(TextDrawTarget::Phase::eLineThrough);
     }
     params.decoration = NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
     for (const LineDecoration& dec : Reversed(aDecorations.mStrikes)) {
       paintDecorationLine(dec, &Metrics::strikeoutSize,
                           &Metrics::strikeoutOffset);
     }
 }
 
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -15,17 +15,16 @@
 #include "nsGenericDOMDataNode.h"
 #include "nsSplittableFrame.h"
 #include "nsLineBox.h"
 #include "gfxSkipChars.h"
 #include "gfxTextRun.h"
 #include "nsDisplayList.h"
 #include "JustificationUtils.h"
 #include "RubyUtils.h"
-#include "TextDrawTarget.h"
 
 // Undo the windows.h damage
 #if defined(XP_WIN) && defined(DrawText)
 #undef DrawText
 #endif
 
 class nsTextPaintStyle;
 class PropertyProvider;
@@ -44,17 +43,16 @@ class nsTextFrame : public nsFrame
   typedef mozilla::LayoutDeviceRect LayoutDeviceRect;
   typedef mozilla::SelectionTypeMask SelectionTypeMask;
   typedef mozilla::SelectionType SelectionType;
   typedef mozilla::TextRangeStyle TextRangeStyle;
   typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::Point Point;
   typedef mozilla::gfx::Rect Rect;
   typedef mozilla::gfx::Size Size;
-  typedef mozilla::layout::TextDrawTarget TextDrawTarget;
   typedef gfxTextRun::Range Range;
 
 public:
   explicit nsTextFrame(nsStyleContext* aContext, ClassID aID = kClassID)
     : nsFrame(aContext, aID)
     , mNextContinuation(nullptr)
     , mContentOffset(0)
     , mContentLengthHint(0)
@@ -438,34 +436,33 @@ public:
      * has been emitted to the gfxContext.
      */
     virtual void NotifySelectionDecorationLinePathEmitted() {}
   };
 
   struct PaintTextParams
   {
     gfxContext* context;
-    TextDrawTarget* textDrawer;
     gfxPoint framePt;
     LayoutDeviceRect dirtyRect;
     mozilla::SVGContextPaint* contextPaint = nullptr;
     DrawPathCallbacks* callbacks = nullptr;
     enum
     {
       PaintText,        // Normal text painting.
       PaintTextBGColor, // Only paint background color of the selected text
                         // range in this state.
       GenerateTextMask  // To generate a mask from a text frame. Should
                         // only paint text itself with opaque color.
                         // Text shadow, text selection color and text
                         // decoration are all discarded in this state.
     };
     uint8_t state = PaintText;
     explicit PaintTextParams(gfxContext* aContext)
-      : context(aContext), textDrawer(nullptr)
+      : context(aContext)
     {
     }
 
     bool IsPaintText() const { return state == PaintText; }
     bool IsGenerateTextMask() const { return state == GenerateTextMask; }
     bool IsPaintBGColor() const { return state == PaintTextBGColor; }
   };
 
@@ -478,27 +475,26 @@ public:
     explicit PaintTextSelectionParams(const PaintTextParams& aParams)
       : PaintTextParams(aParams)
     {}
   };
 
   struct DrawTextRunParams
   {
     gfxContext* context;
-    TextDrawTarget* textDrawer;
     PropertyProvider* provider = nullptr;
     gfxFloat* advanceWidth = nullptr;
     mozilla::SVGContextPaint* contextPaint = nullptr;
     DrawPathCallbacks* callbacks = nullptr;
     nscolor textColor = NS_RGBA(0, 0, 0, 0);
     nscolor textStrokeColor = NS_RGBA(0, 0, 0, 0);
     float textStrokeWidth = 0.0f;
     bool drawSoftHyphen = false;
     explicit DrawTextRunParams(gfxContext* aContext)
-      : context(aContext), textDrawer(nullptr)
+      : context(aContext)
     {}
   };
 
   struct DrawTextParams : DrawTextRunParams
   {
     gfxPoint framePt;
     LayoutDeviceRect dirtyRect;
     const nsTextPaintStyle* textStyle = nullptr;
@@ -533,17 +529,16 @@ public:
     SelectionTypeMask* aAllSelectionTypeMask,
     const nsCharClipDisplayItem::ClipEdges& aClipEdges);
   // helper: paint text decorations for text selected by aSelectionType
   void PaintTextSelectionDecorations(const PaintTextSelectionParams& aParams,
                                      const mozilla::UniquePtr<SelectionDetails>& aDetails,
                                      SelectionType aSelectionType);
 
   void DrawEmphasisMarks(gfxContext* aContext,
-                         TextDrawTarget* aTextDrawer,
                          mozilla::WritingMode aWM,
                          const gfxPoint& aTextBaselinePt,
                          const gfxPoint& aFramePt,
                          Range aRange,
                          const nscolor* aDecorationOverrideColor,
                          PropertyProvider* aProvider);
 
   nscolor GetCaretColorAt(int32_t aOffset) override;
@@ -706,26 +701,24 @@ protected:
 
   struct PaintShadowParams
   {
     gfxTextRun::Range range;
     LayoutDeviceRect dirtyRect;
     gfxPoint framePt;
     gfxPoint textBaselinePt;
     gfxContext* context;
-    TextDrawTarget* textDrawer;
     nscolor foregroundColor = NS_RGBA(0, 0, 0, 0);
     const nsCharClipDisplayItem::ClipEdges* clipEdges = nullptr;
     PropertyProvider* provider = nullptr;
     nscoord leftSideOffset = 0;
     explicit PaintShadowParams(const PaintTextParams& aParams)
       : dirtyRect(aParams.dirtyRect)
       , framePt(aParams.framePt)
       , context(aParams.context)
-      , textDrawer(aParams.textDrawer)
     {
     }
   };
 
   void PaintOneShadow(const PaintShadowParams& aParams,
                       nsCSSShadowItem* aShadowDetails,
                       gfxRect& aBoundingBox,
                       uint32_t aBlurFlags);
@@ -824,17 +817,16 @@ protected:
   // If the result rect is larger than the given rect, this returns true.
   bool CombineSelectionUnderlineRect(nsPresContext* aPresContext,
                                      nsRect& aRect);
 
   /**
    * Utility methods to paint selection.
    */
   void DrawSelectionDecorations(gfxContext* aContext,
-                                TextDrawTarget* aTextDrawer,
                                 const LayoutDeviceRect& aDirtyRect,
                                 mozilla::SelectionType aSelectionType,
                                 nsTextPaintStyle& aTextPaintStyle,
                                 const TextRangeStyle& aRangeStyle,
                                 const Point& aPt,
                                 gfxFloat aICoordInFrame,
                                 gfxFloat aWidth,
                                 gfxFloat aAscent,
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -3063,17 +3063,17 @@ ComputeSpacedRepeatSize(nscoord aImageDi
   }
 }
 
 /* static */ nscoord
 nsCSSRendering::ComputeBorderSpacedRepeatSize(nscoord aImageDimension,
                                               nscoord aAvailableSpace,
                                               nscoord& aSpace)
 {
-  int32_t count = aAvailableSpace / aImageDimension;
+  int32_t count = aImageDimension ? (aAvailableSpace / aImageDimension) : 0;
   aSpace = (aAvailableSpace - aImageDimension * count) / (count + 1);
   return aSpace + aImageDimension;
 }
 
 nsBackgroundLayerState
 nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext,
                                   nsIFrame* aForFrame,
                                   uint32_t aFlags,
@@ -3824,16 +3824,21 @@ nsCSSRendering::PaintDecorationLine(nsIF
   ColorPattern colorPat(color);
   StrokeOptions strokeOptions(lineThickness);
   DrawOptions drawOptions;
 
   Float dash[2];
 
   AutoPopClips autoPopClips(&aDrawTarget);
 
+  mozilla::layout::TextDrawTarget* textDrawer = nullptr;
+  if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) {
+    textDrawer = static_cast<mozilla::layout::TextDrawTarget*>(&aDrawTarget);
+  }
+
   switch (aParams.style) {
     case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
     case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
       break;
     case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
       autoPopClips.PushClipRect(rect);
       Float dashWidth = lineThickness * DOT_LENGTH * DASH_LENGTH;
       dash[0] = dashWidth;
@@ -3894,18 +3899,18 @@ nsCSSRendering::PaintDecorationLine(nsIF
   }
 
   switch (aParams.style) {
     case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
     case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
     case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
       Point p1 = rect.TopLeft();
       Point p2 = aParams.vertical ? rect.BottomLeft() : rect.TopRight();
-      if (aParams.textDrawer) {
-        aParams.textDrawer->AppendDecoration(
+      if (textDrawer) {
+        textDrawer->AppendDecoration(
           p1, p2, lineThickness, aParams.vertical, color, aParams.style);
       } else {
         aDrawTarget.StrokeLine(p1, p2, colorPat, strokeOptions, drawOptions);
       }
       return;
     }
     case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE: {
       /**
@@ -3929,21 +3934,21 @@ nsCSSRendering::PaintDecorationLine(nsIF
         rect.width -= lineThickness;
       } else {
         rect.height -= lineThickness;
       }
 
       Point p1b = aParams.vertical ? rect.TopRight() : rect.BottomLeft();
       Point p2b = rect.BottomRight();
 
-      if (aParams.textDrawer) {
-        aParams.textDrawer->AppendDecoration(
+      if (textDrawer) {
+        textDrawer->AppendDecoration(
           p1a, p2a, lineThickness, aParams.vertical, color,
           NS_STYLE_TEXT_DECORATION_STYLE_SOLID);
-        aParams.textDrawer->AppendDecoration(
+        textDrawer->AppendDecoration(
           p1b, p2b, lineThickness, aParams.vertical, color,
           NS_STYLE_TEXT_DECORATION_STYLE_SOLID);
       } else {
         aDrawTarget.StrokeLine(p1a, p2a, colorPat, strokeOptions, drawOptions);
         aDrawTarget.StrokeLine(p1b, p2b, colorPat, strokeOptions, drawOptions);
       }
       return;
     }
@@ -4003,21 +4008,21 @@ nsCSSRendering::PaintDecorationLine(nsIF
       int32_t skipCycles = floor((dirtyRectICoord - rectICoord) / cycleLength);
       if (skipCycles > 0) {
         rectICoord += skipCycles * cycleLength;
         rectISize -= skipCycles * cycleLength;
       }
 
       rectICoord += lineThickness / 2.0;
 
-      if (aParams.textDrawer) {
+      if (textDrawer) {
         Point p1 = rect.TopLeft();
         Point p2 = aParams.vertical ? rect.BottomLeft() : rect.TopRight();
 
-        aParams.textDrawer->AppendDecoration(
+        textDrawer->AppendDecoration(
           p1, p2, adv, aParams.vertical, color,
           NS_STYLE_TEXT_DECORATION_STYLE_WAVY);
         return;
       }
 
       Point pt(rect.TopLeft());
       Float& ptICoord = aParams.vertical ? pt.y : pt.x;
       Float& ptBCoord = aParams.vertical ? pt.x : pt.y;
--- a/layout/painting/nsCSSRendering.h
+++ b/layout/painting/nsCSSRendering.h
@@ -14,17 +14,16 @@
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/TypedEnumBits.h"
 #include "nsLayoutUtils.h"
 #include "nsStyleStruct.h"
 #include "nsIFrame.h"
 #include "nsImageRenderer.h"
 #include "nsCSSRenderingBorders.h"
-#include "TextDrawTarget.h"
 
 class gfxContext;
 class nsStyleContext;
 class nsPresContext;
 
 namespace mozilla {
 
 namespace gfx {
@@ -595,17 +594,16 @@ struct nsCSSRendering {
     Rect dirtyRect;
     // The top/left edge of the text.
     Point pt;
     // The color of the decoration line.
     nscolor color = NS_RGBA(0, 0, 0, 0);
     // The distance between the left edge of the given frame and the
     // position of the text as positioned without offset of the shadow.
     Float icoordInFrame = 0.0f;
-    mozilla::layout::TextDrawTarget* textDrawer = nullptr;
   };
 
   /**
    * Function for painting the decoration lines for the text.
    *
    *   input:
    *     @param aFrame            the frame which needs the decoration line
    *     @param aGfxContext
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -6260,27 +6260,33 @@ nsDisplayOpacity::CreateWebRenderCommand
   float* opacityForSC = &mOpacity;
 
   RefPtr<WebRenderAnimationData> animationData = aManager->CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(this);
   AnimationInfo& animationInfo = animationData->GetAnimationInfo();
   AddAnimationsForProperty(Frame(), aDisplayListBuilder,
                            this, eCSSProperty_opacity,
                            animationInfo, false);
   animationInfo.StartPendingAnimations(aManager->GetAnimationReadyTime());
-  uint64_t animationsId = 0;
+
+  // Note that animationsId can be 0 (uninitialized in AnimationInfo) if there
+  // are no active animations.
+  uint64_t animationsId = animationInfo.GetCompositorAnimationsId();
 
   if (!animationInfo.GetAnimations().IsEmpty()) {
-    animationsId = animationInfo.GetCompositorAnimationsId();
     opacityForSC = nullptr;
     OptionalOpacity opacityForCompositor = mOpacity;
 
     OpAddCompositorAnimations
       anim(CompositorAnimations(animationInfo.GetAnimations(), animationsId),
            void_t(), opacityForCompositor);
     aManager->WrBridge()->AddWebRenderParentCommand(anim);
+    aManager->AddActiveCompositorAnimationId(animationsId);
+  } else if (animationsId) {
+    aManager->AddCompositorAnimationsIdForDiscard(animationsId);
+    animationsId = 0;
   }
 
   nsTArray<mozilla::wr::WrFilterOp> filters;
   StackingContextHelper sc(aSc,
                            aBuilder,
                            aDisplayListBuilder,
                            this,
                            &mList,
@@ -8023,34 +8029,39 @@ nsDisplayTransform::CreateWebRenderComma
 
   RefPtr<WebRenderAnimationData> animationData = aManager->CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(this);
 
   AnimationInfo& animationInfo = animationData->GetAnimationInfo();
   AddAnimationsForProperty(Frame(), aDisplayListBuilder,
                            this, eCSSProperty_transform,
                            animationInfo, false);
   animationInfo.StartPendingAnimations(aManager->GetAnimationReadyTime());
-  uint64_t animationsId = 0;
+
+  // Note that animationsId can be 0 (uninitialized in AnimationInfo) if there
+  // are no active animations.
+  uint64_t animationsId = animationInfo.GetCompositorAnimationsId();
 
   if (!animationInfo.GetAnimations().IsEmpty()) {