const tabForms = {}; const tabMatches = {}; let lastTabId = null; let currentPageForms = null; let currentPageMatches = null; let addedContexts = []; function askNative(msg) { const port = chrome.runtime.connectNative('ee.lunasqu.password_manager'); return new Promise((resolve, reject) => { let gotResponse = false; port.onMessage.addListener((msg) => { gotResponse = true; resolve(msg); port.disconnect(); }); port.onDisconnect.addListener((msg) => { if (!gotResponse) { gotResponse = true; reject(msg); } }); port.postMessage(msg); }); } chrome.runtime.onInstalled.addListener(() => { chrome.contextMenus.create({ id: 'fillContextMenu', title: 'Autofill password', contexts: ['editable'], }); }); function addContextMenuItems() { if (!currentPageMatches) { return; } clearContextMenu(); for (const match of currentPageMatches) { const ctxID = `fill-${match}`; chrome.contextMenus.create({ id: ctxID, title: match, contexts: ['editable'], parentId: 'fillContextMenu', }); addedContexts.push(ctxID); } } function clearContextMenu() { addedContexts.forEach((id) => { try { chrome.contextMenus.remove(id); } catch (e) { console.error(e); } }); addedContexts.length = 0; } async function injectForeground() { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); if (!tab || !tab.url) { return; } lastTabId = tab.id; await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['./foreground.js'], }); const parsed = new URL(tab.url); askNative({ domain: parsed.hostname, }).then(({ results }) => { if (results && results.length) { currentPageMatches = results; tabMatches[tab.id] = results; chrome.tabs.sendMessage(tab.id, { message: 'has_entries', payload: results, }); addContextMenuItems(); } }); } async function sendPasswordToTab(rdata, existingTab) { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); const realTab = existingTab || tab; if (!realTab || !realTab.url) { return; } chrome.tabs.sendMessage(realTab.id, { message: 'fill_password', payload: rdata, }); } chrome.webNavigation.onCompleted.addListener((ee) => { if (ee.frameType !== 'outermost_frame') { return; } currentPageForms = null; currentPageMatches = null; injectForeground(); }); chrome.tabs.onActivated.addListener(function (activeInfo) { lastTabId = activeInfo.tabId; currentPageForms = tabForms[activeInfo.tabId]; currentPageMatches = tabMatches[activeInfo.tabId]; clearContextMenu(); if (currentPageMatches) { addContextMenuItems(); } }); chrome.tabs.onRemoved.addListener(function (tabId, info) { delete tabForms[tabId]; delete tabMatches[tabId]; }); chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.message === 'login_forms') { if (sender && sender.tab && sender.tab.id) { tabForms[sender.tab.id] = request.payload; } if (sender.tab.id === lastTabId) { currentPageForms = request.payload; } sendResponse(true); } if (request.message === 'autofill') { askNative({ getPassword: request.payload, }).then((response) => { sendPasswordToTab(response); }); sendResponse(true); } }); chrome.contextMenus.onClicked.addListener((info, tab) => { if (info && info.menuItemId && info.menuItemId.startsWith('fill-')) { const fillId = info.menuItemId.substring(5); askNative({ getPassword: fillId, }).then((response) => { sendPasswordToTab(response, tab); }); } });