/*jslint es6: true*/ /*global window*/ (function () { "use strict"; const {packingLib, standards, storageLib, domainMatcherLib} = window.WEB_API_MANAGER; const rootObject = window.browser || window.chrome; const defaultKey = "(default)"; // Once loaded from storage, will be a mapping from regular expressions // (or the default option, "(default)"), to an array of standards // that should be blocked on matching domains. let domainRules; // The extension depends on this fetch happening before the DOM on any // pages is loaded. The Chrome and Firefox docs *do not* promise this, // but in testing this is always the case. storageLib.get(function (loadedDomainRules) { domainRules = loadedDomainRules; }); // Manage the state of the browser activity, by displaying the number // of origins / frames const updateBrowserActionBadge = function (activeInfo) { const tabId = activeInfo.tabId; rootObject.tabs.executeScript( tabId, { allFrames: true, code: "window.location.host" }, function (allHosts) { if (rootObject.runtime.lastError && !allHosts) { rootObject.browserAction.disable(); rootObject.browserAction.setBadgeText({text: "-"}); return; } const numFrames = allHosts ? Array.from(new Set(allHosts)).length.toString() : "-"; rootObject.browserAction.setBadgeText({ text: numFrames, tabId: tabId }); rootObject.browserAction.enable(); } ); }; rootObject.windows.onFocusChanged.addListener(updateBrowserActionBadge); rootObject.tabs.onUpdated.addListener(updateBrowserActionBadge); rootObject.tabs.onActivated.addListener(updateBrowserActionBadge); window.setInterval(function () { rootObject.tabs.getCurrent(function (currentTab) { if (currentTab === undefined) { return; } updateBrowserActionBadge({tabId: currentTab.id}); }); }, 1000); // Listen for updates to the domain rules from the config page. // The two types of messages that are sent to the background page are // "rulesUpdate", which comes from the config page, indicating the domain // blocking / matching rules have changed, and the "rulesForDomains" // message, which comes from the browserAction popup, and is a request // for information about "here are the domains of the frames on the // current page, which rules are being used to match them". rootObject.runtime.onMessage.addListener(function (request, ignore, sendResponse) { const [label, data] = request; if (label === "rulesUpdate") { domainRules = data; return; } if (label === "rulesForDomains") { const matchHostNameBound = domainMatcherLib.matchHostName.bind(undefined, Object.keys(domainRules)); const rulesForDomains = data.map(matchHostNameBound); const domainToRuleMapping = {}; data.forEach(function (aHostName, index) { domainToRuleMapping[aHostName] = rulesForDomains[index] || defaultKey; }); sendResponse(domainToRuleMapping); return; } }); const requestFilter = { urls: [""], types: ["main_frame", "sub_frame"] }; const requestOptions = ["blocking", "responseHeaders"]; // Inject the blocking settings for each visited domain / frame. // This needs to be done syncronously, so that the DOM of the visited // page can be instrumented at "document_start" time. This means we // can't do any of the "obvious" techniques for loading the "what should" // be blocked in this frame" information (ie using the storage API). // So, instead, we halt at the http query point, match the domain being // loaded against the current rule set, pack the set of standards // that should be blocked into a base64 encoded bitfield, and then // push that to the page as a cookie. // // The page then reads the information about what standards to block // out of the cookie (by decoding and unpacking the bitfield), and then // deletes the cookie, so nothing is left behind. rootObject.webRequest.onHeadersReceived.addListener(function (details) { const url = details.url; // Decide which set of blocking rules to use, depending on the host // of the URL being requested. const matchingDomainRule = domainMatcherLib.matchUrl(Object.keys(domainRules), url); const standardsToBlock = domainRules[matchingDomainRule || defaultKey]; const options = Object.keys(standards); const packedValues = packingLib.pack(options, standardsToBlock); details.responseHeaders.push({ name: "Set-Cookie", value: `web-api-manager=${packedValues}` }); return { responseHeaders: details.responseHeaders }; }, requestFilter, requestOptions); }());