Bug 1372067 - Part 2: Add the test cases, r=mossop draft
authorFischer.json <fischer.json@gmail.com>
Mon, 26 Jun 2017 18:05:42 +0800
changeset 605300 5112629973bac21f8f5018f9eb915876bfcf1f0b
parent 605299 77a26d46eb11d623e6e8b726e6aab5e55c1494cf
child 636447 fea027d559b3662247571119e437192e13309d49
push id67359
push userbmo:fliu@mozilla.com
push dateFri, 07 Jul 2017 10:53:29 +0000
reviewersmossop
bugs1372067
milestone56.0a1
Bug 1372067 - Part 2: Add the test cases, r=mossop This commit - adds the test_mute_notification_on_1st_session test case - adds the test_move_on_to_next_notification_when_reaching_max_prompt_count test case - add the test_move_on_to_next_notification_when_reaching_max_life_time test case - adds the test_move_on_to_next_notification_after_interacting_with_notification test case - updates the existing test cases for the new notification timing policy - updates the browser_newtab_focus.js test MozReview-Commit-ID: Ej2bHJSr35z
browser/base/content/test/newtab/browser_newtab_focus.js
browser/extensions/onboarding/test/browser/browser_onboarding_notification.js
browser/extensions/onboarding/test/browser/head.js
--- a/browser/base/content/test/newtab/browser_newtab_focus.js
+++ b/browser/base/content/test/newtab/browser_newtab_focus.js
@@ -16,16 +16,19 @@ add_task(async function() {
   // bar; search button; and toggle button. Additionaly there may or may not be
   // a scroll bar caused by fix to 1180387, which will eat an extra focus
   let FOCUS_COUNT = 30;
 
   // Create a new tab page.
   await setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
+  if (onbardingEnabled) {
+    await promiseNoMuteNotificationOnFirstSession();
+  }
   let tab = await addNewTabPageTab();
   if (onbardingEnabled) {
     FOCUS_COUNT += 2;
     await promiseTourNotificationOpened(tab.linkedBrowser);
   }
   gURLBar.focus();
   // Count the focus with the enabled page.
   countFocus(FOCUS_COUNT);
@@ -51,16 +54,23 @@ function countFocus(aExpectedCount) {
     if (document.activeElement == gBrowser.selectedBrowser) {
       focusCount++;
     }
   } while (document.activeElement != gURLBar.inputField);
   ok(focusCount == aExpectedCount || focusCount == (aExpectedCount + 1),
      "Validate focus count in the new tab page.");
 }
 
+
+async function promiseNoMuteNotificationOnFirstSession() {
+  let maxTime = Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms");
+  let lastTime = Math.floor((Date.now() - maxTime - 1) / 1000);
+  await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.notification.last-time-of-changing-tour-sec", lastTime]]});
+}
+
 /**
  * Wait for the onboarding tour notification opens
  */
 function promiseTourNotificationOpened(browser) {
   let condition = () => {
     return ContentTask.spawn(browser, {}, function() {
       return new Promise(resolve => {
         let bar = content.document.querySelector("#onboarding-notification-bar");
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_notification.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification.js
@@ -1,54 +1,60 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 add_task(async function test_show_tour_notifications_in_order() {
   resetOnboardingDefaultState();
   await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
+  Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 1);
+  skipMuteNotificationOnFirstSession();
 
   let tourIds = TOUR_IDs;
   let tab = null;
   let targetTourId = null;
   let reloadPromise = null;
   let expectedPrefUpdate = null;
-  for (let i = 0; i < tourIds.length; ++i) {
-    expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.lastPrompted", tourIds[i]);
-    if (tab) {
-      reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-      tab.linkedBrowser.reload();
-      await reloadPromise;
-    } else {
-      tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-      await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
-    }
-    await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
-    await promiseTourNotificationOpened(tab.linkedBrowser);
-    targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
-    is(targetTourId, tourIds[i], "Should show tour notifications in order");
-    await expectedPrefUpdate;
-  }
+  await loopTourNotificationQueueOnceInOrder();
+  await loopTourNotificationQueueOnceInOrder();
 
-  expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.lastPrompted", tourIds[0]);
+  expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
   reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   tab.linkedBrowser.reload();
   await reloadPromise;
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
-  await promiseTourNotificationOpened(tab.linkedBrowser);
-  targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
-  is(targetTourId, tourIds[0], "Should loop back to the 1st tour notification after showing all notifications");
   await expectedPrefUpdate;
+  let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
+  ok(!tourId, "Should not prompt each tour for more than 2 chances.");
   await BrowserTestUtils.removeTab(tab);
+
+  async function loopTourNotificationQueueOnceInOrder() {
+    for (let i = 0; i < tourIds.length; ++i) {
+      if (tab) {
+        reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+        tab.linkedBrowser.reload();
+        await reloadPromise;
+      } else {
+        tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+        await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+      }
+      await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+      await promiseTourNotificationOpened(tab.linkedBrowser);
+      targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
+      is(targetTourId, tourIds[i], "Should show tour notifications in order");
+    }
+  }
 });
 
 add_task(async function test_open_target_tour_from_notification() {
   resetOnboardingDefaultState();
   await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
+  Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 1);
+  skipMuteNotificationOnFirstSession();
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
   await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   let targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-action-btn", {}, tab.linkedBrowser);
   await promiseOnboardingOverlayOpened(tab.linkedBrowser);
@@ -57,16 +63,18 @@ add_task(async function test_open_target
   is(targetTourId, activeNavItemId, "Should navigate to the target tour item.");
   is(`${targetTourId}-page`, activePageId, "Should display the target tour page.");
   await BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function test_not_show_notification_for_completed_tour() {
   resetOnboardingDefaultState();
   await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
+  Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 1);
+  skipMuteNotificationOnFirstSession();
 
   let tourIds = TOUR_IDs;
   // Make only the last tour uncompleted
   let lastTourId = tourIds[tourIds.length - 1];
   for (let id of tourIds) {
     if (id != lastTourId) {
       setTourCompletedState(id, true);
     }
@@ -79,16 +87,18 @@ add_task(async function test_not_show_no
   let targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   is(targetTourId, lastTourId, "Should not show notification for completed tour");
   await BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function test_skip_notification_for_completed_tour() {
   resetOnboardingDefaultState();
   await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
+  Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 1);
+  skipMuteNotificationOnFirstSession();
 
   let tourIds = TOUR_IDs;
   // Make only 2nd tour completed
   await setTourCompletedState(tourIds[1], true);
 
   // Test show notification for the 1st tour
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
   await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
@@ -102,8 +112,219 @@ add_task(async function test_skip_notifi
   tab.linkedBrowser.reload();
   await reloadPromise;
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   is(targetTourId, tourIds[2], "Should skip notification for the completed 2nd tour");
   await BrowserTestUtils.removeTab(tab);
 });
+
+add_task(async function test_mute_notification_on_1st_session() {
+  resetOnboardingDefaultState();
+  await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
+  const TEST_MUTE_DURATION = 2000;
+  Preferences.set("browser.onboarding.notification.mute-duration-on-first-session-ms", TEST_MUTE_DURATION);
+
+  // Test no notifications during the mute duration on the 1st session
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  // The tour notification would be prompted on idle, so we wait idle twice here before proceeding
+  await waitUntilWindowIdle(tab.linkedBrowser);
+  await waitUntilWindowIdle(tab.linkedBrowser);
+  let reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  tab.linkedBrowser.reload();
+  await reloadPromise;
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await waitUntilWindowIdle(tab.linkedBrowser);
+  await waitUntilWindowIdle(tab.linkedBrowser);
+  let promptCount = Preferences.get("browser.onboarding.notification.prompt-count");
+  is(0, promptCount, "Should not prompt tour notification during the mute duration on the 1st session");
+
+  // Test notification prompted after the mute duration on the 1st session
+  await new Promise(resolve => setTimeout(resolve, TEST_MUTE_DURATION + 1));
+  reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  tab.linkedBrowser.reload();
+  await reloadPromise;
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await promiseTourNotificationOpened(tab.linkedBrowser);
+  promptCount = Preferences.get("browser.onboarding.notification.prompt-count");
+  is(1, promptCount, "Should prompt tour notification after the mute duration on the 1st session");
+  await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_move_on_to_next_notification_when_reaching_max_prompt_count() {
+  resetOnboardingDefaultState();
+  await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
+  skipMuteNotificationOnFirstSession();
+  let maxCount = Preferences.get("browser.onboarding.notification.max-prompt-count-per-tour");
+
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await promiseTourNotificationOpened(tab.linkedBrowser);
+  let previousTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
+
+  let currentTourId = null;
+  let reloadPromise = null;
+  for (let i = maxCount - 1; i > 0; --i) {
+    reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+    tab.linkedBrowser.reload();
+    await reloadPromise;
+    await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+    await promiseTourNotificationOpened(tab.linkedBrowser);
+    currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
+    is(previousTourId, currentTourId, "Should not move on to next tour notification until reaching the max prompt count per tour");
+  }
+
+  reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  tab.linkedBrowser.reload();
+  await reloadPromise;
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await promiseTourNotificationOpened(tab.linkedBrowser);
+  currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
+  isnot(previousTourId, currentTourId, "Should move on to next tour notification when reaching the max prompt count per tour");
+
+  await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_move_on_to_next_notification_when_reaching_max_life_time() {
+  resetOnboardingDefaultState();
+  await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
+  skipMuteNotificationOnFirstSession();
+  const TEST_MAX_LIFE_TIME = 2000;
+  Preferences.set("browser.onboarding.notification.max-life-time-per-tour-ms", TEST_MAX_LIFE_TIME);
+
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await promiseTourNotificationOpened(tab.linkedBrowser);
+  let previousTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
+
+  await new Promise(resolve => setTimeout(resolve, TEST_MAX_LIFE_TIME + 1));
+  let reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  tab.linkedBrowser.reload();
+  await reloadPromise;
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await promiseTourNotificationOpened(tab.linkedBrowser);
+  let currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
+  isnot(previousTourId, currentTourId, "Should move on to next tour notification when reaching the max life time per tour");
+
+  await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_move_on_to_next_notification_after_interacting_with_notification() {
+  resetOnboardingDefaultState();
+  await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
+  skipMuteNotificationOnFirstSession();
+
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await promiseTourNotificationOpened(tab.linkedBrowser);
+  let previousTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
+  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-close-btn", {}, tab.linkedBrowser);
+
+  let reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  tab.linkedBrowser.reload();
+  await reloadPromise;
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await promiseTourNotificationOpened(tab.linkedBrowser);
+  let currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
+  isnot(previousTourId, currentTourId, "Should move on to next tour notification after clicking #onboarding-notification-close-btn");
+  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-action-btn", {}, tab.linkedBrowser);
+  previousTourId = currentTourId;
+
+  reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  tab.linkedBrowser.reload();
+  await reloadPromise;
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await promiseTourNotificationOpened(tab.linkedBrowser);
+  currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
+  isnot(previousTourId, currentTourId, "Should move on to next tour notification after clicking #onboarding-notification-action-btn");
+
+  await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_remove_all_tour_notifications_through_close_button() {
+  resetOnboardingDefaultState();
+  await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
+  skipMuteNotificationOnFirstSession();
+
+  let tourIds = TOUR_IDs;
+  let tab = null;
+  let targetTourId = null;
+  let reloadPromise = null;
+  let expectedPrefUpdate = null;
+  await closeTourNotificationsOneByOne();
+
+  expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
+  reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  tab.linkedBrowser.reload();
+  await reloadPromise;
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await expectedPrefUpdate;
+  let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
+  ok(!tourId, "Should not prompt tour notifcations any more after closing all notifcations.");
+  await BrowserTestUtils.removeTab(tab);
+
+  async function closeTourNotificationsOneByOne() {
+    for (let i = 0; i < tourIds.length; ++i) {
+      if (tab) {
+        reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+        tab.linkedBrowser.reload();
+        await reloadPromise;
+      } else {
+        tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+        await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+      }
+      await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+      await promiseTourNotificationOpened(tab.linkedBrowser);
+      targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
+      is(targetTourId, tourIds[i], `Should show tour notifications of ${targetTourId}`);
+      await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-close-btn", {}, tab.linkedBrowser);
+      await promiseTourNotificationClosed(tab.linkedBrowser);
+    }
+  }
+});
+
+add_task(async function test_remove_all_tour_notifications_through_action_button() {
+  resetOnboardingDefaultState();
+  await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
+  skipMuteNotificationOnFirstSession();
+
+  let tourIds = TOUR_IDs;
+  let tab = null;
+  let targetTourId = null;
+  let reloadPromise = null;
+  let expectedPrefUpdate = null;
+  await clickTourNotificationActionButtonsOneByOne();
+
+  expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
+  reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  tab.linkedBrowser.reload();
+  await reloadPromise;
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await expectedPrefUpdate;
+  let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
+  ok(!tourId, "Should not prompt tour notifcations any more after taking actions on all notifcations.");
+  await BrowserTestUtils.removeTab(tab);
+
+  async function clickTourNotificationActionButtonsOneByOne() {
+    for (let i = 0; i < tourIds.length; ++i) {
+      if (tab) {
+        reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+        tab.linkedBrowser.reload();
+        await reloadPromise;
+      } else {
+        tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+        await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+      }
+      await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+      await promiseTourNotificationOpened(tab.linkedBrowser);
+      targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
+      is(targetTourId, tourIds[i], `Should show tour notifications of ${targetTourId}`);
+      await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-action-btn", {}, tab.linkedBrowser);
+      await promiseTourNotificationClosed(tab.linkedBrowser);
+    }
+  }
+});
--- a/browser/extensions/onboarding/test/browser/head.js
+++ b/browser/extensions/onboarding/test/browser/head.js
@@ -9,22 +9,29 @@ const TOUR_IDs = [
   "onboarding-tour-private-browsing",
   "onboarding-tour-addons",
   "onboarding-tour-customize",
   "onboarding-tour-search",
   "onboarding-tour-default-browser",
   "onboarding-tour-sync",
 ];
 
+registerCleanupFunction(resetOnboardingDefaultState);
+
 function resetOnboardingDefaultState() {
   // All the prefs should be reset to the default states
   // and no need to revert back so we don't use `SpecialPowers.pushPrefEnv` here.
   Preferences.set("browser.onboarding.hidden", false);
   Preferences.set("browser.onboarding.notification.finished", false);
-  Preferences.set("browser.onboarding.notification.lastPrompted", "");
+  Preferences.set("browser.onboarding.notification.mute-duration-on-first-session-ms", 300000);
+  Preferences.set("browser.onboarding.notification.max-life-time-per-tour-ms", 432000000);
+  Preferences.set("browser.onboarding.notification.last-time-of-changing-tour-sec", 0);
+  Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 8);
+  Preferences.set("browser.onboarding.notification.prompt-count", 0);
+  Preferences.set("browser.onboarding.notification.tour-ids-queue", "");
   TOUR_IDs.forEach(id => Preferences.set(`browser.onboarding.tour.${id}.completed`, false));
 }
 
 function setTourCompletedState(tourId, state) {
   Preferences.set(`browser.onboarding.tour.${tourId}.completed`, state);
 }
 
 function promiseOnboardingOverlayLoaded(browser) {
@@ -98,16 +105,37 @@ function promiseTourNotificationOpened(b
   return BrowserTestUtils.waitForCondition(
     condition,
     "Should open tour notification",
     100,
     30
   );
 }
 
+function promiseTourNotificationClosed(browser) {
+  let condition = () => {
+    return ContentTask.spawn(browser, {}, function() {
+      return new Promise(resolve => {
+        let bar = content.document.querySelector("#onboarding-notification-bar");
+        if (bar && !bar.classList.contains("onboarding-opened")) {
+          resolve(true);
+          return;
+        }
+        resolve(false);
+      });
+    })
+  };
+  return BrowserTestUtils.waitForCondition(
+    condition,
+    "Should close tour notification",
+    100,
+    30
+  );
+}
+
 function getCurrentNotificationTargetTourId(browser) {
   return ContentTask.spawn(browser, {}, function() {
     let bar = content.document.querySelector("#onboarding-notification-bar");
     return bar ? bar.dataset.targetTourId : null;
   });
 }
 
 function getCurrentActiveTour(browser) {
@@ -127,8 +155,20 @@ function getCurrentActiveTour(browser) {
       if (page.style.display != "none") {
         activePageId = page.id;
         break;
       }
     }
     return { activeNavItemId, activePageId };
   });
 }
+
+function waitUntilWindowIdle(browser) {
+  return ContentTask.spawn(browser, {}, function() {
+    return new Promise(resolve => content.requestIdleCallback(resolve));
+  });
+}
+
+function skipMuteNotificationOnFirstSession() {
+  let maxTime = Preferences.get("browser.onboarding.notification.mute-duration-on-first-session-ms");
+  let lastTime = Math.floor((Date.now() - maxTime - 1) / 1000);
+  Preferences.set("browser.onboarding.notification.last-time-of-changing-tour-sec", lastTime);
+}