summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/browser/browser_html_options_ui.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/extensions/test/browser/browser_html_options_ui.js')
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_html_options_ui.js650
1 files changed, 650 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/test/browser/browser_html_options_ui.js b/toolkit/mozapps/extensions/test/browser/browser_html_options_ui.js
new file mode 100644
index 0000000000..29e4b1a4ec
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_html_options_ui.js
@@ -0,0 +1,650 @@
+/* eslint max-len: ["error", 80] */
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+const { ExtensionParent } = ChromeUtils.importESModule(
+ "resource://gre/modules/ExtensionParent.sys.mjs"
+);
+
+AddonTestUtils.initMochitest(this);
+
+// This test function helps to detect when an addon options browser have been
+// inserted in the about:addons page.
+function waitOptionsBrowserInserted() {
+ return new Promise(resolve => {
+ async function listener(eventName, browser) {
+ // wait for a webextension XUL browser element that is owned by the
+ // "about:addons" page.
+ if (browser.ownerGlobal.top.location.href == "about:addons") {
+ ExtensionParent.apiManager.off("extension-browser-inserted", listener);
+ resolve(browser);
+ }
+ }
+ ExtensionParent.apiManager.on("extension-browser-inserted", listener);
+ });
+}
+
+add_task(async function enableHtmlViews() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.htmlaboutaddons.inline-options.enabled", true]],
+ });
+});
+
+add_task(async function testInlineOptions() {
+ const HEIGHT_SHORT = 300;
+ const HEIGHT_TALL = 600;
+
+ let id = "inline@mochi.test";
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ browser_specific_settings: { gecko: { id } },
+ options_ui: {
+ page: "options.html",
+ },
+ },
+ files: {
+ "options.html": `
+ <html>
+ <head>
+ <style type="text/css">
+ body > p { height: ${HEIGHT_SHORT}px; margin: 0; }
+ body.bigger > p { height: ${HEIGHT_TALL}px; }
+ </style>
+ <script src="options.js"></script>
+ </head>
+ <body>
+ <p>Some text</p>
+ </body>
+ </html>
+ `,
+ "options.js": () => {
+ browser.test.onMessage.addListener(msg => {
+ if (msg == "toggle-class") {
+ document.body.classList.toggle("bigger");
+ } else if (msg == "get-height") {
+ browser.test.sendMessage("height", document.body.clientHeight);
+ }
+ });
+
+ browser.test.sendMessage("options-loaded", window.location.href);
+ },
+ },
+ useAddonManager: "temporary",
+ });
+ await extension.startup();
+
+ let win = await loadInitialView("extension");
+ let doc = win.document;
+
+ // Make sure we found the right card.
+ let card = getAddonCard(win, id);
+ ok(card, "Found the card");
+
+ // The preferences option should be visible.
+ let preferences = card.querySelector('[action="preferences"]');
+ ok(!preferences.hidden, "The preferences option is visible");
+
+ // Open the preferences page.
+ let loaded = waitForViewLoad(win);
+ preferences.click();
+ await loaded;
+
+ // Verify we're on the preferences tab.
+ card = doc.querySelector("addon-card");
+ is(card.addon.id, id, "The right page was loaded");
+ let { deck, tabGroup } = card.details;
+ let { selectedViewName } = deck;
+ is(selectedViewName, "preferences", "The preferences tab is shown");
+
+ info("Check that there are two buttons and they're visible");
+ let detailsBtn = tabGroup.querySelector('[name="details"]');
+ ok(!detailsBtn.hidden, "The details button is visible");
+ let prefsBtn = tabGroup.querySelector('[name="preferences"]');
+ ok(!prefsBtn.hidden, "The preferences button is visible");
+
+ // Wait for the browser to load.
+ let url = await extension.awaitMessage("options-loaded");
+
+ // Check the attributes of the options browser.
+ let browser = card.querySelector("inline-options-browser browser");
+ ok(browser, "The visible view has a browser");
+ is(
+ browser.currentURI.spec,
+ card.addon.optionsURL,
+ "The browser has the expected options URL"
+ );
+ is(url, card.addon.optionsURL, "Browser has the expected options URL loaded");
+ let stack = browser.closest("stack");
+ is(
+ browser.clientWidth,
+ stack.clientWidth,
+ "Browser should be the same width as its direct parent"
+ );
+ ok(stack.clientWidth > 0, "The stack has a width");
+ ok(
+ card.querySelector('[action="preferences"]').hidden,
+ "The preferences option is hidden now"
+ );
+
+ let waitForHeightChange = expectedHeight =>
+ TestUtils.waitForCondition(() => browser.clientHeight === expectedHeight);
+
+ await waitForHeightChange(HEIGHT_SHORT);
+
+ // Check resizing the browser through extension CSS.
+ await extension.sendMessage("get-height");
+ let height = await extension.awaitMessage("height");
+ is(height, HEIGHT_SHORT, "The height is smaller to start");
+ is(height, browser.clientHeight, "The browser is the same size");
+
+ info("Resize the browser to be taller");
+ await extension.sendMessage("toggle-class");
+ await waitForHeightChange(HEIGHT_TALL);
+ await extension.sendMessage("get-height");
+ height = await extension.awaitMessage("height");
+ is(height, HEIGHT_TALL, "The height is bigger now");
+ is(height, browser.clientHeight, "The browser is the same size");
+
+ info("Shrink the browser again");
+ await extension.sendMessage("toggle-class");
+ await waitForHeightChange(HEIGHT_SHORT);
+ await extension.sendMessage("get-height");
+ height = await extension.awaitMessage("height");
+ is(height, HEIGHT_SHORT, "The browser shrunk back");
+ is(height, browser.clientHeight, "The browser is the same size");
+
+ info("Switching to details view");
+ detailsBtn.click();
+
+ info("Check the browser dimensions to make sure it's hidden");
+ is(browser.clientWidth, 0, "The browser is hidden now");
+
+ info("Switch back, check browser is shown");
+ prefsBtn.click();
+
+ is(browser.clientWidth, stack.clientWidth, "The browser width is set again");
+ ok(stack.clientWidth > 0, "The stack has a width");
+
+ await closeView(win);
+ await extension.unload();
+});
+
+// Regression test against bug 1409697
+add_task(async function testCardRerender() {
+ let id = "rerender@mochi.test";
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ browser_specific_settings: { gecko: { id } },
+ options_ui: {
+ page: "options.html",
+ },
+ },
+ files: {
+ "options.html": `
+ <html>
+ <body>
+ <p>Some text</p>
+ </body>
+ </html>
+ `,
+ },
+ useAddonManager: "permanent",
+ });
+ await extension.startup();
+
+ let win = await loadInitialView("extension");
+ let doc = win.document;
+
+ let card = getAddonCard(win, id);
+ let loaded = waitForViewLoad(win);
+ card.querySelector('[action="expand"]').click();
+ await loaded;
+
+ card = doc.querySelector("addon-card");
+
+ let browserAdded = waitOptionsBrowserInserted();
+ card.querySelector('.tab-button[name="preferences"]').click();
+ await browserAdded;
+
+ is(
+ doc.querySelectorAll("inline-options-browser").length,
+ 1,
+ "There is 1 inline-options-browser"
+ );
+ is(doc.querySelectorAll("browser").length, 1, "There is 1 browser");
+
+ info("Reload the add-on and ensure there's still only one browser");
+ let updated = BrowserTestUtils.waitForEvent(card, "update");
+ card.addon.reload();
+ await updated;
+
+ // Since the add-on was disabled, we'll be on the details tab.
+ is(card.details.deck.selectedViewName, "details", "View changed to details");
+ is(
+ doc.querySelectorAll("inline-options-browser").length,
+ 1,
+ "There is 1 inline-options-browser"
+ );
+ is(doc.querySelectorAll("browser").length, 0, "The browser was destroyed");
+
+ // Load the permissions tab again.
+ browserAdded = waitOptionsBrowserInserted();
+ card.querySelector('.tab-button[name="preferences"]').click();
+ await browserAdded;
+
+ // Switching to preferences will create a new browser element.
+ is(
+ card.details.deck.selectedViewName,
+ "preferences",
+ "View switched to preferences"
+ );
+ is(
+ doc.querySelectorAll("inline-options-browser").length,
+ 1,
+ "There is 1 inline-options-browser"
+ );
+ is(doc.querySelectorAll("browser").length, 1, "There is a new browser");
+
+ info("Re-rendering card to ensure a second browser isn't added");
+ updated = BrowserTestUtils.waitForEvent(card, "update");
+ card.render();
+ await updated;
+
+ is(
+ card.details.deck.selectedViewName,
+ "details",
+ "Rendering reverted to the details view"
+ );
+ is(
+ doc.querySelectorAll("inline-options-browser").length,
+ 1,
+ "There is still only 1 inline-options-browser after re-render"
+ );
+ is(doc.querySelectorAll("browser").length, 0, "There is no browser");
+
+ let newBrowserAdded = waitOptionsBrowserInserted();
+ card.showPrefs();
+ await newBrowserAdded;
+
+ is(
+ doc.querySelectorAll("inline-options-browser").length,
+ 1,
+ "There is still only 1 inline-options-browser after opening preferences"
+ );
+ is(doc.querySelectorAll("browser").length, 1, "There is 1 browser");
+
+ await closeView(win);
+ await extension.unload();
+});
+
+add_task(async function testRemovedOnDisable() {
+ let id = "disable@mochi.test";
+ const xpiFile = AddonTestUtils.createTempWebExtensionFile({
+ manifest: {
+ browser_specific_settings: { gecko: { id } },
+ options_ui: {
+ page: "options.html",
+ },
+ },
+ files: {
+ "options.html": "<h1>Options!</h1>",
+ },
+ });
+ let addon = await AddonManager.installTemporaryAddon(xpiFile);
+
+ let win = await loadInitialView("extension");
+ let doc = win.document;
+
+ // Opens the prefs page.
+ let loaded = waitForViewLoad(win);
+ getAddonCard(win, id).querySelector("[action=preferences]").click();
+ await loaded;
+
+ let inlineOptions = doc.querySelector("inline-options-browser");
+ ok(inlineOptions, "There's an inline-options-browser element");
+ ok(inlineOptions.querySelector("browser"), "The browser exists");
+
+ let card = getAddonCard(win, id);
+ let { deck } = card.details;
+ is(deck.selectedViewName, "preferences", "Preferences are the active tab");
+
+ info("Disabling the add-on");
+ let updated = BrowserTestUtils.waitForEvent(card, "update");
+ await addon.disable();
+ await updated;
+
+ is(deck.selectedViewName, "details", "Details are now the active tab");
+ ok(inlineOptions, "There's an inline-options-browser element");
+ ok(!inlineOptions.querySelector("browser"), "The browser has been removed");
+
+ info("Enabling the add-on");
+ updated = BrowserTestUtils.waitForEvent(card, "update");
+ await addon.enable();
+ await updated;
+
+ is(deck.selectedViewName, "details", "Details are still the active tab");
+ ok(inlineOptions, "There's an inline-options-browser element");
+ ok(!inlineOptions.querySelector("browser"), "The browser is not created yet");
+
+ info("Switching to preferences tab");
+ let changed = BrowserTestUtils.waitForEvent(deck, "view-changed");
+ let browserAdded = waitOptionsBrowserInserted();
+ deck.selectedViewName = "preferences";
+ await changed;
+ await browserAdded;
+
+ is(deck.selectedViewName, "preferences", "Preferences are selected");
+ ok(inlineOptions, "There's an inline-options-browser element");
+ ok(inlineOptions.querySelector("browser"), "The browser is re-created");
+
+ await closeView(win);
+ await addon.uninstall();
+});
+
+add_task(async function testUpgradeTemporary() {
+ let id = "upgrade-temporary@mochi.test";
+ async function loadExtension(version) {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ browser_specific_settings: { gecko: { id } },
+ version,
+ options_ui: {
+ page: "options.html",
+ },
+ },
+ files: {
+ "options.html": `
+ <html>
+ <head>
+ <script src="options.js"></script>
+ </head>
+ <body>
+ <p>Version <pre>${version}</pre></p>
+ </body>
+ </html>
+ `,
+ "options.js": () => {
+ browser.test.onMessage.addListener(msg => {
+ if (msg === "get-version") {
+ let version = document.querySelector("pre").textContent;
+ browser.test.sendMessage("version", version);
+ }
+ });
+ window.onload = () => browser.test.sendMessage("options-loaded");
+ },
+ },
+ useAddonManager: "temporary",
+ });
+ await extension.startup();
+ return extension;
+ }
+
+ let firstExtension = await loadExtension("1");
+ let win = await loadInitialView("extension");
+ let doc = win.document;
+
+ let card = getAddonCard(win, id);
+ let loaded = waitForViewLoad(win);
+ card.querySelector('[action="expand"]').click();
+ await loaded;
+
+ card = doc.querySelector("addon-card");
+ let browserAdded = waitOptionsBrowserInserted();
+ card.querySelector('.tab-button[name="preferences"]').click();
+ await browserAdded;
+
+ await firstExtension.awaitMessage("options-loaded");
+ await firstExtension.sendMessage("get-version");
+ let version = await firstExtension.awaitMessage("version");
+ is(version, "1", "Version 1 page is loaded");
+
+ let updated = BrowserTestUtils.waitForEvent(card, "update");
+ browserAdded = waitOptionsBrowserInserted();
+ let secondExtension = await loadExtension("2");
+ await updated;
+ await browserAdded;
+ await secondExtension.awaitMessage("options-loaded");
+
+ await secondExtension.sendMessage("get-version");
+ version = await secondExtension.awaitMessage("version");
+ is(version, "2", "Version 2 page is loaded");
+ let { deck } = card.details;
+ is(deck.selectedViewName, "preferences", "Preferences are still shown");
+
+ await closeView(win);
+ await firstExtension.unload();
+ await secondExtension.unload();
+});
+
+add_task(async function testReloadExtension() {
+ let id = "reload@mochi.test";
+ let xpiFile = AddonTestUtils.createTempWebExtensionFile({
+ manifest: {
+ browser_specific_settings: { gecko: { id } },
+ options_ui: {
+ page: "options.html",
+ },
+ },
+ files: {
+ "options.html": `
+ <html>
+ <head>
+ </head>
+ <body>
+ <p>Options</p>
+ </body>
+ </html>
+ `,
+ },
+ });
+ let addon = await AddonManager.installTemporaryAddon(xpiFile);
+
+ let win = await loadInitialView("extension");
+ let doc = win.document;
+
+ let card = getAddonCard(win, id);
+ let loaded = waitForViewLoad(win);
+ card.querySelector('[action="expand"]').click();
+ await loaded;
+
+ card = doc.querySelector("addon-card");
+ let { deck } = card.details;
+ is(deck.selectedViewName, "details", "Details load first");
+
+ let browserAdded = waitOptionsBrowserInserted();
+ card.querySelector('.tab-button[name="preferences"]').click();
+ await browserAdded;
+
+ is(deck.selectedViewName, "preferences", "Preferences are shown");
+
+ let updated = BrowserTestUtils.waitForEvent(card, "update");
+ browserAdded = waitOptionsBrowserInserted();
+ let addonStarted = AddonTestUtils.promiseWebExtensionStartup(id);
+ await addon.reload();
+ await addonStarted;
+ await updated;
+ await browserAdded;
+ is(deck.selectedViewName, "preferences", "Preferences are still shown");
+
+ await closeView(win);
+ await addon.uninstall();
+});
+
+async function testSelectPosition(optionsBrowser, zoom) {
+ let popupShownPromise = BrowserTestUtils.waitForSelectPopupShown(window);
+ await BrowserTestUtils.synthesizeMouseAtCenter("select", {}, optionsBrowser);
+ let popup = await popupShownPromise;
+ let popupLeft = popup.shadowRoot.querySelector(
+ ".menupopup-arrowscrollbox"
+ ).screenX;
+ let browserLeft = optionsBrowser.screenX * zoom;
+ ok(
+ Math.abs(popupLeft - browserLeft) <= 1,
+ `Popup should be correctly positioned: ${popupLeft} vs. ${browserLeft}`
+ );
+ popup.hidePopup();
+}
+
+async function testOptionsZoom(type = "full") {
+ let id = `${type}-zoom@mochi.test`;
+ let zoomProp = `${type}Zoom`;
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ browser_specific_settings: { gecko: { id } },
+ options_ui: {
+ page: "options.html",
+ },
+ },
+ files: {
+ "options.html": `
+ <!doctype html>
+ <script src="options.js"></script>
+ <body style="height: 500px">
+ <p>Some text</p>
+ <p>
+ <select>
+ <option>A</option>
+ <option>B</option>
+ </select>
+ </p>
+ </body>
+ `,
+ "options.js": () => {
+ window.addEventListener("load", function () {
+ browser.test.sendMessage("options-loaded");
+ });
+ },
+ },
+ useAddonManager: "permanent",
+ });
+ await extension.startup();
+
+ let win = await loadInitialView("extension");
+ let doc = win.document;
+
+ gBrowser.selectedBrowser[zoomProp] = 2;
+
+ let card = getAddonCard(win, id);
+ let loaded = waitForViewLoad(win);
+ card.querySelector('[action="expand"]').click();
+ await loaded;
+
+ card = doc.querySelector("addon-card");
+
+ let browserAdded = waitOptionsBrowserInserted();
+ card.querySelector('.tab-button[name="preferences"]').click();
+ let optionsBrowser = await browserAdded;
+ // Wait for the browser to load.
+ await extension.awaitMessage("options-loaded");
+
+ is(optionsBrowser[zoomProp], 2, `Options browser inherited ${zoomProp}`);
+
+ await testSelectPosition(optionsBrowser, type == "full" ? 2 : 1);
+
+ gBrowser.selectedBrowser[zoomProp] = 0.5;
+
+ is(
+ optionsBrowser[zoomProp],
+ 0.5,
+ `Options browser reacts to ${zoomProp} change`
+ );
+
+ await closeView(win);
+ await extension.unload();
+}
+
+add_task(function testOptionsFullZoom() {
+ return testOptionsZoom("full");
+});
+
+add_task(function testOptionsTextZoom() {
+ return testOptionsZoom("text");
+});
+
+add_task(async function testInputAndQuickFind() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ options_ui: {
+ page: "options.html",
+ },
+ },
+ files: {
+ "options.html": `
+ <html>
+ <body>
+ <input name="some-input" type="text">
+ <script src="options.js"></script>
+ </body>
+ </html>
+ `,
+ "options.js": () => {
+ let input = document.querySelector("input");
+ browser.test.assertEq(
+ "some-input",
+ input.getAttribute("name"),
+ "Expected options page input"
+ );
+ input.addEventListener("input", event => {
+ browser.test.sendMessage("input-changed", event.target.value);
+ });
+
+ browser.test.sendMessage("options-loaded", window.location.href);
+ },
+ },
+ useAddonManager: "temporary",
+ });
+ await extension.startup();
+
+ let win = await loadInitialView("extension");
+ let doc = win.document;
+
+ // Make sure we found the right card.
+ let card = getAddonCard(win, extension.id);
+ ok(card, "Found the card");
+
+ // The preferences option should be visible.
+ let preferences = card.querySelector('[action="preferences"]');
+ ok(!preferences.hidden, "The preferences option is visible");
+
+ // Open the preferences page.
+ let loaded = waitForViewLoad(win);
+ preferences.click();
+ await loaded;
+
+ // Verify we're on the preferences tab.
+ card = doc.querySelector("addon-card");
+ is(card.addon.id, extension.id, "The right page was loaded");
+
+ // Wait for the browser to load.
+ let url = await extension.awaitMessage("options-loaded");
+
+ // Check the attributes of the options browser.
+ let browser = card.querySelector("inline-options-browser browser");
+ ok(browser, "The visible view has a browser");
+ ok(card.addon.optionsURL.length, "Options URL is not empty");
+ is(
+ browser.currentURI.spec,
+ card.addon.optionsURL,
+ "The browser has the expected options URL"
+ );
+ is(url, card.addon.optionsURL, "Browser has the expected options URL loaded");
+
+ // Focus the options browser.
+ browser.focus();
+
+ // Focus the input in the options page.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("input").focus();
+ });
+
+ info("input in options page should be focused, typing...");
+ // Type '/'.
+ EventUtils.synthesizeKey("/");
+
+ let inputValue = await extension.awaitMessage("input-changed");
+ is(inputValue, "/", "Expected input to contain a slash");
+
+ await closeView(win);
+ await extension.unload();
+});