Merge inbound to mozilla-central a=merge
authorCoroiu Cristina <ccoroiu@mozilla.com>
Mon, 09 Apr 2018 11:58:23 +0300
changeset 412331 30d72755b1749953d438199456f1524ce84ab5e5
parent 412323 d07aaff2c4e14757b0e0a6d49768ce00f8e2fbf4 (current diff)
parent 412330 8fbe9eda393abf440bf773ff6512a17fa6daad23 (diff)
child 412339 40a679f1cb7cf17bfc564b281a5bb742ea62c3fd
child 412375 fb64935b617281e7acab674f2b41dfc2bfc943fb
push id33799
push userccoroiu@mozilla.com
push dateMon, 09 Apr 2018 08:58:46 +0000
treeherdermozilla-central@30d72755b174 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
30d72755b174 / 61.0a1 / 20180409100035 / files
nightly linux64
30d72755b174 / 61.0a1 / 20180409100035 / files
nightly mac
30d72755b174 / 61.0a1 / 20180409100035 / files
nightly win32
30d72755b174 / 61.0a1 / 20180409100035 / files
nightly win64
30d72755b174 / 61.0a1 / 20180409100035 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central a=merge
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_multi_process.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_multi_process.js
@@ -78,28 +78,29 @@ var gTests = [
     ok(webrtcUI.showGlobalIndicator, "webrtcUI wants the global indicator shown");
     ok(webrtcUI.showCameraIndicator, "webrtcUI wants the camera indicator shown");
     ok(webrtcUI.showMicrophoneIndicator, "webrtcUI wants the mic indicator shown");
     is(webrtcUI.getActiveStreams(false, true).length, 1, "1 active audio stream");
     is(webrtcUI.getActiveStreams(true).length, 1, "1 active video stream");
     is(webrtcUI.getActiveStreams(true, true, true).length, 2, "2 active streams");
 
     info("removing the second tab");
-    // FIXME: This should wait for indicator update instead (bug 1444007).
-    let sessionStorePromise = BrowserTestUtils.waitForSessionStoreUpdate(tab);
+
     BrowserTestUtils.removeTab(tab);
-    await sessionStorePromise;
 
     // Check that we still show the sharing indicators for the first tab's stream.
-    await TestUtils.waitForCondition(() => !webrtcUI.showCameraIndicator);
+    await Promise.all([
+      TestUtils.waitForCondition(() => !webrtcUI.showCameraIndicator),
+      TestUtils.waitForCondition(() => webrtcUI.getActiveStreams(true, true, true).length == 1),
+    ]);
+
     ok(webrtcUI.showGlobalIndicator, "webrtcUI wants the global indicator shown");
     ok(!webrtcUI.showCameraIndicator, "webrtcUI wants the camera indicator hidden");
     ok(webrtcUI.showMicrophoneIndicator, "webrtcUI wants the mic indicator shown");
     is(webrtcUI.getActiveStreams(false, true).length, 1, "1 active audio stream");
-    is(webrtcUI.getActiveStreams(true, true, true).length, 1, "1 active stream");
 
     await checkSharingUI({audio: true});
 
     // When both tabs use the same content process, the frame script for the
     // first tab receives observer notifications for things happening in the
     // second tab, so let's clear the observer call counts before we cleanup
     // in the first tab.
     await ignoreObserversCalled();
@@ -301,17 +302,20 @@ var gTests = [
 
     info("removing the second tab");
     BrowserTestUtils.removeTab(tab);
 
     // When both tabs use the same content process, the frame script for the
     // first tab receives observer notifications for things happening in the
     // second tab, so let's clear the observer call counts before we cleanup
     // in the first tab.
-    await ignoreObserversCalled();
+    await Promise.all([
+      TestUtils.waitForCondition(() => webrtcUI.getActiveStreams(true, true, true).length == 1),
+      ignoreObserversCalled(),
+    ]);
 
     // Close the first tab's stream and verify that all indicators are removed.
     await closeStream();
 
     ok(!webrtcUI.showGlobalIndicator, "webrtcUI wants the global indicator hidden");
     is(webrtcUI.getActiveStreams(true, true, true).length, 0, "0 active streams");
   }
 },
copy from browser/extensions/onboarding/content/onboarding.js
copy to browser/extensions/onboarding/content/Onboarding.jsm
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/Onboarding.jsm
@@ -1,21 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env mozilla/frame-script */
 
 "use strict";
 
+var EXPORTED_SYMBOLS = ["Onboarding"];
+
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 const ONBOARDING_CSS_URL = "resource://onboarding/onboarding.css";
-const ABOUT_HOME_URL = "about:home";
-const ABOUT_NEWTAB_URL = "about:newtab";
 const BUNDLE_URI = "chrome://onboarding/locale/onboarding.properties";
 const UITOUR_JS_URI = "resource://onboarding/lib/UITour-lib.js";
 const TOUR_AGENT_JS_URI = "resource://onboarding/onboarding-tour-agent.js";
 const BRAND_SHORT_NAME = Services.strings
                      .createBundle("chrome://branding/locale/brand.properties")
                      .GetStringFromName("brandShortName");
 const PROMPT_COUNT_PREF = "browser.onboarding.notification.prompt-count";
 const NOTIFICATION_FINISHED_PREF = "browser.onboarding.notification.finished";
@@ -306,18 +306,18 @@ var onboardingTourset = {
       connectDeviceButton.setAttribute("data-l10n-id", "onboarding.tour-sync.connect-device.button");
       aside.appendChild(connectDeviceButton);
 
       div.addEventListener("beforeshow", () => {
         function loginStatusListener(msg) {
           removeMessageListener("Onboarding:ResponseLoginStatus", loginStatusListener);
           div.dataset.loginState = msg.data.isLoggedIn ? STATE_LOGIN : STATE_LOGOUT;
         }
-        sendMessageToChrome("get-login-status");
-        addMessageListener("Onboarding:ResponseLoginStatus", loginStatusListener);
+        this.sendMessageToChrome("get-login-status");
+        this.mm.addMessageListener("Onboarding:ResponseLoginStatus", loginStatusListener);
       });
 
       return div;
     },
   },
   "library": {
     id: "onboarding-tour-library",
     tourNameId: "onboarding.tour-library",
@@ -412,48 +412,50 @@ var onboardingTourset = {
       button.setAttribute("target", "_blank");
 
       return div;
     },
   },
 };
 
 /**
- * @param {String} action the action to ask the chrome to do
- * @param {Array | Object} params the parameters for the action
- */
-function sendMessageToChrome(action, params) {
-  sendAsyncMessage("Onboarding:OnContentMessage", {
-    action, params
-  });
-}
-
-/**
- * Template code for talking to `PingCentre`
- * @param {Object} data the payload for the telemetry
- */
-function telemetry(data) {
-   sendMessageToChrome("ping-centre", {data});
-}
-
-function registerNewTelemetrySession(data) {
-  telemetry(Object.assign(data, {
-    type: "onboarding-register-session",
-  }));
-}
-
-/**
  * The script won't be initialized if we turned off onboarding by
  * setting "browser.onboarding.enabled" to false.
  */
 class Onboarding {
-  constructor(contentWindow) {
+  constructor(mm, contentWindow) {
+    this.mm = mm;
     this.init(contentWindow);
   }
 
+
+  /**
+   * @param {String} action the action to ask the chrome to do
+   * @param {Array | Object} params the parameters for the action
+   */
+  sendMessageToChrome(action, params) {
+    this.mm.sendAsyncMessage("Onboarding:OnContentMessage", {
+      action, params
+    });
+  }
+
+  /**
+   * Template code for talking to `PingCentre`
+   * @param {Object} data the payload for the telemetry
+   */
+  telemetry(data) {
+     this.sendMessageToChrome("ping-centre", {data});
+  }
+
+  registerNewTelemetrySession(data) {
+    this.telemetry(Object.assign(data, {
+      type: "onboarding-register-session",
+    }));
+  }
+
   async init(contentWindow) {
     this._window = contentWindow;
     // session_key is used for telemetry to track the current tab.
     // The number will renew after reloading the page.
     this._session_key = Date.now();
     this._tours = [];
     this._tourType = Services.prefs.getStringPref("browser.onboarding.tour-type", "update");
 
@@ -489,29 +491,29 @@ class Onboarding {
       };
       doc.addEventListener("visibilitychange", onVisible);
     } else {
       this._startUI();
     }
   }
 
   _startUI() {
-    registerNewTelemetrySession({
+    this.registerNewTelemetrySession({
       page: this._window.location.href,
       session_key: this._session_key,
       tour_type: this._tourType,
     });
 
     this._window.addEventListener("beforeunload", this);
     this._window.addEventListener("unload", this);
     this._window.addEventListener("resize", this);
     this._resizeTimerId =
       this._window.requestIdleCallback(() => this._resizeUI());
     // start log the onboarding-session when the tab is visible
-    telemetry({
+    this.telemetry({
       type: "onboarding-session-begin",
       session_key: this._session_key,
     });
   }
 
   _resizeUI() {
     this._windowWidth = this._window.document.body.getBoundingClientRect().width;
     if (this._windowWidth < ONBOARDING_MIN_WIDTH_PX) {
@@ -586,17 +588,17 @@ class Onboarding {
     for (let [name, callback] of this._prefsObserved) {
       Services.prefs.addObserver(name, callback);
     }
   }
 
   _checkWatermarkByTours() {
     let tourDone = this._tours.every(tour => this.isTourCompleted(tour.id));
     if (tourDone) {
-      sendMessageToChrome("set-prefs", [{
+      this.sendMessageToChrome("set-prefs", [{
         name: "browser.onboarding.state",
         value: ICON_STATE_WATERMARK
       }]);
     }
   }
 
   _clearPrefObserver() {
     if (this._prefsObserved) {
@@ -693,17 +695,17 @@ class Onboarding {
     // Only containers receive pointer events in onboarding tour tab list,
     // actual semantic tab is their first child.
     if (classList.contains("onboarding-tour-item-container")) {
       ({ id, classList } = target.firstChild);
     }
 
     switch (id) {
       case "onboarding-overlay-button":
-        telemetry({
+        this.telemetry({
           type: "onboarding-logo-click",
           bubble_state: this._bubbleState,
           logo_state: this._logoState,
           notification_state: this._notificationState,
           session_key: this._session_key,
           width: this._windowWidthRounded,
         });
         this.showOverlay();
@@ -716,76 +718,76 @@ class Onboarding {
         break;
       case "onboarding-overlay-close-btn":
       // If the clicking target is directly on the outer-most overlay,
       // that means clicking outside the tour content area.
       // Let's toggle the overlay.
       case "onboarding-overlay":
         let eventName = id === "onboarding-overlay-close-btn" ?
           "overlay-close-button-click" : "overlay-close-outside-click";
-        telemetry({
+        this.telemetry({
           type: eventName,
           current_tour_id: this._activeTourId,
           session_key: this._session_key,
           target_tour_id: this._activeTourId,
           width: this._windowWidthRounded,
         });
         this.hideOverlay();
         break;
       case "onboarding-notification-close-btn":
         let currentTourId = this._notificationBar.dataset.targetTourId;
         // should trigger before notification-session event is sent
-        telemetry({
+        this.telemetry({
           type: "notification-close-button-click",
           bubble_state: this._bubbleState,
           current_tour_id: currentTourId,
           logo_state: this._logoState,
           notification_impression: this._notificationPromptCount,
           notification_state: this._notificationState,
           session_key: this._session_key,
           target_tour_id: currentTourId,
           width: this._windowWidthRounded,
         });
         this.hideNotification();
         this._removeTourFromNotificationQueue(currentTourId);
         break;
       case "onboarding-notification-action-btn":
         let tourId = this._notificationBar.dataset.targetTourId;
-        telemetry({
+        this.telemetry({
           type: "notification-cta-click",
           bubble_state: this._bubbleState,
           current_tour_id: tourId,
           logo_state: this._logoState,
           notification_impression: this._notificationPromptCount,
           notification_state: this._notificationState,
           session_key: this._session_key,
           target_tour_id: tourId,
           width: this._windowWidthRounded,
         });
         this.showOverlay();
         this.gotoPage(tourId);
         this._removeTourFromNotificationQueue(tourId);
         break;
     }
     if (classList.contains("onboarding-tour-item")) {
-      telemetry({
+      this.telemetry({
         type: "overlay-nav-click",
         current_tour_id: this._activeTourId,
         session_key: this._session_key,
         target_tour_id: id,
         width: this._windowWidthRounded,
       });
       this.gotoPage(id);
       // Keep focus (not visible) on current item for potential keyboard
       // navigation.
       target.focus();
     } else if (classList.contains("onboarding-tour-action-button")) {
       let activeTourId = this._activeTourId;
       this.setToursCompleted([ activeTourId ]);
-      telemetry({
+      this.telemetry({
         type: "overlay-cta-click",
         current_tour_id: activeTourId,
         session_key: this._session_key,
         target_tour_id: activeTourId,
         width: this._windowWidthRounded,
       });
     }
   }
@@ -903,17 +905,17 @@ class Onboarding {
     switch (evt.type) {
       case "beforeunload":
         // To make sure the telemetry pings are sent,
         // we send "onboarding-session-end" ping as well as
         // "overlay-session-end" and "notification-session-end" ping
         // (by hiding the overlay and notificaiton) on beforeunload.
         this.hideOverlay();
         this.hideNotification();
-        telemetry({
+        this.telemetry({
           type: "onboarding-session-end",
           session_key: this._session_key,
         });
         break;
       case "unload":
         // Notice: Cannot do `destroy` on beforeunload, must do on unload.
         // Otherwise, we would hit the docShell leak in the test.
         // See Bug 1413830#c190 and Bug 1429652 for details.
@@ -979,28 +981,28 @@ class Onboarding {
       // Lazy loading until first toggle.
       this._loadTours(this._tours);
     }
 
     if (this._overlay && !this._overlay.classList.contains("onboarding-opened")) {
       this.hideNotification();
       this._overlay.classList.add("onboarding-opened");
       this.toggleModal(true);
-      telemetry({
+      this.telemetry({
         type: "overlay-session-begin",
         session_key: this._session_key,
       });
     }
   }
 
   hideOverlay() {
     if (this._overlay && this._overlay.classList.contains("onboarding-opened")) {
       this._overlay.classList.remove("onboarding-opened");
       this.toggleModal(false);
-      telemetry({
+      this.telemetry({
         type: "overlay-session-end",
         session_key: this._session_key,
       });
     }
   }
 
   /**
    * Set modal dialog state and properties for accessibility purposes.
@@ -1050,17 +1052,17 @@ class Onboarding {
       } else {
         page.style.display = "none";
       }
     }
     for (let tab of this._tourItems) {
       if (tab.id == tourId) {
         tab.classList.add("onboarding-active");
         tab.setAttribute("aria-selected", true);
-        telemetry({
+        this.telemetry({
           type: "overlay-current-tour",
           current_tour_id: tourId,
           session_key: this._session_key,
           width: this._windowWidthRounded,
         });
 
         // Some tours should complete instantly upon showing.
         if (tab.getAttribute("data-instant-complete")) {
@@ -1083,17 +1085,17 @@ class Onboarding {
       if (!this.isTourCompleted(id)) {
         params.push({
           name: `browser.onboarding.tour.${id}.completed`,
           value: true
         });
       }
     });
     if (params.length > 0) {
-      sendMessageToChrome("set-prefs", params);
+      this.sendMessageToChrome("set-prefs", params);
     }
   }
 
   markTourCompletionState(tourId) {
     // We are doing lazy load so there might be no items.
     if (!this._tourItems || this._tourItems.length === 0) {
       return;
     }
@@ -1152,17 +1154,17 @@ class Onboarding {
   }
 
   _muteNotificationOnFirstSession(lastTourChangeTime) {
     if (!this._isFirstSession) {
       return false;
     }
 
     if (lastTourChangeTime <= 0) {
-      sendMessageToChrome("set-prefs", [{
+      this.sendMessageToChrome("set-prefs", [{
         name: "browser.onboarding.notification.last-time-of-changing-tour-sec",
         value: Math.floor(Date.now() / 1000)
       }]);
       return true;
     }
     let muteDuration = Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms");
     return Date.now() - lastTourChangeTime <= muteDuration;
   }
@@ -1191,34 +1193,34 @@ class Onboarding {
     params.push({
       name: "browser.onboarding.notification.last-time-of-changing-tour-sec",
       value: 0
     });
     params.push({
       name: "browser.onboarding.notification.prompt-count",
       value: 0
     });
-    sendMessageToChrome("set-prefs", params);
+    this.sendMessageToChrome("set-prefs", params);
   }
 
   _getNotificationQueue() {
     let queue = "";
     if (Services.prefs.prefHasUserValue("browser.onboarding.notification.tour-ids-queue")) {
       queue = Services.prefs.getStringPref("browser.onboarding.notification.tour-ids-queue");
     } else {
       // For each tour, it only gets 2 chances to prompt with notification
       // (each chance includes 8 impressions or 5-days max life time)
       // if user never interact with it.
       // Assume there are tour #0 ~ #5. Here would form the queue as
       // "#0,#1,#2,#3,#4,#5,#0,#1,#2,#3,#4,#5".
       // Then we would loop through this queue and remove prompted tour from the queue
       // until the queue is empty.
       let ids = this._tours.map(tour => tour.id).join(",");
       queue = `${ids},${ids}`;
-      sendMessageToChrome("set-prefs", [{
+      this.sendMessageToChrome("set-prefs", [{
         name: "browser.onboarding.notification.tour-ids-queue",
         value: queue
       }]);
     }
     return queue ? queue.split(",") : [];
   }
 
   showNotification() {
@@ -1249,17 +1251,17 @@ class Onboarding {
       queue.shift();
     }
     // We don't want to prompt the completed tour.
     while (queue.length > 0 && this.isTourCompleted(queue[0])) {
       queue.shift();
     }
 
     if (queue.length == 0) {
-      sendMessageToChrome("set-prefs", [
+      this.sendMessageToChrome("set-prefs", [
         {
           name: NOTIFICATION_FINISHED_PREF,
           value: true
         },
         {
           name: "browser.onboarding.notification.tour-ids-queue",
           value: ""
         },
@@ -1305,40 +1307,40 @@ class Onboarding {
       });
     } else {
       promptCount = this._notificationPromptCount + 1;
       params.push({
         name: PROMPT_COUNT_PREF,
         value: promptCount
       });
     }
-    sendMessageToChrome("set-prefs", params);
-    telemetry({
+    this.sendMessageToChrome("set-prefs", params);
+    this.telemetry({
       type: "notification-session-begin",
       session_key: this._session_key
     });
     // since set-perfs is async, pass promptCount directly to avoid gathering the wrong
     // notification_impression.
-    telemetry({
+    this.telemetry({
       type: "notification-appear",
       bubble_state: this._bubbleState,
       current_tour_id: targetTourId,
       logo_state: this._logoState,
       notification_impression: promptCount,
       notification_state: this._notificationState,
       session_key: this._session_key,
       width: this._windowWidthRounded,
     });
   }
 
   hideNotification() {
     if (this._notificationBar) {
       if (this._notificationBar.classList.contains("onboarding-opened")) {
         this._notificationBar.classList.remove("onboarding-opened");
-        telemetry({
+        this.telemetry({
           type: "notification-session-end",
           session_key: this._session_key,
         });
       }
     }
   }
 
   _renderNotificationBar() {
@@ -1383,27 +1385,27 @@ class Onboarding {
     closeButton.setAttribute("title",
       this._bundle.GetStringFromName("onboarding.notification-close-button-tooltip"));
 
     return footer;
   }
 
   skipTour() {
     this.setToursCompleted(this._tours.map(tour => tour.id));
-    sendMessageToChrome("set-prefs", [
+    this.sendMessageToChrome("set-prefs", [
       {
         name: NOTIFICATION_FINISHED_PREF,
         value: true
       },
       {
         name: "browser.onboarding.state",
         value: ICON_STATE_WATERMARK
       }
     ]);
-    telemetry({
+    this.telemetry({
       type: "overlay-skip-tour",
       current_tour_id: this._activeTourId,
       session_key: this._session_key,
       width: this._windowWidthRounded,
     });
   }
 
   _renderOverlay() {
@@ -1517,17 +1519,17 @@ class Onboarding {
       tab.setAttribute("role", "tab");
 
       let tourPanelId = `${tour.id}-page`;
       tab.setAttribute("aria-controls", tourPanelId);
 
       li.appendChild(tab);
       itemsFrag.appendChild(li);
       // Dynamically create tour pages
-      let div = tour.getPage(this._window, this._bundle);
+      let div = tour.getPage.call(this, this._window, this._bundle);
 
       // Do a traverse for elements in the page that need to be localized.
       let l10nElements = div.querySelectorAll("[data-l10n-id]");
       for (let i = 0; i < l10nElements.length; i++) {
         let element = l10nElements[i];
         // We always put brand short name as the first argument for it's the
         // only and frequently used arguments in our l10n case. Rewrite it if
         // other arguments appear.
@@ -1571,31 +1573,8 @@ class Onboarding {
   _loadJS(uri) {
     let doc = this._window.document;
     let script = doc.createElement("script");
     script.type = "text/javascript";
     script.src = uri;
     doc.head.appendChild(script);
   }
 }
-
-// Load onboarding module only when we enable it.
-if (Services.prefs.getBoolPref("browser.onboarding.enabled", false)) {
-  addEventListener("load", function onLoad(evt) {
-    if (!content || evt.target != content.document) {
-      return;
-    }
-
-    let window = evt.target.defaultView;
-    let location = window.location.href;
-    if (location == ABOUT_NEWTAB_URL || location == ABOUT_HOME_URL) {
-      // We just want to run tests as quickly as possible
-      // so in the automation test, we don't do `requestIdleCallback`.
-      if (Cu.isInAutomation) {
-        new Onboarding(window);
-        return;
-      }
-      window.requestIdleCallback(() => {
-        new Onboarding(window);
-      });
-    }
-  }, true);
-}
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -2,1600 +2,35 @@
  * 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/. */
 
 /* eslint-env mozilla/frame-script */
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.defineModuleGetter(this, "Onboarding", "resource://onboarding/Onboarding.jsm");
 
-const ONBOARDING_CSS_URL = "resource://onboarding/onboarding.css";
 const ABOUT_HOME_URL = "about:home";
 const ABOUT_NEWTAB_URL = "about:newtab";
-const BUNDLE_URI = "chrome://onboarding/locale/onboarding.properties";
-const UITOUR_JS_URI = "resource://onboarding/lib/UITour-lib.js";
-const TOUR_AGENT_JS_URI = "resource://onboarding/onboarding-tour-agent.js";
-const BRAND_SHORT_NAME = Services.strings
-                     .createBundle("chrome://branding/locale/brand.properties")
-                     .GetStringFromName("brandShortName");
-const PROMPT_COUNT_PREF = "browser.onboarding.notification.prompt-count";
-const NOTIFICATION_FINISHED_PREF = "browser.onboarding.notification.finished";
-const ONBOARDING_DIALOG_ID = "onboarding-overlay-dialog";
-const ONBOARDING_MIN_WIDTH_PX = 960;
-const SPEECH_BUBBLE_MIN_WIDTH_PX = 1130;
-const SPEECH_BUBBLE_NEWTOUR_STRING_ID = "onboarding.overlay-icon-tooltip2";
-const SPEECH_BUBBLE_UPDATETOUR_STRING_ID = "onboarding.overlay-icon-tooltip-updated2";
-const ICON_STATE_WATERMARK = "watermark";
-const ICON_STATE_DEFAULT = "default";
-
-/**
- * Helper function to create the tour description UI element.
- */
-function createOnboardingTourDescription(div, title, description) {
-  let doc = div.ownerDocument;
-  let section = doc.createElement("section");
-  section.className = "onboarding-tour-description";
-
-  let h1 = doc.createElement("h1");
-  h1.setAttribute("data-l10n-id", title);
-  section.appendChild(h1);
-
-  let p = doc.createElement("p");
-  p.setAttribute("data-l10n-id", description);
-  section.appendChild(p);
-
-  div.appendChild(section);
-  return section;
-}
-
-/**
- * Helper function to create the tour content UI element.
- */
-function createOnboardingTourContent(div, imageSrc) {
-  let doc = div.ownerDocument;
-  let section = doc.createElement("section");
-  section.className = "onboarding-tour-content";
-
-  let img = doc.createElement("img");
-  img.setAttribute("src", imageSrc);
-  img.setAttribute("role", "presentation");
-  section.appendChild(img);
-
-  div.appendChild(section);
-  return section;
-}
-
-/**
- * Helper function to create the tour button UI element.
- */
-function createOnboardingTourButton(div, buttonId, l10nId, buttonElementTagName = "button") {
-  let doc = div.ownerDocument;
-  let aside = doc.createElement("aside");
-  aside.className = "onboarding-tour-button-container";
-
-  let button = doc.createElement(buttonElementTagName);
-  button.id = buttonId;
-  button.className = "onboarding-tour-action-button";
-  button.setAttribute("data-l10n-id", l10nId);
-  aside.appendChild(button);
-
-  div.appendChild(aside);
-  return aside;
-}
-
-/**
- * Add any number of tours, key is the tourId, value should follow the format below
- * "tourId": { // The short tour id which could be saved in pref
- *   // The unique tour id
- *   id: "onboarding-tour-addons",
- *   // (optional) mark tour as complete instantly when the user enters the tour
- *   instantComplete: false,
- *   // The string id of tour name which would be displayed on the navigation bar
- *   tourNameId: "onboarding.tour-addon",
- *   // The method returing strings used on tour notification
- *   getNotificationStrings(bundle):
- *     - title: // The string of tour notification title
- *     - message: // The string of tour notification message
- *     - button: // The string of tour notification action button title
- *   // Return a div appended with elements for this tours.
- *   // Each tour should contain the following 3 sections in the div:
- *   // .onboarding-tour-description, .onboarding-tour-content, .onboarding-tour-button-container.
- *   // If there was a .onboarding-tour-action-button present and was clicked, tour would be marked as completed.
- *   getPage() {},
- * },
- **/
-var onboardingTourset = {
-  "private": {
-    id: "onboarding-tour-private-browsing",
-    tourNameId: "onboarding.tour-private-browsing",
-    getNotificationStrings(bundle) {
-      return {
-        title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-private-browsing.title"),
-        message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-private-browsing.message2"),
-        button: bundle.GetStringFromName("onboarding.button.learnMore"),
-      };
-    },
-    getPage(win) {
-      let div = win.document.createElement("div");
-
-      createOnboardingTourDescription(div,
-        "onboarding.tour-private-browsing.title2", "onboarding.tour-private-browsing.description3");
-      createOnboardingTourContent(div, "resource://onboarding/img/figure_private.svg");
-      createOnboardingTourButton(div,
-        "onboarding-tour-private-browsing-button", "onboarding.tour-private-browsing.button");
-
-      return div;
-    },
-  },
-  "addons": {
-    id: "onboarding-tour-addons",
-    tourNameId: "onboarding.tour-addons",
-    getNotificationStrings(bundle) {
-      return {
-        title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-addons.title"),
-        message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-addons.message", [BRAND_SHORT_NAME], 1),
-        button: bundle.GetStringFromName("onboarding.button.learnMore"),
-      };
-    },
-    getPage(win) {
-      let div = win.document.createElement("div");
-
-      createOnboardingTourDescription(div,
-        "onboarding.tour-addons.title2", "onboarding.tour-addons.description2");
-      createOnboardingTourContent(div, "resource://onboarding/img/figure_addons.svg");
-      createOnboardingTourButton(div,
-        "onboarding-tour-addons-button", "onboarding.tour-addons.button");
-
-      return div;
-    },
-  },
-  "customize": {
-    id: "onboarding-tour-customize",
-    tourNameId: "onboarding.tour-customize",
-    getNotificationStrings(bundle) {
-      return {
-        title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-customize.title"),
-        message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-customize.message", [BRAND_SHORT_NAME], 1),
-        button: bundle.GetStringFromName("onboarding.button.learnMore"),
-      };
-    },
-    getPage(win) {
-      let div = win.document.createElement("div");
-
-      createOnboardingTourDescription(div,
-        "onboarding.tour-customize.title2", "onboarding.tour-customize.description2");
-      createOnboardingTourContent(div, "resource://onboarding/img/figure_customize.svg");
-      createOnboardingTourButton(div,
-        "onboarding-tour-customize-button", "onboarding.tour-customize.button");
-
-      return div;
-    },
-  },
-  "default": {
-    id: "onboarding-tour-default-browser",
-    instantComplete: true,
-    tourNameId: "onboarding.tour-default-browser",
-    getNotificationStrings(bundle) {
-      return {
-        title: bundle.formatStringFromName("onboarding.notification.onboarding-tour-default-browser.title", [BRAND_SHORT_NAME], 1),
-        message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-default-browser.message", [BRAND_SHORT_NAME], 1),
-        button: bundle.GetStringFromName("onboarding.button.learnMore"),
-      };
-    },
-    getPage(win, bundle) {
-      let div = win.document.createElement("div");
-      let setFromBackGround = bundle.formatStringFromName("onboarding.tour-default-browser.win7.button", [BRAND_SHORT_NAME], 1);
-      let setFromPanel = bundle.GetStringFromName("onboarding.tour-default-browser.button");
-      let isDefaultMessage = bundle.GetStringFromName("onboarding.tour-default-browser.is-default.message");
-      let isDefault2ndMessage = bundle.formatStringFromName("onboarding.tour-default-browser.is-default.2nd-message", [BRAND_SHORT_NAME], 1);
-
-      createOnboardingTourDescription(div,
-        "onboarding.tour-default-browser.title2", "onboarding.tour-default-browser.description2");
-      createOnboardingTourContent(div, "resource://onboarding/img/figure_default.svg");
-
-      let aside = win.document.createElement("aside");
-      aside.className = "onboarding-tour-button-container";
-      div.appendChild(aside);
-
-      let button = win.document.createElement("button");
-      button.id = "onboarding-tour-default-browser-button";
-      button.className = "onboarding-tour-action-button";
-      button.setAttribute("data-bg", setFromBackGround);
-      button.setAttribute("data-panel", setFromPanel);
-      aside.appendChild(button);
-
-      let isDefaultBrowserMsg = win.document.createElement("div");
-      isDefaultBrowserMsg.id = "onboarding-tour-is-default-browser-msg";
-      isDefaultBrowserMsg.className = "onboarding-hidden";
-      aside.appendChild(isDefaultBrowserMsg);
-      isDefaultBrowserMsg.append(isDefaultMessage);
-
-      let br = win.document.createElement("br");
-      isDefaultBrowserMsg.appendChild(br);
-      isDefaultBrowserMsg.append(isDefault2ndMessage);
-
-      div.addEventListener("beforeshow", () => {
-        win.document.dispatchEvent(new Event("Agent:CanSetDefaultBrowserInBackground"));
-      });
-      return div;
-    },
-  },
-  "sync": {
-    id: "onboarding-tour-sync",
-    instantComplete: true,
-    tourNameId: "onboarding.tour-sync2",
-    getNotificationStrings(bundle) {
-      return {
-        title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-sync.title"),
-        message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-sync.message"),
-        button: bundle.GetStringFromName("onboarding.button.learnMore"),
-      };
-    },
-    getPage(win, bundle) {
-      const STATE_LOGOUT = "logged-out";
-      const STATE_LOGIN = "logged-in";
-      let div = win.document.createElement("div");
-      div.dataset.loginState = STATE_LOGOUT;
-      // The email validation pattern used in the form comes from IETF rfc5321,
-      // which is identical to server-side checker of Firefox Account. See
-      // discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1378770#c6
-      // for detail.
-      let emailRegex = "^[\\w.!#$%&’*+\\/=?^`{|}~-]{1,64}@[a-z\\d](?:[a-z\\d-]{0,253}[a-z\\d])?(?:\\.[a-z\\d](?:[a-z\\d-]{0,253}[a-z\\d])?)+$";
-
-      let description = createOnboardingTourDescription(div,
-        "onboarding.tour-sync.title2", "onboarding.tour-sync.description2");
-
-      description.querySelector("h1").className = "show-on-logged-out";
-      description.querySelector("p").className = "show-on-logged-out";
-
-      let h1LoggedIn = win.document.createElement("h1");
-      h1LoggedIn.setAttribute("data-l10n-id", "onboarding.tour-sync.logged-in.title");
-      h1LoggedIn.className = "show-on-logged-in";
-      description.appendChild(h1LoggedIn);
-
-      let pLoggedIn = win.document.createElement("p");
-      pLoggedIn.setAttribute("data-l10n-id", "onboarding.tour-sync.logged-in.description");
-      pLoggedIn.className = "show-on-logged-in";
-      description.appendChild(pLoggedIn);
-
-      let content = win.document.createElement("section");
-      content.className = "onboarding-tour-content";
-      div.appendChild(content);
-
-      let form = win.document.createElement("form");
-      form.className = "show-on-logged-out";
-      content.appendChild(form);
-
-      let h3 = win.document.createElement("h3");
-      h3.setAttribute("data-l10n-id", "onboarding.tour-sync.form.title");
-      form.appendChild(h3);
-
-      let p = win.document.createElement("p");
-      p.setAttribute("data-l10n-id", "onboarding.tour-sync.form.description");
-      form.appendChild(p);
-
-      let input = win.document.createElement("input");
-      input.id = "onboarding-tour-sync-email-input";
-      input.setAttribute("required", "true");
-      input.setAttribute("type", "email");
-      input.placeholder =
-        bundle.GetStringFromName("onboarding.tour-sync.email-input.placeholder");
-      input.pattern = emailRegex;
-      form.appendChild(input);
-
-      let br = win.document.createElement("br");
-      form.appendChild(br);
-
-      let button = win.document.createElement("button");
-      button.id = "onboarding-tour-sync-button";
-      button.className = "onboarding-tour-action-button";
-      button.setAttribute("data-l10n-id", "onboarding.tour-sync.button");
-      form.appendChild(button);
-
-      let img = win.document.createElement("img");
-      img.setAttribute("src", "resource://onboarding/img/figure_sync.svg");
-      img.setAttribute("role", "presentation");
-      content.appendChild(img);
-
-      let aside = win.document.createElement("aside");
-      aside.className = "onboarding-tour-button-container show-on-logged-in";
-      div.appendChild(aside);
-
-      let connectDeviceButton = win.document.createElement("button");
-      connectDeviceButton.id = "onboarding-tour-sync-connect-device-button";
-      connectDeviceButton.className = "onboarding-tour-action-button";
-      connectDeviceButton.setAttribute("data-l10n-id", "onboarding.tour-sync.connect-device.button");
-      aside.appendChild(connectDeviceButton);
-
-      div.addEventListener("beforeshow", () => {
-        function loginStatusListener(msg) {
-          removeMessageListener("Onboarding:ResponseLoginStatus", loginStatusListener);
-          div.dataset.loginState = msg.data.isLoggedIn ? STATE_LOGIN : STATE_LOGOUT;
-        }
-        sendMessageToChrome("get-login-status");
-        addMessageListener("Onboarding:ResponseLoginStatus", loginStatusListener);
-      });
-
-      return div;
-    },
-  },
-  "library": {
-    id: "onboarding-tour-library",
-    tourNameId: "onboarding.tour-library",
-    getNotificationStrings(bundle) {
-      return {
-        title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-library.title"),
-        message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-library.message", [BRAND_SHORT_NAME], 1),
-        button: bundle.GetStringFromName("onboarding.button.learnMore"),
-      };
-    },
-    getPage(win) {
-      let div = win.document.createElement("div");
-
-      createOnboardingTourDescription(div,
-        "onboarding.tour-library.title", "onboarding.tour-library.description2");
-      createOnboardingTourContent(div, "resource://onboarding/img/figure_library.svg");
-      createOnboardingTourButton(div,
-        "onboarding-tour-library-button", "onboarding.tour-library.button2");
-
-      return div;
-    },
-  },
-  "singlesearch": {
-    id: "onboarding-tour-singlesearch",
-    tourNameId: "onboarding.tour-singlesearch",
-    getNotificationStrings(bundle) {
-      return {
-        title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-singlesearch.title"),
-        message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-singlesearch.message"),
-        button: bundle.GetStringFromName("onboarding.button.learnMore"),
-      };
-    },
-    getPage(win, bundle) {
-      let div = win.document.createElement("div");
-
-      createOnboardingTourDescription(div,
-        "onboarding.tour-singlesearch.title", "onboarding.tour-singlesearch.description");
-      createOnboardingTourContent(div, "resource://onboarding/img/figure_singlesearch.svg");
-      createOnboardingTourButton(div,
-        "onboarding-tour-singlesearch-button", "onboarding.tour-singlesearch.button");
-
-      return div;
-    },
-  },
-  "performance": {
-    id: "onboarding-tour-performance",
-    instantComplete: true,
-    tourNameId: "onboarding.tour-performance",
-    getNotificationStrings(bundle) {
-      return {
-        title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-performance.title"),
-        message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-performance.message", [BRAND_SHORT_NAME], 1),
-        button: bundle.GetStringFromName("onboarding.button.learnMore"),
-      };
-    },
-    getPage(win, bundle) {
-      let div = win.document.createElement("div");
-
-      createOnboardingTourDescription(div,
-        "onboarding.tour-performance.title", "onboarding.tour-performance.description");
-      createOnboardingTourContent(div, "resource://onboarding/img/figure_performance.svg");
-
-      return div;
-    },
-  },
-  "screenshots": {
-    id: "onboarding-tour-screenshots",
-    tourNameId: "onboarding.tour-screenshots",
-    getNotificationStrings(bundle) {
-      return {
-        title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-screenshots.title"),
-        message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-screenshots.message", [BRAND_SHORT_NAME], 1),
-        button: bundle.GetStringFromName("onboarding.button.learnMore"),
-      };
-    },
-    getPage(win, bundle) {
-      let div = win.document.createElement("div");
-      // Screenshot tour opens the screenshot page directly, see below a#onboarding-tour-screenshots-button.
-      // The screenshots page should be responsible for highlighting the Screenshots button
-
-      createOnboardingTourDescription(div,
-        "onboarding.tour-screenshots.title", "onboarding.tour-screenshots.description");
-      createOnboardingTourContent(div, "resource://onboarding/img/figure_screenshots.svg");
-
-      let aside = createOnboardingTourButton(div,
-                                             "onboarding-tour-screenshots-button",
-                                             "onboarding.tour-screenshots.button",
-                                             "a");
-
-      let button = aside.querySelector("a");
-      button.setAttribute("href", "https://screenshots.firefox.com/#tour");
-      button.setAttribute("target", "_blank");
-
-      return div;
-    },
-  },
-};
-
-/**
- * @param {String} action the action to ask the chrome to do
- * @param {Array | Object} params the parameters for the action
- */
-function sendMessageToChrome(action, params) {
-  sendAsyncMessage("Onboarding:OnContentMessage", {
-    action, params
-  });
-}
-
-/**
- * Template code for talking to `PingCentre`
- * @param {Object} data the payload for the telemetry
- */
-function telemetry(data) {
-   sendMessageToChrome("ping-centre", {data});
-}
-
-function registerNewTelemetrySession(data) {
-  telemetry(Object.assign(data, {
-    type: "onboarding-register-session",
-  }));
-}
-
-/**
- * The script won't be initialized if we turned off onboarding by
- * setting "browser.onboarding.enabled" to false.
- */
-class Onboarding {
-  constructor(contentWindow) {
-    this.init(contentWindow);
-  }
-
-  async init(contentWindow) {
-    this._window = contentWindow;
-    // session_key is used for telemetry to track the current tab.
-    // The number will renew after reloading the page.
-    this._session_key = Date.now();
-    this._tours = [];
-    this._tourType = Services.prefs.getStringPref("browser.onboarding.tour-type", "update");
-
-    let tourIds = this._getTourIDList();
-    tourIds.forEach(tourId => {
-      if (onboardingTourset[tourId]) {
-        this._tours.push(onboardingTourset[tourId]);
-      }
-    });
-
-    if (this._tours.length === 0) {
-      return;
-    }
-
-    // We want to create and append elements after CSS is loaded so
-    // no flash of style changes and no additional reflow.
-    await this._loadCSS();
-    this._bundle = Services.strings.createBundle(BUNDLE_URI);
-
-    this._loadJS(UITOUR_JS_URI);
-
-    this.uiInitialized = false;
-    let doc = this._window.document;
-    if (doc.hidden) {
-      // When the preloaded-browser feature is on,
-      // it would preload a hidden about:newtab in the background.
-      // We don't want to show onboarding experience in that hidden state.
-      let onVisible = () => {
-        if (!doc.hidden) {
-          doc.removeEventListener("visibilitychange", onVisible);
-          this._startUI();
-        }
-      };
-      doc.addEventListener("visibilitychange", onVisible);
-    } else {
-      this._startUI();
-    }
-  }
-
-  _startUI() {
-    registerNewTelemetrySession({
-      page: this._window.location.href,
-      session_key: this._session_key,
-      tour_type: this._tourType,
-    });
-
-    this._window.addEventListener("beforeunload", this);
-    this._window.addEventListener("unload", this);
-    this._window.addEventListener("resize", this);
-    this._resizeTimerId =
-      this._window.requestIdleCallback(() => this._resizeUI());
-    // start log the onboarding-session when the tab is visible
-    telemetry({
-      type: "onboarding-session-begin",
-      session_key: this._session_key,
-    });
-  }
-
-  _resizeUI() {
-    this._windowWidth = this._window.document.body.getBoundingClientRect().width;
-    if (this._windowWidth < ONBOARDING_MIN_WIDTH_PX) {
-      // Don't show the overlay UI before we get to a better, responsive design.
-      this.destroy();
-      return;
-    }
-
-    this._initUI();
-    if (this._isFirstSession && this._windowWidth >= SPEECH_BUBBLE_MIN_WIDTH_PX) {
-      this._overlayIcon.classList.add("onboarding-speech-bubble");
-    } else {
-      this._overlayIcon.classList.remove("onboarding-speech-bubble");
-    }
-  }
-
-  _initUI() {
-    if (this.uiInitialized) {
-      return;
-    }
-    this.uiInitialized = true;
-    this._tourItems = [];
-    this._tourPages = [];
-
-    let { body } = this._window.document;
-    this._overlayIcon = this._renderOverlayButton();
-    this._overlayIcon.addEventListener("click", this);
-    this._overlayIcon.addEventListener("keypress", this);
-    body.insertBefore(this._overlayIcon, body.firstChild);
-
-    this._overlay = this._renderOverlay();
-    this._overlay.addEventListener("click", this);
-    this._overlay.addEventListener("keydown", this);
-    this._overlay.addEventListener("keypress", this);
-    body.appendChild(this._overlay);
-
-    this._loadJS(TOUR_AGENT_JS_URI);
-
-    this._initPrefObserver();
-    this._onIconStateChange(Services.prefs.getStringPref("browser.onboarding.state", ICON_STATE_DEFAULT));
-
-    // Doing tour notification takes some effort. Let's do it on idle.
-    this._window.requestIdleCallback(() => this.showNotification());
-  }
-
-  _getTourIDList() {
-    let tours = Services.prefs.getStringPref(`browser.onboarding.${this._tourType}tour`, "");
-    return tours.split(",").filter(tourId => {
-      if (tourId === "sync" && !Services.prefs.getBoolPref("identity.fxaccounts.enabled")) {
-        return false;
-      }
-      return tourId !== "";
-    }).map(tourId => tourId.trim());
-  }
-
-  _initPrefObserver() {
-    if (this._prefsObserved) {
-      return;
-    }
-
-    this._prefsObserved = new Map();
-    this._prefsObserved.set("browser.onboarding.state", () => {
-      this._onIconStateChange(Services.prefs.getStringPref("browser.onboarding.state", ICON_STATE_DEFAULT));
-    });
-    this._tours.forEach(tour => {
-      let tourId = tour.id;
-      this._prefsObserved.set(`browser.onboarding.tour.${tourId}.completed`, () => {
-        this.markTourCompletionState(tourId);
-        this._checkWatermarkByTours();
-      });
-    });
-    for (let [name, callback] of this._prefsObserved) {
-      Services.prefs.addObserver(name, callback);
-    }
-  }
-
-  _checkWatermarkByTours() {
-    let tourDone = this._tours.every(tour => this.isTourCompleted(tour.id));
-    if (tourDone) {
-      sendMessageToChrome("set-prefs", [{
-        name: "browser.onboarding.state",
-        value: ICON_STATE_WATERMARK
-      }]);
-    }
-  }
-
-  _clearPrefObserver() {
-    if (this._prefsObserved) {
-      for (let [name, callback] of this._prefsObserved) {
-        Services.prefs.removeObserver(name, callback);
-      }
-      this._prefsObserved = null;
-    }
-  }
-
-  /**
-   * Find a tour that should be selected. It is either a first tour that was not
-   * yet complete or the first one in the tab list.
-   */
-  get _firstUncompleteTour() {
-    return this._tours.find(tour => !this.isTourCompleted(tour.id)) ||
-           this._tours[0];
-  }
-
-  /*
-   * Return currently showing tour navigation item
-   */
-  get _activeTourId() {
-    // We are doing lazy load so there might be no items.
-    if (!this._tourItems) {
-      return "";
-    }
-
-    let tourItem = this._tourItems.find(item => item.classList.contains("onboarding-active"));
-    return tourItem ? tourItem.id : "";
-  }
-
-  /**
-   * Return current logo state as "logo" or "watermark".
-   */
-  get _logoState() {
-    return this._overlayIcon.classList.contains("onboarding-watermark") ?
-      "watermark" : "logo";
-  }
-
-  /**
-   * Return current speech bubble state as "bubble", "dot" or "hide".
-   */
-  get _bubbleState() {
-    let state;
-    if (this._overlayIcon.classList.contains("onboarding-watermark")) {
-      state = "hide";
-    } else if (this._overlayIcon.classList.contains("onboarding-speech-bubble")) {
-      state = "bubble";
-    } else {
-      state = "dot";
-    }
-    return state;
-  }
-
-  /**
-   * Return current notification state as "show", "hide" or "finished".
-   */
-  get _notificationState() {
-    if (this._notificationCachedState === "finished") {
-      return this._notificationCachedState;
-    }
-
-    if (Services.prefs.getBoolPref(NOTIFICATION_FINISHED_PREF, false)) {
-      this._notificationCachedState = "finished";
-    } else if (this._notification) {
-      this._notificationCachedState = "show";
-    } else {
-      // we know it is in the hidden state if there's no notification bar
-      this._notificationCachedState = "hide";
-    }
-
-    return this._notificationCachedState;
-  }
-
-  /**
-   * Return current notification prompt count.
-   */
-  get _notificationPromptCount() {
-    return Services.prefs.getIntPref(PROMPT_COUNT_PREF, 0);
-  }
-
-  /**
-   * Return current screen width and round it up to the nearest 50 pixels.
-   * Collecting rounded values reduces the risk that this could be used to
-   * derive a unique user identifier
-   */
-  get _windowWidthRounded() {
-    return Math.round(this._windowWidth / 50) * 50;
-  }
-
-  handleClick(target) {
-    let { id, classList } = target;
-    // Only containers receive pointer events in onboarding tour tab list,
-    // actual semantic tab is their first child.
-    if (classList.contains("onboarding-tour-item-container")) {
-      ({ id, classList } = target.firstChild);
-    }
-
-    switch (id) {
-      case "onboarding-overlay-button":
-        telemetry({
-          type: "onboarding-logo-click",
-          bubble_state: this._bubbleState,
-          logo_state: this._logoState,
-          notification_state: this._notificationState,
-          session_key: this._session_key,
-          width: this._windowWidthRounded,
-        });
-        this.showOverlay();
-        this.gotoPage(this._firstUncompleteTour.id);
-        break;
-      case "onboarding-skip-tour-button":
-        this.hideNotification();
-        this.hideOverlay();
-        this.skipTour();
-        break;
-      case "onboarding-overlay-close-btn":
-      // If the clicking target is directly on the outer-most overlay,
-      // that means clicking outside the tour content area.
-      // Let's toggle the overlay.
-      case "onboarding-overlay":
-        let eventName = id === "onboarding-overlay-close-btn" ?
-          "overlay-close-button-click" : "overlay-close-outside-click";
-        telemetry({
-          type: eventName,
-          current_tour_id: this._activeTourId,
-          session_key: this._session_key,
-          target_tour_id: this._activeTourId,
-          width: this._windowWidthRounded,
-        });
-        this.hideOverlay();
-        break;
-      case "onboarding-notification-close-btn":
-        let currentTourId = this._notificationBar.dataset.targetTourId;
-        // should trigger before notification-session event is sent
-        telemetry({
-          type: "notification-close-button-click",
-          bubble_state: this._bubbleState,
-          current_tour_id: currentTourId,
-          logo_state: this._logoState,
-          notification_impression: this._notificationPromptCount,
-          notification_state: this._notificationState,
-          session_key: this._session_key,
-          target_tour_id: currentTourId,
-          width: this._windowWidthRounded,
-        });
-        this.hideNotification();
-        this._removeTourFromNotificationQueue(currentTourId);
-        break;
-      case "onboarding-notification-action-btn":
-        let tourId = this._notificationBar.dataset.targetTourId;
-        telemetry({
-          type: "notification-cta-click",
-          bubble_state: this._bubbleState,
-          current_tour_id: tourId,
-          logo_state: this._logoState,
-          notification_impression: this._notificationPromptCount,
-          notification_state: this._notificationState,
-          session_key: this._session_key,
-          target_tour_id: tourId,
-          width: this._windowWidthRounded,
-        });
-        this.showOverlay();
-        this.gotoPage(tourId);
-        this._removeTourFromNotificationQueue(tourId);
-        break;
-    }
-    if (classList.contains("onboarding-tour-item")) {
-      telemetry({
-        type: "overlay-nav-click",
-        current_tour_id: this._activeTourId,
-        session_key: this._session_key,
-        target_tour_id: id,
-        width: this._windowWidthRounded,
-      });
-      this.gotoPage(id);
-      // Keep focus (not visible) on current item for potential keyboard
-      // navigation.
-      target.focus();
-    } else if (classList.contains("onboarding-tour-action-button")) {
-      let activeTourId = this._activeTourId;
-      this.setToursCompleted([ activeTourId ]);
-      telemetry({
-        type: "overlay-cta-click",
-        current_tour_id: activeTourId,
-        session_key: this._session_key,
-        target_tour_id: activeTourId,
-        width: this._windowWidthRounded,
-      });
-    }
-  }
-
-  /**
-   * Wrap keyboard focus within the dialog.
-   * When moving forward, focus on the first element when the current focused
-   * element is the last one.
-   * When moving backward, focus on the last element when the current focused
-   * element is the first one.
-   * Do nothing if focus is moving in the middle of the list of dialog's focusable
-   * elements.
-   *
-   * @param  {DOMNode} current  currently focused element
-   * @param  {Boolean} back     direction
-   * @return {DOMNode}          newly focused element if any
-   */
-  wrapMoveFocus(current, back) {
-    let elms = [...this._dialog.querySelectorAll(
-      `button, input[type="checkbox"], input[type="email"], [tabindex="0"]`)];
-    let next;
-    if (back) {
-      if (elms.indexOf(current) === 0) {
-        next = elms[elms.length - 1];
-        next.focus();
-      }
-    } else if (elms.indexOf(current) === elms.length - 1) {
-      next = elms[0];
-      next.focus();
-    }
-    return next;
-  }
-
-  handleKeydown(event) {
-    let { target, key, shiftKey } = event;
-
-    // Currently focused item could be tab container if previous navigation was done
-    // via mouse.
-    if (target.classList.contains("onboarding-tour-item-container")) {
-      target = target.firstChild;
-    }
-    let targetIndex;
-    switch (key) {
-      case "ArrowUp":
-        // Go to and focus on the previous tab if it's available.
-        targetIndex = this._tourItems.indexOf(target);
-        if (targetIndex > 0) {
-          let previous = this._tourItems[targetIndex - 1];
-          this.handleClick(previous);
-          previous.focus();
-        }
-        event.preventDefault();
-        break;
-      case "ArrowDown":
-        // Go to and focus on the next tab if it's available.
-        targetIndex = this._tourItems.indexOf(target);
-        if (targetIndex > -1 && targetIndex < this._tourItems.length - 1) {
-          let next = this._tourItems[targetIndex + 1];
-          this.handleClick(next);
-          next.focus();
-        }
-        event.preventDefault();
-        break;
-      case "Escape":
-        this.hideOverlay();
-        break;
-      case "Tab":
-        let next = this.wrapMoveFocus(target, shiftKey);
-        // If focus was wrapped, prevent Tab key default action.
-        if (next) {
-          event.preventDefault();
-        }
-        break;
-      default:
-        break;
-    }
-    event.stopPropagation();
-  }
-
-  handleKeypress(event) {
-    let { target, key } = event;
-
-    if (target === this._overlayIcon) {
-      if ([" ", "Enter"].includes(key)) {
-        // Remember that the dialog was opened with a keyboard.
-        this._overlayIcon.dataset.keyboardFocus = true;
-        this.handleClick(target);
-        event.preventDefault();
-      }
-      return;
-    }
-
-    // Currently focused item could be tab container if previous navigation was done
-    // via mouse.
-    if (target.classList.contains("onboarding-tour-item-container")) {
-      target = target.firstChild;
-    }
-    switch (key) {
-      case " ":
-      case "Enter":
-        // Assume that the handle function should be identical for keyboard
-        // activation if there is a click handler for the target.
-        if (target.classList.contains("onboarding-tour-item")) {
-          this.handleClick(target);
-          target.focus();
-        }
-        break;
-      default:
-        break;
-    }
-    event.stopPropagation();
-  }
-
-  handleEvent(evt) {
-    switch (evt.type) {
-      case "beforeunload":
-        // To make sure the telemetry pings are sent,
-        // we send "onboarding-session-end" ping as well as
-        // "overlay-session-end" and "notification-session-end" ping
-        // (by hiding the overlay and notificaiton) on beforeunload.
-        this.hideOverlay();
-        this.hideNotification();
-        telemetry({
-          type: "onboarding-session-end",
-          session_key: this._session_key,
-        });
-        break;
-      case "unload":
-        // Notice: Cannot do `destroy` on beforeunload, must do on unload.
-        // Otherwise, we would hit the docShell leak in the test.
-        // See Bug 1413830#c190 and Bug 1429652 for details.
-        this.destroy();
-        break;
-      case "resize":
-        this._window.cancelIdleCallback(this._resizeTimerId);
-        this._resizeTimerId =
-          this._window.requestIdleCallback(() => this._resizeUI());
-        break;
-      case "keydown":
-        this.handleKeydown(evt);
-        break;
-      case "keypress":
-        this.handleKeypress(evt);
-        break;
-      case "click":
-        this.handleClick(evt.target);
-        break;
-      default:
-        break;
-    }
-  }
-
-  destroy() {
-    if (!this.uiInitialized) {
-      return;
-    }
-    this.uiInitialized = false;
-
-    this._overlayIcon.dispatchEvent(new this._window.CustomEvent("Agent:Destroy"));
-
-    this._clearPrefObserver();
-    this._overlayIcon.remove();
-    if (this._overlay) {
-      // send overlay-session telemetry
-      this.hideOverlay();
-      this._overlay.remove();
-    }
-    if (this._notificationBar) {
-      // send notification-session telemetry
-      this.hideNotification();
-      this._notificationBar.remove();
-    }
-    this._tourItems = this._tourPages =
-    this._overlayIcon = this._overlay = this._notificationBar = null;
-  }
-
-  _onIconStateChange(state) {
-    switch (state) {
-      case ICON_STATE_DEFAULT:
-        this._overlayIcon.classList.remove("onboarding-watermark");
-        break;
-      case ICON_STATE_WATERMARK:
-        this._overlayIcon.classList.add("onboarding-watermark");
-        break;
-    }
-    return true;
-  }
-
-  showOverlay() {
-    if (this._tourItems.length == 0) {
-      // Lazy loading until first toggle.
-      this._loadTours(this._tours);
-    }
-
-    if (this._overlay && !this._overlay.classList.contains("onboarding-opened")) {
-      this.hideNotification();
-      this._overlay.classList.add("onboarding-opened");
-      this.toggleModal(true);
-      telemetry({
-        type: "overlay-session-begin",
-        session_key: this._session_key,
-      });
-    }
-  }
-
-  hideOverlay() {
-    if (this._overlay && this._overlay.classList.contains("onboarding-opened")) {
-      this._overlay.classList.remove("onboarding-opened");
-      this.toggleModal(false);
-      telemetry({
-        type: "overlay-session-end",
-        session_key: this._session_key,
-      });
-    }
-  }
-
-  /**
-   * Set modal dialog state and properties for accessibility purposes.
-   * @param  {Boolean} opened  whether the dialog is opened or closed.
-   */
-  toggleModal(opened) {
-    let { document: doc } = this._window;
-    if (opened) {
-      // Set aria-hidden to true for the rest of the document.
-      [...doc.body.children].forEach(
-        child => child.id !== "onboarding-overlay" &&
-                 child.setAttribute("aria-hidden", true));
-      // When dialog is opened with the keyboard, focus on the first
-      // uncomplete tour because it will be the selected tour.
-      if (this._overlayIcon.dataset.keyboardFocus) {
-        doc.getElementById(this._firstUncompleteTour.id).focus();
-      } else {
-        // When the dialog is opened with the mouse, focus on the dialog
-        // itself to avoid visible keyboard focus styling.
-        this._dialog.focus();
-      }
-    } else {
-      // Remove all set aria-hidden attributes.
-      [...doc.body.children].forEach(
-        child => child.removeAttribute("aria-hidden"));
-      // If dialog was opened with a keyboard, set the focus back to the overlay
-      // button.
-      if (this._overlayIcon.dataset.keyboardFocus) {
-        delete this._overlayIcon.dataset.keyboardFocus;
-        this._overlayIcon.focus();
-      } else {
-        this._window.document.activeElement.blur();
-      }
-    }
-  }
-
-  /**
-   * Switch to proper tour.
-   * @param {String} tourId specify which tour should be switched.
-   */
-  gotoPage(tourId) {
-    let targetPageId = `${tourId}-page`;
-    for (let page of this._tourPages) {
-      if (page.id === targetPageId) {
-        page.style.display = "";
-        page.dispatchEvent(new this._window.CustomEvent("beforeshow"));
-      } else {
-        page.style.display = "none";
-      }
-    }
-    for (let tab of this._tourItems) {
-      if (tab.id == tourId) {
-        tab.classList.add("onboarding-active");
-        tab.setAttribute("aria-selected", true);
-        telemetry({
-          type: "overlay-current-tour",
-          current_tour_id: tourId,
-          session_key: this._session_key,
-          width: this._windowWidthRounded,
-        });
-
-        // Some tours should complete instantly upon showing.
-        if (tab.getAttribute("data-instant-complete")) {
-          this.setToursCompleted([tourId]);
-        }
-      } else {
-        tab.classList.remove("onboarding-active");
-        tab.setAttribute("aria-selected", false);
-      }
-    }
-  }
-
-  isTourCompleted(tourId) {
-    return Services.prefs.getBoolPref(`browser.onboarding.tour.${tourId}.completed`, false);
-  }
-
-  setToursCompleted(tourIds) {
-    let params = [];
-    tourIds.forEach(id => {
-      if (!this.isTourCompleted(id)) {
-        params.push({
-          name: `browser.onboarding.tour.${id}.completed`,
-          value: true
-        });
-      }
-    });
-    if (params.length > 0) {
-      sendMessageToChrome("set-prefs", params);
-    }
-  }
-
-  markTourCompletionState(tourId) {
-    // We are doing lazy load so there might be no items.
-    if (!this._tourItems || this._tourItems.length === 0) {
-      return;
-    }
-
-    let completed = this.isTourCompleted(tourId);
-    let targetItem = this._tourItems.find(item => item.id == tourId);
-    let completedTextId = `onboarding-complete-${tourId}-text`;
-    // Accessibility: Text version of the auxiliary information about the tour
-    // item completion is provided via an invisible node with an aria-label that
-    // the tab is pointing to via aria-described by.
-    let completedText = targetItem.querySelector(`#${completedTextId}`);
-    if (completed) {
-      targetItem.classList.add("onboarding-complete");
-      if (!completedText) {
-        completedText = this._window.document.createElement("span");
-        completedText.id = completedTextId;
-        completedText.setAttribute("aria-label",
-          this._bundle.GetStringFromName("onboarding.complete"));
-        targetItem.appendChild(completedText);
-        targetItem.setAttribute("aria-describedby", completedTextId);
-      }
-    } else {
-      targetItem.classList.remove("onboarding-complete");
-      targetItem.removeAttribute("aria-describedby");
-      if (completedText) {
-        completedText.remove();
-      }
-    }
-  }
-
-  get _isFirstSession() {
-    // Should only directly return on the "false" case. Consider:
-    //   1. On the 1st session, `_firstSession` is true
-    //   2. During the 1st session, user resizes window so that the UI is destroyed
-    //   3. After the 1st mute session, user resizes window so that the UI is re-init
-    if (this._firstSession === false) {
-      return false;
-    }
-    this._firstSession = true;
-
-    // There is a queue, which means we had prompted tour notifications before. Therefore this is not the 1st session.
-    if (Services.prefs.prefHasUserValue("browser.onboarding.notification.tour-ids-queue")) {
-      this._firstSession = false;
-    }
-
-    // When this is set to 0 on purpose, always judge as not the 1st session
-    if (Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms") === 0) {
-      this._firstSession = false;
-    }
-
-    return this._firstSession;
-  }
-
-  _getLastTourChangeTime() {
-    return 1000 * Services.prefs.getIntPref("browser.onboarding.notification.last-time-of-changing-tour-sec", 0);
-  }
-
-  _muteNotificationOnFirstSession(lastTourChangeTime) {
-    if (!this._isFirstSession) {
-      return false;
-    }
-
-    if (lastTourChangeTime <= 0) {
-      sendMessageToChrome("set-prefs", [{
-        name: "browser.onboarding.notification.last-time-of-changing-tour-sec",
-        value: Math.floor(Date.now() / 1000)
-      }]);
-      return true;
-    }
-    let muteDuration = Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms");
-    return Date.now() - lastTourChangeTime <= muteDuration;
-  }
-
-  _isTimeForNextTourNotification(lastTourChangeTime) {
-    let maxCount = Services.prefs.getIntPref("browser.onboarding.notification.max-prompt-count-per-tour");
-    if (this._notificationPromptCount >= maxCount) {
-      return true;
-    }
-
-    let maxTime = Services.prefs.getIntPref("browser.onboarding.notification.max-life-time-per-tour-ms");
-    if (lastTourChangeTime && Date.now() - lastTourChangeTime >= maxTime) {
-      return true;
-    }
-
-    return false;
-  }
-
-  _removeTourFromNotificationQueue(tourId) {
-    let params = [];
-    let queue = this._getNotificationQueue();
-    params.push({
-      name: "browser.onboarding.notification.tour-ids-queue",
-      value: queue.filter(id => id != tourId).join(",")
-    });
-    params.push({
-      name: "browser.onboarding.notification.last-time-of-changing-tour-sec",
-      value: 0
-    });
-    params.push({
-      name: "browser.onboarding.notification.prompt-count",
-      value: 0
-    });
-    sendMessageToChrome("set-prefs", params);
-  }
-
-  _getNotificationQueue() {
-    let queue = "";
-    if (Services.prefs.prefHasUserValue("browser.onboarding.notification.tour-ids-queue")) {
-      queue = Services.prefs.getStringPref("browser.onboarding.notification.tour-ids-queue");
-    } else {
-      // For each tour, it only gets 2 chances to prompt with notification
-      // (each chance includes 8 impressions or 5-days max life time)
-      // if user never interact with it.
-      // Assume there are tour #0 ~ #5. Here would form the queue as
-      // "#0,#1,#2,#3,#4,#5,#0,#1,#2,#3,#4,#5".
-      // Then we would loop through this queue and remove prompted tour from the queue
-      // until the queue is empty.
-      let ids = this._tours.map(tour => tour.id).join(",");
-      queue = `${ids},${ids}`;
-      sendMessageToChrome("set-prefs", [{
-        name: "browser.onboarding.notification.tour-ids-queue",
-        value: queue
-      }]);
-    }
-    return queue ? queue.split(",") : [];
-  }
-
-  showNotification() {
-    if (this._notificationState === "finished") {
-      return;
-    }
-
-    let lastTime = this._getLastTourChangeTime();
-    if (this._muteNotificationOnFirstSession(lastTime)) {
-      return;
-    }
-
-    // After the notification mute on the 1st session,
-    // we don't want to show the speech bubble by default
-    this._overlayIcon.classList.remove("onboarding-speech-bubble");
-
-    let queue = this._getNotificationQueue();
-    let totalMaxTime = Services.prefs.getIntPref("browser.onboarding.notification.max-life-time-all-tours-ms");
-    if (lastTime && Date.now() - lastTime >= totalMaxTime) {
-      // Reach total max life time for all tour notifications.
-      // Clear the queue so that we would finish tour notifications below
-      queue = [];
-    }
-
-    let startQueueLength = queue.length;
-    // See if need to move on to the next tour
-    if (queue.length > 0 && this._isTimeForNextTourNotification(lastTime)) {
-      queue.shift();
-    }
-    // We don't want to prompt the completed tour.
-    while (queue.length > 0 && this.isTourCompleted(queue[0])) {
-      queue.shift();
-    }
-
-    if (queue.length == 0) {
-      sendMessageToChrome("set-prefs", [
-        {
-          name: NOTIFICATION_FINISHED_PREF,
-          value: true
-        },
-        {
-          name: "browser.onboarding.notification.tour-ids-queue",
-          value: ""
-        },
-        {
-          name: "browser.onboarding.state",
-          value: ICON_STATE_WATERMARK
-        }
-      ]);
-      return;
-    }
-    let targetTourId = queue[0];
-    let targetTour = this._tours.find(tour => tour.id == targetTourId);
-
-    // Show the target tour notification
-    this._notificationBar = this._renderNotificationBar();
-    this._notificationBar.addEventListener("click", this);
-    this._notificationBar.dataset.targetTourId = targetTour.id;
-    let notificationStrings = targetTour.getNotificationStrings(this._bundle);
-    let actionBtn = this._notificationBar.querySelector("#onboarding-notification-action-btn");
-    actionBtn.textContent = notificationStrings.button;
-    let tourTitle = this._notificationBar.querySelector("#onboarding-notification-tour-title");
-    tourTitle.textContent = notificationStrings.title;
-    let tourMessage = this._notificationBar.querySelector("#onboarding-notification-tour-message");
-    tourMessage.textContent = notificationStrings.message;
-    this._notificationBar.classList.add("onboarding-opened");
-    this._window.document.body.appendChild(this._notificationBar);
-
-    let params = [];
-    let promptCount = 1;
-    if (startQueueLength != queue.length) {
-      // We just change tour so update the time, the count and the queue
-      params.push({
-        name: "browser.onboarding.notification.last-time-of-changing-tour-sec",
-        value: Math.floor(Date.now() / 1000)
-      });
-      params.push({
-        name: PROMPT_COUNT_PREF,
-        value: promptCount
-      });
-      params.push({
-        name: "browser.onboarding.notification.tour-ids-queue",
-        value: queue.join(",")
-      });
-    } else {
-      promptCount = this._notificationPromptCount + 1;
-      params.push({
-        name: PROMPT_COUNT_PREF,
-        value: promptCount
-      });
-    }
-    sendMessageToChrome("set-prefs", params);
-    telemetry({
-      type: "notification-session-begin",
-      session_key: this._session_key
-    });
-    // since set-perfs is async, pass promptCount directly to avoid gathering the wrong
-    // notification_impression.
-    telemetry({
-      type: "notification-appear",
-      bubble_state: this._bubbleState,
-      current_tour_id: targetTourId,
-      logo_state: this._logoState,
-      notification_impression: promptCount,
-      notification_state: this._notificationState,
-      session_key: this._session_key,
-      width: this._windowWidthRounded,
-    });
-  }
-
-  hideNotification() {
-    if (this._notificationBar) {
-      if (this._notificationBar.classList.contains("onboarding-opened")) {
-        this._notificationBar.classList.remove("onboarding-opened");
-        telemetry({
-          type: "notification-session-end",
-          session_key: this._session_key,
-        });
-      }
-    }
-  }
-
-  _renderNotificationBar() {
-    let footer = this._window.document.createElement("footer");
-    footer.id = "onboarding-notification-bar";
-    footer.setAttribute("aria-live", "polite");
-    footer.setAttribute("aria-labelledby", "onboarding-notification-tour-title");
-
-    let section = this._window.document.createElement("section");
-    section.id = "onboarding-notification-message-section";
-    section.setAttribute("role", "presentation");
-    footer.appendChild(section);
-
-    let icon = this._window.document.createElement("div");
-    icon.id = "onboarding-notification-tour-icon";
-    icon.setAttribute("role", "presentation");
-    section.appendChild(icon);
-
-    let onboardingNotificationBody = this._window.document.createElement("div");
-    onboardingNotificationBody.id = "onboarding-notification-body";
-    onboardingNotificationBody.setAttribute("role", "presentation");
-    section.appendChild(onboardingNotificationBody);
-
-    let title = this._window.document.createElement("h1");
-    title.id = "onboarding-notification-tour-title";
-    onboardingNotificationBody.appendChild(title);
-
-    let message = this._window.document.createElement("p");
-    message.id = "onboarding-notification-tour-message";
-    onboardingNotificationBody.appendChild(message);
-
-    let actionButton = this._window.document.createElement("button");
-    actionButton.id = "onboarding-notification-action-btn";
-    actionButton.className = "onboarding-action-button";
-    section.appendChild(actionButton);
-
-    let closeButton = this._window.document.createElement("button");
-    closeButton.id = "onboarding-notification-close-btn";
-    closeButton.className = "onboarding-close-btn";
-    footer.appendChild(closeButton);
-
-    closeButton.setAttribute("title",
-      this._bundle.GetStringFromName("onboarding.notification-close-button-tooltip"));
-
-    return footer;
-  }
-
-  skipTour() {
-    this.setToursCompleted(this._tours.map(tour => tour.id));
-    sendMessageToChrome("set-prefs", [
-      {
-        name: NOTIFICATION_FINISHED_PREF,
-        value: true
-      },
-      {
-        name: "browser.onboarding.state",
-        value: ICON_STATE_WATERMARK
-      }
-    ]);
-    telemetry({
-      type: "overlay-skip-tour",
-      current_tour_id: this._activeTourId,
-      session_key: this._session_key,
-      width: this._windowWidthRounded,
-    });
-  }
-
-  _renderOverlay() {
-    let div = this._window.document.createElement("div");
-    div.id = "onboarding-overlay";
-
-    this._dialog = this._window.document.createElement("div");
-    this._dialog.setAttribute("role", "dialog");
-    this._dialog.setAttribute("tabindex", "-1");
-    this._dialog.setAttribute("aria-labelledby", "onboarding-header");
-    this._dialog.id = ONBOARDING_DIALOG_ID;
-    div.appendChild(this._dialog);
-
-    let header = this._window.document.createElement("header");
-    header.id = "onboarding-header";
-    header.textContent = this._bundle.GetStringFromName("onboarding.overlay-title2");
-    this._dialog.appendChild(header);
-
-    let nav = this._window.document.createElement("nav");
-    this._dialog.appendChild(nav);
-
-    let tourList = this._window.document.createElement("ul");
-    tourList.id = "onboarding-tour-list";
-    tourList.setAttribute("role", "tablist");
-    nav.appendChild(tourList);
-
-    let footer = this._window.document.createElement("footer");
-    footer.id = "onboarding-footer";
-    this._dialog.appendChild(footer);
-
-    let button = this._window.document.createElement("button");
-    button.id = "onboarding-overlay-close-btn";
-    button.className = "onboarding-close-btn";
-    button.setAttribute("title",
-      this._bundle.GetStringFromName("onboarding.overlay-close-button-tooltip"));
-    this._dialog.appendChild(button);
-
-    // support show/hide skip tour button via pref
-    if (!Services.prefs.getBoolPref("browser.onboarding.skip-tour-button.hide", false)) {
-      let skipButton = this._window.document.createElement("button");
-      skipButton.id = "onboarding-skip-tour-button";
-      skipButton.classList.add("onboarding-action-button");
-      skipButton.textContent = this._bundle.GetStringFromName("onboarding.skip-tour-button-label");
-      footer.appendChild(skipButton);
-    }
-
-    return div;
-  }
-
-  _renderOverlayButton() {
-    let button = this._window.document.createElement("button");
-    // support customize speech bubble string via pref
-    let tooltipStringPrefId = "";
-    let defaultTourStringId = "";
-    if (this._tourType === "new") {
-      tooltipStringPrefId = "browser.onboarding.newtour.tooltip";
-      defaultTourStringId = SPEECH_BUBBLE_NEWTOUR_STRING_ID;
-    } else {
-      tooltipStringPrefId = "browser.onboarding.updatetour.tooltip";
-      defaultTourStringId = SPEECH_BUBBLE_UPDATETOUR_STRING_ID;
-    }
-    let tooltip = "";
-    try {
-      let tooltipStringId = Services.prefs.getStringPref(tooltipStringPrefId, defaultTourStringId);
-      tooltip = this._bundle.formatStringFromName(tooltipStringId, [BRAND_SHORT_NAME], 1);
-    } catch (e) {
-      Cu.reportError(`the provided ${tooltipStringPrefId} string is in wrong format `, e);
-      // fallback to defaultTourStringId to proceed
-      tooltip = this._bundle.formatStringFromName(defaultTourStringId, [BRAND_SHORT_NAME], 1);
-    }
-    button.setAttribute("aria-label", tooltip);
-    button.id = "onboarding-overlay-button";
-    button.setAttribute("aria-haspopup", true);
-    button.setAttribute("aria-controls", `${ONBOARDING_DIALOG_ID}`);
-    let defaultImg = this._window.document.createElement("img");
-    defaultImg.id = "onboarding-overlay-button-icon";
-    defaultImg.setAttribute("role", "presentation");
-    defaultImg.src = Services.prefs.getStringPref("browser.onboarding.default-icon-src",
-      "chrome://branding/content/icon64.png");
-    button.appendChild(defaultImg);
-    let watermarkImg = this._window.document.createElement("img");
-    watermarkImg.id = "onboarding-overlay-button-watermark-icon";
-    watermarkImg.setAttribute("role", "presentation");
-    watermarkImg.src = Services.prefs.getStringPref("browser.onboarding.watermark-icon-src",
-      "resource://onboarding/img/watermark.svg");
-    button.appendChild(watermarkImg);
-    return button;
-  }
-
-  _loadTours(tours) {
-    let itemsFrag = this._window.document.createDocumentFragment();
-    let pagesFrag = this._window.document.createDocumentFragment();
-    for (let tour of tours) {
-      // Create tour navigation items dynamically
-      let li = this._window.document.createElement("li");
-      // List item should have no semantics. It is just a container for an
-      // actual tab.
-      li.setAttribute("role", "presentation");
-      li.className = "onboarding-tour-item-container";
-      // Focusable but not tabbable.
-      li.tabIndex = -1;
-
-      let tab = this._window.document.createElement("span");
-      tab.id = tour.id;
-      tab.textContent = this._bundle.GetStringFromName(tour.tourNameId);
-      tab.className = "onboarding-tour-item";
-      if (tour.instantComplete) {
-        tab.dataset.instantComplete = true;
-      }
-      tab.tabIndex = 0;
-      tab.setAttribute("role", "tab");
-
-      let tourPanelId = `${tour.id}-page`;
-      tab.setAttribute("aria-controls", tourPanelId);
-
-      li.appendChild(tab);
-      itemsFrag.appendChild(li);
-      // Dynamically create tour pages
-      let div = tour.getPage(this._window, this._bundle);
-
-      // Do a traverse for elements in the page that need to be localized.
-      let l10nElements = div.querySelectorAll("[data-l10n-id]");
-      for (let i = 0; i < l10nElements.length; i++) {
-        let element = l10nElements[i];
-        // We always put brand short name as the first argument for it's the
-        // only and frequently used arguments in our l10n case. Rewrite it if
-        // other arguments appear.
-        element.textContent = this._bundle.formatStringFromName(
-                                element.dataset.l10nId, [BRAND_SHORT_NAME], 1);
-      }
-
-      div.id = tourPanelId;
-      div.classList.add("onboarding-tour-page");
-      div.setAttribute("role", "tabpanel");
-      div.setAttribute("aria-labelledby", tour.id);
-      div.style.display = "none";
-      pagesFrag.appendChild(div);
-      // Cache elements in arrays for later use to avoid cost of querying elements
-      this._tourItems.push(tab);
-      this._tourPages.push(div);
-
-      this.markTourCompletionState(tour.id);
-    }
-
-    let ul = this._window.document.getElementById("onboarding-tour-list");
-    ul.appendChild(itemsFrag);
-    let footer = this._window.document.getElementById("onboarding-footer");
-    this._dialog.insertBefore(pagesFrag, footer);
-  }
-
-  _loadCSS() {
-    // Returning a Promise so we can inform caller of loading complete
-    // by resolving it.
-    return new Promise(resolve => {
-      let doc = this._window.document;
-      let link = doc.createElement("link");
-      link.rel = "stylesheet";
-      link.type = "text/css";
-      link.href = ONBOARDING_CSS_URL;
-      link.addEventListener("load", resolve);
-      doc.head.appendChild(link);
-    });
-  }
-
-  _loadJS(uri) {
-    let doc = this._window.document;
-    let script = doc.createElement("script");
-    script.type = "text/javascript";
-    script.src = uri;
-    doc.head.appendChild(script);
-  }
-}
 
 // Load onboarding module only when we enable it.
 if (Services.prefs.getBoolPref("browser.onboarding.enabled", false)) {
   addEventListener("load", function onLoad(evt) {
     if (!content || evt.target != content.document) {
       return;
     }
 
     let window = evt.target.defaultView;
     let location = window.location.href;
     if (location == ABOUT_NEWTAB_URL || location == ABOUT_HOME_URL) {
       // We just want to run tests as quickly as possible
       // so in the automation test, we don't do `requestIdleCallback`.
       if (Cu.isInAutomation) {
-        new Onboarding(window);
+        new Onboarding(this, window);
         return;
       }
       window.requestIdleCallback(() => {
-        new Onboarding(window);
+        new Onboarding(this, window);
       });
     }
   }, true);
 }
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -165,16 +165,17 @@ support-files =
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_console.js]
 [browser_console_clear_method.js]
 skip-if = true # Bug 1437843
 [browser_console_consolejsm_output.js]
 [browser_console_context_menu_entries.js]
+skip-if = (os == "linux" && (debug || ccov)) # Bug 1440059
 [browser_console_dead_objects.js]
 [browser_console_devtools_loader_exception.js]
 [browser_console_error_source_click.js]
 [browser_console_filters.js]
 [browser_console_nsiconsolemessage.js]
 [browser_console_open_or_focus.js]
 [browser_console_restore.js]
 [browser_console_webconsole_console_api_calls.js]
--- a/editor/composer/nsComposerDocumentCommands.cpp
+++ b/editor/composer/nsComposerDocumentCommands.cpp
@@ -7,17 +7,16 @@
 #include "mozilla/HTMLEditor.h"         // for HTMLEditor
 #include "mozilla/TextEditor.h"         // for TextEditor
 #include "nsCOMPtr.h"                   // for nsCOMPtr, do_QueryInterface, etc
 #include "nsCRT.h"                      // for nsCRT
 #include "nsComposerCommands.h"         // for nsSetDocumentOptionsCommand, etc
 #include "nsDebug.h"                    // for NS_ENSURE_ARG_POINTER, etc
 #include "nsError.h"                    // for NS_ERROR_INVALID_ARG, etc
 #include "nsICommandParams.h"           // for nsICommandParams
-#include "nsIDOMDocument.h"             // for nsIDOMDocument
 #include "nsIDocShell.h"                // for nsIDocShell
 #include "nsIDocument.h"                // for nsIDocument
 #include "nsIEditingSession.h"          // for nsIEditingSession, etc
 #include "nsIEditor.h"                  // for nsIEditor
 #include "nsIPlaintextEditor.h"         // for nsIPlaintextEditor, etc
 #include "nsIPresShell.h"               // for nsIPresShell
 #include "nsISelectionController.h"     // for nsISelectionController
 #include "nsISupportsImpl.h"            // for nsPresContext::Release
--- a/editor/composer/nsEditingSession.cpp
+++ b/editor/composer/nsEditingSession.cpp
@@ -19,17 +19,16 @@
 #include "nsError.h"                    // for NS_ERROR_FAILURE, NS_OK, etc
 #include "nsIChannel.h"                 // for nsIChannel
 #include "nsICommandManager.h"          // for nsICommandManager
 #include "nsIContentViewer.h"           // for nsIContentViewer
 #include "nsIController.h"              // for nsIController
 #include "nsIControllerContext.h"       // for nsIControllerContext
 #include "nsIControllers.h"             // for nsIControllers
 #include "nsID.h"                       // for NS_GET_IID, etc
-#include "nsIDOMDocument.h"             // for nsIDOMDocument
 #include "nsHTMLDocument.h"             // for nsHTMLDocument
 #include "nsIDOMWindow.h"               // for nsIDOMWindow
 #include "nsIDocShell.h"                // for nsIDocShell
 #include "nsIDocument.h"                // for nsIDocument
 #include "nsIDocumentStateListener.h"
 #include "nsIEditor.h"                  // for nsIEditor
 #include "nsIHTMLDocument.h"            // for nsIHTMLDocument, etc
 #include "nsIInterfaceRequestorUtils.h"  // for do_GetInterface
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -1527,17 +1527,17 @@ public:
 
   /**
    * Used to insert content from a data transfer into the editable area.
    * This is called for each item in the data transfer, with the index of
    * each item passed as aIndex.
    */
   virtual nsresult InsertFromDataTransfer(dom::DataTransfer* aDataTransfer,
                                           int32_t aIndex,
-                                          nsIDOMDocument* aSourceDoc,
+                                          nsIDocument* aSourceDoc,
                                           nsINode* aDestinationNode,
                                           int32_t aDestOffset,
                                           bool aDoDeleteSelection) = 0;
 
   virtual nsresult InsertFromDrop(dom::DragEvent* aDropEvent) = 0;
 
   /**
    * GetIMESelectionStartOffsetIn() returns the start offset of IME selection in
--- a/editor/libeditor/EditorCommands.cpp
+++ b/editor/libeditor/EditorCommands.cpp
@@ -12,17 +12,16 @@
 #include "mozilla/dom/Selection.h"
 #include "nsCOMPtr.h"
 #include "nsCRT.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsIClipboard.h"
 #include "nsICommandParams.h"
 #include "nsID.h"
-#include "nsIDOMDocument.h"
 #include "nsIDocument.h"
 #include "nsIEditor.h"
 #include "nsIEditorMailSupport.h"
 #include "nsIPlaintextEditor.h"
 #include "nsISelectionController.h"
 #include "nsITransferable.h"
 #include "nsString.h"
 #include "nsAString.h"
--- a/editor/libeditor/EditorEventListener.cpp
+++ b/editor/libeditor/EditorEventListener.cpp
@@ -26,17 +26,16 @@
 #include "nsGkAtoms.h"                  // for nsGkAtoms, nsGkAtoms::input
 #include "nsIClipboard.h"               // for nsIClipboard, etc.
 #include "nsIContent.h"                 // for nsIContent
 #include "nsIController.h"              // for nsIController
 #include "nsID.h"
 #include "mozilla/dom/DOMStringList.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/DragEvent.h"
-#include "nsIDOMDocument.h"             // for nsIDOMDocument
 #include "nsIDOMEvent.h"                // for nsIDOMEvent
 #include "nsIDOMEventTarget.h"          // for nsIDOMEventTarget
 #include "nsIDocument.h"                // for nsIDocument
 #include "nsIFocusManager.h"            // for nsIFocusManager
 #include "nsIFormControl.h"             // for nsIFormControl, etc.
 #include "nsINode.h"                    // for nsINode, ::NODE_IS_EDITABLE, etc.
 #include "nsIPlaintextEditor.h"         // for nsIPlaintextEditor, etc.
 #include "nsIPresShell.h"               // for nsIPresShell
--- a/editor/libeditor/EditorUtils.cpp
+++ b/editor/libeditor/EditorUtils.cpp
@@ -7,17 +7,16 @@
 
 #include "mozilla/EditorDOMPoint.h"
 #include "mozilla/OwningNonNull.h"
 #include "mozilla/dom/Selection.h"
 #include "nsComponentManagerUtils.h"
 #include "nsError.h"
 #include "nsIContent.h"
 #include "nsIContentIterator.h"
-#include "nsIDOMDocument.h"
 #include "nsIDocShell.h"
 #include "nsIDocument.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsINode.h"
 
 class nsISupports;
 class nsRange;
 
--- a/editor/libeditor/EditorUtils.h
+++ b/editor/libeditor/EditorUtils.h
@@ -14,17 +14,16 @@
 #include "mozilla/GuardObjects.h"
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsIEditor.h"
 #include "nscore.h"
 
 class nsAtom;
 class nsIContentIterator;
-class nsIDOMDocument;
 class nsIDOMEvent;
 class nsISimpleEnumerator;
 class nsITransferable;
 class nsRange;
 
 namespace mozilla {
 template <class T> class OwningNonNull;
 
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -32,24 +32,23 @@
 #include "nsCRT.h"
 #include "nsCRTGlue.h"
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsGkAtoms.h"
 #include "nsAtom.h"
+#include "nsHTMLDocument.h"
 #include "nsIContent.h"
 #include "nsIContentIterator.h"
 #include "nsID.h"
-#include "nsIDOMDocument.h"
 #include "nsIDOMElement.h"
 #include "nsIFrame.h"
 #include "nsIHTMLAbsPosEditor.h"
-#include "nsIHTMLDocument.h"
 #include "nsINode.h"
 #include "nsLiteralString.h"
 #include "nsRange.h"
 #include "nsReadableUtils.h"
 #include "nsString.h"
 #include "nsStringFwd.h"
 #include "nsTArray.h"
 #include "nsTextNode.h"
@@ -368,20 +367,20 @@ HTMLEditRules::BeforeEdit(EditAction aAc
         IsStyleCachePreservingAction(aAction)) {
       nsCOMPtr<nsINode> selNode =
         aDirection == nsIEditor::eNext ? selEndNode : selStartNode;
       nsresult rv = CacheInlineStyles(selNode);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     // Stabilize the document against contenteditable count changes
-    nsCOMPtr<nsIDOMDocument> doc = htmlEditor->GetDOMDocument();
-    NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
-    nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc);
-    NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE);
+    nsHTMLDocument* htmlDoc = htmlEditor->GetHTMLDocument();
+    if (NS_WARN_IF(!htmlDoc)) {
+      return NS_ERROR_FAILURE;
+    }
     if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) {
       htmlDoc->ChangeContentEditableCount(nullptr, +1);
       mRestoreContentEditableCount = true;
     }
 
     // Check that selection is in subtree defined by body node
     ConfirmSelectionInBody();
     // Let rules remember the top level action
@@ -411,20 +410,20 @@ HTMLEditRules::AfterEdit(EditAction aAct
     // Do all the tricky stuff
     rv = AfterEditInner(aAction, aDirection);
 
     // Free up selectionState range item
     htmlEditor->mRangeUpdater.DropRangeItem(mRangeItem);
 
     // Reset the contenteditable count to its previous value
     if (mRestoreContentEditableCount) {
-      nsCOMPtr<nsIDOMDocument> doc = htmlEditor->GetDOMDocument();
-      NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
-      nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc);
-      NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE);
+      nsHTMLDocument* htmlDoc = htmlEditor->GetHTMLDocument();
+      if (NS_WARN_IF(!htmlDoc)) {
+        return NS_ERROR_FAILURE;
+      }
       if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) {
         htmlDoc->ChangeContentEditableCount(nullptr, -1);
       }
       mRestoreContentEditableCount = false;
     }
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -20,16 +20,17 @@
 #include "HTMLEditorEventListener.h"
 #include "HTMLEditRules.h"
 #include "HTMLEditUtils.h"
 #include "HTMLURIRefObject.h"
 #include "StyleSheetTransactions.h"
 #include "TextEditUtils.h"
 #include "TypeInState.h"
 
+#include "nsHTMLDocument.h"
 #include "nsIDOMDocument.h"
 #include "nsIDocumentInlines.h"
 #include "nsISelectionController.h"
 #include "nsILinkHandler.h"
 #include "nsIInlineSpellChecker.h"
 
 #include "mozilla/css/Loader.h"
 
@@ -4972,9 +4973,19 @@ HTMLEditor::GetInputEventTargetContent()
 }
 
 Element*
 HTMLEditor::GetEditorRoot()
 {
   return GetActiveEditingHost();
 }
 
+nsHTMLDocument*
+HTMLEditor::GetHTMLDocument() const
+{
+  nsIDocument* doc = GetDocument();
+  if (NS_WARN_IF(!doc)) {
+    return nullptr;
+  }
+  return doc->AsHTMLDocument();
+}
+
 } // namespace mozilla
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -30,16 +30,17 @@
 #include "nsIHTMLInlineTableEditor.h"
 #include "nsIHTMLObjectResizer.h"
 #include "nsITableEditor.h"
 #include "nsPoint.h"
 #include "nsStubMutationObserver.h"
 #include "nsTArray.h"
 
 class nsDocumentFragment;
+class nsHTMLDocument;
 class nsITransferable;
 class nsIClipboard;
 class nsIDOMDocument;
 class nsIDOMElement;
 class nsILinkHandler;
 class nsTableWrapperFrame;
 class nsIDOMRange;
 class nsRange;
@@ -561,37 +562,39 @@ public:
    *                        you call this with setting this to true, you can
    *                        keep selection ranges if user has already been
    *                        changed.
    */
   nsresult
   MaybeCollapseSelectionAtFirstEditableNode(
     bool aIgnoreIfSelectionInEditingHost);
 
+  nsHTMLDocument* GetHTMLDocument() const;
+
 protected:
   class BlobReader final : public nsIEditorBlobListener
   {
   public:
     BlobReader(dom::BlobImpl* aBlob, HTMLEditor* aHTMLEditor,
-               bool aIsSafe, nsIDOMDocument* aSourceDoc,
+               bool aIsSafe, nsIDocument* aSourceDoc,
                nsINode* aDestinationNode, int32_t aDestOffset,
                bool aDoDeleteSelection);
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIEDITORBLOBLISTENER
 
   private:
     ~BlobReader()
     {
     }
 
     RefPtr<dom::BlobImpl> mBlob;
     RefPtr<HTMLEditor> mHTMLEditor;
     bool mIsSafe;
-    nsCOMPtr<nsIDOMDocument> mSourceDoc;
+    nsCOMPtr<nsIDocument> mSourceDoc;
     nsCOMPtr<nsINode> mDestinationNode;
     int32_t mDestOffset;
     bool mDoDeleteSelection;
   };
 
   NS_IMETHOD InitRules() override;
 
   virtual void CreateEventListeners() override;
@@ -764,34 +767,34 @@ protected:
    *                        If aAddCites is false, this will be null.
    */
   nsresult InsertAsPlaintextQuotation(const nsAString& aQuotedText,
                                       bool aAddCites,
                                       nsIDOMNode** aNodeInserted);
 
   nsresult InsertObject(const nsACString& aType, nsISupports* aObject,
                         bool aIsSafe,
-                        nsIDOMDocument* aSourceDoc,
+                        nsIDocument* aSourceDoc,
                         nsINode* aDestinationNode,
                         int32_t aDestOffset,
                         bool aDoDeleteSelection);
 
   // factored methods for handling insertion of data from transferables
   // (drag&drop or clipboard)
   NS_IMETHOD PrepareTransferable(nsITransferable** transferable) override;
   nsresult PrepareHTMLTransferable(nsITransferable** transferable);
   nsresult InsertFromTransferable(nsITransferable* transferable,
-                                    nsIDOMDocument* aSourceDoc,
+                                    nsIDocument* aSourceDoc,
                                     const nsAString& aContextStr,
                                     const nsAString& aInfoStr,
                                     bool havePrivateHTMLFlavor,
                                     bool aDoDeleteSelection);
   virtual nsresult InsertFromDataTransfer(dom::DataTransfer* aDataTransfer,
                                           int32_t aIndex,
-                                          nsIDOMDocument* aSourceDoc,
+                                          nsIDocument* aSourceDoc,
                                           nsINode* aDestinationNode,
                                           int32_t aDestOffset,
                                           bool aDoDeleteSelection) override;
   bool HavePrivateHTMLFlavor(nsIClipboard* clipboard );
   nsresult ParseCFHTML(nsCString& aCfhtml, char16_t** aStuffToPaste,
                        char16_t** aCfcontext);
 
   bool IsInLink(nsINode* aNode, nsCOMPtr<nsINode>* outLink = nullptr);
@@ -1106,17 +1109,17 @@ protected:
    *
    * aClearStyle should be set to false if you want the paste to be affected by
    * local style (e.g., for the insertHTML command).
    */
   nsresult DoInsertHTMLWithContext(const nsAString& aInputString,
                                    const nsAString& aContextStr,
                                    const nsAString& aInfoStr,
                                    const nsAString& aFlavor,
-                                   nsIDOMDocument* aSourceDoc,
+                                   nsIDocument* aSourceDoc,
                                    nsINode* aDestNode,
                                    int32_t aDestOffset,
                                    bool aDeleteSelection,
                                    bool aTrustedInput,
                                    bool aClearStyle = true);
 
   nsresult ClearStyle(nsCOMPtr<nsINode>* aNode, int32_t* aOffset,
                       nsAtom* aProperty, nsAtom* aAttribute);
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -34,17 +34,16 @@
 #include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsDependentSubstring.h"
 #include "nsError.h"
 #include "nsGkAtoms.h"
 #include "nsIClipboard.h"
 #include "nsIContent.h"
 #include "nsIDOMDocument.h"
-#include "nsIDOMDocumentFragment.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMNode.h"
 #include "nsIDocument.h"
 #include "nsIFile.h"
 #include "nsIInputStream.h"
 #include "nsIMIMEService.h"
 #include "nsNameSpaceManager.h"
 #include "nsINode.h"
@@ -177,30 +176,31 @@ HTMLEditor::InsertHTMLWithContext(const 
                                   const nsAString& aContextStr,
                                   const nsAString& aInfoStr,
                                   const nsAString& aFlavor,
                                   nsIDOMDocument* aSourceDoc,
                                   nsIDOMNode* aDestNode,
                                   int32_t aDestOffset,
                                   bool aDeleteSelection)
 {
+  nsCOMPtr<nsIDocument> sourceDoc = do_QueryInterface(aSourceDoc);
   nsCOMPtr<nsINode> destNode = do_QueryInterface(aDestNode);
   return DoInsertHTMLWithContext(aInputString, aContextStr, aInfoStr,
-                                 aFlavor, aSourceDoc, destNode, aDestOffset,
+                                 aFlavor, sourceDoc, destNode, aDestOffset,
                                  aDeleteSelection,
                                  /* trusted input */ true,
                                  /* clear style */ false);
 }
 
 nsresult
 HTMLEditor::DoInsertHTMLWithContext(const nsAString& aInputString,
                                     const nsAString& aContextStr,
                                     const nsAString& aInfoStr,
                                     const nsAString& aFlavor,
-                                    nsIDOMDocument* aSourceDoc,
+                                    nsIDocument* aSourceDoc,
                                     nsINode* aDestNode,
                                     int32_t aDestOffset,
                                     bool aDeleteSelection,
                                     bool aTrustedInput,
                                     bool aClearStyle)
 {
   NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
 
@@ -955,17 +955,17 @@ ImgFromData(const nsACString& aType, con
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(HTMLEditor::BlobReader, nsIEditorBlobListener)
 
 HTMLEditor::BlobReader::BlobReader(BlobImpl* aBlob,
                                    HTMLEditor* aHTMLEditor,
                                    bool aIsSafe,
-                                   nsIDOMDocument* aSourceDoc,
+                                   nsIDocument* aSourceDoc,
                                    nsINode* aDestinationNode,
                                    int32_t aDestOffset,
                                    bool aDoDeleteSelection)
   : mBlob(aBlob)
   , mHTMLEditor(aHTMLEditor)
   , mIsSafe(aIsSafe)
   , mSourceDoc(aSourceDoc)
   , mDestinationNode(aDestinationNode)
@@ -1012,17 +1012,17 @@ HTMLEditor::BlobReader::OnError(const ns
                                   &error, 1);
   return NS_OK;
 }
 
 nsresult
 HTMLEditor::InsertObject(const nsACString& aType,
                          nsISupports* aObject,
                          bool aIsSafe,
-                         nsIDOMDocument* aSourceDoc,
+                         nsIDocument* aSourceDoc,
                          nsINode* aDestinationNode,
                          int32_t aDestOffset,
                          bool aDoDeleteSelection)
 {
   nsresult rv;
 
   if (nsCOMPtr<BlobImpl> blob = do_QueryInterface(aObject)) {
     RefPtr<BlobReader> br = new BlobReader(blob, this, aIsSafe, aSourceDoc,
@@ -1092,17 +1092,17 @@ HTMLEditor::InsertObject(const nsACStrin
                                  aIsSafe, false);
   }
 
   return NS_OK;
 }
 
 nsresult
 HTMLEditor::InsertFromTransferable(nsITransferable* transferable,
-                                   nsIDOMDocument* aSourceDoc,
+                                   nsIDocument* aSourceDoc,
                                    const nsAString& aContextStr,
                                    const nsAString& aInfoStr,
                                    bool havePrivateHTMLFlavor,
                                    bool aDoDeleteSelection)
 {
   nsresult rv = NS_OK;
   nsAutoCString bestFlavor;
   nsCOMPtr<nsISupports> genericDataObj;
@@ -1218,17 +1218,17 @@ GetStringFromDataTransfer(DataTransfer* 
   if (variant) {
     variant->GetAsAString(aOutputString);
   }
 }
 
 nsresult
 HTMLEditor::InsertFromDataTransfer(DataTransfer* aDataTransfer,
                                    int32_t aIndex,
-                                   nsIDOMDocument* aSourceDoc,
+                                   nsIDocument* aSourceDoc,
                                    nsINode* aDestinationNode,
                                    int32_t aDestOffset,
                                    bool aDoDeleteSelection)
 {
   ErrorResult rv;
   RefPtr<DOMStringList> types =
     aDataTransfer->MozTypesAt(aIndex, CallerType::System, rv);
   if (rv.Failed()) {
--- a/editor/libeditor/HTMLEditorObjectResizer.cpp
+++ b/editor/libeditor/HTMLEditorObjectResizer.cpp
@@ -19,17 +19,16 @@
 #include "nsAlgorithm.h"
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsGkAtoms.h"
 #include "nsAtom.h"
 #include "nsIContent.h"
 #include "nsID.h"
-#include "nsIDOMDocument.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMEvent.h"
 #include "nsIDocument.h"
 #include "nsIPresShell.h"
 #include "nsISupportsUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsReadableUtils.h"
 #include "nsString.h"
--- a/editor/libeditor/TextEditRules.cpp
+++ b/editor/libeditor/TextEditRules.cpp
@@ -25,17 +25,16 @@
 #include "nsCRTGlue.h"
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsGkAtoms.h"
 #include "nsIContent.h"
 #include "nsIDocumentEncoder.h"
-#include "nsIDOMDocument.h"
 #include "nsNameSpaceManager.h"
 #include "nsINode.h"
 #include "nsIPlaintextEditor.h"
 #include "nsISupportsBase.h"
 #include "nsLiteralString.h"
 #include "nsTextNode.h"
 #include "nsUnicharUtils.h"
 #include "nsIHTMLCollection.h"
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -31,17 +31,16 @@
 #include "nsCopySupport.h"
 #include "nsDebug.h"
 #include "nsDependentSubstring.h"
 #include "nsError.h"
 #include "nsGkAtoms.h"
 #include "nsIClipboard.h"
 #include "nsIContent.h"
 #include "nsIContentIterator.h"
-#include "nsIDOMDocument.h"
 #include "nsIDOMEventTarget.h"
 #include "nsIDOMNode.h"
 #include "nsIDocumentEncoder.h"
 #include "nsINode.h"
 #include "nsIPresShell.h"
 #include "nsISelectionController.h"
 #include "nsISupportsPrimitives.h"
 #include "nsITransferable.h"
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -11,17 +11,16 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsIEditor.h"
 #include "nsIEditorMailSupport.h"
 #include "nsIPlaintextEditor.h"
 #include "nsISupportsImpl.h"
 #include "nscore.h"
 
 class nsIContent;
-class nsIDOMDocument;
 class nsIDOMEvent;
 class nsIDocumentEncoder;
 class nsIOutputStream;
 class nsISelectionController;
 class nsITransferable;
 
 namespace mozilla {
 
@@ -144,17 +143,17 @@ public:
 
   nsresult InsertTextAt(const nsAString& aStringToInsert,
                         nsINode* aDestinationNode,
                         int32_t aDestOffset,
                         bool aDoDeleteSelection);
 
   virtual nsresult InsertFromDataTransfer(dom::DataTransfer* aDataTransfer,
                                           int32_t aIndex,
-                                          nsIDOMDocument* aSourceDoc,
+                                          nsIDocument* aSourceDoc,
                                           nsINode* aDestinationNode,
                                           int32_t aDestOffset,
                                           bool aDoDeleteSelection) override;
 
   virtual nsresult InsertFromDrop(dom::DragEvent* aDropEvent) override;
 
   /**
    * Extends the selection for given deletion operation
@@ -164,17 +163,17 @@ public:
   nsresult ExtendSelectionForDelete(Selection* aSelection,
                                     nsIEditor::EDirection* aAction);
 
   /**
    * Return true if the data is safe to insert as the source and destination
    * principals match, or we are in a editor context where this doesn't matter.
    * Otherwise, the data must be sanitized first.
    */
-  bool IsSafeToInsertData(nsIDOMDocument* aSourceDoc);
+  bool IsSafeToInsertData(nsIDocument* aSourceDoc);
 
   static void GetDefaultEditorPrefs(int32_t& aNewLineHandling,
                                     int32_t& aCaretStyle);
 
   /**
     * The maximum number of characters allowed.
     *   default: -1 (unlimited).
     */
--- a/editor/libeditor/TextEditorDataTransfer.cpp
+++ b/editor/libeditor/TextEditorDataTransfer.cpp
@@ -15,17 +15,16 @@
 #include "nsAString.h"
 #include "nsCOMPtr.h"
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsIClipboard.h"
 #include "nsIContent.h"
-#include "nsIDOMDocument.h"
 #include "nsIDOMEvent.h"
 #include "nsIDocument.h"
 #include "nsIDragService.h"
 #include "nsIDragSession.h"
 #include "nsIEditor.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIPrincipal.h"
@@ -130,17 +129,17 @@ TextEditor::InsertTextFromTransferable(n
   }
 
   return rv;
 }
 
 nsresult
 TextEditor::InsertFromDataTransfer(DataTransfer* aDataTransfer,
                                    int32_t aIndex,
-                                   nsIDOMDocument* aSourceDoc,
+                                   nsIDocument* aSourceDoc,
                                    nsINode* aDestinationNode,
                                    int32_t aDestOffset,
                                    bool aDoDeleteSelection)
 {
   nsCOMPtr<nsIVariant> data;
   aDataTransfer->GetDataAtNoSecurityCheck(NS_LITERAL_STRING("text/plain"), aIndex,
                                           getter_AddRefs(data));
   if (data) {
@@ -165,33 +164,35 @@ TextEditor::InsertFromDrop(DragEvent* aD
   RefPtr<DataTransfer> dataTransfer = aDropEvent->GetDataTransfer();
   NS_ENSURE_TRUE(dataTransfer, NS_ERROR_FAILURE);
 
   nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
   NS_ASSERTION(dragSession, "No drag session");
 
   nsCOMPtr<nsINode> sourceNode = dataTransfer->GetMozSourceNode();
 
-  nsCOMPtr<nsIDOMDocument> srcdomdoc;
+  nsCOMPtr<nsIDocument> srcdoc;
   if (sourceNode) {
-    srcdomdoc = do_QueryInterface(sourceNode->OwnerDoc());
+    srcdoc = sourceNode->OwnerDoc();
   }
 
   if (nsContentUtils::CheckForSubFrameDrop(dragSession,
         aDropEvent->WidgetEventPtr()->AsDragEvent())) {
     // Don't allow drags from subframe documents with different origins than
     // the drop destination.
-    if (srcdomdoc && !IsSafeToInsertData(srcdomdoc)) {
+    if (srcdoc && !IsSafeToInsertData(srcdoc)) {
       return NS_OK;
     }
   }
 
   // Current doc is destination
-  nsCOMPtr<nsIDOMDocument> destdomdoc = GetDOMDocument();
-  NS_ENSURE_TRUE(destdomdoc, NS_ERROR_NOT_INITIALIZED);
+  nsIDocument* destdoc = GetDocument();
+  if (NS_WARN_IF(!destdoc)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
 
   uint32_t numItems = dataTransfer->MozItemCount();
   if (numItems < 1) {
     return NS_ERROR_FAILURE;  // Nothing to drop?
   }
 
   // Combine any deletion and drop insertion into one transaction
   AutoPlaceholderBatch beginBatching(this);
@@ -235,27 +236,27 @@ TextEditor::InsertFromDrop(DragEvent* aD
       }
       if (cursorIsInSelection) {
         break;
       }
     }
 
     if (cursorIsInSelection) {
       // Dragging within same doc can't drop on itself -- leave!
-      if (srcdomdoc == destdomdoc) {
+      if (srcdoc == destdoc) {
         return NS_OK;
       }
 
       // Dragging from another window onto a selection
       // XXX Decision made to NOT do this,
       //     note that 4.x does replace if dropped on
       //deleteSelection = true;
     } else {
       // We are NOT over the selection
-      if (srcdomdoc == destdomdoc) {
+      if (srcdoc == destdoc) {
         // Within the same doc: delete if user doesn't want to copy
         uint32_t dropEffect = dataTransfer->DropEffectInt();
         deleteSelection = !(dropEffect & nsIDragService::DRAGDROP_ACTION_COPY);
       } else {
         // Different source doc: Don't delete
         deleteSelection = false;
       }
     }
@@ -270,17 +271,17 @@ TextEditor::InsertFromDrop(DragEvent* aD
         // dropped into.
         return NS_OK;
       }
       content = content->GetParent();
     }
   }
 
   for (uint32_t i = 0; i < numItems; ++i) {
-    InsertFromDataTransfer(dataTransfer, i, srcdomdoc,
+    InsertFromDataTransfer(dataTransfer, i, srcdoc,
                            newSelectionParent,
                            newSelectionOffset, deleteSelection);
   }
 
   ScrollSelectionIntoView(false);
 
   return NS_OK;
 }
@@ -394,17 +395,17 @@ TextEditor::CanPasteTransferable(nsITran
   } else {
     *aCanPaste = false;
   }
 
   return NS_OK;
 }
 
 bool
-TextEditor::IsSafeToInsertData(nsIDOMDocument* aSourceDoc)
+TextEditor::IsSafeToInsertData(nsIDocument* aSourceDoc)
 {
   // Try to determine whether we should use a sanitizing fragment sink
   bool isSafe = false;
 
   nsCOMPtr<nsIDocument> destdoc = GetDocument();
   NS_ASSERTION(destdoc, "Where is our destination doc?");
   nsCOMPtr<nsIDocShellTreeItem> dsti = destdoc->GetDocShell();
   nsCOMPtr<nsIDocShellTreeItem> root;
@@ -412,20 +413,17 @@ TextEditor::IsSafeToInsertData(nsIDOMDoc
     dsti->GetRootTreeItem(getter_AddRefs(root));
   }
   nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(root);
   uint32_t appType;
   if (docShell && NS_SUCCEEDED(docShell->GetAppType(&appType))) {
     isSafe = appType == nsIDocShell::APP_TYPE_EDITOR;
   }
   if (!isSafe && aSourceDoc) {
-    nsCOMPtr<nsIDocument> srcdoc = do_QueryInterface(aSourceDoc);
-    NS_ASSERTION(srcdoc, "Where is our source doc?");
-
-    nsIPrincipal* srcPrincipal = srcdoc->NodePrincipal();
+    nsIPrincipal* srcPrincipal = aSourceDoc->NodePrincipal();
     nsIPrincipal* destPrincipal = destdoc->NodePrincipal();
     NS_ASSERTION(srcPrincipal && destPrincipal, "How come we don't have a principal?");
     srcPrincipal->Subsumes(destPrincipal, &isSafe);
   }
 
   return isSafe;
 }
 
--- a/editor/libeditor/TextEditorTest.cpp
+++ b/editor/libeditor/TextEditorTest.cpp
@@ -8,17 +8,16 @@
 #include "TextEditorTest.h"
 
 #include <stdio.h>
 
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsGkAtoms.h"
 #include "nsIDocument.h"
-#include "nsIDOMDocument.h"
 #include "nsIEditor.h"
 #include "nsIHTMLEditor.h"
 #include "nsINodeList.h"
 #include "nsIPlaintextEditor.h"
 #include "nsISelection.h"
 #include "nsLiteralString.h"
 #include "nsReadableUtils.h"
 #include "nsString.h"
@@ -124,35 +123,31 @@ nsresult TextEditorTest::TestInsertBreak
   TEST_RESULT(rv);
   mEditor->DebugDumpContent();
 
   return rv;
 }
 
 nsresult TextEditorTest::TestTextProperties()
 {
-  nsCOMPtr<nsIDOMDocument>domDoc;
-  nsresult rv = mEditor->GetDocument(getter_AddRefs(domDoc));
-  TEST_RESULT(rv);
-  TEST_POINTER(domDoc.get());
-  nsCOMPtr<nsIDocument>doc = do_QueryInterface(domDoc);
+  nsCOMPtr<nsIDocument> doc = mEditor->AsEditorBase()->GetDocument();
   TEST_POINTER(doc.get());
   // XXX This is broken, text nodes are not elements.
   nsAutoString textTag(NS_LITERAL_STRING("#text"));
   nsCOMPtr<nsINodeList>nodeList = doc->GetElementsByTagName(textTag);
   TEST_POINTER(nodeList.get());
   uint32_t count = nodeList->Length();
   NS_ASSERTION(0 != count, "there are no text nodes in the document!");
   nsCOMPtr<nsINode>textNode = nodeList->Item(count - 1);
   TEST_POINTER(textNode.get());
 
   // set the whole text node to bold
   printf("set the whole first text node to bold\n");
   nsCOMPtr<nsISelection>selection;
-  rv = mEditor->GetSelection(getter_AddRefs(selection));
+  nsresult rv = mEditor->GetSelection(getter_AddRefs(selection));
   TEST_RESULT(rv);
   TEST_POINTER(selection.get());
   uint32_t length = textNode->Length();
   selection->AsSelection()->Collapse(textNode, 0);
   selection->AsSelection()->Extend(textNode, length);
 
   nsCOMPtr<nsIHTMLEditor> htmlEditor (do_QueryInterface(mTextEditor));
   NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
--- a/editor/libeditor/TypeInState.cpp
+++ b/editor/libeditor/TypeInState.cpp
@@ -21,17 +21,16 @@
 #include "nsStringFwd.h"
 
 // Workaround for windows headers
 #ifdef SetProp
 #undef SetProp
 #endif
 
 class nsAtom;
-class nsIDOMDocument;
 
 namespace mozilla {
 
 using namespace dom;
 
 /********************************************************************
  * mozilla::TypeInState
  *******************************************************************/
--- a/editor/libeditor/WSRunObject.cpp
+++ b/editor/libeditor/WSRunObject.cpp
@@ -17,17 +17,16 @@
 #include "mozilla/SelectionState.h"
 
 #include "nsAString.h"
 #include "nsCRT.h"
 #include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsIContent.h"
-#include "nsIDOMDocument.h"
 #include "nsISupportsImpl.h"
 #include "nsRange.h"
 #include "nsString.h"
 #include "nsTextFragment.h"
 
 namespace mozilla {
 
 using namespace dom;
--- a/editor/libeditor/moz.build
+++ b/editor/libeditor/moz.build
@@ -72,16 +72,17 @@ UNIFIED_SOURCES += [
     'TextEditRulesBidi.cpp',
     'TextEditUtils.cpp',
     'TypeInState.cpp',
     'WSRunObject.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/base',
+    '/dom/html',
     '/extensions/spellcheck/src',
     '/layout/generic',
     '/layout/style',
     '/layout/tables',
     '/layout/xul',
 ]
 
 EXTRA_COMPONENTS += [
--- a/editor/spellchecker/EditorSpellCheck.cpp
+++ b/editor/spellchecker/EditorSpellCheck.cpp
@@ -3,34 +3,33 @@
 /* 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 "EditorSpellCheck.h"
 
 #include "mozilla/Attributes.h"         // for final
 #include "mozilla/EditorBase.h"         // for EditorBase
+#include "mozilla/HTMLEditor.h"         // for HTMLEditor
 #include "mozilla/dom/Element.h"        // for Element
 #include "mozilla/dom/Selection.h"
 #include "mozilla/intl/LocaleService.h" // for retrieving app locale
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "mozilla/mozSpellChecker.h"    // for mozSpellChecker
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/TextServicesDocument.h" // for TextServicesDocument
 #include "nsAString.h"                  // for nsAString::IsEmpty, etc
 #include "nsComponentManagerUtils.h"    // for do_CreateInstance
 #include "nsDebug.h"                    // for NS_ENSURE_TRUE, etc
 #include "nsDependentSubstring.h"       // for Substring
 #include "nsError.h"                    // for NS_ERROR_NOT_INITIALIZED, etc
 #include "nsIContent.h"                 // for nsIContent
 #include "nsIContentPrefService2.h"     // for nsIContentPrefService2, etc
-#include "nsIDOMDocument.h"             // for nsIDOMDocument
 #include "nsIDocument.h"                // for nsIDocument
 #include "nsIEditor.h"                  // for nsIEditor
-#include "nsIHTMLEditor.h"              // for nsIHTMLEditor
 #include "nsILoadContext.h"
 #include "nsISelection.h"               // for nsISelection
 #include "nsISupportsBase.h"            // for nsISupports
 #include "nsISupportsUtils.h"           // for NS_ADDREF
 #include "nsITextServicesFilter.h"      // for nsITextServicesFilter
 #include "nsIURI.h"                     // for nsIURI
 #include "nsThreadUtils.h"              // for GetMainThreadSerialEventTarget
 #include "nsVariant.h"                  // for nsIWritableVariant, etc
@@ -71,49 +70,38 @@ public:
   }
 };
 
 #define CPS_PREF_NAME NS_LITERAL_STRING("spellcheck.lang")
 
 /**
  * Gets the URI of aEditor's document.
  */
-static nsresult
-GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI)
+static nsIURI*
+GetDocumentURI(EditorBase* aEditor)
 {
-  NS_ENSURE_ARG_POINTER(aEditor);
-  NS_ENSURE_ARG_POINTER(aURI);
-
-  nsCOMPtr<nsIDOMDocument> domDoc;
-  aEditor->GetDocument(getter_AddRefs(domDoc));
-  NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
+  MOZ_ASSERT(aEditor);
 
-  nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
-  NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+  nsIDocument* doc = aEditor->AsEditorBase()->GetDocument();
+  if (NS_WARN_IF(!doc)) {
+    return nullptr;
+  }
 
-  nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI();
-  NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE);
-
-  *aURI = docUri;
-  NS_ADDREF(*aURI);
-  return NS_OK;
+  return doc->GetDocumentURI();
 }
 
-static already_AddRefed<nsILoadContext>
+static nsILoadContext*
 GetLoadContext(nsIEditor* aEditor)
 {
-  nsCOMPtr<nsIDOMDocument> domDoc;
-  aEditor->GetDocument(getter_AddRefs(domDoc));
-  NS_ENSURE_TRUE(domDoc, nullptr);
+  nsIDocument* doc = aEditor->AsEditorBase()->GetDocument();
+  if (NS_WARN_IF(!doc)) {
+    return nullptr;
+  }
 
-  nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
-  NS_ENSURE_TRUE(doc, nullptr);
-
-  nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
-  return loadContext.forget();
+  return doc->GetLoadContext();
 }
 
 /**
  * Fetches the dictionary stored in content prefs and maintains state during the
  * fetch, which is asynchronous.
  */
 class DictionaryFetcher final : public nsIContentPrefCallback2
 {
@@ -166,63 +154,62 @@ private:
 NS_IMPL_ISUPPORTS(DictionaryFetcher, nsIContentPrefCallback2)
 
 class ContentPrefInitializerRunnable final : public Runnable
 {
 public:
   ContentPrefInitializerRunnable(nsIEditor* aEditor,
                                  nsIContentPrefCallback2* aCallback)
     : Runnable("ContentPrefInitializerRunnable")
-    , mEditor(aEditor)
+    , mEditorBase(aEditor->AsEditorBase())
     , mCallback(aCallback)
   {
   }
 
   NS_IMETHOD Run() override
   {
-    if (mEditor->AsEditorBase()->Destroyed()) {
+    if (mEditorBase->Destroyed()) {
       mCallback->HandleError(NS_ERROR_NOT_AVAILABLE);
       return NS_OK;
     }
 
     nsCOMPtr<nsIContentPrefService2> contentPrefService =
       do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
     if (NS_WARN_IF(!contentPrefService)) {
       mCallback->HandleError(NS_ERROR_NOT_AVAILABLE);
       return NS_OK;
     }
 
-    nsCOMPtr<nsIURI> docUri;
-    nsresult rv = GetDocumentURI(mEditor, getter_AddRefs(docUri));
+    nsCOMPtr<nsIURI> docUri = GetDocumentURI(mEditorBase);
+    if (NS_WARN_IF(!docUri)) {
+      mCallback->HandleError(NS_ERROR_FAILURE);
+      return NS_OK;
+    }
+
+    nsAutoCString docUriSpec;
+    nsresult rv = docUri->GetSpec(docUriSpec);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mCallback->HandleError(rv);
       return NS_OK;
     }
 
-    nsAutoCString docUriSpec;
-    rv = docUri->GetSpec(docUriSpec);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      mCallback->HandleError(rv);
-      return NS_OK;
-    }
-
-    nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(mEditor);
     rv = contentPrefService->GetByDomainAndName(
                                NS_ConvertUTF8toUTF16(docUriSpec),
-                               CPS_PREF_NAME, loadContext,
+                               CPS_PREF_NAME,
+                               GetLoadContext(mEditorBase),
                                mCallback);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mCallback->HandleError(rv);
       return NS_OK;
     }
     return NS_OK;
   }
 
 private:
-  nsCOMPtr<nsIEditor> mEditor;
+  RefPtr<EditorBase> mEditorBase;
   nsCOMPtr<nsIContentPrefCallback2> mCallback;
 };
 
 NS_IMETHODIMP
 DictionaryFetcher::Fetch(nsIEditor* aEditor)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
 
@@ -232,68 +219,70 @@ DictionaryFetcher::Fetch(nsIEditor* aEdi
 
   return NS_OK;
 }
 
 /**
  * Stores the current dictionary for aEditor's document URL.
  */
 static nsresult
-StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary)
+StoreCurrentDictionary(EditorBase* aEditorBase, const nsAString& aDictionary)
 {
-  NS_ENSURE_ARG_POINTER(aEditor);
+  NS_ENSURE_ARG_POINTER(aEditorBase);
 
   nsresult rv;
 
-  nsCOMPtr<nsIURI> docUri;
-  rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIURI> docUri = GetDocumentURI(aEditorBase);
+  if (NS_WARN_IF(!docUri)) {
+    return NS_ERROR_FAILURE;
+  }
 
   nsAutoCString docUriSpec;
   rv = docUri->GetSpec(docUriSpec);
   NS_ENSURE_SUCCESS(rv, rv);
 
   RefPtr<nsVariant> prefValue = new nsVariant();
   prefValue->SetAsAString(aDictionary);
 
   nsCOMPtr<nsIContentPrefService2> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
 
-  nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
   return contentPrefService->Set(NS_ConvertUTF8toUTF16(docUriSpec),
-                                 CPS_PREF_NAME, prefValue, loadContext,
+                                 CPS_PREF_NAME, prefValue,
+                                 GetLoadContext(aEditorBase),
                                  nullptr);
 }
 
 /**
  * Forgets the current dictionary stored for aEditor's document URL.
  */
 static nsresult
-ClearCurrentDictionary(nsIEditor* aEditor)
+ClearCurrentDictionary(EditorBase* aEditorBase)
 {
-  NS_ENSURE_ARG_POINTER(aEditor);
+  NS_ENSURE_ARG_POINTER(aEditorBase);
 
   nsresult rv;
 
-  nsCOMPtr<nsIURI> docUri;
-  rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIURI> docUri = GetDocumentURI(aEditorBase);
+  if (NS_WARN_IF(!docUri)) {
+    return NS_ERROR_FAILURE;
+  }
 
   nsAutoCString docUriSpec;
   rv = docUri->GetSpec(docUriSpec);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIContentPrefService2> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
 
-  nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
   return contentPrefService->RemoveByDomainAndName(
-    NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME, loadContext, nullptr);
+                               NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME,
+                               GetLoadContext(aEditorBase), nullptr);
 }
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(EditorSpellCheck)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(EditorSpellCheck)
 
 NS_INTERFACE_MAP_BEGIN(EditorSpellCheck)
   NS_INTERFACE_MAP_ENTRY(nsIEditorSpellCheck)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditorSpellCheck)
@@ -379,22 +368,22 @@ private:
 };
 
 NS_IMETHODIMP
 EditorSpellCheck::InitSpellChecker(nsIEditor* aEditor,
                                    bool aEnableSelectionChecking,
                                    nsIEditorSpellCheckCallback* aCallback)
 {
   NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER);
-  mEditor = aEditor;
+  mEditor = aEditor->AsEditorBase();
 
-  nsCOMPtr<nsIDOMDocument> domDoc;
-  mEditor->GetDocument(getter_AddRefs(domDoc));
-  nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
-  NS_ENSURE_STATE(doc);
+  nsCOMPtr<nsIDocument> doc = mEditor->GetDocument();
+  if (NS_WARN_IF(!doc)) {
+    return NS_ERROR_FAILURE;
+  }
 
   nsresult rv;
 
   // We can spell check with any editor type
   RefPtr<TextServicesDocument> textServicesDocument =
     new TextServicesDocument();
   textServicesDocument->SetFilter(mTxtSrvFilter);
 
@@ -728,21 +717,21 @@ EditorSpellCheck::UpdateCurrentDictionar
   }
 
   nsresult rv;
 
   RefPtr<EditorSpellCheck> kungFuDeathGrip = this;
 
   // Get language with html5 algorithm
   nsCOMPtr<nsIContent> rootContent;
-  nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(mEditor);
+  HTMLEditor* htmlEditor = mEditor->AsHTMLEditor();
   if (htmlEditor) {
     rootContent = htmlEditor->GetActiveEditingHost();
   } else {
-    rootContent = mEditor->AsEditorBase()->GetRoot();
+    rootContent = mEditor->GetRoot();
   }
 
   // Try to get topmost document's document element for embedded mail editor.
   uint32_t flags = 0;
   mEditor->GetFlags(&flags);
   if (flags & nsIPlaintextEditor::eEditorMailMask) {
     nsCOMPtr<nsIDocument> ownerDoc = rootContent->OwnerDoc();
     NS_ENSURE_TRUE(ownerDoc, NS_ERROR_FAILURE);
--- a/editor/spellchecker/EditorSpellCheck.h
+++ b/editor/spellchecker/EditorSpellCheck.h
@@ -24,16 +24,17 @@ class nsITextServicesFilter;
 { /* {75656ad9-bd13-4c5d-939a-ec6351eea0cc} */        \
     0x75656ad9, 0xbd13, 0x4c5d,                       \
     { 0x93, 0x9a, 0xec, 0x63, 0x51, 0xee, 0xa0, 0xcc }\
 }
 
 namespace mozilla {
 
 class DictionaryFetcher;
+class EditorBase;
 
 enum dictCompare
 {
   DICT_NORMAL_COMPARE,
   DICT_COMPARE_CASE_INSENSITIVE,
   DICT_COMPARE_DASHMATCH
 };
 
@@ -52,17 +53,17 @@ public:
 
   mozSpellChecker* GetSpellChecker();
 
 protected:
   virtual ~EditorSpellCheck();
 
   RefPtr<mozSpellChecker> mSpellChecker;
   nsCOMPtr<nsITextServicesFilter> mTxtSrvFilter;
-  nsCOMPtr<nsIEditor> mEditor;
+  RefPtr<EditorBase> mEditor;
 
   nsTArray<nsString> mSuggestedWordList;
 
   // these are the words in the current personal dictionary,
   // GetPersonalDictionary must be called to load them.
   nsTArray<nsString>  mDictionaryList;
 
   nsString mPreferredLang;
--- a/editor/spellchecker/TextServicesDocument.cpp
+++ b/editor/spellchecker/TextServicesDocument.cpp
@@ -1,30 +1,30 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "TextServicesDocument.h"
 
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
+#include "mozilla/dom/Element.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/mozalloc.h"           // for operator new, etc
 #include "mozilla/TextEditor.h"         // for TextEditor
 #include "nsAString.h"                  // for nsAString::Length, etc
 #include "nsContentUtils.h"             // for nsContentUtils
 #include "nsDebug.h"                    // for NS_ENSURE_TRUE, etc
 #include "nsDependentSubstring.h"       // for Substring
 #include "nsError.h"                    // for NS_OK, NS_ERROR_FAILURE, etc
 #include "nsFilteredContentIterator.h"  // for nsFilteredContentIterator
 #include "nsGenericHTMLElement.h"       // for nsGenericHTMLElement
 #include "nsIContent.h"                 // for nsIContent, etc
 #include "nsIContentIterator.h"         // for nsIContentIterator
 #include "nsID.h"                       // for NS_GET_IID
-#include "nsIDOMDocument.h"             // for nsIDOMDocument
 #include "nsIDOMNode.h"                 // for nsIDOMNode, etc
 #include "nsIEditor.h"                  // for nsIEditor, etc
 #include "nsINode.h"                    // for nsINode
 #include "nsIPlaintextEditor.h"         // for nsIPlaintextEditor
 #include "nsISelection.h"               // for nsISelection
 #include "nsISelectionController.h"     // for nsISelectionController, etc
 #include "nsISupportsBase.h"            // for nsISupports
 #include "nsISupportsUtils.h"           // for NS_IF_ADDREF, NS_ADDREF, etc
@@ -93,30 +93,29 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(TextSer
 
 NS_INTERFACE_MAP_BEGIN(TextServicesDocument)
   NS_INTERFACE_MAP_ENTRY(nsIEditActionListener)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditActionListener)
   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(TextServicesDocument)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION(TextServicesDocument,
-                         mDOMDocument,
+                         mDocument,
                          mSelCon,
                          mTextEditor,
                          mIterator,
                          mPrevTextBlock,
                          mNextTextBlock,
                          mExtent,
                          mTxtSvcFilter)
 
 nsresult
 TextServicesDocument::InitWithEditor(nsIEditor* aEditor)
 {
   nsCOMPtr<nsISelectionController> selCon;
-  nsCOMPtr<nsIDOMDocument> doc;
 
   NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER);
 
   LOCK_DOC(this);
 
   // Check to see if we already have an mSelCon. If we do, it
   // better be the same one the editor uses!
 
@@ -131,33 +130,27 @@ TextServicesDocument::InitWithEditor(nsI
     UNLOCK_DOC(this);
     return NS_ERROR_FAILURE;
   }
 
   if (!mSelCon) {
     mSelCon = selCon;
   }
 
-  // Check to see if we already have an mDOMDocument. If we do, it
+  // Check to see if we already have an mDocument. If we do, it
   // better be the same one the editor uses!
 
-  rv = aEditor->GetDocument(getter_AddRefs(doc));
-
-  if (NS_FAILED(rv)) {
-    UNLOCK_DOC(this);
-    return rv;
-  }
-
-  if (!doc || (mDOMDocument && doc != mDOMDocument)) {
+  nsCOMPtr<nsIDocument> doc = aEditor->AsEditorBase()->GetDocument();
+  if (!doc || (mDocument && doc != mDocument)) {
     UNLOCK_DOC(this);
     return NS_ERROR_FAILURE;
   }
 
-  if (!mDOMDocument) {
-    mDOMDocument = doc;
+  if (!mDocument) {
+    mDocument = doc;
 
     rv = CreateDocumentContentIterator(getter_AddRefs(mIterator));
 
     if (NS_FAILED(rv)) {
       UNLOCK_DOC(this);
       return rv;
     }
 
@@ -176,34 +169,20 @@ TextServicesDocument::InitWithEditor(nsI
   rv = aEditor->AddEditActionListener(this);
 
   UNLOCK_DOC(this);
 
   return rv;
 }
 
 nsresult
-TextServicesDocument::GetDocument(nsIDOMDocument** aDoc)
-{
-  NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER);
-
-  *aDoc = nullptr; // init out param
-  NS_ENSURE_TRUE(mDOMDocument, NS_ERROR_NOT_INITIALIZED);
-
-  *aDoc = mDOMDocument;
-  NS_ADDREF(*aDoc);
-
-  return NS_OK;
-}
-
-nsresult
 TextServicesDocument::SetExtent(nsRange* aRange)
 {
   NS_ENSURE_ARG_POINTER(aRange);
-  NS_ENSURE_TRUE(mDOMDocument, NS_ERROR_FAILURE);
+  NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
 
   LOCK_DOC(this);
 
   // We need to store a copy of aDOMRange since we don't
   // know where it came from.
 
   mExtent = aRange->CloneRange();
 
@@ -1639,34 +1618,30 @@ TextServicesDocument::CreateContentItera
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   filter.forget(aIterator);
   return NS_OK;
 }
 
-already_AddRefed<nsINode>
-TextServicesDocument::GetDocumentContentRootNode()
+Element*
+TextServicesDocument::GetDocumentContentRootNode() const
 {
-  if (NS_WARN_IF(!mDOMDocument)) {
+  if (NS_WARN_IF(!mDocument)) {
     return nullptr;
   }
 
-  nsCOMPtr<nsIDocument> doc = do_QueryInterface(mDOMDocument);
-
-  if (doc->IsHTMLOrXHTML()) {
+  if (mDocument->IsHTMLOrXHTML()) {
     // For HTML documents, the content root node is the body.
-    nsCOMPtr<nsINode> node = doc->GetBody();
-    return node.forget();
+    return mDocument->GetBody();
   }
 
   // For non-HTML documents, the content root node will be the document element.
-  nsCOMPtr<nsINode> node = doc->GetDocumentElement();
-  return node.forget();
+  return mDocument->GetDocumentElement();
 }
 
 already_AddRefed<nsRange>
 TextServicesDocument::CreateDocumentContentRange()
 {
   nsCOMPtr<nsINode> node = GetDocumentContentRootNode();
   if (NS_WARN_IF(!node)) {
     return nullptr;
@@ -2091,17 +2066,17 @@ TextServicesDocument::GetSelection(Block
                                    int32_t* aSelLength)
 {
   NS_ENSURE_TRUE(aSelStatus && aSelOffset && aSelLength, NS_ERROR_NULL_POINTER);
 
   *aSelStatus = BlockSelectionStatus::eBlockNotFound;
   *aSelOffset = -1;
   *aSelLength = -1;
 
-  NS_ENSURE_TRUE(mDOMDocument && mSelCon, NS_ERROR_FAILURE);
+  NS_ENSURE_TRUE(mDocument && mSelCon, NS_ERROR_FAILURE);
 
   if (mIteratorStatus == IteratorStatus::eDone) {
     return NS_OK;
   }
 
   RefPtr<Selection> selection =
     mSelCon->GetDOMSelection(nsISelectionController::SELECTION_NORMAL);
   NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
--- a/editor/spellchecker/TextServicesDocument.h
+++ b/editor/spellchecker/TextServicesDocument.h
@@ -11,30 +11,33 @@
 #include "nsIEditActionListener.h"
 #include "nsISupportsImpl.h"
 #include "nsStringFwd.h"
 #include "nsTArray.h"
 #include "nscore.h"
 
 class nsIContent;
 class nsIContentIterator;
-class nsIDOMDocument;
 class nsIDOMNode;
 class nsIEditor;
 class nsINode;
 class nsISelection;
 class nsISelectionController;
 class nsITextServicesFilter;
 class nsRange;
 
 namespace mozilla {
 
 class OffsetEntry;
 class TextEditor;
 
+namespace dom {
+class Elemenent;
+};
+
 /**
  * The TextServicesDocument presents the document in as a bunch of flattened
  * text blocks. Each text block can be retrieved as an nsString.
  */
 class TextServicesDocument final : public nsIEditActionListener
 {
 private:
   enum class IteratorStatus : uint8_t
@@ -44,17 +47,17 @@ private:
     // I points to first text node (TN) in current block (CB).
     eValid,
     // No TN in CB, I points to first TN in prev block.
     ePrev,
     // No TN in CB, I points to first TN in next block.
     eNext,
   };
 
-  nsCOMPtr<nsIDOMDocument> mDOMDocument;
+  nsCOMPtr<nsIDocument> mDocument;
   nsCOMPtr<nsISelectionController> mSelCon;
   RefPtr<TextEditor> mTextEditor;
   nsCOMPtr<nsIContentIterator> mIterator;
   nsCOMPtr<nsIContent> mPrevTextBlock;
   nsCOMPtr<nsIContent> mNextTextBlock;
   nsTArray<OffsetEntry*> mOffsetTable;
   RefPtr<nsRange> mExtent;
   nsCOMPtr<nsITextServicesFilter> mTxtSvcFilter;
@@ -80,23 +83,16 @@ public:
    * text services document will use the DOM document and presentation shell
    * used by the editor.
    *
    * @param aEditor             The editor to use.
    */
   nsresult InitWithEditor(nsIEditor* aEditor);
 
   /**
-   * Get the DOM document for the document in use.
-   *
-   * @return aDOMDocument       The dom document.
-   */
-  nsresult GetDocument(nsIDOMDocument** aDOMDocument);
-
-  /**
    * Sets the range/extent over which the text services document will iterate.
    * Note that InitWithEditor() should have been called prior to calling this
    * method.  If this method is never called, the text services defaults to
    * iterating over the entire document.
    *
    * @param aDOMRange           The range to use. aDOMRange must point to a
    *                            valid range object.
    */
@@ -238,17 +234,17 @@ public:
                                     int32_t* aStartOffset,
                                     nsINode** aEndContainer,
                                     int32_t* aEndOffset);
 
 private:
   nsresult CreateContentIterator(nsRange* aRange,
                                  nsIContentIterator** aIterator);
 
-  already_AddRefed<nsINode> GetDocumentContentRootNode();
+  dom::Element* GetDocumentContentRootNode() const;
   already_AddRefed<nsRange> CreateDocumentContentRange();
   already_AddRefed<nsRange> CreateDocumentContentRootToNodeOffsetRange(
                               nsINode* aParent,
                               uint32_t aOffset,
                               bool aToStart);
   nsresult CreateDocumentContentIterator(nsIContentIterator** aIterator);
 
   nsresult AdjustContentIterator();
--- a/gfx/webrender_bindings/Moz2DImageRenderer.cpp
+++ b/gfx/webrender_bindings/Moz2DImageRenderer.cpp
@@ -49,19 +49,60 @@ struct FontTemplate {
   uint32_t mIndex;
   const VecU8 *mVec;
   RefPtr<UnscaledFont> mUnscaledFont;
 };
 
 StaticMutex sFontDataTableLock;
 std::unordered_map<FontKey, FontTemplate> sFontDataTable;
 
+// Fixed-size ring buffer logging font deletion events to aid debugging.
+static struct FontDeleteLog {
+  static const size_t MAX_ENTRIES = 256;
+
+  uint64_t mEntries[MAX_ENTRIES] = { 0 };
+  size_t mNextEntry = 0;
+
+  void AddEntry(uint64_t aEntry) {
+    mEntries[mNextEntry] = aEntry;
+    mNextEntry = (mNextEntry + 1) % MAX_ENTRIES;
+  }
+
+  void Add(WrFontKey aKey) {
+    AddEntry(AsUint64(aKey));
+  }
+
+  // Store namespace clears as font id 0, since this will never be allocated.
+  void Add(WrIdNamespace aNamespace) {
+    AddEntry(AsUint64(WrFontKey { aNamespace, 0 }));
+  }
+
+  // Find a matching entry in the log, searching backwards starting at the newest
+  // entry and finishing with the oldest entry. Returns a brief description of why
+  // the font was deleted, if known.
+  const char* Find(WrFontKey aKey) {
+    uint64_t keyEntry = AsUint64(aKey);
+    uint64_t namespaceEntry = AsUint64(WrFontKey { aKey.mNamespace, 0 });
+    size_t offset = mNextEntry;
+    do {
+      offset = (offset + MAX_ENTRIES - 1) % MAX_ENTRIES;
+      if (mEntries[offset] == keyEntry) {
+        return "deleted font";
+      } else if (mEntries[offset] == namespaceEntry) {
+        return "cleared namespace";
+      }
+    } while (offset != mNextEntry);
+    return "unknown font";
+  }
+} sFontDeleteLog;
+
 void
 ClearBlobImageResources(WrIdNamespace aNamespace) {
   StaticMutexAutoLock lock(sFontDataTableLock);
+  sFontDeleteLog.Add(aNamespace);
   for (auto i = sFontDataTable.begin(); i != sFontDataTable.end();) {
     if (i->first.mNamespace == aNamespace) {
       if (i->second.mVec) {
         wr_dec_ref_arc(i->second.mVec);
       }
       i = sFontDataTable.erase(i);
     } else {
       i++;
@@ -105,32 +146,34 @@ AddNativeFontHandle(WrFontKey aKey, void
 #endif
     sFontDataTable[aKey] = font;
   }
 }
 
 void
 DeleteFontData(WrFontKey aKey) {
   StaticMutexAutoLock lock(sFontDataTableLock);
+  sFontDeleteLog.Add(aKey);
   auto i = sFontDataTable.find(aKey);
   if (i != sFontDataTable.end()) {
     if (i->second.mVec) {
       wr_dec_ref_arc(i->second.mVec);
     }
     sFontDataTable.erase(i);
   }
 }
 }
 
 RefPtr<UnscaledFont>
 GetUnscaledFont(Translator *aTranslator, wr::FontKey key) {
   StaticMutexAutoLock lock(sFontDataTableLock);
   auto i = sFontDataTable.find(key);
   if (i == sFontDataTable.end()) {
-    gfxDevCrash(LogReason::UnscaledFontNotFound) << "Failed to get UnscaledFont entry for FontKey " << key.mHandle;
+    gfxDevCrash(LogReason::UnscaledFontNotFound) << "Failed to get UnscaledFont entry for FontKey " << key.mHandle
+                                                 << " because " << sFontDeleteLog.Find(key);
     return nullptr;
   }
   auto &data = i->second;
   if (data.mUnscaledFont) {
     return data.mUnscaledFont;
   }
   MOZ_ASSERT(data.mData);
   FontType type =
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -226,17 +226,16 @@ fail-if = os == "android"
 # Bug 1246231
 skip-if = os == "mac" && debug
 [test_syncGUID.js]
 [test_system_allowed.js]
 [test_system_delay_update.js]
 [test_system_repository.js]
 [test_system_reset.js]
 [test_system_update_blank.js]
-fail-if = os == 'win' && ccov
 [test_system_update_checkSizeHash.js]
 [test_system_update_custom.js]
 [test_system_update_empty.js]
 skip-if = true # Failing intermittently due to a race condition in the test, see bug 1348981
 [test_system_update_enterprisepolicy.js]
 skip-if = appname == "thunderbird"
 [test_system_update_fail.js]
 [test_system_update_newset.js]