/* 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": `

Some text

`, "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": `

Some text

`, }, 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": "

Options!

", }, }); 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": `

Version

${version}

`, "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": `

Options

`, }, }); 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": `

Some text

`, "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": ` `, "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(); });