/* 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/. */ "use strict"; /** * Tests that can show multiple auth prompts in different tabs and handle them * correctly. */ const ORIGIN1 = "https://example.com"; const ORIGIN2 = "https://example.org"; const AUTH_PATH = "/browser/toolkit/components/passwordmgr/test/browser/authenticate.sjs"; /** * Opens a tab and navigates to the auth test path. * @param {String} origin - Origin to open with test path. * @param {Object} authOptions - Authentication options to pass to server and * test for. * @param {String} authOptions.user - Expected username. * @param {String} authOptions.pass - Expected password. * @param {String} authOptions.realm - Realm to return on auth request. * @returns {Object} - An object containing passed origin and authOptions, * opened tab, a promise which resolves once the tab loads, a promise which * resolves once the prompt has been opened. */ async function openTabWithAuthPrompt(origin, authOptions) { let tab = await BrowserTestUtils.openNewForegroundTab( gBrowser, "https://example.com" ); let promptPromise = PromptTestUtils.waitForPrompt(tab.linkedBrowser, { modalType: Services.prompt.MODAL_TYPE_TAB, promptType: "promptUserAndPass", }); let url = new URL(origin + AUTH_PATH); Object.entries(authOptions).forEach(([key, value]) => url.searchParams.append(key, value) ); let loadPromise = BrowserTestUtils.browserLoaded( tab.linkedBrowser, false, url.toString() ); info("Loading " + url.toString()); BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, url.toString()); return { origin, tab, authOptions, loadPromise, promptPromise }; } /** * Waits for tab to load and tests for expected auth state. * @param {boolean} expectAuthed - true: auth success, false otherwise. * @param {Object} tabInfo - Information about the tab as generated by * openTabWithAuthPrompt. */ async function testTabAuthed(expectAuthed, { tab, loadPromise, authOptions }) { // Wait for tab to load after auth. await loadPromise; Assert.ok(true, "Tab loads after auth"); // Fetch auth results from body (set by authenticate.sjs). let { loginResult, user, pass } = await SpecialPowers.spawn( tab.linkedBrowser, [], () => { let result = {}; result.loginResult = content.document.getElementById("ok").innerText; result.user = content.document.getElementById("user").innerText; result.pass = content.document.getElementById("pass").innerText; return result; } ); Assert.equal( loginResult == "PASS", expectAuthed, "Site has expected auth state" ); Assert.equal(user, expectAuthed ? authOptions.user : "", "Sent correct user"); Assert.equal( pass, expectAuthed ? authOptions.pass : "", "Sent correct password" ); } add_task(async function test() { let tabA = await openTabWithAuthPrompt(ORIGIN1, { user: "userA", pass: "passA", realm: "realmA", }); // Tab B and C share realm and credentials. // However, since the auth happens in separate tabs we should get two prompts. let tabB = await openTabWithAuthPrompt(ORIGIN2, { user: "userB", pass: "passB", realm: "realmB", }); let tabC = await openTabWithAuthPrompt(ORIGIN2, { user: "userB", pass: "passB", realm: "realmB", }); let tabs = [tabA, tabB, tabC]; info(`Opening ${tabs.length} tabs with auth prompts`); let prompts = await Promise.all(tabs.map(tab => tab.promptPromise)); Assert.equal(prompts.length, tabs.length, "Should have one prompt per tab"); for (let i = 0; i < prompts.length; i++) { let titleEl = prompts[i].ui.prompt.document.querySelector("#titleText"); Assert.equal( titleEl.textContent, new URL(tabs[i].origin).host, "Prompt matches the tab's host" ); } // Interact with the prompts. This is deliberately done out of order // (no FIFO, LIFO). let [promptA, promptB, promptC] = prompts; // Accept prompt B with correct login details. await PromptTestUtils.handlePrompt(promptB, { loginInput: tabB.authOptions.user, passwordInput: tabB.authOptions.pass, }); await testTabAuthed(true, tabB); // Accept prompt A with correct login details await PromptTestUtils.handlePrompt(promptA, { loginInput: tabA.authOptions.user, passwordInput: tabA.authOptions.pass, }); await testTabAuthed(true, tabA); // Cancel prompt C await PromptTestUtils.handlePrompt(promptC, { buttonNumClick: 1, }); await testTabAuthed(false, tabC); // Cleanup tabs tabs.forEach(({ tab }) => BrowserTestUtils.removeTab(tab)); });