Bug 1440747: Don't load onboarding frame script into every frameloader. r?Mossop draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 23 Feb 2018 13:53:19 -0500
changeset 759157 6592a7ce188cbab5682242d6783883726788130b
parent 758086 9fc0c79890cb95ea48d83cd358b19e8f90e030cc
push id100280
push usermaglione.k@gmail.com
push dateFri, 23 Feb 2018 18:54:16 +0000
reviewersMossop
bugs1440747
milestone60.0a1
Bug 1440747: Don't load onboarding frame script into every frameloader. r?Mossop Loading this script into every frameloader wasts a massive amount of memory in every content process, even though it's only needed when we load about:home or about:newtab. MozReview-Commit-ID: 5YtEKLGVX4
browser/extensions/onboarding/content/Onboarding.jsm
browser/extensions/onboarding/content/onboarding.js
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";
@@ -214,18 +214,18 @@ var onboardingTourset = {
         bundle.GetStringFromName("onboarding.tour-sync.email-input.placeholder");
       emailInput.pattern = emailRegex;
 
       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",
@@ -333,48 +333,50 @@ var onboardingTourset = {
         </aside>
       `;
       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");
 
@@ -410,29 +412,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) {
@@ -501,17 +503,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) {
@@ -608,17 +610,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();
@@ -631,76 +633,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,
       });
     }
   }
@@ -803,17 +805,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.
@@ -876,28 +878,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.
@@ -947,17 +949,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")) {
@@ -980,17 +982,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;
     }
@@ -1049,17 +1051,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;
   }
@@ -1088,34 +1090,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() {
@@ -1146,17 +1148,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: ""
         },
@@ -1202,40 +1204,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() {
@@ -1260,27 +1262,27 @@ class Onboarding {
     let closeBtn = footer.querySelector("#onboarding-notification-close-btn");
     closeBtn.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() {
@@ -1382,17 +1384,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.
@@ -1436,31 +1438,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,1465 +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";
-/**
- * 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");
-      div.innerHTML = `
-        <section class="onboarding-tour-description">
-          <h1 data-l10n-id="onboarding.tour-private-browsing.title2"></h1>
-          <p data-l10n-id="onboarding.tour-private-browsing.description3"></p>
-        </section>
-        <section class="onboarding-tour-content">
-          <img src="resource://onboarding/img/figure_private.svg" role="presentation"/>
-        </section>
-        <aside class="onboarding-tour-button-container">
-          <button id="onboarding-tour-private-browsing-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-private-browsing.button"></button>
-        </aside>
-      `;
-      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");
-      div.innerHTML = `
-        <section class="onboarding-tour-description">
-          <h1 data-l10n-id="onboarding.tour-addons.title2"></h1>
-          <p data-l10n-id="onboarding.tour-addons.description2"></p>
-        </section>
-        <section class="onboarding-tour-content">
-          <img src="resource://onboarding/img/figure_addons.svg" role="presentation"/>
-        </section>
-        <aside class="onboarding-tour-button-container">
-          <button id="onboarding-tour-addons-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-addons.button"></button>
-        </aside>
-      `;
-      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");
-      div.innerHTML = `
-        <section class="onboarding-tour-description">
-          <h1 data-l10n-id="onboarding.tour-customize.title2"></h1>
-          <p data-l10n-id="onboarding.tour-customize.description2"></p>
-        </section>
-        <section class="onboarding-tour-content">
-          <img src="resource://onboarding/img/figure_customize.svg" role="presentation"/>
-        </section>
-        <aside class="onboarding-tour-button-container">
-          <button id="onboarding-tour-customize-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-customize.button"></button>
-        </aside>
-      `;
-      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);
-      // eslint-disable-next-line no-unsanitized/property
-      div.innerHTML = `
-        <section class="onboarding-tour-description">
-          <h1 data-l10n-id="onboarding.tour-default-browser.title2"></h1>
-          <p data-l10n-id="onboarding.tour-default-browser.description2"></p>
-        </section>
-        <section class="onboarding-tour-content">
-          <img src="resource://onboarding/img/figure_default.svg" role="presentation"/>
-        </section>
-        <aside class="onboarding-tour-button-container">
-          <button id="onboarding-tour-default-browser-button" class="onboarding-tour-action-button"
-            data-bg="${setFromBackGround}" data-panel="${setFromPanel}"></button>
-          <div id="onboarding-tour-is-default-browser-msg" class="onboarding-hidden">${isDefaultMessage}<br/>${isDefault2ndMessage}</div>
-        </aside>
-      `;
-
-      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])?)+$";
-      div.innerHTML = `
-        <section class="onboarding-tour-description">
-          <h1 data-l10n-id="onboarding.tour-sync.title2" class="show-on-logged-out"></h1>
-          <p data-l10n-id="onboarding.tour-sync.description2" class="show-on-logged-out"></p>
-          <h1 data-l10n-id="onboarding.tour-sync.logged-in.title" class="show-on-logged-in"></h1>
-          <p data-l10n-id="onboarding.tour-sync.logged-in.description" class="show-on-logged-in"></p>
-        </section>
-        <section class="onboarding-tour-content">
-          <form class="show-on-logged-out">
-            <h3 data-l10n-id="onboarding.tour-sync.form.title"></h3>
-            <p data-l10n-id="onboarding.tour-sync.form.description"></p>
-            <input id="onboarding-tour-sync-email-input" type="email" required="true"></input><br />
-            <button id="onboarding-tour-sync-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-sync.button"></button>
-          </form>
-          <img src="resource://onboarding/img/figure_sync.svg" role="presentation"/>
-        </section>
-        <aside class="onboarding-tour-button-container show-on-logged-in">
-          <button id="onboarding-tour-sync-connect-device-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-sync.connect-device.button"></button>
-        </aside>
-      `;
-      let emailInput = div.querySelector("#onboarding-tour-sync-email-input");
-      emailInput.placeholder =
-        bundle.GetStringFromName("onboarding.tour-sync.email-input.placeholder");
-      emailInput.pattern = emailRegex;
-
-      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");
-      div.innerHTML = `
-        <section class="onboarding-tour-description">
-          <h1 data-l10n-id="onboarding.tour-library.title"></h1>
-          <p data-l10n-id="onboarding.tour-library.description2"></p>
-        </section>
-        <section class="onboarding-tour-content">
-          <img src="resource://onboarding/img/figure_library.svg" role="presentation"/>
-        </section>
-        <aside class="onboarding-tour-button-container">
-          <button id="onboarding-tour-library-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-library.button2"></button>
-        </aside>
-      `;
-      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");
-      div.innerHTML = `
-        <section class="onboarding-tour-description">
-          <h1 data-l10n-id="onboarding.tour-singlesearch.title"></h1>
-          <p data-l10n-id="onboarding.tour-singlesearch.description"></p>
-        </section>
-        <section class="onboarding-tour-content">
-          <img src="resource://onboarding/img/figure_singlesearch.svg" role="presentation"/>
-        </section>
-        <aside class="onboarding-tour-button-container">
-          <button id="onboarding-tour-singlesearch-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-singlesearch.button"></button>
-        </aside>
-      `;
-      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");
-      div.innerHTML = `
-        <section class="onboarding-tour-description">
-          <h1 data-l10n-id="onboarding.tour-performance.title"></h1>
-          <p data-l10n-id="onboarding.tour-performance.description"></p>
-        </section>
-        <section class="onboarding-tour-content">
-          <img src="resource://onboarding/img/figure_performance.svg" role="presentation"/>
-        </section>
-      `;
-      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
-      div.innerHTML = `
-        <section class="onboarding-tour-description">
-          <h1 data-l10n-id="onboarding.tour-screenshots.title"></h1>
-          <p data-l10n-id="onboarding.tour-screenshots.description"></p>
-        </section>
-        <section class="onboarding-tour-content">
-          <img src="resource://onboarding/img/figure_screenshots.svg" role="presentation"/>
-        </section>
-        <aside class="onboarding-tour-button-container">
-          <a id="onboarding-tour-screenshots-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-screenshots.button"
-             href="https://screenshots.firefox.com/#tour" target="_blank"></a>
-        </aside>
-      `;
-      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("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 => 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;
-  }
-
-  handleKeypress(event) {
-    let { target, key, shiftKey } = 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;
-    }
-    let targetIndex;
-    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;
-      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();
-  }
-
-  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 "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");
-    // We use `innerHTML` for more friendly reading.
-    // The security should be fine because this is not from an external input.
-    footer.innerHTML = `
-      <section id="onboarding-notification-message-section" role="presentation">
-        <div id="onboarding-notification-tour-icon" role="presentation"></div>
-        <div id="onboarding-notification-body" role="presentation">
-          <h1 id="onboarding-notification-tour-title"></h1>
-          <p id="onboarding-notification-tour-message"></p>
-        </div>
-        <button id="onboarding-notification-action-btn" class="onboarding-action-button"></button>
-      </section>
-      <button id="onboarding-notification-close-btn" class="onboarding-close-btn"></button>
-    `;
-
-    let closeBtn = footer.querySelector("#onboarding-notification-close-btn");
-    closeBtn.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";
-    // We use `innerHTML` for more friendly reading.
-    // The security should be fine because this is not from an external input.
-    div.innerHTML = `
-      <div role="dialog" tabindex="-1" aria-labelledby="onboarding-header">
-        <header id="onboarding-header"></header>
-        <nav>
-          <ul id="onboarding-tour-list" role="tablist"></ul>
-        </nav>
-        <footer id="onboarding-footer"></footer>
-        <button id="onboarding-overlay-close-btn" class="onboarding-close-btn"></button>
-      </div>
-    `;
-
-    this._dialog = div.querySelector(`[role="dialog"]`);
-    this._dialog.id = ONBOARDING_DIALOG_ID;
-    div.querySelector("#onboarding-header").textContent =
-      this._bundle.GetStringFromName("onboarding.overlay-title2");
-    // support show/hide skip tour button via pref
-    if (!Services.prefs.getBoolPref("browser.onboarding.skip-tour-button.hide", false)) {
-      let footer = div.querySelector("#onboarding-footer");
-      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);
-    }
-    let closeBtn = div.querySelector("#onboarding-overlay-close-btn");
-    closeBtn.setAttribute("title",
-      this._bundle.GetStringFromName("onboarding.overlay-close-button-tooltip"));
-    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);
 }