diff options
Diffstat (limited to 'toolkit/components/passwordmgr/test/unit/test_context_menu.js')
-rw-r--r-- | toolkit/components/passwordmgr/test/unit/test_context_menu.js | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/test/unit/test_context_menu.js b/toolkit/components/passwordmgr/test/unit/test_context_menu.js new file mode 100644 index 0000000000..56d4620338 --- /dev/null +++ b/toolkit/components/passwordmgr/test/unit/test_context_menu.js @@ -0,0 +1,345 @@ +/** + * Test the password manager context menu. + */ + +"use strict"; + +const { LoginManagerContextMenu } = ChromeUtils.importESModule( + "resource://gre/modules/LoginManagerContextMenu.sys.mjs" +); + +const dateAndTimeFormatter = new Services.intl.DateTimeFormat(undefined, { + dateStyle: "medium", +}); + +const ORIGIN_HTTP_EXAMPLE_ORG = "http://example.org"; +const ORIGIN_HTTPS_EXAMPLE_ORG = "https://example.org"; +const ORIGIN_HTTPS_EXAMPLE_ORG_8080 = "https://example.org:8080"; + +const FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1 = formLogin({ + formActionOrigin: ORIGIN_HTTPS_EXAMPLE_ORG, + guid: "FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1", + origin: ORIGIN_HTTPS_EXAMPLE_ORG, +}); + +// HTTP version of the above +const FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P1 = formLogin({ + formActionOrigin: ORIGIN_HTTP_EXAMPLE_ORG, + guid: "FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P1", + origin: ORIGIN_HTTP_EXAMPLE_ORG, +}); + +// Same as above but with a different password +const FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P2 = formLogin({ + formActionOrigin: ORIGIN_HTTP_EXAMPLE_ORG, + guid: "FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P2", + origin: ORIGIN_HTTP_EXAMPLE_ORG, + password: "pass2", +}); + +// Non-default port + +const FORM_LOGIN_HTTPS_EXAMPLE_ORG_8080_U1_P2 = formLogin({ + formActionOrigin: ORIGIN_HTTPS_EXAMPLE_ORG_8080, + guid: "FORM_LOGIN_HTTPS_EXAMPLE_ORG_8080_U1_P2", + origin: ORIGIN_HTTPS_EXAMPLE_ORG_8080, + password: "pass2", +}); + +// HTTP Auth. + +const HTTP_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1 = authLogin({ + guid: "FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1", + origin: ORIGIN_HTTPS_EXAMPLE_ORG, +}); + +XPCOMUtils.defineLazyGetter(this, "_stringBundle", function () { + return Services.strings.createBundle( + "chrome://passwordmgr/locale/passwordmgr.properties" + ); +}); + +/** + * Prepare data for the following tests. + */ +add_task(async function test_initialize() { + Services.prefs.setBoolPref("signon.schemeUpgrades", true); +}); + +add_task(async function test_sameOriginBothHTTPAndHTTPSDeduped() { + await runTestcase({ + formOrigin: FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1.origin, + savedLogins: [FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1], + expectedItems: [ + { + login: FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1, + }, + ], + }); +}); + +add_task(async function test_sameOriginOnlyHTTPS_noUsername() { + let loginWithoutUsername = FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1.clone(); + loginWithoutUsername.QueryInterface(Ci.nsILoginMetaInfo).guid = "no-username"; + loginWithoutUsername.username = ""; + await runTestcase({ + formOrigin: loginWithoutUsername.origin, + savedLogins: [loginWithoutUsername], + expectedItems: [ + { + login: loginWithoutUsername, + time: true, + }, + ], + }); +}); + +add_task(async function test_sameOriginOnlyHTTP() { + await runTestcase({ + formOrigin: FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P1.origin, + savedLogins: [FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P1], + expectedItems: [ + { + login: FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P1, + }, + ], + }); +}); + +// Scheme upgrade/downgrade tasks + +add_task(async function test_sameOriginDedupeSchemeUpgrade() { + await runTestcase({ + formOrigin: FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1.origin, + savedLogins: [ + FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1, + FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P1, + ], + expectedItems: [ + { + login: FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1, + }, + ], + }); +}); + +add_task(async function test_sameOriginSchemeDowngrade() { + // Should have no https: when formOrigin is https: + await runTestcase({ + formOrigin: FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P1.origin, + savedLogins: [ + FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1, + FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P1, + ], + expectedItems: [ + { + login: FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P1, + }, + ], + }); +}); + +add_task(async function test_sameOriginNotShadowedSchemeUpgrade() { + await runTestcase({ + formOrigin: FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1.origin, + savedLogins: [ + FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1, + FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P2, // Different password + ], + expectedItems: [ + { + login: FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1, + time: true, + }, + { + login: FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P2, + time: true, + }, + ], + }); +}); + +add_task(async function test_sameOriginShadowedSchemeDowngrade() { + // Should have no https: when formOrigin is https: + await runTestcase({ + formOrigin: FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P1.origin, + savedLogins: [ + FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1, + FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P2, // Different password + ], + expectedItems: [ + { + login: FORM_LOGIN_HTTP_EXAMPLE_ORG_U1_P2, + }, + ], + }); +}); + +// Non-default port tasks + +add_task(async function test_sameDomainDifferentPort_onDefault() { + await runTestcase({ + formOrigin: FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1.origin, + savedLogins: [ + FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1, + FORM_LOGIN_HTTPS_EXAMPLE_ORG_8080_U1_P2, + ], + expectedItems: [ + { + login: FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1, + }, + ], + }); +}); + +add_task(async function test_sameDomainDifferentPort_onNonDefault() { + await runTestcase({ + // Swap the formOrigin compared to above + formOrigin: FORM_LOGIN_HTTPS_EXAMPLE_ORG_8080_U1_P2.origin, + savedLogins: [ + FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1, + FORM_LOGIN_HTTPS_EXAMPLE_ORG_8080_U1_P2, + ], + expectedItems: [ + { + login: FORM_LOGIN_HTTPS_EXAMPLE_ORG_8080_U1_P2, + }, + ], + }); +}); + +// HTTP auth. suggestions + +add_task(async function test_sameOriginOnlyHTTPAuth() { + await runTestcase({ + formOrigin: FORM_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1.origin, + savedLogins: [HTTP_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1], + expectedItems: [ + { + login: HTTP_LOGIN_HTTPS_EXAMPLE_ORG_U1_P1, + }, + ], + }); +}); + +// Helpers + +function formLogin(modifications = {}) { + let mods = Object.assign( + {}, + { + timePasswordChanged: 1573821296000, + }, + modifications + ); + return TestData.formLogin(mods); +} + +function authLogin(modifications = {}) { + let mods = Object.assign( + {}, + { + timePasswordChanged: 1573821296000, + }, + modifications + ); + return TestData.authLogin(mods); +} + +/** + * Tests if the LoginManagerContextMenu returns the correct login items. + */ +async function runTestcase({ formOrigin, savedLogins, expectedItems }) { + const DOCUMENT_CONTENT = "<form><input id='pw' type=password></form>"; + + await Services.logins.addLogins(savedLogins); + + // Create the logins menuitems fragment. + let { fragment, document } = createLoginsFragment( + formOrigin, + DOCUMENT_CONTENT + ); + + if (!expectedItems.length) { + Assert.ok(fragment === null, "Null returned. No logins were found."); + return; + } + let actualItems = [...fragment.children]; + + // Check if the items are those expected to be listed. + checkLoginItems(actualItems, expectedItems); + + document.body.appendChild(fragment); + + // Try to clear the fragment. + LoginManagerContextMenu.clearLoginsFromMenu(document); + Assert.equal( + document.querySelectorAll("menuitem").length, + 0, + "All items correctly cleared." + ); + + Services.logins.removeAllUserFacingLogins(); +} + +/** + * Create a fragment with a menuitem for each login. + */ +function createLoginsFragment(url, content) { + const CHROME_URL = "chrome://mock-chrome/content/"; + + // Create a mock document. + let document = MockDocument.createTestDocument( + CHROME_URL, + content, + undefined, + true + ); + + // We also need a simple mock Browser object for this test. + let browser = { + ownerDocument: document, + }; + + let formOrigin = LoginHelper.getLoginOrigin(url); + return { + document, + fragment: LoginManagerContextMenu.addLoginsToMenu( + null, + browser, + formOrigin + ), + }; +} + +function checkLoginItems(actualItems, expectedDetails) { + for (let [i, expectedDetail] of expectedDetails.entries()) { + let actualElement = actualItems[i]; + + Assert.equal(actualElement.localName, "menuitem", "Check localName"); + + let expectedLabel = expectedDetail.login.username; + if (!expectedLabel) { + expectedLabel += _stringBundle.GetStringFromName("noUsername"); + } + if (expectedDetail.time) { + expectedLabel += + " (" + + dateAndTimeFormatter.format( + new Date(expectedDetail.login.timePasswordChanged) + ) + + ")"; + } + Assert.equal( + actualElement.getAttribute("label"), + expectedLabel, + `Check label ${i}` + ); + } + + Assert.equal( + actualItems.length, + expectedDetails.length, + "Should have the correct number of menu items" + ); +} |