summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/test/unit/test_context_menu.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/passwordmgr/test/unit/test_context_menu.js')
-rw-r--r--toolkit/components/passwordmgr/test/unit/test_context_menu.js345
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..c0947ea22b
--- /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,
+});
+
+ChromeUtils.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"
+ );
+}