/* 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"; const TEST_URL = "https://example.com/browser/dom/webauthn/tests/browser/tab_webauthn_result.html"; add_task(async function test_setup() { return SpecialPowers.pushPrefEnv({ set: [ ["security.webauth.webauthn_enable_softtoken", false], ["security.webauth.webauthn_enable_usbtoken", true], ], }); }); add_task(test_switch_tab); add_task(test_new_window_make); add_task(test_new_window_get); add_task(test_minimize_make); add_task(test_minimize_get); async function assertStatus(tab, expected) { let actual = await SpecialPowers.spawn( tab.linkedBrowser, [], async function () { info("visbility state: " + content.document.visibilityState); info("active: " + content.browsingContext.isActive); return content.document.getElementById("status").value; } ); is(actual, expected, "webauthn request " + expected); } async function waitForStatus(tab, expected) { /* eslint-disable no-shadow */ await SpecialPowers.spawn( tab.linkedBrowser, [[expected]], async function (expected) { return ContentTaskUtils.waitForCondition(() => { info( "expecting " + expected + ", visbility state: " + content.document.visibilityState ); info( "expecting " + expected + ", active: " + content.browsingContext.isActive ); return content.document.getElementById("status").value == expected; }); } ); /* eslint-enable no-shadow */ await assertStatus(tab, expected); } function startMakeCredentialRequest(tab) { return SpecialPowers.spawn(tab.linkedBrowser, [], async function () { const cose_alg_ECDSA_w_SHA256 = -7; let publicKey = { rp: { id: content.document.domain, name: "none" }, user: { id: new Uint8Array(), name: "none", displayName: "none", }, challenge: content.crypto.getRandomValues(new Uint8Array(16)), timeout: 5000, // the minimum timeout is actually 15 seconds pubKeyCredParams: [{ type: "public-key", alg: cose_alg_ECDSA_w_SHA256 }], }; let status = content.document.getElementById("status"); info( "Attempting to create credential for origin: " + content.document.nodePrincipal.origin ); content.navigator.credentials .create({ publicKey }) .then(() => { status.value = "completed"; }) .catch(() => { status.value = "aborted"; }); status.value = "pending"; }); } function startGetAssertionRequest(tab) { return SpecialPowers.spawn(tab.linkedBrowser, [], async function () { let newCredential = { type: "public-key", id: content.crypto.getRandomValues(new Uint8Array(16)), transports: ["usb"], }; let publicKey = { challenge: content.crypto.getRandomValues(new Uint8Array(16)), timeout: 5000, // the minimum timeout is actually 15 seconds rpId: content.document.domain, allowCredentials: [newCredential], }; let status = content.document.getElementById("status"); info( "Attempting to get credential for origin: " + content.document.nodePrincipal.origin ); content.navigator.credentials .get({ publicKey }) .then(() => { status.value = "completed"; }) .catch(ex => { info("aborted: " + ex); status.value = "aborted"; }); status.value = "pending"; }); } // Test that MakeCredential() and GetAssertion() requests // are aborted when the current tab loses its focus. async function test_switch_tab() { // Create a new tab for the MakeCredential() request. let tab_create = await BrowserTestUtils.openNewForegroundTab( gBrowser, TEST_URL ); // Start the request. await startMakeCredentialRequest(tab_create); await assertStatus(tab_create, "pending"); // Open another tab and switch to it. The first will lose focus. let tab_get = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); await assertStatus(tab_create, "pending"); // Start a GetAssertion() request in the second tab, the first is aborted await startGetAssertionRequest(tab_get); await waitForStatus(tab_create, "aborted"); await assertStatus(tab_get, "pending"); // Start a second request in the second tab. It should abort. await startGetAssertionRequest(tab_get); await waitForStatus(tab_get, "aborted"); // Close tabs. BrowserTestUtils.removeTab(tab_create); BrowserTestUtils.removeTab(tab_get); } function waitForWindowActive(win, active) { return Promise.all([ BrowserTestUtils.waitForEvent(win, active ? "focus" : "blur"), BrowserTestUtils.waitForEvent(win, active ? "activate" : "deactivate"), ]); } async function test_new_window_make() { // Create a new tab for the MakeCredential() request. let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); // Start a MakeCredential request. await startMakeCredentialRequest(tab); await assertStatus(tab, "pending"); let windowGonePromise = waitForWindowActive(window, false); // Open a new window. The tab will lose focus. let win = await BrowserTestUtils.openNewBrowserWindow(); await windowGonePromise; await assertStatus(tab, "pending"); let windowBackPromise = waitForWindowActive(window, true); await BrowserTestUtils.closeWindow(win); await windowBackPromise; // Close tab. await BrowserTestUtils.removeTab(tab); } async function test_new_window_get() { // Create a new tab for the GetAssertion() request. let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); // Start a GetAssertion request. await startGetAssertionRequest(tab); await assertStatus(tab, "pending"); let windowGonePromise = waitForWindowActive(window, false); // Open a new window. The tab will lose focus. let win = await BrowserTestUtils.openNewBrowserWindow(); await windowGonePromise; await assertStatus(tab, "pending"); let windowBackPromise = waitForWindowActive(window, true); await BrowserTestUtils.closeWindow(win); await windowBackPromise; // Close tab. BrowserTestUtils.removeTab(tab); } async function test_minimize_make() { // Minimizing windows doesn't supported in headless mode. if (Services.env.get("MOZ_HEADLESS")) { return; } // Create a new window for the MakeCredential() request. let win = await BrowserTestUtils.openNewBrowserWindow(); let tab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, TEST_URL); // Start a MakeCredential request. await startMakeCredentialRequest(tab); await assertStatus(tab, "pending"); // Minimize the window. let windowGonePromise = waitForWindowActive(win, false); win.minimize(); await assertStatus(tab, "pending"); await windowGonePromise; // Restore the window. await new Promise(resolve => SimpleTest.waitForFocus(resolve, win)); await assertStatus(tab, "pending"); // Close window and wait for main window to be focused again. let windowBackPromise = waitForWindowActive(window, true); await BrowserTestUtils.removeTab(tab); await BrowserTestUtils.closeWindow(win); await windowBackPromise; } async function test_minimize_get() { // Minimizing windows doesn't supported in headless mode. if (Services.env.get("MOZ_HEADLESS")) { return; } // Create a new window for the GetAssertion() request. let win = await BrowserTestUtils.openNewBrowserWindow(); let tab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, TEST_URL); // Start a GetAssertion request. await startGetAssertionRequest(tab); await assertStatus(tab, "pending"); // Minimize the window. let windowGonePromise = waitForWindowActive(win, false); win.minimize(); await assertStatus(tab, "pending"); await windowGonePromise; // Restore the window. await new Promise(resolve => SimpleTest.waitForFocus(resolve, win)); await assertStatus(tab, "pending"); // Close window and wait for main window to be focused again. let windowBackPromise = waitForWindowActive(window, true); await BrowserTestUtils.removeTab(tab); await BrowserTestUtils.closeWindow(win); await windowBackPromise; }