/******************************************************************************* uMatrix - a browser extension to black/white list requests. Copyright (C) 2014-present Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see {http://www.gnu.org/licenses/}. Home: https://github.com/gorhill/uMatrix */ /* globals publicSuffixList */ 'use strict'; /******************************************************************************/ /******************************************************************************/ // Default handler // priviledged { // >>>>> start of local scope const µm = µMatrix; const onMessage = function(request, sender, callback) { // Async switch ( request.what ) { case 'getAssetContent': µm.assets.get(request.url, { dontCache: true, }).then(response => { callback(response); }); return; case 'selectAssets': µm.selectAssets(request).then(response => { callback(response); }); return; default: break; } // Sync let response; switch ( request.what ) { case 'blacklistMatrixCell': µm.tMatrix.blacklistCell( request.srcHostname, request.desHostname, request.type ); break; case 'forceReloadTab': µm.forceReload(request.tabId, request.bypassCache); break; case 'forceUpdateAssets': µm.scheduleAssetUpdater(0); µm.assets.updateStart({ delay: 2000 }); break; case 'getCellColors': const ruleParts = request.ruleParts; const tColors = []; const pColors = []; for ( let i = 0, n = ruleParts.length; i < n; i += 3 ) { tColors.push(µm.tMatrix.evaluateCellZXY( ruleParts[i+0], ruleParts[i+1], ruleParts[i+2] )); pColors.push(µm.pMatrix.evaluateCellZXY( ruleParts[i+0], ruleParts[i+1], ruleParts[i+2] )); } response = { tColors, pColors }; break; case 'getDomainNames': response = request.targets.map(target => { if ( typeof target !== 'string' ) { return ''; } return target.indexOf('/') !== -1 ? vAPI.domainFromURI(target) || '' : vAPI.domainFromHostname(target) || target; }); break; case 'getUserSettings': response = { userSettings: µm.userSettings, matrixSwitches: { 'https-strict': µm.pMatrix.evaluateSwitch('https-strict', '*') === 1, 'referrer-spoof': µm.pMatrix.evaluateSwitch('referrer-spoof', '*') === 1, 'noscript-spoof': µm.pMatrix.evaluateSwitch('noscript-spoof', '*') === 1 } }; break; case 'gotoExtensionURL': µm.gotoExtensionURL(request); break; case 'graylistMatrixCell': µm.tMatrix.graylistCell( request.srcHostname, request.desHostname, request.type ); break; case 'mustBlock': response = µm.mustBlock( request.scope, request.hostname, request.type ); break; case 'rulesetRevert': µm.tMatrix.copyRuleset(request.entries, µm.pMatrix, true); break; case 'rulesetPersist': if ( µm.pMatrix.copyRuleset(request.entries, µm.tMatrix, true) ) { µm.saveMatrix(); } break; case 'readRawSettings': response = µm.stringFromRawSettings(); break; case 'reloadHostsFiles': µm.reloadHostsFiles(); break; case 'reloadRecipeFiles': µm.loadRecipes(true); break; case 'setMatrixSwitch': µm.tMatrix.setSwitch(request.switchName, '*', request.state); if ( µm.pMatrix.setSwitch(request.switchName, '*', request.state) ) { µm.saveMatrix(); } break; case 'userSettings': if ( request.hasOwnProperty('value') === false ) { request.value = undefined; } response = µm.changeUserSettings(request.name, request.value); break; case 'whitelistMatrixCell': µm.tMatrix.whitelistCell( request.srcHostname, request.desHostname, request.type ); break; case 'writeRawSettings': µm.rawSettingsFromString(request.content); break; default: return vAPI.messaging.UNHANDLED; } callback(response); }; vAPI.messaging.setup(onMessage); // <<<<< end of local scope } /******************************************************************************/ /******************************************************************************/ // Channel: // popupPanel // privileged { // >>>>> start of local scope const µm = µMatrix; const RowSnapshot = function(srcHostname, desHostname, desDomain) { this.domain = desDomain; this.temporary = µm.tMatrix.evaluateRowZXY(srcHostname, desHostname); this.permanent = µm.pMatrix.evaluateRowZXY(srcHostname, desHostname); this.counts = RowSnapshot.counts.slice(); this.totals = RowSnapshot.counts.slice(); }; RowSnapshot.counts = (( ) => { const aa = []; for ( let i = 0, n = µm.Matrix.columnHeaderIndices.size; i < n; i++ ) { aa[i] = 0; } return aa; })(); const matrixSnapshotFromPage = function(pageStore, details) { const µmuser = µm.userSettings; const headerIndices = µm.Matrix.columnHeaderIndices; const r = { appVersion: vAPI.app.version, blockedCount: pageStore.perLoadBlockedRequestCount, collapseAllDomains: µmuser.popupCollapseAllDomains, collapseBlacklistedDomains: µmuser.popupCollapseBlacklistedDomains, diff: [], domain: pageStore.pageDomain, has3pReferrer: pageStore.has3pReferrer, hasMixedContent: pageStore.hasMixedContent, hasNoscriptTags: pageStore.hasNoscriptTags, hasWebWorkers: pageStore.hasWebWorkers, hasHostnameAliases: pageStore.hasHostnameAliases, headerIndices: Array.from(headerIndices), hostname: pageStore.pageHostname, mtxContentModified: pageStore.mtxContentModifiedTime !== details.mtxContentModifiedTime, mtxCountModified: pageStore.mtxCountModifiedTime !== details.mtxCountModifiedTime, mtxContentModifiedTime: pageStore.mtxContentModifiedTime, mtxCountModifiedTime: pageStore.mtxCountModifiedTime, pMatrixModified: µm.pMatrix.modifiedTime !== details.pMatrixModifiedTime, pMatrixModifiedTime: µm.pMatrix.modifiedTime, pSwitches: {}, rows: {}, rowCount: 0, scope: '*', tabId: pageStore.tabId, tMatrixModified: µm.tMatrix.modifiedTime !== details.tMatrixModifiedTime, tMatrixModifiedTime: µm.tMatrix.modifiedTime, tSwitches: {}, url: pageStore.pageUrl, userSettings: { colorBlindFriendly: µmuser.colorBlindFriendly, displayTextSize: µmuser.displayTextSize, noTooltips: µmuser.noTooltips, popupScopeLevel: µmuser.popupScopeLevel } }; if ( (typeof details.scope === 'string') && (details.scope === '*' || r.hostname.endsWith(details.scope)) ) { r.scope = details.scope; } else if ( µmuser.popupScopeLevel === 'site' ) { r.scope = r.hostname; } else if ( µmuser.popupScopeLevel === 'domain' ) { r.scope = r.domain; } for ( const switchName of µm.Matrix.switchNames ) { r.tSwitches[switchName] = µm.tMatrix.evaluateSwitchZ(switchName, r.scope); r.pSwitches[switchName] = µm.pMatrix.evaluateSwitchZ(switchName, r.scope); } // These rows always exist r.rows['*'] = new RowSnapshot(r.scope, '*', '*'); r.rows['1st-party'] = new RowSnapshot(r.scope, '1st-party', '1st-party'); r.rowCount += 1; const µmuri = µm.URI; const anyIndex = headerIndices.get('*'); // Ensure that the current scope is also reported in the matrix. This may // not be the case for documents which are fetched without going through // our webRequest listener (ex. `file:`). if ( pageStore.hostnameTypeCells.has(r.hostname + ' doc') === false ) { pageStore.hostnameTypeCells.set(r.hostname + ' doc', new Set([ 0 ])); } for ( const [ rule, urls ] of pageStore.hostnameTypeCells ) { const pos = rule.indexOf(' '); let reqHostname = rule.slice(0, pos); // rhill 2013-10-23: hostname can be empty if the request is a data url // https://github.com/gorhill/httpswitchboard/issues/26 if ( reqHostname === '' ) { reqHostname = r.hostname; } const reqType = rule.slice(pos + 1); const reqDomain = µmuri.domainFromHostname(reqHostname) || reqHostname; // We want rows of self and ancestors let desHostname = reqHostname; for (;;) { // If row exists, ancestors exist if ( r.rows.hasOwnProperty(desHostname) !== false ) { break; } r.rows[desHostname] = new RowSnapshot(r.scope, desHostname, reqDomain); r.rowCount += 1; if ( desHostname === reqDomain ) { break; } const pos = desHostname.indexOf('.'); if ( pos === -1 ) { break; } desHostname = desHostname.slice(pos + 1); } const count = urls.size; const typeIndex = headerIndices.get(reqType); let row = r.rows[reqHostname]; row.counts[typeIndex] += count; row.counts[anyIndex] += count; row = r.rows[reqDomain]; row.totals[typeIndex] += count; row.totals[anyIndex] += count; row = r.rows['*']; row.totals[typeIndex] += count; row.totals[anyIndex] += count; } r.diff = µm.tMatrix.diff(µm.pMatrix, r.hostname, Object.keys(r.rows)); return r; }; const matrixSnapshotFromTabId = function(tabId, details) { const pageStore = µm.pageStoreFromTabId(tabId); if ( pageStore === null ) { return 'ENOTFOUND'; } return matrixSnapshotFromPage(pageStore, details); }; const matrixSnapshotFromRule = function(rule, details) { const µmuser = µm.userSettings; const headerIndices = µm.Matrix.columnHeaderIndices; const [ srchn, deshn, type ] = rule.trim().split(/\s+/); const now = Date.now(); const r = { appVersion: vAPI.app.version, blockedCount: 0, collapseAllDomains: µmuser.popupCollapseAllDomains, collapseBlacklistedDomains: µmuser.popupCollapseBlacklistedDomains, diff: [], domain: vAPI.domainFromHostname(srchn), headerIndices: Array.from(headerIndices), hostname: srchn, mtxContentModified: false, mtxCountModified: false, mtxContentModifiedTime: now, mtxCountModifiedTime: now, pMatrixModified: µm.pMatrix.modifiedTime !== details.pMatrixModifiedTime, pMatrixModifiedTime: µm.pMatrix.modifiedTime, pSwitches: {}, rows: {}, rowCount: 0, scope: '*', tMatrixModified: µm.tMatrix.modifiedTime !== details.tMatrixModifiedTime, tMatrixModifiedTime: µm.tMatrix.modifiedTime, tSwitches: {}, url: `https://${srchn}/`, userSettings: { colorBlindFriendly: µmuser.colorBlindFriendly, displayTextSize: µmuser.displayTextSize, noTooltips: µmuser.noTooltips, popupScopeLevel: µmuser.popupScopeLevel } }; if ( (typeof details.scope === 'string') && (details.scope === '*' || r.hostname.endsWith(details.scope)) ) { r.scope = details.scope; } else if ( µmuser.popupScopeLevel === 'site' ) { r.scope = r.hostname; } else if ( µmuser.popupScopeLevel === 'domain' ) { r.scope = r.domain; } for ( const switchName of µm.Matrix.switchNames ) { r.tSwitches[switchName] = µm.tMatrix.evaluateSwitchZ(switchName, r.scope); r.pSwitches[switchName] = µm.pMatrix.evaluateSwitchZ(switchName, r.scope); } // These rows always exist r.rows['*'] = new RowSnapshot(r.scope, '*', '*'); r.rows['1st-party'] = new RowSnapshot(r.scope, '1st-party', '1st-party'); r.rowCount += 1; const µmuri = µm.URI; const anyIndex = headerIndices.get('*'); const hostnameTypeCells = new Map(); hostnameTypeCells.set(`${srchn} doc`, new Set([ 0 ])); hostnameTypeCells.set(`${deshn} ${type}`, new Set([ 1 ])); for ( const [ rule, urls ] of hostnameTypeCells ) { const pos = rule.indexOf(' '); const reqHostname = rule.slice(0, pos); const reqType = rule.slice(pos + 1); const reqDomain = µmuri.domainFromHostname(reqHostname) || reqHostname; // We want rows of self and ancestors let desHostname = reqHostname; for (;;) { // If row exists, ancestors exist if ( r.rows.hasOwnProperty(desHostname) !== false ) { break; } r.rows[desHostname] = new RowSnapshot(r.scope, desHostname, reqDomain); r.rowCount += 1; if ( desHostname === reqDomain ) { break; } const pos = desHostname.indexOf('.'); if ( pos === -1 ) { break; } desHostname = desHostname.slice(pos + 1); } const count = urls.size; const typeIndex = headerIndices.get(reqType); let row = r.rows[reqHostname]; row.counts[typeIndex] += count; row.counts[anyIndex] += count; row = r.rows[reqDomain]; row.totals[typeIndex] += count; row.totals[anyIndex] += count; row = r.rows['*']; row.totals[typeIndex] += count; row.totals[anyIndex] += count; } r.diff = µm.tMatrix.diff(µm.pMatrix, r.hostname, Object.keys(r.rows)); return r; }; const matrixSnapshotFromAny = async function(sender, details) { // Specific tab id? if ( typeof details.tabId === 'number' && details.tabId !== 0 ) { const pageStore = µm.pageStoreFromTabId(details.tabId); if ( pageStore === null ) { return 'ENOTFOUND'; } if ( µm.tMatrix.modifiedTime === details.tMatrixModifiedTime && µm.pMatrix.modifiedTime === details.pMatrixModifiedTime && pageStore.mtxContentModifiedTime === details.mtxContentModifiedTime && pageStore.mtxCountModifiedTime === details.mtxCountModifiedTime ) { return 'ENOCHANGE'; } return matrixSnapshotFromPage(pageStore, details); } // Target encoded in URL? if ( typeof sender.url === 'string' ) { const url = new URL(sender.url); const params = url.searchParams; if ( params.has('tabid') ) { const tabId = parseInt(params.get('tabid'), 10); if ( isNaN(tabId) === false ) { return matrixSnapshotFromTabId(tabId, details); } } if ( params.has('rule') ) { return matrixSnapshotFromRule(params.get('rule'), details); } } // Fall back to currently active tab const tab = await vAPI.tabs.getCurrent(); return tab instanceof Object !== false ? matrixSnapshotFromTabId(tab.id, details) : 'ENOTFOUND'; }; /******************************************************************************/ const onMessage = function(request, sender, callback) { // Async switch ( request.what ) { case 'fetchRecipes': µm.recipeManager.fetch( request.srcHostname, request.desHostnames, callback ); return; case 'matrixSnapshot': matrixSnapshotFromAny(sender, request).then(response => { callback(response); }); return; default: break; } // Sync let response; switch ( request.what ) { case 'applyRecipe': µm.recipeManager.apply(request); break; case 'fetchRecipeCommitStatuses': response = µm.recipeManager.statuses(request); break; case 'toggleMatrixSwitch': µm.tMatrix.setSwitchZ( request.switchName, request.srcHostname, µm.tMatrix.evaluateSwitchZ(request.switchName, request.srcHostname) === false ); break; case 'applyDiffToPermanentMatrix': // aka "persist" if ( µm.pMatrix.applyDiff(request.diff, µm.tMatrix) ) { µm.saveMatrix(); } break; case 'applyDiffToTemporaryMatrix': // aka "revert" µm.tMatrix.applyDiff(request.diff, µm.pMatrix); break; case 'revertTemporaryMatrix': µm.tMatrix.assign(µm.pMatrix); break; default: return vAPI.messaging.UNHANDLED; } callback(response); }; vAPI.messaging.listen({ name: 'popup.js', listener: onMessage, privileged: true, }); // <<<<< end of local scope } /******************************************************************************/ /******************************************************************************/ // Channel: // contentscript // unprivileged { // >>>>> start of local scope const µm = µMatrix; /******************************************************************************/ const foundInlineCode = function(tabId, pageStore, details, type) { if ( pageStore === null ) { return; } const srcHn = pageStore.pageHostname; const docOrigin = µm.URI.originFromURI(details.documentURI); const desHn = vAPI.hostnameFromURI(docOrigin); let blocked = details.blocked; if ( blocked === undefined ) { blocked = µm.mustBlock(srcHn, desHn, type); } const mapTo = { css: 'style', script: 'script' }; // https://github.com/gorhill/httpswitchboard/issues/333 // Look-up here whether inline scripting is blocked for the frame. const desURL = `${docOrigin}/{inline_${mapTo[type]}}`; pageStore.recordRequest(type, desURL, blocked); if ( µm.logger.enabled ) { µm.filteringContext.duplicate() .fromTabId(tabId) .setRealm('network') .setURL(desURL) .setType(type) .setFilter(blocked) .toLogger(); } }; /******************************************************************************/ const contentScriptLocalStorageHandler = function(tabId, originURL) { const tabContext = µm.tabContextManager.lookup(tabId); if ( tabContext === null ) { return; } const srcHn = tabContext.rootHostname; const desHn = vAPI.hostnameFromURI(originURL); const blocked = µm.mustBlock(srcHn, desHn, 'cookie'); const pageStore = µm.pageStoreFromTabId(tabId); if ( pageStore !== null ) { const desURL = `${originURL}/{localStorage}`; pageStore.recordRequest('cookie', desURL, blocked); if ( µm.logger.enabled ) { µm.filteringContext.duplicate() .fromTabId(tabId) .setRealm('network') .setURL(desURL) .setType('cookie') .setFilter(blocked) .toLogger(); } } const removeStorage = blocked && µm.userSettings.deleteLocalStorage; if ( removeStorage ) { µm.localStorageRemovedCounter++; } return removeStorage; }; /******************************************************************************/ // Evaluate many URLs against the matrix. const lookupBlockedCollapsibles = function(tabId, requests) { if ( placeholdersReadTime < µm.rawSettingsWriteTime ) { placeholders = undefined; } if ( placeholders === undefined ) { placeholders = { frame: µm.rawSettings.framePlaceholder, image: µm.rawSettings.imagePlaceholder }; if ( placeholders.frame ) { placeholders.frameDocument = µm.rawSettings.framePlaceholderDocument.replace( '{{bg}}', µm.rawSettings.framePlaceholderBackground !== 'default' ? µm.rawSettings.framePlaceholderBackground : µm.rawSettings.placeholderBackground ); } if ( placeholders.image ) { placeholders.imageBorder = µm.rawSettings.imagePlaceholderBorder !== 'default' ? µm.rawSettings.imagePlaceholderBorder : µm.rawSettings.placeholderBorder; placeholders.imageBackground = µm.rawSettings.imagePlaceholderBackground !== 'default' ? µm.rawSettings.imagePlaceholderBackground : µm.rawSettings.placeholderBackground; } placeholdersReadTime = Date.now(); } var response = { blockedResources: [], hash: requests.hash, id: requests.id, placeholders: placeholders }; var tabContext = µm.tabContextManager.lookup(tabId); if ( tabContext === null ) { return response; } var pageStore = µm.pageStoreFromTabId(tabId); if ( pageStore !== null ) { pageStore.lookupBlockedCollapsibles(requests, response); } return response; }; var placeholders, placeholdersReadTime = 0; /******************************************************************************/ const onMessage = function(request, sender, callback) { // Async switch ( request.what ) { default: break; } let tabId = sender && sender.tab ? sender.tab.id || 0 : 0, tabContext = µm.tabContextManager.lookup(tabId), srcHn = tabContext && tabContext.rootHostname, pageStore = µm.pageStoreFromTabId(tabId); // Sync let response; switch ( request.what ) { case 'contentScriptHasLocalStorage': response = contentScriptLocalStorageHandler(tabId, request.originURL); break; case 'lookupBlockedCollapsibles': response = lookupBlockedCollapsibles(tabId, request); break; case 'mustRenderNoscriptTags?': if ( tabContext === null ) { break; } response = µm.tMatrix.mustBlock(srcHn, srcHn, 'script') && µm.tMatrix.evaluateSwitchZ('noscript-spoof', srcHn); if ( pageStore !== null ) { pageStore.hasNoscriptTags = true; } break; case 'securityPolicyViolation': if ( request.directive === 'worker-src' ) { let desURL = request.blockedURI; let desHn = µm.URI.hostnameFromURI(desURL); if ( desHn === '' ) { desURL = request.documentURI; desHn = µm.URI.hostnameFromURI(desURL); } if ( pageStore !== null ) { pageStore.hasWebWorkers = true; pageStore.recordRequest('script', desURL, request.blocked); } if ( tabContext !== null && µm.logger.enabled ) { µm.filteringContext.duplicate() .fromTabId(tabId) .setRealm('network') .setURL(desURL) .setType('worker') .setFilter(request.blocked) .toLogger(); } } else if ( request.directive === 'script-src' ) { foundInlineCode(tabId, pageStore, request, 'script'); } else if ( request.directive === 'style-src' ) { foundInlineCode(tabId, pageStore, request, 'css'); } break; case 'shutdown?': if ( tabContext !== null ) { response = µm.tMatrix.evaluateSwitchZ('matrix-off', srcHn); } break; default: return vAPI.messaging.UNHANDLED; } callback(response); }; vAPI.messaging.listen({ name: 'contentscript.js', listener: onMessage, }); // <<<<< end of local scope } /******************************************************************************/ /******************************************************************************/ // Channel: // cloudWidget // privileged { // >>>>> start of local scope const µm = µMatrix; const onMessage = function(request, sender, callback) { // Async switch ( request.what ) { case 'cloudGetOptions': vAPI.cloud.getOptions(function(options) { options.enabled = µm.userSettings.cloudStorageEnabled === true; callback(options); }); return; case 'cloudSetOptions': vAPI.cloud.setOptions(request.options, callback); return; case 'cloudPull': return vAPI.cloud.pull(request.datakey, callback); case 'cloudPush': return vAPI.cloud.push(request.datakey, request.data, callback); default: break; } // Sync var response; switch ( request.what ) { // For when cloud storage is disabled. case 'cloudPull': // fallthrough case 'cloudPush': break; default: return vAPI.messaging.UNHANDLED; } callback(response); }; vAPI.messaging.listen({ name: 'cloud-ui.js', listener: onMessage, privileged: true, }); // <<<<< end of local scope } /******************************************************************************/ /******************************************************************************/ // Channel: // dashboard // privileged { // >>>>> start of local scope const µm = µMatrix; const modifyRuleset = function(details) { let ruleset = details.permanent ? µm.pMatrix : µm.tMatrix, modifiedTime = ruleset.modifiedTime; let toRemove = new Set(details.toRemove.trim().split(/\s*[\n\r]+\s*/)); for ( let rule of toRemove ) { ruleset.removeFromLine(rule); } let toAdd = new Set(details.toAdd.trim().split(/\s*[\n\r]+\s*/)); for ( let rule of toAdd ) { ruleset.addFromLine(rule); } if ( details.permanent && ruleset.modifiedTime !== modifiedTime ) { µm.saveMatrix(); } }; const getAssets = function() { return Promise.all([ µm.getAvailableHostsFiles(), µm.getAvailableRecipeFiles(), µm.assets.metadata(), ]).then(results => { return { autoUpdate: µm.userSettings.autoUpdate, blockedHostnameCount: µm.ubiquitousBlacklistRef.addedCount, hosts: Array.from(results[0]), recipes: Array.from(results[1]), userRecipes: µm.userSettings.userRecipes, cache: results[2], contributor: µm.rawSettings.contributorMode }; }); }; const restoreUserData = async function(userData) { await Promise.all([ µMatrix.cacheStorage.clear(), vAPI.storage.clear(), ]); const promises = [ vAPI.storage.set(userData.settings) ]; const bin = { userMatrix: userData.rules }; if ( userData.hostsFiles instanceof Object ) { bin.liveHostsFiles = userData.hostsFiles; } promises.push(vAPI.storage.set(bin)); if ( userData.rawSettings instanceof Object ) { promises.push(µMatrix.saveRawSettings(userData.rawSettings)); } await Promise.all(promises); vAPI.app.restart(); }; const resetUserData = async function() { await Promise.all([ µMatrix.cacheStorage.clear(), vAPI.storage.clear(), ]); vAPI.app.restart(); }; /******************************************************************************/ const onMessage = function(request, sender, callback) { // Async switch ( request.what ) { case 'getAssets': getAssets().then(response => { callback(response); }); return; case 'getSomeStats': µm.getBytesInUse().then(bytesInUse => { callback({ version: vAPI.app.version, storageUsed: bytesInUse, }); }); return; default: break; } // Sync let response; switch ( request.what ) { case 'getAllUserData': response = { app: vAPI.app.name, version: vAPI.app.version, when: Date.now(), settings: µm.userSettings, rules: µm.pMatrix.toArray().sort(), rawSettings: µm.rawSettings }; break; case 'modifyRuleset': modifyRuleset(request); /* falls through */ case 'getRuleset': response = { temporaryRules: µm.tMatrix.toArray(), permanentRules: µm.pMatrix.toArray() }; break; case 'purgeCache': µm.assets.purge(request.assetKey); µm.assets.remove('compiled/' + request.assetKey); break; case 'purgeAllCaches': if ( request.hard ) { µm.assets.remove(/./); } else { µm.assets.purge(/./, 'public_suffix_list.dat'); } break; case 'resetAllUserData': resetUserData(); break; case 'restoreAllUserData': restoreUserData(request.userData); break; default: return vAPI.messaging.UNHANDLED; } callback(response); }; vAPI.messaging.listen({ name: 'dashboard', listener: onMessage, privileged: true, }); // <<<<< end of local scope } /******************************************************************************/ /******************************************************************************/ // Channel: // loggerUI // privileged { // >>>>> start of local scope /******************************************************************************/ const µm = µMatrix; const extensionOriginURL = vAPI.getURL(''); const getLoggerData = async function(details, activeTabId) { const response = { activeTabId, colorBlind: µm.userSettings.colorBlindFriendly, entries: µm.logger.readAll(details.ownerId), pageStoresToken: µm.pageStoresToken }; if ( µm.pageStoresToken !== details.pageStoresToken ) { const pageStores = new Map(); for ( const [ tabId, pageStore ] of µm.pageStores ) { if ( pageStore.rawURL.startsWith(extensionOriginURL) ) { continue; } let title = pageStore.title; if ( title === '' ) { title = pageStore.rawURL; } pageStores.set(tabId, title); } response.pageStores = Array.from(pageStores); } if ( activeTabId ) { const pageStore = µm.pageStoreFromTabId(activeTabId); if ( pageStore === null || pageStore.rawURL.startsWith(extensionOriginURL) ) { response.activeTabId = undefined; } } if ( details.popupLoggerBoxChanged && vAPI.windows instanceof Object ) { const tabs = await vAPI.tabs.query({ url: vAPI.getURL('/logger-ui.html?popup=1') }); if ( tabs.length !== 0 ) { const win = await vAPI.windows.get(tabs[0].windowId); if ( win ) { vAPI.localStorage.setItem('popupLoggerBox', JSON.stringify({ left: win.left, top: win.top, width: win.width, height: win.height, })); } } } return response; }; /******************************************************************************/ const onMessage = function(request, sender, callback) { // Async switch ( request.what ) { case 'readAll': if ( µm.logger.ownerId !== undefined && µm.logger.ownerId !== request.ownerId ) { return callback({ unavailable: true }); } vAPI.tabs.getCurrent().then(tab => { return getLoggerData(request, tab && tab.id); }).then(response => { callback(response); }); return; default: break; } // Sync let response; switch ( request.what ) { case 'getPublicSuffixListData': response = publicSuffixList.toSelfie(); break; case 'getRuleEditorOptions': response = { colorBlindFriendly: µm.userSettings.colorBlindFriendly, popupScopeLevel: µm.userSettings.popupScopeLevel }; break; case 'releaseView': if ( request.ownerId === µm.logger.ownerId ) { µm.logger.ownerId = undefined; } break; default: return vAPI.messaging.UNHANDLED; } callback(response); }; vAPI.messaging.listen({ name: 'loggerUI', listener: onMessage, privileged: true, }); // <<<<< end of local scope } /******************************************************************************/ /******************************************************************************/