LocalCDN-Firefox-Chrome-Brave/core/state-manager.js

390 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* State Manager
* Belongs to Decentraleyes.
*
* @author Thomas Rientjes
* @since 2017-03-10
*
* @author nobody42
* @since 2020-02-26
*
* @license MPL 2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
'use strict';
/**
* State Manager
*/
var stateManager = {};
/**
* Public Methods
*/
stateManager.registerInjection = function (tabIdentifier, injection) {
let injectionIdentifier, registeredTab, injectionCount;
injectionIdentifier = injection.source + injection.path + injection.version;
registeredTab = stateManager.tabs[tabIdentifier];
registeredTab.injections[injectionIdentifier] = injection;
injectionCount = Object.keys(registeredTab.injections).length || 0;
if (injectionCount > 0) {
chrome.browserAction.setTitle({
'tabId': tabIdentifier,
'title': `LocalCDN (${injectionCount})`
});
if (stateManager.showIconBadge === true) {
wrappers.setBadgeText({
'tabId': tabIdentifier,
'text': injectionCount.toString()
});
}
}
if (isNaN(interceptor.amountInjected)) {
chrome.storage.local.get(Setting.AMOUNT_INJECTED, function (items) {
interceptor.amountInjected = items.amountInjected;
chrome.storage.local.set({
[Setting.AMOUNT_INJECTED]: ++interceptor.amountInjected
});
});
} else {
chrome.storage.local.set({
[Setting.AMOUNT_INJECTED]: ++interceptor.amountInjected
});
}
};
stateManager.setEnvironment = function (environment) {
if (environment === Environment.STABLE) {
// Strike a balance between coverage and website stability.
files.active = files.stable;
} else if (environment === Environment.STAGING) {
// Improve coverage at the expense of website stability.
files.active = Object.assign({}, files.stable, files.staging);
}
};
stateManager.updateEnvironment = function (preferredEnvironment) {
return new Promise((resolve) => {
if (preferredEnvironment === Environment.STABLE) {
let requiredItems = [Setting.BLOCK_MISSING, Setting.ENFORCE_STAGING];
chrome.storage.local.get(requiredItems, function (items) {
if (items.blockMissing === true || items.enforceStaging === true) {
stateManager.setEnvironment(Environment.STAGING);
} else {
stateManager.setEnvironment(Environment.STABLE);
}
resolve();
});
} else if (preferredEnvironment === Environment.STAGING) {
stateManager.setEnvironment(Environment.STAGING);
resolve();
}
});
};
stateManager.addDomainToWhitelist = function (domain) {
return new Promise((resolve) => {
let whitelistedDomains = requestAnalyzer.whitelistedDomains;
whitelistedDomains[domain] = true;
chrome.storage.local.set({whitelistedDomains}, resolve);
});
};
stateManager.removeDomainFromWhitelist = function (domain) {
return new Promise((resolve) => {
let whitelistedDomains = requestAnalyzer.whitelistedDomains;
delete whitelistedDomains[domain];
chrome.storage.local.set({whitelistedDomains}, resolve);
});
};
/**
* Private Methods
*/
stateManager._createTab = function (tab) {
let tabIdentifier, requestFilters;
tabIdentifier = tab.id;
stateManager.tabs[tabIdentifier] = {
'injections': {}
};
requestFilters = {
'tabId': tabIdentifier,
'urls': stateManager.validHosts
};
chrome.webRequest.onBeforeRequest.addListener(function (requestDetails) {
let tab = stateManager.tabs[tabIdentifier].details || {};
return interceptor.handleRequest(requestDetails, tabIdentifier, tab);
}, requestFilters, [WebRequest.BLOCKING]);
};
stateManager._removeTab = function (tabIdentifier) {
delete stateManager.tabs[tabIdentifier];
};
stateManager._updateTab = function (details) {
let tabDomain, domainIsWhitelisted, frameIdentifier, tabIdentifier;
tabDomain = helpers.extractDomainFromUrl(details.url, true);
domainIsWhitelisted = stateManager._domainIsWhitelisted(tabDomain);
frameIdentifier = details.frameId;
tabIdentifier = details.tabId;
if (frameIdentifier !== 0 || tabIdentifier === -1) {
return;
}
chrome.browserAction.setTitle({
'tabId': tabIdentifier,
'title': 'LocalCDN (0)'
});
if (domainIsWhitelisted) {
stateManager._setIconDisabled(tabIdentifier);
chrome.browserAction.setTitle({
'tabId': tabIdentifier,
'title': 'LocalCDN ()'
});
}
if (stateManager.showIconBadge === true) {
stateManager._clearBadgeText(tabIdentifier);
}
if (stateManager.tabs[tabIdentifier]) {
stateManager.tabs[tabIdentifier].injections = {};
}
};
stateManager._handleStorageChanged = function (changes) {
if (Setting.BLOCK_MISSING in changes) {
if (changes.blockMissing.newValue === true) {
stateManager.updateEnvironment(Environment.STAGING);
} else {
stateManager.updateEnvironment(Environment.STABLE);
}
}
if (Setting.ENFORCE_STAGING in changes) {
if (changes.enforceStaging.newValue === true) {
stateManager.updateEnvironment(Environment.STAGING);
} else {
stateManager.updateEnvironment(Environment.STABLE);
}
}
if (Setting.SHOW_ICON_BADGE in changes) {
stateManager.showIconBadge = changes.showIconBadge.newValue;
if (changes.showIconBadge.newValue !== true) {
chrome.tabs.query({}, function (tabs) {
tabs.forEach(stateManager._removeIconBadgeFromTab);
});
}
}
if (Setting.STRIP_METADATA in changes) {
requestSanitizer.disable();
if (changes.stripMetadata.newValue !== false) {
requestSanitizer.enable();
}
}
};
stateManager._clearBadgeText = function (tabIdentifier) {
wrappers.setBadgeText({
'tabId': tabIdentifier,
'text': ''
});
};
stateManager._removeIconBadgeFromTab = function (tab) {
stateManager._clearBadgeText(tab.id);
};
stateManager._domainIsWhitelisted = function (domain) {
if (domain !== null) {
let whitelistRecord, isWhitelisted;
whitelistRecord = requestAnalyzer.whitelistedDomains[domain];
isWhitelisted = Boolean(whitelistRecord);
return isWhitelisted;
}
return false;
};
stateManager._setIconDisabled = function (tabIdentifier) {
wrappers.setIcon({
'path': stateManager.disabledIconPath,
'tabId': tabIdentifier
});
};
/**
* Initializations
*/
stateManager.requests = {};
stateManager.tabs = {};
stateManager.disabledIconPath = {
'16': chrome.runtime.getURL('icons/action/icon16-disabled.png'),
'18': chrome.runtime.getURL('icons/action/icon18-disabled.png'),
'19': chrome.runtime.getURL('icons/action/icon19-disabled.png'),
'32': chrome.runtime.getURL('icons/action/icon32-disabled.png'),
'36': chrome.runtime.getURL('icons/action/icon36-disabled.png'),
'38': chrome.runtime.getURL('icons/action/icon38-disabled.png'),
'64': chrome.runtime.getURL('icons/action/icon64-disabled.png')
};
stateManager.validHosts = [];
for (let mapping in mappings) {
let supportedHost = Address.ANY_PROTOCOL + mapping + Address.ANY_PATH;
stateManager.validHosts.push(supportedHost);
}
chrome.tabs.query({}, function (tabs) {
tabs.forEach(stateManager._createTab);
});
chrome.storage.local.get(Setting.SHOW_ICON_BADGE, function (items) {
if (items.showIconBadge === undefined) {
items.showIconBadge = true;
}
stateManager.showIconBadge = items.showIconBadge;
});
stateManager._removeCrossoriginAndIntegrityAttr = function (details) {
// by Jaap (https://gitlab.com/Jaaap)
let filter = chrome.webRequest.filterResponseData(details.requestId);
let decoder = new TextDecoder("utf-8"); //FIXME: get content-encoding from headers
let encoder = new TextEncoder();
filter.ondata = evt => {
//remove crossorigin and integrity attributes
//Note that this will not work if the crossorigin="anonymous" string is divided into two chunks, but we want to flush this data asap.
let str = decoder.decode(evt.data, {stream: true})
.replace(/<(link|script)[^>]+>/ig, m => m.replace(/\s+(integrity|crossorigin)(="[^"]*"|='[^']*'|=[^"'`=\s]+|)/ig, ""));
filter.write(encoder.encode(str));
}
filter.onstop = evt => {
let str = decoder.decode(); // end-of-stream
filter.write(encoder.encode(str));
filter.close();
}
}
/**
* Event Handlers
*/
chrome.tabs.onCreated.addListener(stateManager._createTab);
chrome.tabs.onRemoved.addListener(stateManager._removeTab);
chrome.webRequest.onBeforeRequest.addListener(function (requestDetails) {
stateManager._removeCrossoriginAndIntegrityAttr(requestDetails);
if (requestDetails.tabId !== -1 && stateManager.tabs[requestDetails.tabId]) {
stateManager.tabs[requestDetails.tabId].details = {
'url': requestDetails.url
};
}
}, {'types': [WebRequestType.MAIN_FRAME], 'urls': [Address.ANY]}, [WebRequest.BLOCKING]);
chrome.webNavigation.onCommitted.addListener(stateManager._updateTab, {
'url': [{'urlContains': ':'}]
});
chrome.webRequest.onErrorOccurred.addListener(function (requestDetails) {
if (stateManager.requests[requestDetails.requestId]) {
delete stateManager.requests[requestDetails.requestId];
}
}, {'urls': [Address.ANY]});
chrome.webRequest.onBeforeRedirect.addListener(function (requestDetails) {
let knownRequest = stateManager.requests[requestDetails.requestId];
if (knownRequest) {
stateManager.registerInjection(knownRequest.tabIdentifier, knownRequest.targetDetails);
delete stateManager.requests[requestDetails.requestId];
}
}, {'urls': [Address.ANY]});
chrome.storage.onChanged.addListener(stateManager._handleStorageChanged);