1
0
Fork 0
mirror of https://github.com/gorhill/uMatrix.git synced 2024-05-04 12:23:35 +12:00
uMatrix/src/js/messaging.js
Raymond Hill 9b292304d3
Bring uMatrix up to date
Notably:
- Import logger improvements from uBO
- Import CNAME uncloaking from uBO
- Import more improvements from uBO
- Make use of modern JS features

This should un-stall further development of uMatrix.
2019-12-20 12:24:18 -05:00

1133 lines
33 KiB
JavaScript

/*******************************************************************************
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
}
/******************************************************************************/
/******************************************************************************/