/* 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 webextensions */ let { cloudFileAccounts } = ChromeUtils.import( "resource:///modules/cloudFileAccounts.jsm" ); let { MockRegistrar } = ChromeUtils.importESModule( "resource://testing-common/MockRegistrar.sys.mjs" ); function ManagementScript() { browser.test.onMessage.addListener((message, assertMessage, browserStyle) => { if (message !== "check-style") { return; } function verifyButton(buttonElement, expected) { let buttonStyle = window.getComputedStyle(buttonElement); let buttonBackgroundColor = buttonStyle.backgroundColor; if (browserStyle && expected.hasBrowserStyleClass) { browser.test.assertEq( "rgb(9, 150, 248)", buttonBackgroundColor, assertMessage ); } else { browser.test.assertTrue( buttonBackgroundColor !== "rgb(9, 150, 248)", assertMessage ); } } function verifyCheckboxOrRadio(element, expected) { let style = window.getComputedStyle(element); let styledBackground = element.checked ? "rgb(9, 150, 248)" : "rgb(255, 255, 255)"; if (browserStyle && expected.hasBrowserStyleClass) { browser.test.assertEq( styledBackground, style.backgroundColor, assertMessage ); } else { browser.test.assertTrue( style.backgroundColor != styledBackground, assertMessage ); } } let normalButton = document.getElementById("normalButton"); let browserStyleButton = document.getElementById("browserStyleButton"); verifyButton(normalButton, { hasBrowserStyleClass: false }); verifyButton(browserStyleButton, { hasBrowserStyleClass: true }); let normalCheckbox1 = document.getElementById("normalCheckbox1"); let normalCheckbox2 = document.getElementById("normalCheckbox2"); let browserStyleCheckbox = document.getElementById("browserStyleCheckbox"); verifyCheckboxOrRadio(normalCheckbox1, { hasBrowserStyleClass: false }); verifyCheckboxOrRadio(normalCheckbox2, { hasBrowserStyleClass: false }); verifyCheckboxOrRadio(browserStyleCheckbox, { hasBrowserStyleClass: true, }); let normalRadio1 = document.getElementById("normalRadio1"); let normalRadio2 = document.getElementById("normalRadio2"); let browserStyleRadio = document.getElementById("browserStyleRadio"); verifyCheckboxOrRadio(normalRadio1, { hasBrowserStyleClass: false }); verifyCheckboxOrRadio(normalRadio2, { hasBrowserStyleClass: false }); verifyCheckboxOrRadio(browserStyleRadio, { hasBrowserStyleClass: true }); browser.test.notifyPass("management-ui-browser_style"); }); browser.test.sendMessage("management-ui-ready"); } let extension; async function startExtension(browser_style) { let cloud_file = { name: "Mochitest", management_url: "management.html", }; switch (browser_style) { case "true": cloud_file.browser_style = true; break; case "false": cloud_file.browser_style = false; break; } extension = ExtensionTestUtils.loadExtension({ async background() { browser.test.onMessage.addListener(async message => { if (message != "set-configured") { return; } let accounts = await browser.cloudFile.getAllAccounts(); for (let account of accounts) { await browser.cloudFile.updateAccount(account.id, { configured: true, }); } browser.test.sendMessage("ready"); }); }, files: { "management.html": ` Click me!
`, "management.js": ManagementScript, }, manifest: { cloud_file, applications: { gecko: { id: "cloudfile@mochitest" } }, }, }); info("Starting extension"); await extension.startup(); if (accountIsConfigured) { extension.sendMessage("set-configured"); await extension.awaitMessage("ready"); } } add_task(async () => { // Register a fake provider representing a built-in provider. We don't // currently ship any built-in providers, but if we did, we should check // if they are present before doing this. Built-in providers can be // problematic for artifact builds. cloudFileAccounts.registerProvider("Fake-Test", { displayName: "XYZ Fake", type: "ext-fake@extensions.thunderbird.net", }); registerCleanupFunction(() => { cloudFileAccounts.unregisterProvider("Fake-Test"); }); }); let accountIsConfigured = false; // Mock the prompt service. We're going to be asked if we're sure // we want to remove an account, so let's say yes. /** @implements {nsIPromptService} */ let mockPromptService = { confirmCount: 0, confirm() { this.confirmCount++; return true; }, QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]), }; /** @implements {nsIExternalProtocolService} */ let mockExternalProtocolService = { _loadedURLs: [], externalProtocolHandlerExists(aProtocolScheme) {}, getApplicationDescription(aScheme) {}, getProtocolHandlerInfo(aProtocolScheme) {}, getProtocolHandlerInfoFromOS(aProtocolScheme, aFound) {}, isExposedProtocol(aProtocolScheme) {}, loadURI(aURI, aWindowContext) { this._loadedURLs.push(aURI.spec); }, setProtocolHandlerDefaults(aHandlerInfo, aOSHandlerExists) {}, urlLoaded(aURL) { return this._loadedURLs.includes(aURL); }, QueryInterface: ChromeUtils.generateQI(["nsIExternalProtocolService"]), }; let originalPromptService = Services.prompt; Services.prompt = mockPromptService; let mockExternalProtocolServiceCID = MockRegistrar.register( "@mozilla.org/uriloader/external-protocol-service;1", mockExternalProtocolService ); registerCleanupFunction(() => { Services.prompt = originalPromptService; MockRegistrar.unregister(mockExternalProtocolServiceCID); }); add_task(async function addRemoveAccounts() { is(cloudFileAccounts.providers.length, 1); is(cloudFileAccounts.accounts.length, 0); // Load the preferences tab. let { prefsDocument, prefsWindow } = await openNewPrefsTab( "paneCompose", "compositionAttachmentsCategory" ); // Check everything is as it should be. let accountList = prefsDocument.getElementById("cloudFileView"); is(accountList.itemCount, 0); let buttonList = prefsDocument.getElementById("addCloudFileAccountButtons"); ok(!buttonList.hidden); is(buttonList.childElementCount, 1); is( buttonList.children[0].getAttribute("value"), "ext-fake@extensions.thunderbird.net" ); let menuButton = prefsDocument.getElementById("addCloudFileAccount"); ok(menuButton.hidden); is(menuButton.itemCount, 1); is( menuButton.getItemAtIndex(0).getAttribute("value"), "ext-fake@extensions.thunderbird.net" ); let removeButton = prefsDocument.getElementById("removeCloudFileAccount"); ok(removeButton.disabled); let cloudFileDefaultPanel = prefsDocument.getElementById( "cloudFileDefaultPanel" ); ok(!cloudFileDefaultPanel.hidden); let browserWrapper = prefsDocument.getElementById("cloudFileSettingsWrapper"); is(browserWrapper.childElementCount, 0); // Register our test provider. await startExtension(); is(cloudFileAccounts.providers.length, 2); is(cloudFileAccounts.accounts.length, 0); await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); is(buttonList.childElementCount, 2); is( buttonList.children[0].getAttribute("value"), "ext-fake@extensions.thunderbird.net" ); is(buttonList.children[1].getAttribute("value"), "ext-cloudfile@mochitest"); is( buttonList.children[1].style.listStyleImage, `url("chrome://messenger/content/extension.svg")` ); is(menuButton.itemCount, 2); is( menuButton.getItemAtIndex(0).getAttribute("value"), "ext-fake@extensions.thunderbird.net" ); is( menuButton.getItemAtIndex(1).getAttribute("value"), "ext-cloudfile@mochitest" ); is( menuButton.getItemAtIndex(1).getAttribute("image"), "chrome://messenger/content/extension.svg" ); // Create a new account. EventUtils.synthesizeMouseAtCenter( buttonList.children[1], { clickCount: 1 }, prefsWindow ); is(cloudFileAccounts.accounts.length, 1); is(cloudFileAccounts.configuredAccounts.length, 0); let account = cloudFileAccounts.accounts[0]; let accountKey = account.accountKey; is(cloudFileAccounts.accounts[0].type, "ext-cloudfile@mochitest"); // Check prefs were updated. is( Services.prefs.getCharPref( `mail.cloud_files.accounts.${accountKey}.displayName` ), "Mochitest" ); is( Services.prefs.getCharPref(`mail.cloud_files.accounts.${accountKey}.type`), "ext-cloudfile@mochitest" ); // Check UI was updated. is(accountList.itemCount, 1); is(accountList.selectedIndex, 0); ok(!removeButton.disabled); let accountListItem = accountList.selectedItem; is(accountListItem.getAttribute("value"), accountKey); is( accountListItem.querySelector(".typeIcon:not(.configuredWarning)").src, "chrome://messenger/content/extension.svg" ); is(accountListItem.querySelector("label").value, "Mochitest"); is(accountListItem.querySelector(".configuredWarning").hidden, false); ok(cloudFileDefaultPanel.hidden); is(browserWrapper.childElementCount, 1); let browser = browserWrapper.firstElementChild; if ( browser.webProgress?.isLoadingDocument || browser.currentURI?.spec == "about:blank" ) { await BrowserTestUtils.browserLoaded(browser); } is( browser.currentURI.pathQueryRef, `/management.html?accountId=${accountKey}` ); await extension.awaitMessage("management-ui-ready"); let tabmail = document.getElementById("tabmail"); let tabCount = tabmail.tabInfo.length; BrowserTestUtils.synthesizeMouseAtCenter("a", {}, browser); // It might take a moment to get to the external protocol service. // eslint-disable-next-line mozilla/no-arbitrary-setTimeout await new Promise(resolve => setTimeout(resolve, 500)); ok( mockExternalProtocolService.urlLoaded("https://www.example.com/"), "Link click sent to external protocol service." ); is(tabmail.tabInfo.length, tabCount, "No new tab opened"); // Rename the account. EventUtils.synthesizeMouseAtCenter( accountListItem, { clickCount: 1 }, prefsWindow ); await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); is( prefsDocument.activeElement.closest("input"), accountListItem.querySelector("input") ); ok(accountListItem.querySelector("label").hidden); ok(!accountListItem.querySelector("input").hidden); is(accountListItem.querySelector("input").value, "Mochitest"); EventUtils.synthesizeKey("VK_RIGHT", undefined, prefsWindow); EventUtils.synthesizeKey("!", undefined, prefsWindow); EventUtils.synthesizeKey("VK_RETURN", undefined, prefsWindow); await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); is(prefsDocument.activeElement, accountList); ok(!accountListItem.querySelector("label").hidden); is(accountListItem.querySelector("label").value, "Mochitest!"); ok(accountListItem.querySelector("input").hidden); is( Services.prefs.getCharPref( `mail.cloud_files.accounts.${accountKey}.displayName` ), "Mochitest!" ); // Start to rename the account, but bail out. EventUtils.synthesizeMouseAtCenter( accountListItem, { clickCount: 1 }, prefsWindow ); await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); is( prefsDocument.activeElement.closest("input"), accountListItem.querySelector("input") ); EventUtils.synthesizeKey("O", undefined, prefsWindow); EventUtils.synthesizeKey("o", undefined, prefsWindow); EventUtils.synthesizeKey("p", undefined, prefsWindow); EventUtils.synthesizeKey("s", undefined, prefsWindow); EventUtils.synthesizeKey("VK_ESCAPE", undefined, prefsWindow); await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); is(prefsDocument.activeElement, accountList); ok(!accountListItem.querySelector("label").hidden); is(accountListItem.querySelector("label").value, "Mochitest!"); ok(accountListItem.querySelector("input").hidden); is( Services.prefs.getCharPref( `mail.cloud_files.accounts.${accountKey}.displayName` ), "Mochitest!" ); // Configure the account. account.configured = true; accountIsConfigured = true; cloudFileAccounts.emit("accountConfigured", account); await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); is(accountListItem.querySelector(".configuredWarning").hidden, true); is(cloudFileAccounts.accounts.length, 1); is(cloudFileAccounts.configuredAccounts.length, 1); // Remove the test provider. The list item, button, and browser should disappear. info("Stopping extension"); await extension.unload(); is(cloudFileAccounts.providers.length, 1); is(cloudFileAccounts.accounts.length, 0); await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); is(buttonList.childElementCount, 1); is( buttonList.children[0].getAttribute("value"), "ext-fake@extensions.thunderbird.net" ); is(menuButton.itemCount, 1); is( menuButton.getItemAtIndex(0).getAttribute("value"), "ext-fake@extensions.thunderbird.net" ); is(accountList.itemCount, 0); ok(!cloudFileDefaultPanel.hidden); is(browserWrapper.childElementCount, 0); // Re-add the test provider. await startExtension(); is(cloudFileAccounts.providers.length, 2); is(cloudFileAccounts.accounts.length, 1); is(cloudFileAccounts.configuredAccounts.length, 1); await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); is(buttonList.childElementCount, 2); is( buttonList.children[0].getAttribute("value"), "ext-fake@extensions.thunderbird.net" ); is(buttonList.children[1].getAttribute("value"), "ext-cloudfile@mochitest"); is(menuButton.itemCount, 2); is( menuButton.getItemAtIndex(0).getAttribute("value"), "ext-fake@extensions.thunderbird.net" ); is( menuButton.getItemAtIndex(1).getAttribute("value"), "ext-cloudfile@mochitest" ); is(accountList.itemCount, 1); is(accountList.selectedIndex, -1); ok(removeButton.disabled); accountListItem = accountList.getItemAtIndex(0); is( Services.prefs.getCharPref( `mail.cloud_files.accounts.${accountKey}.displayName` ), "Mochitest!" ); EventUtils.synthesizeMouseAtCenter( accountList.getItemAtIndex(0), { clickCount: 1 }, prefsWindow ); ok(!removeButton.disabled); EventUtils.synthesizeMouseAtCenter( removeButton, { clickCount: 1 }, prefsWindow ); is(mockPromptService.confirmCount, 1); ok( !Services.prefs.prefHasUserValue( `mail.cloud_files.accounts.${accountKey}.displayName` ) ); ok( !Services.prefs.prefHasUserValue( `mail.cloud_files.accounts.${accountKey}.type` ) ); is(cloudFileAccounts.providers.length, 2); is(cloudFileAccounts.accounts.length, 0); info("Stopping extension"); await extension.unload(); is(cloudFileAccounts.providers.length, 1); is(cloudFileAccounts.accounts.length, 0); // Close the preferences tab. await closePrefsTab(); }); async function subtestBrowserStyle(assertMessage, expected) { is(cloudFileAccounts.providers.length, 1); is(cloudFileAccounts.accounts.length, 0); // Load the preferences tab. let { prefsDocument, prefsWindow } = await openNewPrefsTab( "paneCompose", "compositionAttachmentsCategory" ); // Minimal check everything is as it should be. let accountList = prefsDocument.getElementById("cloudFileView"); is(accountList.itemCount, 0); let buttonList = prefsDocument.getElementById("addCloudFileAccountButtons"); ok(!buttonList.hidden); let browserWrapper = prefsDocument.getElementById("cloudFileSettingsWrapper"); is(browserWrapper.childElementCount, 0); // Register our test provider. await startExtension(expected.browser_style); is(cloudFileAccounts.providers.length, 2); is(cloudFileAccounts.accounts.length, 0); await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); is(buttonList.childElementCount, 2); is(buttonList.children[1].getAttribute("value"), "ext-cloudfile@mochitest"); // Create a new account. EventUtils.synthesizeMouseAtCenter( buttonList.children[1], { clickCount: 1 }, prefsWindow ); is(cloudFileAccounts.accounts.length, 1); is(cloudFileAccounts.configuredAccounts.length, 0); let account = cloudFileAccounts.accounts[0]; let accountKey = account.accountKey; is(cloudFileAccounts.accounts[0].type, "ext-cloudfile@mochitest"); // Minimal check UI was updated. is(accountList.itemCount, 1); is(accountList.selectedIndex, 0); let accountListItem = accountList.selectedItem; is(accountListItem.getAttribute("value"), accountKey); is(browserWrapper.childElementCount, 1); let browser = browserWrapper.firstElementChild; if ( browser.webProgress?.isLoadingDocument || browser.currentURI?.spec == "about:blank" ) { await BrowserTestUtils.browserLoaded(browser); } is( browser.currentURI.pathQueryRef, `/management.html?accountId=${accountKey}` ); await extension.awaitMessage("management-ui-ready"); // Test browser_style extension.sendMessage( "check-style", assertMessage, expected.browser_style == "true" ); await extension.awaitFinish("management-ui-browser_style"); // Remove the account accountListItem = accountList.getItemAtIndex(0); EventUtils.synthesizeMouseAtCenter( accountList.getItemAtIndex(0), { clickCount: 1 }, prefsWindow ); let removeButton = prefsDocument.getElementById("removeCloudFileAccount"); ok(!removeButton.disabled); EventUtils.synthesizeMouseAtCenter( removeButton, { clickCount: 1 }, prefsWindow ); is(mockPromptService.confirmCount, expected.confirmCount); is(cloudFileAccounts.providers.length, 2); is(cloudFileAccounts.accounts.length, 0); info("Stopping extension"); await extension.unload(); is(cloudFileAccounts.providers.length, 1); is(cloudFileAccounts.accounts.length, 0); // Close the preferences tab. await closePrefsTab(); } add_task(async function test_without_setting_browser_style() { await subtestBrowserStyle( "Expected correct style when browser_style is excluded", { confirmCount: 2, browser_style: "default", } ); }); add_task(async function test_with_browser_style_set_to_true() { await subtestBrowserStyle( "Expected correct style when browser_style is set to `true`", { confirmCount: 3, browser_style: "true", } ); }); add_task(async function test_with_browser_style_set_to_false() { await subtestBrowserStyle( "Expected no style when browser_style is set to `false`", { confirmCount: 4, browser_style: "false", } ); }); add_task(async function accountListOverflow() { is(cloudFileAccounts.providers.length, 1); is(cloudFileAccounts.accounts.length, 0); // Register our test provider. await startExtension(); is(cloudFileAccounts.providers.length, 2); is(cloudFileAccounts.accounts.length, 0); // Load the preferences tab. let { prefsDocument, prefsWindow } = await openNewPrefsTab( "paneCompose", "compositionAttachmentsCategory" ); let accountList = prefsDocument.getElementById("cloudFileView"); is(accountList.itemCount, 0); let buttonList = prefsDocument.getElementById("addCloudFileAccountButtons"); ok(!buttonList.hidden); is(buttonList.childElementCount, 2); is(buttonList.children[0].getAttribute("value"), "ext-cloudfile@mochitest"); let menuButton = prefsDocument.getElementById("addCloudFileAccount"); ok(menuButton.hidden); // Add new accounts until the list overflows. The list of buttons should be hidden // and the button with the drop-down should appear. let count = 0; do { let readyPromise = extension.awaitMessage("management-ui-ready"); EventUtils.synthesizeMouseAtCenter( buttonList.children[0], { clickCount: 1 }, prefsWindow ); await readyPromise; // eslint-disable-next-line mozilla/no-arbitrary-setTimeout await new Promise(r => setTimeout(r, 500)); if (buttonList.hidden) { break; } } while (++count < 25); ok(count < 24); // If count reaches 25, we have a problem. ok(!menuButton.hidden); // Remove the added accounts. The list of buttons should not reappear and the // button with the drop-down should remain. let removeButton = prefsDocument.getElementById("removeCloudFileAccount"); do { EventUtils.synthesizeMouseAtCenter( accountList.getItemAtIndex(0), { clickCount: 1 }, prefsWindow ); EventUtils.synthesizeMouseAtCenter( removeButton, { clickCount: 1 }, prefsWindow ); await new Promise(resolve => setTimeout(resolve)); } while (--count > 0); ok(buttonList.hidden); ok(!menuButton.hidden); // Close the preferences tab. await closePrefsTab(); info("Stopping extension"); await extension.unload(); Services.prefs.deleteBranch("mail.cloud_files.accounts"); }); add_task(async function accountListOrder() { is(cloudFileAccounts.providers.length, 1); is(cloudFileAccounts.accounts.length, 0); for (let [key, displayName] of [ ["someKey1", "carl's Account"], ["someKey2", "Amber's Account"], ["someKey3", "alice's Account"], ["someKey4", "Bob's Account"], ]) { Services.prefs.setCharPref( `mail.cloud_files.accounts.${key}.type`, "ext-cloudfile@mochitest" ); Services.prefs.setCharPref( `mail.cloud_files.accounts.${key}.displayName`, displayName ); } // Register our test provider. await startExtension(); is(cloudFileAccounts.providers.length, 2); is(cloudFileAccounts.accounts.length, 4); let { prefsDocument } = await openNewPrefsTab( "paneCompose", "compositionAttachmentsCategory" ); let accountList = prefsDocument.getElementById("cloudFileView"); is(accountList.itemCount, 4); is(accountList.getItemAtIndex(0).value, "someKey3"); is(accountList.getItemAtIndex(1).value, "someKey2"); is(accountList.getItemAtIndex(2).value, "someKey4"); is(accountList.getItemAtIndex(3).value, "someKey1"); await closePrefsTab(); info("Stopping extension"); await extension.unload(); Services.prefs.deleteBranch("mail.cloud_files.accounts"); });