// A page whose origin has no host is never told an address by accountsChanged // (https://git.eeqj.de/sneak/AutistMask/issues/564). // // When the user switches address, the background tells each open tab the new // active address if the site at the tab's URL is connected. A page at a // connected site's URL can still have another origin: one its server // sandboxes with a Content-Security-Policy has the origin "null", and every // request it makes is refused. Each test here switches the address in the real // background, takes the event it sent to a tab at the site's URL, and hands // that event to the real content script in a page of the given origin. const { networkById } = require("../src/shared/networks"); const { makeStorageStub } = require("./support/storageStub"); const ADDRESS = "0x66133E8ea0f5D1d612D2502a968757D1048c214a"; const SITE = "https://dapp.example"; const EXT_URL = "chrome-extension://autistmask/"; const FROM_POPUP = { url: EXT_URL + "src/popup/index.html" }; // One address, with SITE remembered as allowed for it. function storedProfile() { const mainnet = networkById("mainnet"); return { hasWallet: true, wallets: [ { name: "Wallet 1", type: "hd", addresses: [ { address: ADDRESS, balance: "0", tokenBalances: [] }, ], }, ], activeAddress: ADDRESS, networkId: "mainnet", rpcUrl: mainnet.defaultRpcUrl, blockscoutUrl: mainnet.defaultBlockscoutUrl, allowedSites: { [ADDRESS]: [SITE] }, deniedSites: {}, trackedTokens: [], }; } async function settle() { for (let i = 0; i < 50; i++) await Promise.resolve(); } afterEach(() => { delete global.chrome; delete global.window; jest.resetModules(); }); // The popup tells the background the active address changed, and the one // open tab, at SITE's URL, is sent an event. That event is returned. async function eventSentToSiteTab() { jest.resetModules(); jest.doMock("../src/shared/balances", () => ({ getProvider: () => ({}), refreshBalances: jest.fn(async () => {}), })); jest.doMock("../src/shared/phishingDomains", () => ({ isPhishingDomain: () => false, })); jest.doMock("../src/shared/alarms", () => ({ BALANCE_REFRESH_ALARM: "balance", BALANCE_REFRESH_PERIOD_MINUTES: 1, ensureRecurringAlarms: jest.fn(async () => {}), registerAlarmHandlers: jest.fn(), })); let messageListener = null; const sentToTabs = []; global.chrome = { storage: makeStorageStub({ autistmask: storedProfile() }), runtime: { getURL: (path) => EXT_URL + path, onMessage: { addListener: (fn) => { messageListener = fn; }, }, onConnect: { addListener: () => {} }, getPlatformInfo: (cb) => cb({ os: "linux" }), lastError: null, }, windows: { remove: (id, cb) => { if (cb) cb(); }, onRemoved: { addListener: () => {} }, }, tabs: { query: (q, cb) => cb([{ id: 1, url: SITE + "/app" }]), sendMessage: (tabId, msg, cb) => { sentToTabs.push(msg); if (cb) cb(); }, }, action: { setPopup: () => {} }, }; require("../src/background/index"); messageListener( { type: "AUTISTMASK_ACTIVE_CHANGED" }, FROM_POPUP, () => {}, ); await settle(); expect(sentToTabs).toHaveLength(1); return sentToTabs[0]; } // The content script in a page whose origin is `pageOrigin` is handed `event` // from the background. What it posts to the page is returned. function postedToPage(event, pageOrigin) { jest.resetModules(); let eventListener = null; const posted = []; global.chrome = { runtime: { onMessage: { addListener: (fn) => { eventListener = fn; }, }, }, }; global.window = { origin: pageOrigin, addEventListener: () => {}, postMessage: (data) => posted.push(data), }; require("../src/content/index"); eventListener(event); return posted; } describe("accountsChanged after an address switch, at a connected site's URL", () => { test("the site's own page is told the active address", async () => { const event = await eventSentToSiteTab(); expect(postedToPage(event, SITE)).toEqual([ { type: "AUTISTMASK_EVENT", eventName: "accountsChanged", data: [ADDRESS], }, ]); }); test("a page whose origin has no host is told no address", async () => { const event = await eventSentToSiteTab(); expect(postedToPage(event, "null")).toEqual([ { type: "AUTISTMASK_EVENT", eventName: "accountsChanged", data: [], }, ]); }); });