/** * State Manager * Belongs to LocalCDN (since 2020-02-26) * (Origin: Decentraleyes) * * @author Thomas Rientjes * @since 2017-03-10 * * @author nobody * @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 */ let stateManager = {}; /** * Public Methods */ stateManager.registerInjection = function (tabIdentifier, injection, url) { let injectionIdentifier, registeredTab, injectionCount, missingCount, blockedCount, initiatorDomain, htmlFilterIsActive; if (injection['result'] !== false) { injectionIdentifier = injection.source + injection.path; registeredTab = stateManager.tabs[tabIdentifier]; registeredTab.injections[injectionIdentifier] = injection; } initiatorDomain = helpers.extractDomainFromUrl(url, true) || Address.EXAMPLE; htmlFilterIsActive = manipulateDOM.checkHtmlFilterEnabled(initiatorDomain); if (registeredTab !== undefined) { injectionCount = Object.keys(registeredTab.injections).length || 0; missingCount = registeredTab.missing || 0; blockedCount = registeredTab.blocked || 0; } else { return; } if (injectionCount > 0) { chrome.browserAction.setTitle({ 'tabId': tabIdentifier, 'title': `LocalCDN (${injectionCount})` }); } if (stateManager.showIconBadge === true) { if ((missingCount > 0 || blockedCount > 0) && stateManager.changeBadgeColorMissingResources) { wrappers.setBadgeColoring(tabIdentifier, BadgeSettingMissingResource.TYPE); } else if (htmlFilterIsActive) { wrappers.setBadgeColoring(tabIdentifier, BadgeSettingHTMLFilter.TYPE); } else { wrappers.setBadgeColoring(tabIdentifier, BadgeSetting.TYPE); } wrappers.setBadgeText(tabIdentifier, (injectionCount + missingCount + blockedCount)); } if (isNaN(storageManager.amountInjected)) { storageManager.type.get(Setting.AMOUNT_INJECTED, function (items) { storageManager.amountInjected = items.amountInjected; storageManager.type.set({ [Setting.AMOUNT_INJECTED]: ++storageManager.amountInjected }); }); } else { chrome.storage.local.set({ [Setting.AMOUNT_INJECTED]: ++storageManager.amountInjected }); } if (stateManager.internalStatistics) { stats.setStats(injection); } }; stateManager.addDomainToAllowlist = function (domain) { return new Promise((resolve) => { let allowlistedDomains = requestAnalyzer.allowlistedDomains; allowlistedDomains[domain] = true; storageManager.type.set({allowlistedDomains}, resolve); }); }; stateManager.removeDomainFromAllowlist = function (domain) { return new Promise((resolve) => { let allowlistedDomains; allowlistedDomains = requestAnalyzer.allowlistedDomains; if (allowlistedDomains[domain]) { delete allowlistedDomains[domain]; } else { for (const key in allowlistedDomains) { if (key.startsWith('*.') && domain.endsWith(key.substring(2))) { delete allowlistedDomains[key]; break; } } } storageManager.type.set({allowlistedDomains}, resolve); }); }; stateManager.addDomainToManipulateDOMlist = function (domain) { return new Promise((resolve) => { let domainsManipulateDOM = requestAnalyzer.domainsManipulateDOM; domainsManipulateDOM[domain] = true; storageManager.type.set({domainsManipulateDOM}, resolve); }); }; stateManager.removeDomainFromManipulateDOMlist = function (domain) { return new Promise((resolve) => { let domainsManipulateDOM = requestAnalyzer.domainsManipulateDOM; delete domainsManipulateDOM[domain]; storageManager.type.set({domainsManipulateDOM}, resolve); }); }; stateManager.addDomainToGoogleFontsList = function (domain) { return new Promise((resolve) => { let allowedDomainsGoogleFonts = interceptor.allowedDomainsGoogleFonts; allowedDomainsGoogleFonts[domain] = true; storageManager.type.set({allowedDomainsGoogleFonts}, resolve); }); }; stateManager.removeDomainFromGoogleFontsList = function (domain) { return new Promise((resolve) => { let allowedDomainsGoogleFonts = interceptor.allowedDomainsGoogleFonts; delete allowedDomainsGoogleFonts[domain]; storageManager.type.set({allowedDomainsGoogleFonts}, resolve); }); }; /** * Private Methods */ stateManager._createTab = function (tab) { let tabIdentifier, requestFilters; tabIdentifier = tab.id; stateManager.tabs[tabIdentifier] = { 'injections': {}, 'missing': 0, 'blocked': 0 }; requestFilters = { 'tabId': tabIdentifier, 'urls': stateManager.validHosts }; chrome.webRequest.onBeforeRequest.addListener(function (requestDetails) { 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, domainIsAllowlisted, frameIdentifier, tabIdentifier; tabDomain = helpers.extractDomainFromUrl(details.url, true); domainIsAllowlisted = stateManager._domainIsListed(tabDomain); frameIdentifier = details.frameId; tabIdentifier = details.tabId; if (frameIdentifier !== 0 || tabIdentifier === -1) { return; } chrome.browserAction.setTitle({ 'tabId': tabIdentifier, 'title': 'LocalCDN (0)' }); if (domainIsAllowlisted) { 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.tabs[tabIdentifier].missing = 0; stateManager.tabs[tabIdentifier].blocked = 0; } }; stateManager._handleStorageChanged = function (changes) { 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); }); } } else if (Setting.STRIP_METADATA in changes) { requestSanitizer.disable(); if (changes.stripMetadata.newValue !== false) { requestSanitizer.enable(); } } else if (Setting.NEGATE_HTML_FILTER_LIST in changes) { stateManager.getInvertOption = changes.negateHtmlFilterList.newValue; } else if (Setting.SELECTED_ICON in changes) { stateManager.selectedIcon = changes.selectedIcon.newValue; } else if (Setting.INTERNAL_STATISTICS in changes) { stateManager.internalStatistics = changes.internalStatistics.newValue; } else if (Setting.INTERNAL_STATISTICS_DATA in changes) { stats.data = changes.internalStatisticsData.newValue; } else if (Setting.HIDE_DONATION_BUTTON in changes) { stateManager.hideDonationButton = changes.hideDonationButton.newValue; } else if (Setting.CHANGE_BADGE_COLOR_MISSING_RESOURCES in changes) { stateManager.changeBadgeColorMissingResources = changes.changeBadgeColorMissingResources.newValue; } else if (Setting.LOGGING in changes) { stateManager.logging = changes.enableLogging.newValue; } else if (Setting.BADGE_DEFAULT_TEXT_COLOR in changes) { wrappers.badgeDefaultTextColor = changes.badgeDefaultTextColor.newValue; } else if (Setting.BADGE_DEFAULT_BACKGROUND_COLOR in changes) { wrappers.badgeDefaultBackgroundColor = changes.badgeDefaultBackgroundColor.newValue; } else if (Setting.BADGE_HTML_FILTER_TEXT_COLOR in changes) { wrappers.badgeHTMLfilterTextColor = changes.badgeHTMLfilterTextColor.newValue; } else if (Setting.BADGE_HTML_FILTER_BACKGROUND_COLOR in changes) { wrappers.badgeHTMLFilterBackgroundColor = changes.badgeHTMLFilterBackgroundColor.newValue; } }; stateManager._clearBadgeText = function (tabIdentifier) { wrappers.setBadgeText(tabIdentifier, ''); }; stateManager._removeIconBadgeFromTab = function (tab) { stateManager._clearBadgeText(tab.id); }; stateManager._domainIsListed = function (domain, listname) { if (domain !== null) { let allowlistRecord, isAllowlisted; if (listname === 'manipulate-dom') { allowlistRecord = helpers.checkAllowlisted(domain, requestAnalyzer.domainsManipulateDOM); isAllowlisted = Boolean(allowlistRecord); } else { allowlistRecord = helpers.checkAllowlisted(domain, requestAnalyzer.allowlistedDomains); isAllowlisted = Boolean(allowlistRecord); } return isAllowlisted; } return false; }; stateManager._setIconDisabled = function (tabIdentifier) { wrappers.setIcon({ 'path': stateManager.selectedIcon || 'Default', 'tabId': tabIdentifier }, 'Disabled'); }; /** * Initializations */ stateManager.requests = {}; stateManager.tabs = {}; stateManager.getInvertOption = true; stateManager.validHosts = []; stateManager.selectedIcon = 'Default'; stateManager.internalStatistics = false; stateManager.hideDonationButton = false; stateManager.changeBadgeColorMissingResources = false; stateManager.logging = false; for (let mapping in mappings.cdn) { let supportedHost = Address.ANY_PROTOCOL + mapping + Address.ANY_PATH; stateManager.validHosts.push(supportedHost); } chrome.tabs.query({}, function (tabs) { tabs.forEach(stateManager._createTab); }); storageManager.type.get([ Setting.SHOW_ICON_BADGE, Setting.NEGATE_HTML_FILTER_LIST, Setting.SELECTED_ICON, Setting.INTERNAL_STATISTICS, Setting.HIDE_DONATION_BUTTON, Setting.CHANGE_BADGE_COLOR_MISSING_RESOURCES, Setting.LOGGING, Setting.AMOUNT_INJECTED ], function (items) { if (items.showIconBadge === undefined) { items.showIconBadge = true; } if (items.selectedIcon === undefined) { stateManager.selectedIcon = 'Default'; } if (items.negateHtmlFilterList === undefined) { stateManager.getInvertOption = true; } else { stateManager.getInvertOption = items.negateHtmlFilterList; } stateManager.showIconBadge = items.showIconBadge; stateManager.selectedIcon = items.selectedIcon; stateManager.internalStatistics = items.internalStatistics; stateManager.hideDonationButton = items.hideDonationButton; stateManager.changeBadgeColorMissingResources = items.changeBadgeColorMissingResources; stateManager.logging = items.enableLogging; stateManager.amountInjected = items.amountInjected; }); chrome.storage.local.get([Setting.INTERNAL_STATISTICS], function (items) { stateManager.internalStatistics = items.internalStatistics; }); /** * Event Handlers */ chrome.tabs.onCreated.addListener(stateManager._createTab); chrome.tabs.onRemoved.addListener(stateManager._removeTab); chrome.webRequest.onBeforeRequest.addListener(function (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, requestDetails.originUrl ); delete stateManager.requests[requestDetails.requestId]; } }, {'urls': [Address.ANY]}); chrome.storage.onChanged.addListener(stateManager._handleStorageChanged);