summaryrefslogtreecommitdiffstats
path: root/browser/components/uitour/test/browser_UITour.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/uitour/test/browser_UITour.js')
-rw-r--r--browser/components/uitour/test/browser_UITour.js751
1 files changed, 751 insertions, 0 deletions
diff --git a/browser/components/uitour/test/browser_UITour.js b/browser/components/uitour/test/browser_UITour.js
new file mode 100644
index 0000000000..fc2353117a
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour.js
@@ -0,0 +1,751 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+
+ChromeUtils.defineESModuleGetters(this, {
+ ProfileAge: "resource://gre/modules/ProfileAge.sys.mjs",
+ TelemetryArchiveTesting:
+ "resource://testing-common/TelemetryArchiveTesting.sys.mjs",
+ TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
+ UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
+});
+
+function test() {
+ UITourTest();
+}
+
+var tests = [
+ function test_untrusted_host(done) {
+ loadUITourTestPage(function () {
+ CustomizableUI.addWidgetToArea(
+ "bookmarks-menu-button",
+ CustomizableUI.AREA_NAVBAR,
+ 0
+ );
+ registerCleanupFunction(() =>
+ CustomizableUI.removeWidgetFromArea("bookmarks-menu-button")
+ );
+ let bookmarksMenu = document.getElementById("bookmarks-menu-button");
+ is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
+
+ gContentAPI.showMenu("bookmarks");
+ is(
+ bookmarksMenu.open,
+ false,
+ "Bookmark menu should not open on a untrusted host"
+ );
+
+ done();
+ }, "http://mochi.test:8888/");
+ },
+ function test_testing_host(done) {
+ // Add two testing origins intentionally surrounded by whitespace to be ignored.
+ Services.prefs.setCharPref(
+ "browser.uitour.testingOrigins",
+ "https://test1.example.org, https://test2.example.org:443 "
+ );
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.uitour.testingOrigins");
+ });
+ function callback(result) {
+ ok(result, "Callback should be called on a testing origin");
+ done();
+ }
+
+ loadUITourTestPage(function () {
+ gContentAPI.getConfiguration("appinfo", callback);
+ }, "https://test2.example.org/");
+ },
+ function test_unsecure_host(done) {
+ loadUITourTestPage(function () {
+ let bookmarksMenu = document.getElementById("bookmarks-menu-button");
+ is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
+
+ gContentAPI.showMenu("bookmarks");
+ is(
+ bookmarksMenu.open,
+ false,
+ "Bookmark menu should not open on a unsecure host"
+ );
+
+ done();
+ }, "http://example.org/");
+ },
+ function test_unsecure_host_override(done) {
+ Services.prefs.setBoolPref("browser.uitour.requireSecure", false);
+ loadUITourTestPage(function () {
+ let highlight = document.getElementById("UITourHighlight");
+ is_element_hidden(highlight, "Highlight should initially be hidden");
+
+ gContentAPI.showHighlight("urlbar").then(() => {
+ waitForElementToBeVisible(
+ highlight,
+ done,
+ "Highlight should be shown on a unsecure host when override pref is set"
+ );
+
+ Services.prefs.setBoolPref("browser.uitour.requireSecure", true);
+ });
+ }, "http://example.org/");
+ },
+ function test_disabled(done) {
+ Services.prefs.setBoolPref("browser.uitour.enabled", false);
+
+ let bookmarksMenu = document.getElementById("bookmarks-menu-button");
+ is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
+
+ gContentAPI.showMenu("bookmarks").then(() => {
+ is(
+ bookmarksMenu.open,
+ false,
+ "Bookmark menu should not open when feature is disabled"
+ );
+
+ Services.prefs.setBoolPref("browser.uitour.enabled", true);
+ });
+ done();
+ },
+ function test_highlight(done) {
+ function test_highlight_2() {
+ let highlight = document.getElementById("UITourHighlight");
+ gContentAPI.hideHighlight();
+
+ waitForElementToBeHidden(
+ highlight,
+ test_highlight_3,
+ "Highlight should be hidden after hideHighlight()"
+ );
+ }
+ function test_highlight_3() {
+ is_element_hidden(
+ highlight,
+ "Highlight should be hidden after hideHighlight()"
+ );
+
+ gContentAPI.showHighlight("urlbar");
+ waitForElementToBeVisible(
+ highlight,
+ test_highlight_4,
+ "Highlight should be shown after showHighlight()"
+ );
+ }
+ function test_highlight_4() {
+ let highlight = document.getElementById("UITourHighlight");
+ gContentAPI.showHighlight("backForward");
+ waitForElementToBeVisible(
+ highlight,
+ done,
+ "Highlight should be shown after showHighlight()"
+ );
+ }
+
+ let highlight = document.getElementById("UITourHighlight");
+ is_element_hidden(highlight, "Highlight should initially be hidden");
+
+ gContentAPI.showHighlight("urlbar");
+ waitForElementToBeVisible(
+ highlight,
+ test_highlight_2,
+ "Highlight should be shown after showHighlight()"
+ );
+ },
+ function test_highlight_toolbar_button(done) {
+ function check_highlight_size() {
+ let panel = highlight.parentElement;
+ let anchor = panel.anchorNode;
+ let anchorRect = anchor.getBoundingClientRect();
+ info(
+ "addons target: width: " +
+ anchorRect.width +
+ " height: " +
+ anchorRect.height
+ );
+ let dimension = anchorRect.width;
+ let highlightRect = highlight.getBoundingClientRect();
+ info(
+ "highlight: width: " +
+ highlightRect.width +
+ " height: " +
+ highlightRect.height
+ );
+ is(
+ Math.round(highlightRect.width),
+ dimension,
+ "The width of the highlight should be equal to the width of the target"
+ );
+ is(
+ Math.round(highlightRect.height),
+ dimension,
+ "The height of the highlight should be equal to the width of the target"
+ );
+ is(
+ highlight.classList.contains("rounded-highlight"),
+ true,
+ "Highlight should be rounded-rectangle styled"
+ );
+ CustomizableUI.removeWidgetFromArea("home-button");
+ done();
+ }
+ info("Adding home button.");
+ CustomizableUI.addWidgetToArea("home-button", "nav-bar");
+ // Force the button to get layout so we can show the highlight.
+ document.getElementById("home-button").clientHeight;
+ let highlight = document.getElementById("UITourHighlight");
+ is_element_hidden(highlight, "Highlight should initially be hidden");
+
+ gContentAPI.showHighlight("home");
+ waitForElementToBeVisible(
+ highlight,
+ check_highlight_size,
+ "Highlight should be shown after showHighlight()"
+ );
+ },
+ function test_highlight_addons_auto_open_close(done) {
+ let highlight = document.getElementById("UITourHighlight");
+ gContentAPI.showHighlight("addons");
+ waitForElementToBeVisible(
+ highlight,
+ function checkPanelIsOpen() {
+ isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+ isnot(
+ highlight.classList.contains("rounded-highlight"),
+ true,
+ "Highlight should not be round-rectangle styled."
+ );
+
+ let hiddenPromise = promisePanelElementHidden(window, PanelUI.panel);
+ // Move the highlight outside which should close the app menu.
+ gContentAPI.showHighlight("appMenu");
+ hiddenPromise.then(() => {
+ waitForElementToBeVisible(
+ highlight,
+ function checkPanelIsClosed() {
+ isnot(
+ PanelUI.panel.state,
+ "open",
+ "Panel should have closed after the highlight moved elsewhere."
+ );
+ done();
+ },
+ "Highlight should move to the appMenu button"
+ );
+ });
+ },
+ "Highlight should be shown after showHighlight() for fixed panel items"
+ );
+ },
+ function test_highlight_addons_manual_open_close(done) {
+ let highlight = document.getElementById("UITourHighlight");
+ // Manually open the app menu then show a highlight there. The menu should remain open.
+ let shownPromise = promisePanelShown(window);
+ gContentAPI.showMenu("appMenu");
+ shownPromise
+ .then(() => {
+ isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+ gContentAPI.showHighlight("addons");
+
+ waitForElementToBeVisible(
+ highlight,
+ function checkPanelIsStillOpen() {
+ isnot(PanelUI.panel.state, "closed", "Panel should still be open");
+
+ // Move the highlight outside which shouldn't close the app menu since it was manually opened.
+ gContentAPI.showHighlight("appMenu");
+ waitForElementToBeVisible(
+ highlight,
+ function () {
+ isnot(
+ PanelUI.panel.state,
+ "closed",
+ "Panel should remain open since UITour didn't open it in the first place"
+ );
+ gContentAPI.hideMenu("appMenu");
+ done();
+ },
+ "Highlight should move to the appMenu button"
+ );
+ },
+ "Highlight should be shown after showHighlight() for fixed panel items"
+ );
+ })
+ .catch(console.error);
+ },
+ function test_highlight_effect(done) {
+ function waitForHighlightWithEffect(highlightEl, effect, next, error) {
+ return waitForCondition(
+ () => highlightEl.getAttribute("active") == effect,
+ next,
+ error
+ );
+ }
+ function checkDefaultEffect() {
+ is(
+ highlight.getAttribute("active"),
+ "none",
+ "The default should be no effect"
+ );
+
+ gContentAPI.showHighlight("urlbar", "none");
+ waitForHighlightWithEffect(
+ highlight,
+ "none",
+ checkZoomEffect,
+ "There should be no effect"
+ );
+ }
+ function checkZoomEffect() {
+ gContentAPI.showHighlight("urlbar", "zoom");
+ waitForHighlightWithEffect(
+ highlight,
+ "zoom",
+ () => {
+ let style = window.getComputedStyle(highlight);
+ is(
+ style.animationName,
+ "uitour-zoom",
+ "The animation-name should be uitour-zoom"
+ );
+ checkSameEffectOnDifferentTarget();
+ },
+ "There should be a zoom effect"
+ );
+ }
+ function checkSameEffectOnDifferentTarget() {
+ gContentAPI.showHighlight("appMenu", "wobble");
+ waitForHighlightWithEffect(
+ highlight,
+ "wobble",
+ () => {
+ highlight.addEventListener(
+ "animationstart",
+ function (aEvent) {
+ ok(
+ true,
+ "Animation occurred again even though the effect was the same"
+ );
+ checkRandomEffect();
+ },
+ { once: true }
+ );
+ gContentAPI.showHighlight("backForward", "wobble");
+ },
+ "There should be a wobble effect"
+ );
+ }
+ function checkRandomEffect() {
+ function waitForActiveHighlight(highlightEl, next, error) {
+ return waitForCondition(
+ () => highlightEl.hasAttribute("active"),
+ next,
+ error
+ );
+ }
+
+ gContentAPI.hideHighlight();
+ gContentAPI.showHighlight("urlbar", "random");
+ waitForActiveHighlight(
+ highlight,
+ () => {
+ ok(
+ highlight.hasAttribute("active"),
+ "The highlight should be active"
+ );
+ isnot(
+ highlight.getAttribute("active"),
+ "none",
+ "A random effect other than none should have been chosen"
+ );
+ isnot(
+ highlight.getAttribute("active"),
+ "random",
+ "The random effect shouldn't be 'random'"
+ );
+ isnot(
+ UITour.highlightEffects.indexOf(highlight.getAttribute("active")),
+ -1,
+ "Check that a supported effect was randomly chosen"
+ );
+ done();
+ },
+ "There should be an active highlight with a random effect"
+ );
+ }
+
+ let highlight = document.getElementById("UITourHighlight");
+ is_element_hidden(highlight, "Highlight should initially be hidden");
+
+ gContentAPI.showHighlight("urlbar");
+ waitForElementToBeVisible(
+ highlight,
+ checkDefaultEffect,
+ "Highlight should be shown after showHighlight()"
+ );
+ },
+ function test_highlight_effect_unsupported(done) {
+ function checkUnsupportedEffect() {
+ is(
+ highlight.getAttribute("active"),
+ "none",
+ "No effect should be used when an unsupported effect is requested"
+ );
+ done();
+ }
+
+ let highlight = document.getElementById("UITourHighlight");
+ is_element_hidden(highlight, "Highlight should initially be hidden");
+
+ gContentAPI.showHighlight("urlbar", "__UNSUPPORTED__");
+ waitForElementToBeVisible(
+ highlight,
+ checkUnsupportedEffect,
+ "Highlight should be shown after showHighlight()"
+ );
+ },
+ function test_info_1(done) {
+ let popup = document.getElementById("UITourTooltip");
+ let title = document.getElementById("UITourTooltipTitle");
+ let desc = document.getElementById("UITourTooltipDescription");
+ let icon = document.getElementById("UITourTooltipIcon");
+ let buttons = document.getElementById("UITourTooltipButtons");
+
+ popup.addEventListener(
+ "popupshown",
+ function () {
+ is(
+ popup.anchorNode,
+ document.getElementById("urlbar"),
+ "Popup should be anchored to the urlbar"
+ );
+ is(title.textContent, "test title", "Popup should have correct title");
+ is(
+ desc.textContent,
+ "test text",
+ "Popup should have correct description text"
+ );
+ is(icon.src, "", "Popup should have no icon");
+ is(buttons.hasChildNodes(), false, "Popup should have no buttons");
+
+ popup.addEventListener(
+ "popuphidden",
+ function () {
+ popup.addEventListener(
+ "popupshown",
+ function () {
+ done();
+ },
+ { once: true }
+ );
+
+ gContentAPI.showInfo("urlbar", "test title", "test text");
+ },
+ { once: true }
+ );
+ gContentAPI.hideInfo();
+ },
+ { once: true }
+ );
+
+ gContentAPI.showInfo("urlbar", "test title", "test text");
+ },
+ taskify(async function test_info_2() {
+ let popup = document.getElementById("UITourTooltip");
+ let title = document.getElementById("UITourTooltipTitle");
+ let desc = document.getElementById("UITourTooltipDescription");
+ let icon = document.getElementById("UITourTooltipIcon");
+ let buttons = document.getElementById("UITourTooltipButtons");
+
+ await showInfoPromise("urlbar", "urlbar title", "urlbar text");
+
+ is(
+ popup.anchorNode,
+ document.getElementById("urlbar"),
+ "Popup should be anchored to the urlbar"
+ );
+ is(title.textContent, "urlbar title", "Popup should have correct title");
+ is(
+ desc.textContent,
+ "urlbar text",
+ "Popup should have correct description text"
+ );
+ is(icon.src, "", "Popup should have no icon");
+ is(buttons.hasChildNodes(), false, "Popup should have no buttons");
+
+ // Place the search bar in the navigation toolbar temporarily.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.widget.inNavBar", true]],
+ });
+
+ await showInfoPromise("search", "search title", "search text");
+
+ is(
+ popup.anchorNode,
+ document.getElementById("searchbar"),
+ "Popup should be anchored to the searchbar"
+ );
+ is(title.textContent, "search title", "Popup should have correct title");
+ is(
+ desc.textContent,
+ "search text",
+ "Popup should have correct description text"
+ );
+
+ await SpecialPowers.popPrefEnv();
+ }),
+ function test_getConfigurationVersion(done) {
+ function callback(result) {
+ ok(
+ typeof result.version !== "undefined",
+ "Check version isn't undefined."
+ );
+ is(
+ result.version,
+ Services.appinfo.version,
+ "Should have the same version property."
+ );
+ is(
+ result.defaultUpdateChannel,
+ UpdateUtils.getUpdateChannel(false),
+ "Should have the correct update channel."
+ );
+ done();
+ }
+
+ gContentAPI.getConfiguration("appinfo", callback);
+ },
+ function test_getConfigurationDistribution(done) {
+ gContentAPI.getConfiguration("appinfo", result => {
+ ok(
+ typeof result.distribution !== "undefined",
+ "Check distribution isn't undefined."
+ );
+ // distribution id defaults to "default" for most builds, and
+ // "mozilla-MSIX" for MSIX builds.
+ is(
+ result.distribution,
+ AppConstants.platform === "win" &&
+ Services.sysinfo.getProperty("hasWinPackageId")
+ ? "mozilla-MSIX"
+ : "default",
+ 'Should be "default" without preference set.'
+ );
+
+ let defaults = Services.prefs.getDefaultBranch("distribution.");
+ let testDistributionID = "TestDistribution";
+ defaults.setCharPref("id", testDistributionID);
+ gContentAPI.getConfiguration("appinfo", result2 => {
+ ok(
+ typeof result2.distribution !== "undefined",
+ "Check distribution isn't undefined."
+ );
+ is(
+ result2.distribution,
+ testDistributionID,
+ "Should have the distribution as set in preference."
+ );
+
+ done();
+ });
+ });
+ },
+ function test_getConfigurationProfileAge(done) {
+ gContentAPI.getConfiguration("appinfo", result => {
+ ok(
+ typeof result.profileCreatedWeeksAgo === "number",
+ "profileCreatedWeeksAgo should be number."
+ );
+ ok(
+ result.profileResetWeeksAgo === null,
+ "profileResetWeeksAgo should be null."
+ );
+
+ // Set profile reset date to 15 days ago.
+ ProfileAge().then(profileAccessor => {
+ profileAccessor.recordProfileReset(
+ Date.now() - 15 * 24 * 60 * 60 * 1000
+ );
+ gContentAPI.getConfiguration("appinfo", result2 => {
+ ok(
+ typeof result2.profileResetWeeksAgo === "number",
+ "profileResetWeeksAgo should be number."
+ );
+ is(
+ result2.profileResetWeeksAgo,
+ 2,
+ "profileResetWeeksAgo should be 2."
+ );
+ done();
+ });
+ });
+ });
+ },
+ function test_addToolbarButton(done) {
+ let placement = CustomizableUI.getPlacementOfWidget("panic-button");
+ is(placement, null, "default UI has panic button in the palette");
+
+ gContentAPI.getConfiguration("availableTargets", data => {
+ let available = data.targets.includes("forget");
+ ok(!available, "Forget button should not be available by default");
+
+ gContentAPI.addNavBarWidget("forget", () => {
+ info("addNavBarWidget callback successfully called");
+
+ let updatedPlacement =
+ CustomizableUI.getPlacementOfWidget("panic-button");
+ is(updatedPlacement.area, CustomizableUI.AREA_NAVBAR);
+
+ gContentAPI.getConfiguration("availableTargets", data2 => {
+ let updatedAvailable = data2.targets.includes("forget");
+ ok(updatedAvailable, "Forget button should now be available");
+
+ // Cleanup
+ CustomizableUI.removeWidgetFromArea("panic-button");
+ done();
+ });
+ });
+ });
+ },
+ taskify(async function test_search() {
+ let defaultEngine = await Services.search.getDefault();
+ let visibleEngines = await Services.search.getVisibleEngines();
+ let expectedEngines = visibleEngines
+ .filter(engine => engine.identifier)
+ .map(engine => "searchEngine-" + engine.identifier);
+
+ let data = await new Promise(resolve =>
+ gContentAPI.getConfiguration("search", resolve)
+ );
+ let engines = data.engines;
+ ok(Array.isArray(engines), "data.engines should be an array");
+ is(
+ engines.sort().toString(),
+ expectedEngines.sort().toString(),
+ "Engines should be as expected"
+ );
+
+ is(
+ data.searchEngineIdentifier,
+ defaultEngine.identifier,
+ "the searchEngineIdentifier property should contain the defaultEngine's identifier"
+ );
+
+ let someOtherEngineID = data.engines.filter(
+ t => t != "searchEngine-" + defaultEngine.identifier
+ )[0];
+ someOtherEngineID = someOtherEngineID.replace(/^searchEngine-/, "");
+
+ Services.telemetry.clearEvents();
+ Services.fog.testResetFOG();
+
+ await new Promise(resolve => {
+ let observe = function (subject, topic, verb) {
+ Services.obs.removeObserver(observe, "browser-search-engine-modified");
+ info("browser-search-engine-modified: " + verb);
+ if (verb == "engine-default") {
+ is(
+ Services.search.defaultEngine.identifier,
+ someOtherEngineID,
+ "correct engine was switched to"
+ );
+ resolve();
+ }
+ };
+ Services.obs.addObserver(observe, "browser-search-engine-modified");
+ registerCleanupFunction(async () => {
+ await Services.search.setDefault(
+ defaultEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ });
+
+ gContentAPI.setDefaultSearchEngine(someOtherEngineID);
+ });
+
+ let engine = (await Services.search.getVisibleEngines()).filter(
+ e => e.identifier == someOtherEngineID
+ )[0];
+
+ let submissionUrl = engine
+ .getSubmission("dummy")
+ .uri.spec.replace("dummy", "");
+
+ TelemetryTestUtils.assertEvents(
+ [
+ {
+ object: "change_default",
+ value: "uitour",
+ extra: {
+ prev_id: defaultEngine.telemetryId,
+ new_id: engine.telemetryId,
+ new_name: engine.name,
+ new_load_path: engine.wrappedJSObject._loadPath,
+ // Telemetry has a limit of 80 characters.
+ new_sub_url: submissionUrl.slice(0, 80),
+ },
+ },
+ ],
+ { category: "search", method: "engine" }
+ );
+
+ let snapshot = await Glean.searchEngineDefault.changed.testGetValue();
+ delete snapshot[0].timestamp;
+ Assert.deepEqual(
+ snapshot[0],
+ {
+ category: "search.engine.default",
+ name: "changed",
+ extra: {
+ change_source: "uitour",
+ previous_engine_id: defaultEngine.telemetryId,
+ new_engine_id: engine.telemetryId,
+ new_display_name: engine.name,
+ new_load_path: engine.wrappedJSObject._loadPath,
+ // Glean has a limit of 100 characters.
+ new_submission_url: submissionUrl.slice(0, 100),
+ },
+ },
+ "Should have received the correct event details"
+ );
+ }),
+ taskify(async function test_treatment_tag() {
+ let ac = new TelemetryArchiveTesting.Checker();
+ await ac.promiseInit();
+ await gContentAPI.setTreatmentTag("foobar", "baz");
+ // Wait until the treatment telemetry is sent before looking in the archive.
+ await BrowserTestUtils.waitForContentEvent(
+ gTestTab.linkedBrowser,
+ "mozUITourNotification",
+ false,
+ event => event.detail.event === "TreatmentTag:TelemetrySent"
+ );
+ await new Promise(resolve => {
+ gContentAPI.getTreatmentTag("foobar", data => {
+ is(data.value, "baz", "set and retrieved treatmentTag");
+ ac.promiseFindPing("uitour-tag", [
+ [["payload", "tagName"], "foobar"],
+ [["payload", "tagValue"], "baz"],
+ ]).then(
+ found => {
+ ok(found, "Telemetry ping submitted for setTreatmentTag");
+ resolve();
+ },
+ err => {
+ ok(false, "Exception finding uitour telemetry ping: " + err);
+ resolve();
+ }
+ );
+ });
+ });
+ }),
+
+ // Make sure this test is last in the file so the appMenu gets left open and done will confirm it got tore down.
+ taskify(async function cleanupMenus() {
+ let shownPromise = promisePanelShown(window);
+ gContentAPI.showMenu("appMenu");
+ await shownPromise;
+ }),
+];