/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const { ON_PROFILE_CHANGE_NOTIFICATION, WEBCHANNEL_ID, log } = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js"); const { CryptoUtils } = ChromeUtils.importESModule( "resource://services-crypto/utils.sys.mjs" ); const { FxAccountsWebChannel, FxAccountsWebChannelHelpers } = ChromeUtils.importESModule( "resource://gre/modules/FxAccountsWebChannel.sys.mjs" ); const URL_STRING = "https://example.com"; const mockSendingContext = { browsingContext: { top: { embedderElement: {} } }, principal: {}, eventTarget: {}, }; add_test(function () { validationHelper(undefined, "Error: Missing configuration options"); validationHelper( { channel_id: WEBCHANNEL_ID, }, "Error: Missing 'content_uri' option" ); validationHelper( { content_uri: "bad uri", channel_id: WEBCHANNEL_ID, }, /NS_ERROR_MALFORMED_URI/ ); validationHelper( { content_uri: URL_STRING, }, "Error: Missing 'channel_id' option" ); run_next_test(); }); add_task(async function test_rejection_reporting() { Services.prefs.setBoolPref( "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", false ); let mockMessage = { command: "fxaccounts:login", messageId: "1234", data: { email: "testuser@testuser.com" }, }; let channel = new FxAccountsWebChannel({ channel_id: WEBCHANNEL_ID, content_uri: URL_STRING, helpers: { login(accountData) { equal( accountData.email, "testuser@testuser.com", "Should forward incoming message data to the helper" ); return Promise.reject(new Error("oops")); }, }, }); let promiseSend = new Promise(resolve => { channel._channel.send = (message, context) => { resolve({ message, context }); }; }); channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); let { message, context } = await promiseSend; equal(context, mockSendingContext, "Should forward the original context"); equal( message.command, "fxaccounts:login", "Should include the incoming command" ); equal(message.messageId, "1234", "Should include the message ID"); equal( message.data.error.message, "Error: oops", "Should convert the error message to a string" ); notStrictEqual( message.data.error.stack, null, "Should include the stack for JS error rejections" ); }); add_test(function test_exception_reporting() { let mockMessage = { command: "fxaccounts:sync_preferences", messageId: "5678", data: { entryPoint: "fxa:verification_complete" }, }; let channel = new FxAccountsWebChannel({ channel_id: WEBCHANNEL_ID, content_uri: URL_STRING, helpers: { openSyncPreferences(browser, entryPoint) { equal( entryPoint, "fxa:verification_complete", "Should forward incoming message data to the helper" ); throw new TypeError("splines not reticulated"); }, }, }); channel._channel.send = (message, context) => { equal(context, mockSendingContext, "Should forward the original context"); equal( message.command, "fxaccounts:sync_preferences", "Should include the incoming command" ); equal(message.messageId, "5678", "Should include the message ID"); equal( message.data.error.message, "TypeError: splines not reticulated", "Should convert the exception to a string" ); notStrictEqual( message.data.error.stack, null, "Should include the stack for JS exceptions" ); run_next_test(); }; channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); }); add_test(function test_error_message_remove_profile_path() { const errors = { windows: { err: new Error( "Win error 183 during operation rename on file C:\\Users\\Some Computer\\AppData\\Roaming\\" + "Mozilla\\Firefox\\Profiles\\dbzjmzxa.default\\signedInUser.json (Cannot create a file)" ), expected: "Error: Win error 183 during operation rename on file C:[REDACTED]signedInUser.json (Cannot create a file)", }, unix: { err: new Error( "Unix error 28 during operation write on file /Users/someuser/Library/Application Support/" + "Firefox/Profiles/dbzjmzxa.default-release-7/signedInUser.json (No space left on device)" ), expected: "Error: Unix error 28 during operation write on file [REDACTED]signedInUser.json (No space left on device)", }, netpath: { err: new Error( "Win error 32 during operation rename on file \\\\SVC.LOC\\HOMEDIRS$\\USERNAME\\Mozilla\\" + "Firefox\\Profiles\\dbzjmzxa.default-release-7\\signedInUser.json (No space left on device)" ), expected: "Error: Win error 32 during operation rename on file [REDACTED]signedInUser.json (No space left on device)", }, mount: { err: new Error( "Win error 649 during operation rename on file C:\\SnapVolumes\\MountPoints\\" + "{9e399ec5-0000-0000-0000-100000000000}\\SVROOT\\Users\\username\\AppData\\Roaming\\Mozilla\\Firefox\\" + "Profiles\\dbzjmzxa.default-release\\signedInUser.json (The create operation failed)" ), expected: "Error: Win error 649 during operation rename on file C:[REDACTED]signedInUser.json " + "(The create operation failed)", }, }; const mockMessage = { command: "fxaccounts:sync_preferences", messageId: "1234", }; const channel = new FxAccountsWebChannel({ channel_id: WEBCHANNEL_ID, content_uri: URL_STRING, }); let testNum = 0; const toTest = Object.keys(errors).length; for (const key in errors) { let error = errors[key]; channel._channel.send = (message, context) => { equal( message.data.error.message, error.expected, "Should remove the profile path from the error message" ); testNum++; if (testNum === toTest) { run_next_test(); } }; channel._sendError(error.err, mockMessage, mockSendingContext); } }); add_test(function test_profile_image_change_message() { var mockMessage = { command: "profile:change", data: { uid: "foo" }, }; makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) { Assert.equal(data, "foo"); run_next_test(); }); var channel = new FxAccountsWebChannel({ channel_id: WEBCHANNEL_ID, content_uri: URL_STRING, }); channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); }); add_test(function test_login_message() { let mockMessage = { command: "fxaccounts:login", data: { email: "testuser@testuser.com" }, }; let channel = new FxAccountsWebChannel({ channel_id: WEBCHANNEL_ID, content_uri: URL_STRING, helpers: { login(accountData) { Assert.equal(accountData.email, "testuser@testuser.com"); run_next_test(); return Promise.resolve(); }, }, }); channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); }); add_test(function test_logout_message() { let mockMessage = { command: "fxaccounts:logout", data: { uid: "foo" }, }; let channel = new FxAccountsWebChannel({ channel_id: WEBCHANNEL_ID, content_uri: URL_STRING, helpers: { logout(uid) { Assert.equal(uid, "foo"); run_next_test(); return Promise.resolve(); }, }, }); channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); }); add_test(function test_delete_message() { let mockMessage = { command: "fxaccounts:delete", data: { uid: "foo" }, }; let channel = new FxAccountsWebChannel({ channel_id: WEBCHANNEL_ID, content_uri: URL_STRING, helpers: { logout(uid) { Assert.equal(uid, "foo"); run_next_test(); return Promise.resolve(); }, }, }); channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); }); add_test(function test_can_link_account_message() { let mockMessage = { command: "fxaccounts:can_link_account", data: { email: "testuser@testuser.com" }, }; let channel = new FxAccountsWebChannel({ channel_id: WEBCHANNEL_ID, content_uri: URL_STRING, helpers: { shouldAllowRelink(email) { Assert.equal(email, "testuser@testuser.com"); run_next_test(); }, }, }); channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); }); add_test(function test_sync_preferences_message() { let mockMessage = { command: "fxaccounts:sync_preferences", data: { entryPoint: "fxa:verification_complete" }, }; let channel = new FxAccountsWebChannel({ channel_id: WEBCHANNEL_ID, content_uri: URL_STRING, helpers: { openSyncPreferences(browser, entryPoint) { Assert.equal(entryPoint, "fxa:verification_complete"); Assert.equal( browser, mockSendingContext.browsingContext.top.embedderElement ); run_next_test(); }, }, }); channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); }); add_test(function test_fxa_status_message() { let mockMessage = { command: "fxaccounts:fxa_status", messageId: 123, data: { service: "sync", context: "fx_desktop_v3", }, }; let channel = new FxAccountsWebChannel({ channel_id: WEBCHANNEL_ID, content_uri: URL_STRING, helpers: { async getFxaStatus(service, sendingContext, isPairing, context) { Assert.equal(service, "sync"); Assert.equal(sendingContext, mockSendingContext); Assert.ok(!isPairing); Assert.equal(context, "fx_desktop_v3"); return { signedInUser: { email: "testuser@testuser.com", sessionToken: "session-token", uid: "uid", verified: true, }, capabilities: { engines: ["creditcards", "addresses"], }, }; }, }, }); channel._channel = { send(response, sendingContext) { Assert.equal(response.command, "fxaccounts:fxa_status"); Assert.equal(response.messageId, 123); let signedInUser = response.data.signedInUser; Assert.ok(!!signedInUser); Assert.equal(signedInUser.email, "testuser@testuser.com"); Assert.equal(signedInUser.sessionToken, "session-token"); Assert.equal(signedInUser.uid, "uid"); Assert.equal(signedInUser.verified, true); deepEqual(response.data.capabilities.engines, [ "creditcards", "addresses", ]); run_next_test(); }, }; channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); }); add_test(function test_unrecognized_message() { let mockMessage = { command: "fxaccounts:unrecognized", data: {}, }; let channel = new FxAccountsWebChannel({ channel_id: WEBCHANNEL_ID, content_uri: URL_STRING, }); // no error is expected. channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); run_next_test(); }); add_test(function test_helpers_should_allow_relink_same_email() { let helpers = new FxAccountsWebChannelHelpers(); helpers.setPreviousAccountNameHashPref("testuser@testuser.com"); Assert.ok(helpers.shouldAllowRelink("testuser@testuser.com")); run_next_test(); }); add_test(function test_helpers_should_allow_relink_different_email() { let helpers = new FxAccountsWebChannelHelpers(); helpers.setPreviousAccountNameHashPref("testuser@testuser.com"); helpers._promptForRelink = acctName => { return acctName === "allowed_to_relink@testuser.com"; }; Assert.ok(helpers.shouldAllowRelink("allowed_to_relink@testuser.com")); Assert.ok(!helpers.shouldAllowRelink("not_allowed_to_relink@testuser.com")); run_next_test(); }); add_task(async function test_helpers_login_without_customize_sync() { let helpers = new FxAccountsWebChannelHelpers({ fxAccounts: { _internal: { setSignedInUser(accountData) { return new Promise(resolve => { // ensure fxAccounts is informed of the new user being signed in. Assert.equal(accountData.email, "testuser@testuser.com"); // verifiedCanLinkAccount should be stripped in the data. Assert.equal(false, "verifiedCanLinkAccount" in accountData); resolve(); }); }, }, telemetry: { recordConnection: sinon.spy(), }, }, weaveXPCOM: { whenLoaded() {}, Weave: { Service: { configure() {}, }, }, }, }); // ensure the previous account pref is overwritten. helpers.setPreviousAccountNameHashPref("lastuser@testuser.com"); await helpers.login({ email: "testuser@testuser.com", verifiedCanLinkAccount: true, customizeSync: false, }); Assert.ok( helpers._fxAccounts.telemetry.recordConnection.calledWith([], "webchannel") ); }); add_task(async function test_helpers_login_set_previous_account_name_hash() { let helpers = new FxAccountsWebChannelHelpers({ fxAccounts: { _internal: { setSignedInUser(accountData) { return new Promise(resolve => { // previously signed in user preference is updated. Assert.equal( helpers.getPreviousAccountNameHashPref(), CryptoUtils.sha256Base64("newuser@testuser.com") ); resolve(); }); }, }, telemetry: { recordConnection() {}, }, }, weaveXPCOM: { whenLoaded() {}, Weave: { Service: { configure() {}, }, }, }, }); // ensure the previous account pref is overwritten. helpers.setPreviousAccountNameHashPref("lastuser@testuser.com"); await helpers.login({ email: "newuser@testuser.com", verifiedCanLinkAccount: true, customizeSync: false, verified: true, }); }); add_task( async function test_helpers_login_dont_set_previous_account_name_hash_for_unverified_emails() { let helpers = new FxAccountsWebChannelHelpers({ fxAccounts: { _internal: { setSignedInUser(accountData) { return new Promise(resolve => { // previously signed in user preference should not be updated. Assert.equal( helpers.getPreviousAccountNameHashPref(), CryptoUtils.sha256Base64("lastuser@testuser.com") ); resolve(); }); }, }, telemetry: { recordConnection() {}, }, }, weaveXPCOM: { whenLoaded() {}, Weave: { Service: { configure() {}, }, }, }, }); // ensure the previous account pref is overwritten. helpers.setPreviousAccountNameHashPref("lastuser@testuser.com"); await helpers.login({ email: "newuser@testuser.com", verifiedCanLinkAccount: true, customizeSync: false, }); } ); add_task(async function test_helpers_login_with_customize_sync() { let helpers = new FxAccountsWebChannelHelpers({ fxAccounts: { _internal: { setSignedInUser(accountData) { return new Promise(resolve => { // ensure fxAccounts is informed of the new user being signed in. Assert.equal(accountData.email, "testuser@testuser.com"); // customizeSync should be stripped in the data. Assert.equal(false, "customizeSync" in accountData); resolve(); }); }, }, telemetry: { recordConnection: sinon.spy(), }, }, weaveXPCOM: { whenLoaded() {}, Weave: { Service: { configure() {}, }, }, }, }); await helpers.login({ email: "testuser@testuser.com", verifiedCanLinkAccount: true, customizeSync: true, }); Assert.ok( helpers._fxAccounts.telemetry.recordConnection.calledWith([], "webchannel") ); }); add_task( async function test_helpers_login_with_customize_sync_and_declined_engines() { let configured = false; let helpers = new FxAccountsWebChannelHelpers({ fxAccounts: { _internal: { setSignedInUser(accountData) { return new Promise(resolve => { // ensure fxAccounts is informed of the new user being signed in. Assert.equal(accountData.email, "testuser@testuser.com"); // customizeSync should be stripped in the data. Assert.equal(false, "customizeSync" in accountData); Assert.equal(false, "services" in accountData); resolve(); }); }, }, telemetry: { recordConnection: sinon.spy(), }, }, weaveXPCOM: { whenLoaded() {}, Weave: { Service: { configure() { configured = true; }, }, }, }, }); Assert.equal( Services.prefs.getBoolPref("services.sync.engine.addons"), true ); Assert.equal( Services.prefs.getBoolPref("services.sync.engine.bookmarks"), true ); Assert.equal( Services.prefs.getBoolPref("services.sync.engine.history"), true ); Assert.equal( Services.prefs.getBoolPref("services.sync.engine.passwords"), true ); Assert.equal( Services.prefs.getBoolPref("services.sync.engine.prefs"), true ); Assert.equal(Services.prefs.getBoolPref("services.sync.engine.tabs"), true); await helpers.login({ email: "testuser@testuser.com", verifiedCanLinkAccount: true, customizeSync: true, services: { sync: { offeredEngines: [ "addons", "bookmarks", "history", "passwords", "prefs", ], declinedEngines: ["addons", "prefs"], }, }, }); Assert.equal( Services.prefs.getBoolPref("services.sync.engine.addons"), false ); Assert.equal( Services.prefs.getBoolPref("services.sync.engine.bookmarks"), true ); Assert.equal( Services.prefs.getBoolPref("services.sync.engine.history"), true ); Assert.equal( Services.prefs.getBoolPref("services.sync.engine.passwords"), true ); Assert.equal( Services.prefs.getBoolPref("services.sync.engine.prefs"), false ); Assert.equal(Services.prefs.getBoolPref("services.sync.engine.tabs"), true); Assert.ok(configured, "sync was configured"); Assert.ok( helpers._fxAccounts.telemetry.recordConnection.calledWith( ["sync"], "webchannel" ) ); } ); add_task(async function test_helpers_login_with_offered_sync_engines() { let helpers; let configured = false; const setSignedInUserCalled = new Promise(resolve => { helpers = new FxAccountsWebChannelHelpers({ fxAccounts: { _internal: { async setSignedInUser(accountData) { resolve(accountData); }, }, telemetry: { recordConnection() {}, }, }, weaveXPCOM: { whenLoaded() {}, Weave: { Service: { configure() { configured = true; }, }, }, }, }); }); Services.prefs.setBoolPref("services.sync.engine.creditcards", false); Services.prefs.setBoolPref("services.sync.engine.addresses", false); await helpers.login({ email: "testuser@testuser.com", verifiedCanLinkAccount: true, customizeSync: true, services: { sync: { declinedEngines: ["addresses"], offeredEngines: ["creditcards", "addresses"], }, }, }); const accountData = await setSignedInUserCalled; // ensure fxAccounts is informed of the new user being signed in. equal(accountData.email, "testuser@testuser.com"); // services should be stripped in the data. ok(!("services" in accountData)); // credit cards was offered but not declined. equal(Services.prefs.getBoolPref("services.sync.engine.creditcards"), true); // addresses was offered and explicitely declined. equal(Services.prefs.getBoolPref("services.sync.engine.addresses"), false); ok(configured); }); add_task(async function test_helpers_login_nothing_offered() { let helpers; let configured = false; const setSignedInUserCalled = new Promise(resolve => { helpers = new FxAccountsWebChannelHelpers({ fxAccounts: { _internal: { async setSignedInUser(accountData) { resolve(accountData); }, }, telemetry: { recordConnection() {}, }, }, weaveXPCOM: { whenLoaded() {}, Weave: { Service: { configure() { configured = true; }, }, }, }, }); }); // doesn't really matter if it's *all* engines... const allEngines = [ "addons", "addresses", "bookmarks", "creditcards", "history", "passwords", "prefs", ]; for (let name of allEngines) { Services.prefs.clearUserPref("services.sync.engine." + name); } await helpers.login({ email: "testuser@testuser.com", verifiedCanLinkAccount: true, services: { sync: {}, }, }); const accountData = await setSignedInUserCalled; // ensure fxAccounts is informed of the new user being signed in. equal(accountData.email, "testuser@testuser.com"); for (let name of allEngines) { Assert.ok(!Services.prefs.prefHasUserValue("services.sync.engine." + name)); } Assert.ok(configured); }); add_test(function test_helpers_open_sync_preferences() { let helpers = new FxAccountsWebChannelHelpers({ fxAccounts: {}, }); let mockBrowser = { loadURI(uri) { Assert.equal( uri.spec, "about:preferences?entrypoint=fxa%3Averification_complete#sync" ); run_next_test(); }, }; helpers.openSyncPreferences(mockBrowser, "fxa:verification_complete"); }); add_task(async function test_helpers_getFxAStatus_extra_engines() { let helpers = new FxAccountsWebChannelHelpers({ fxAccounts: { _internal: { getUserAccountData() { return Promise.resolve({ email: "testuser@testuser.com", sessionToken: "sessionToken", uid: "uid", verified: true, }); }, }, }, privateBrowsingUtils: { isBrowserPrivate: () => true, }, }); Services.prefs.setBoolPref( "services.sync.engine.creditcards.available", true ); // Not defining "services.sync.engine.addresses.available" on purpose. let fxaStatus = await helpers.getFxaStatus("sync", mockSendingContext); ok(!!fxaStatus); ok(!!fxaStatus.signedInUser); deepEqual(fxaStatus.capabilities.engines, ["creditcards"]); }); add_task(async function test_helpers_getFxaStatus_allowed_signedInUser() { let wasCalled = { getUserAccountData: false, shouldAllowFxaStatus: false, }; let helpers = new FxAccountsWebChannelHelpers({ fxAccounts: { _internal: { getUserAccountData() { wasCalled.getUserAccountData = true; return Promise.resolve({ email: "testuser@testuser.com", sessionToken: "sessionToken", uid: "uid", verified: true, }); }, }, }, }); helpers.shouldAllowFxaStatus = (service, sendingContext) => { wasCalled.shouldAllowFxaStatus = true; Assert.equal(service, "sync"); Assert.equal(sendingContext, mockSendingContext); return true; }; return helpers.getFxaStatus("sync", mockSendingContext).then(fxaStatus => { Assert.ok(!!fxaStatus); Assert.ok(wasCalled.getUserAccountData); Assert.ok(wasCalled.shouldAllowFxaStatus); Assert.ok(!!fxaStatus.signedInUser); let { signedInUser } = fxaStatus; Assert.equal(signedInUser.email, "testuser@testuser.com"); Assert.equal(signedInUser.sessionToken, "sessionToken"); Assert.equal(signedInUser.uid, "uid"); Assert.ok(signedInUser.verified); // These properties are filtered and should not // be returned to the requester. Assert.equal(false, "kSync" in signedInUser); Assert.equal(false, "kXCS" in signedInUser); Assert.equal(false, "kExtSync" in signedInUser); Assert.equal(false, "kExtKbHash" in signedInUser); }); }); add_task(async function test_helpers_getFxaStatus_allowed_no_signedInUser() { let wasCalled = { getUserAccountData: false, shouldAllowFxaStatus: false, }; let helpers = new FxAccountsWebChannelHelpers({ fxAccounts: { _internal: { getUserAccountData() { wasCalled.getUserAccountData = true; return Promise.resolve(null); }, }, }, }); helpers.shouldAllowFxaStatus = (service, sendingContext) => { wasCalled.shouldAllowFxaStatus = true; Assert.equal(service, "sync"); Assert.equal(sendingContext, mockSendingContext); return true; }; return helpers.getFxaStatus("sync", mockSendingContext).then(fxaStatus => { Assert.ok(!!fxaStatus); Assert.ok(wasCalled.getUserAccountData); Assert.ok(wasCalled.shouldAllowFxaStatus); Assert.equal(null, fxaStatus.signedInUser); }); }); add_task(async function test_helpers_getFxaStatus_not_allowed() { let wasCalled = { getUserAccountData: false, shouldAllowFxaStatus: false, }; let helpers = new FxAccountsWebChannelHelpers({ fxAccounts: { _internal: { getUserAccountData() { wasCalled.getUserAccountData = true; return Promise.resolve(null); }, }, }, }); helpers.shouldAllowFxaStatus = ( service, sendingContext, isPairing, context ) => { wasCalled.shouldAllowFxaStatus = true; Assert.equal(service, "sync"); Assert.equal(sendingContext, mockSendingContext); Assert.ok(!isPairing); Assert.equal(context, "fx_desktop_v3"); return false; }; return helpers .getFxaStatus("sync", mockSendingContext, false, "fx_desktop_v3") .then(fxaStatus => { Assert.ok(!!fxaStatus); Assert.ok(!wasCalled.getUserAccountData); Assert.ok(wasCalled.shouldAllowFxaStatus); Assert.equal(null, fxaStatus.signedInUser); }); }); add_task( async function test_helpers_shouldAllowFxaStatus_sync_service_not_private_browsing() { let wasCalled = { isPrivateBrowsingMode: false, }; let helpers = new FxAccountsWebChannelHelpers({}); helpers.isPrivateBrowsingMode = sendingContext => { wasCalled.isPrivateBrowsingMode = true; Assert.equal(sendingContext, mockSendingContext); return false; }; let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( "sync", mockSendingContext, false ); Assert.ok(shouldAllowFxaStatus); Assert.ok(wasCalled.isPrivateBrowsingMode); } ); add_task( async function test_helpers_shouldAllowFxaStatus_desktop_context_not_private_browsing() { let wasCalled = { isPrivateBrowsingMode: false, }; let helpers = new FxAccountsWebChannelHelpers({}); helpers.isPrivateBrowsingMode = sendingContext => { wasCalled.isPrivateBrowsingMode = true; Assert.equal(sendingContext, mockSendingContext); return false; }; let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( "", mockSendingContext, false, "fx_desktop_v3" ); Assert.ok(shouldAllowFxaStatus); Assert.ok(wasCalled.isPrivateBrowsingMode); } ); add_task( async function test_helpers_shouldAllowFxaStatus_oauth_service_not_private_browsing() { let wasCalled = { isPrivateBrowsingMode: false, }; let helpers = new FxAccountsWebChannelHelpers({}); helpers.isPrivateBrowsingMode = sendingContext => { wasCalled.isPrivateBrowsingMode = true; Assert.equal(sendingContext, mockSendingContext); return false; }; let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( "dcdb5ae7add825d2", mockSendingContext, false ); Assert.ok(shouldAllowFxaStatus); Assert.ok(wasCalled.isPrivateBrowsingMode); } ); add_task( async function test_helpers_shouldAllowFxaStatus_no_service_not_private_browsing() { let wasCalled = { isPrivateBrowsingMode: false, }; let helpers = new FxAccountsWebChannelHelpers({}); helpers.isPrivateBrowsingMode = sendingContext => { wasCalled.isPrivateBrowsingMode = true; Assert.equal(sendingContext, mockSendingContext); return false; }; let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( "", mockSendingContext, false ); Assert.ok(shouldAllowFxaStatus); Assert.ok(wasCalled.isPrivateBrowsingMode); } ); add_task( async function test_helpers_shouldAllowFxaStatus_sync_service_private_browsing() { let wasCalled = { isPrivateBrowsingMode: false, }; let helpers = new FxAccountsWebChannelHelpers({}); helpers.isPrivateBrowsingMode = sendingContext => { wasCalled.isPrivateBrowsingMode = true; Assert.equal(sendingContext, mockSendingContext); return true; }; let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( "sync", mockSendingContext, false ); Assert.ok(shouldAllowFxaStatus); Assert.ok(wasCalled.isPrivateBrowsingMode); } ); add_task( async function test_helpers_shouldAllowFxaStatus_desktop_context_private_browsing() { let wasCalled = { isPrivateBrowsingMode: false, }; let helpers = new FxAccountsWebChannelHelpers({}); helpers.isPrivateBrowsingMode = sendingContext => { wasCalled.isPrivateBrowsingMode = true; Assert.equal(sendingContext, mockSendingContext); return true; }; let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( "", mockSendingContext, false, "fx_desktop_v3" ); Assert.ok(shouldAllowFxaStatus); Assert.ok(wasCalled.isPrivateBrowsingMode); } ); add_task( async function test_helpers_shouldAllowFxaStatus_oauth_service_private_browsing() { let wasCalled = { isPrivateBrowsingMode: false, }; let helpers = new FxAccountsWebChannelHelpers({}); helpers.isPrivateBrowsingMode = sendingContext => { wasCalled.isPrivateBrowsingMode = true; Assert.equal(sendingContext, mockSendingContext); return true; }; let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( "dcdb5ae7add825d2", mockSendingContext, false ); Assert.ok(!shouldAllowFxaStatus); Assert.ok(wasCalled.isPrivateBrowsingMode); } ); add_task( async function test_helpers_shouldAllowFxaStatus_oauth_service_pairing_private_browsing() { let wasCalled = { isPrivateBrowsingMode: false, }; let helpers = new FxAccountsWebChannelHelpers({}); helpers.isPrivateBrowsingMode = sendingContext => { wasCalled.isPrivateBrowsingMode = true; Assert.equal(sendingContext, mockSendingContext); return true; }; let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( "dcdb5ae7add825d2", mockSendingContext, true ); Assert.ok(shouldAllowFxaStatus); Assert.ok(wasCalled.isPrivateBrowsingMode); } ); add_task( async function test_helpers_shouldAllowFxaStatus_no_service_private_browsing() { let wasCalled = { isPrivateBrowsingMode: false, }; let helpers = new FxAccountsWebChannelHelpers({}); helpers.isPrivateBrowsingMode = sendingContext => { wasCalled.isPrivateBrowsingMode = true; Assert.equal(sendingContext, mockSendingContext); return true; }; let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( "", mockSendingContext, false ); Assert.ok(!shouldAllowFxaStatus); Assert.ok(wasCalled.isPrivateBrowsingMode); } ); add_task(async function test_helpers_isPrivateBrowsingMode_private_browsing() { let wasCalled = { isBrowserPrivate: false, }; let helpers = new FxAccountsWebChannelHelpers({ privateBrowsingUtils: { isBrowserPrivate(browser) { wasCalled.isBrowserPrivate = true; Assert.equal( browser, mockSendingContext.browsingContext.top.embedderElement ); return true; }, }, }); let isPrivateBrowsingMode = helpers.isPrivateBrowsingMode(mockSendingContext); Assert.ok(isPrivateBrowsingMode); Assert.ok(wasCalled.isBrowserPrivate); }); add_task(async function test_helpers_isPrivateBrowsingMode_private_browsing() { let wasCalled = { isBrowserPrivate: false, }; let helpers = new FxAccountsWebChannelHelpers({ privateBrowsingUtils: { isBrowserPrivate(browser) { wasCalled.isBrowserPrivate = true; Assert.equal( browser, mockSendingContext.browsingContext.top.embedderElement ); return false; }, }, }); let isPrivateBrowsingMode = helpers.isPrivateBrowsingMode(mockSendingContext); Assert.ok(!isPrivateBrowsingMode); Assert.ok(wasCalled.isBrowserPrivate); }); add_task(async function test_helpers_change_password() { let wasCalled = { updateUserAccountData: false, updateDeviceRegistration: false, }; let helpers = new FxAccountsWebChannelHelpers({ fxAccounts: { _internal: { updateUserAccountData(credentials) { return new Promise(resolve => { Assert.ok(credentials.hasOwnProperty("email")); Assert.ok(credentials.hasOwnProperty("uid")); Assert.ok(credentials.hasOwnProperty("unwrapBKey")); Assert.ok(credentials.hasOwnProperty("device")); Assert.equal(null, credentials.device); Assert.equal(null, credentials.encryptedSendTabKeys); // "foo" isn't a field known by storage, so should be dropped. Assert.ok(!credentials.hasOwnProperty("foo")); wasCalled.updateUserAccountData = true; resolve(); }); }, updateDeviceRegistration() { Assert.equal(arguments.length, 0); wasCalled.updateDeviceRegistration = true; return Promise.resolve(); }, }, }, }); await helpers.changePassword({ email: "email", uid: "uid", unwrapBKey: "unwrapBKey", foo: "foo", }); Assert.ok(wasCalled.updateUserAccountData); Assert.ok(wasCalled.updateDeviceRegistration); }); add_task(async function test_helpers_change_password_with_error() { let wasCalled = { updateUserAccountData: false, updateDeviceRegistration: false, }; let helpers = new FxAccountsWebChannelHelpers({ fxAccounts: { _internal: { updateUserAccountData() { wasCalled.updateUserAccountData = true; return Promise.reject(); }, updateDeviceRegistration() { wasCalled.updateDeviceRegistration = true; return Promise.resolve(); }, }, }, }); try { await helpers.changePassword({}); Assert.equal(false, "changePassword should have rejected"); } catch (_) { Assert.ok(wasCalled.updateUserAccountData); Assert.ok(!wasCalled.updateDeviceRegistration); } }); function makeObserver(aObserveTopic, aObserveFunc) { let callback = function (aSubject, aTopic, aData) { log.debug("observed " + aTopic + " " + aData); if (aTopic == aObserveTopic) { removeMe(); aObserveFunc(aSubject, aTopic, aData); } }; function removeMe() { log.debug("removing observer for " + aObserveTopic); Services.obs.removeObserver(callback, aObserveTopic); } Services.obs.addObserver(callback, aObserveTopic); return removeMe; } function validationHelper(params, expected) { try { new FxAccountsWebChannel(params); } catch (e) { if (typeof expected === "string") { return Assert.equal(e.toString(), expected); } return Assert.ok(e.toString().match(expected)); } throw new Error("Validation helper error"); }