const TEST_URL_PATH = "/browser/toolkit/components/passwordmgr/test/browser/"; const INITIAL_URL = `about:blank`; const FORM_URL = `https://example.org${TEST_URL_PATH}form_basic.html`; const FORMLESS_URL = `https://example.org${TEST_URL_PATH}formless_basic.html`; const testUrls = [FORM_URL, FORMLESS_URL]; const BRAND_BUNDLE = Services.strings.createBundle( "chrome://branding/locale/brand.properties" ); const BRAND_FULL_NAME = BRAND_BUNDLE.GetStringFromName("brandFullName"); async function getDocumentVisibilityState(browser) { let visibility = await SpecialPowers.spawn(browser, [], async function() { return content.document.visibilityState; }); return visibility; } // Waits for the master password prompt and cancels it. function observeMasterPasswordDialog(window, result) { let closedPromise; function topicObserver(subject) { let expected = "Password Required - " + BRAND_FULL_NAME; if (subject.Dialog.args.title == expected) { result.wasShown = true; subject.Dialog.ui.button1.click(); closedPromise = BrowserTestUtils.waitForEvent( window, "DOMModalDialogClosed" ); } } Services.obs.addObserver(topicObserver, "common-dialog-loaded"); let waited = TestUtils.waitForCondition(() => { return result.wasShown; }, "Wait for master password dialog"); return Promise.all([waited, closedPromise]) .catch(ex => { info( `observeMasterPasswordDialog, caught exception from topicObserved: ${ex}` ); }) .finally(() => { Services.obs.removeObserver(topicObserver, "common-dialog-loaded"); }); } add_task(async function setup() { Services.logins.removeAllUserFacingLogins(); let login = LoginTestUtils.testData.formLogin({ origin: "http://example.org", formActionOrigin: "http://example.org", username: "user1", password: "pass1", }); Services.logins.addLogin(login); }); add_task(async function test_processed_form_fired() { // Sanity check. If this doesnt work any results for the subsequent tasks are suspect const tab1 = await BrowserTestUtils.openNewForegroundTab( gBrowser, INITIAL_URL ); let tab1Visibility = await getDocumentVisibilityState(tab1.linkedBrowser); is(tab1Visibility, "visible", "The first tab should be foreground"); let formProcessedPromise = listenForTestNotification("FormProcessed"); BrowserTestUtils.loadURI(tab1.linkedBrowser, FORM_URL); await formProcessedPromise; gBrowser.removeTab(tab1); }); testUrls.forEach(testUrl => { add_task(async function test_defer_autofill_until_visible() { let result, tab1Visibility; // open 2 tabs const tab1 = await BrowserTestUtils.openNewForegroundTab( gBrowser, INITIAL_URL ); const tab2 = await BrowserTestUtils.openNewForegroundTab( gBrowser, INITIAL_URL ); // confirm document is hidden tab1Visibility = await getDocumentVisibilityState(tab1.linkedBrowser); is(tab1Visibility, "hidden", "The first tab should be backgrounded"); // we shouldn't even try to autofill while hidden, so wait for the document to be in the // non-visible pending queue instead. let formFilled = false; listenForTestNotification("FormProcessed").then(() => { formFilled = true; }); BrowserTestUtils.loadURI(tab1.linkedBrowser, testUrl); await TestUtils.waitForCondition(() => { let windowGlobal = tab1.linkedBrowser.browsingContext.currentWindowGlobal; if (!windowGlobal || windowGlobal.documentURI.spec == "about:blank") { return false; } let actor = windowGlobal.getActor("LoginManager"); return actor.sendQuery("PasswordManager:formIsPending"); }); ok( !formFilled, "Observer should not be notified when form is loaded into a hidden document" ); // Add the observer before switching tab let formProcessedPromise = listenForTestNotification("FormProcessed"); await BrowserTestUtils.switchTab(gBrowser, tab1); result = await formProcessedPromise; tab1Visibility = await getDocumentVisibilityState(tab1.linkedBrowser); is(tab1Visibility, "visible", "The first tab should be foreground"); ok( result, "Observer should be notified when input's document becomes visible" ); // the form should have been autofilled with the login let fieldValues = await SpecialPowers.spawn( tab1.linkedBrowser, [], function() { let doc = content.document; return { username: doc.getElementById("form-basic-username").value, password: doc.getElementById("form-basic-password").value, }; } ); is(fieldValues.username, "user1", "Checking filled username"); is(fieldValues.password, "pass1", "Checking filled password"); gBrowser.removeTab(tab1); gBrowser.removeTab(tab2); }); }); add_task(async function test_immediate_autofill_with_masterpassword() { // Set master password prompt timeout to 3s. // If this test goes intermittent, you likely have to increase this value. await SpecialPowers.pushPrefEnv({ set: [["signon.masterPasswordReprompt.timeout_ms", 3000]], }); LoginTestUtils.masterPassword.enable(); await LoginTestUtils.reloadData(); info( `Have enabled masterPassword, now isLoggedIn? ${Services.logins.isLoggedIn}` ); registerCleanupFunction(async function() { LoginTestUtils.masterPassword.disable(); await LoginTestUtils.reloadData(); }); let dialogResult, tab1Visibility, dialogObserved; // open 2 tabs const tab1 = await BrowserTestUtils.openNewForegroundTab( gBrowser, INITIAL_URL ); const tab2 = await BrowserTestUtils.openNewForegroundTab( gBrowser, INITIAL_URL ); info( "load a background login form tab with a matching saved login " + "and wait to see if the master password dialog is shown" ); is( await getDocumentVisibilityState(tab2.linkedBrowser), "visible", "The second tab should be visible" ); tab1Visibility = await getDocumentVisibilityState(tab1.linkedBrowser); is(tab1Visibility, "hidden", "The first tab should be backgrounded"); dialogResult = { wasShown: false }; dialogObserved = observeMasterPasswordDialog(tab1.ownerGlobal, dialogResult); // In this case we will try to autofill while hidden, so look for the passwordmgr-processed-form // to be observed let formProcessedPromise = listenForTestNotification("FormProcessed"); BrowserTestUtils.loadURI(tab1.linkedBrowser, FORM_URL); await Promise.all([formProcessedPromise, dialogObserved]); let wasProcessed = await formProcessedPromise; ok( wasProcessed, "Observer should be notified when form is loaded into a hidden document" ); ok( dialogResult && dialogResult.wasShown, "MP Dialog should be shown when form is loaded into a hidden document" ); gBrowser.removeTab(tab1); gBrowser.removeTab(tab2); });