diff --git a/core/interceptor.js b/core/interceptor.js index f4e2d41407c2d9da6ac9f3a81e5a0809dc6d9f58..2fc65eab3aea26929594879bc8659127d452629f 100644 --- a/core/interceptor.js +++ b/core/interceptor.js @@ -29,28 +29,11 @@ const HTTP_EXPRESSION = /^http?:\/\//; * Public Methods */ -interceptor.register = function () { +interceptor.handleRequest = function (requestDetails, tabIdentifier, tabUrl) { - chrome.tabs.onUpdated.addListener(function (tabIdentifier, changeInformation, tabDetails) { + let validCandidate, targetDetails, targetPath, amountInjected; - if (changeInformation.status === 'loading') { - - chrome.webRequest.onBeforeRequest.addListener(function (requestDetails) { - return interceptor._handleRequest(requestDetails, tabIdentifier, tabDetails); - }, { 'urls': ['*://*/*'], 'tabId': tabIdentifier }, ['blocking']); - } - }); -}; - -/** - * Private Methods - */ - -interceptor._handleRequest = function (requestDetails, tabIdentifier, tabDetails) { - - let validCandidate, targetPath; - - validCandidate = requestAnalyzer.isValidCandidate(requestDetails, tabDetails); + validCandidate = requestAnalyzer.isValidCandidate(requestDetails, tabUrl); if (!validCandidate) { @@ -59,7 +42,8 @@ interceptor._handleRequest = function (requestDetails, tabIdentifier, tabDetails }; } - targetPath = requestAnalyzer.getLocalTarget(requestDetails); + targetDetails = requestAnalyzer.getLocalTarget(requestDetails); + targetPath = targetDetails.path; if (!targetPath) { return interceptor._handleMissingCandidate(requestDetails.url); @@ -69,15 +53,10 @@ interceptor._handleRequest = function (requestDetails, tabIdentifier, tabDetails return interceptor._handleMissingCandidate(requestDetails.url); } - chrome.storage.local.get('amountInjected', function (items) { + stateManager.registerInjection(tabIdentifier, targetDetails); - let amountInjected; - - amountInjected = items.amountInjected || 0; - - chrome.storage.local.set({ - 'amountInjected': (parseInt(amountInjected) + 1) - }); + chrome.storage.local.set({ + 'amountInjected': ++interceptor.amountInjected }); return { @@ -85,6 +64,10 @@ interceptor._handleRequest = function (requestDetails, tabIdentifier, tabDetails }; }; +/** + * Private Methods + */ + interceptor._handleMissingCandidate = function (requestUrl) { if (interceptor.blockMissing === true) { @@ -110,22 +93,28 @@ interceptor._handleMissingCandidate = function (requestUrl) { } }; -interceptor._applyBlockMissingPreference = function () { +interceptor._handleStorageChanged = function (changes) { - chrome.storage.local.get('blockMissing', function (items) { - interceptor.blockMissing = items.blockMissing || false; - }); + if ('blockMissing' in changes) { + interceptor.blockMissing = changes.blockMissing.newValue; + } }; /** * Initializations */ +interceptor.amountInjected = 0; interceptor.blockMissing = false; -interceptor._applyBlockMissingPreference(); + +chrome.storage.local.get(['amountInjected', 'blockMissing'], function (items) { + + interceptor.amountInjected = items.amountInjected || 0; + interceptor.blockMissing = items.blockMissing || false; +}); /** * Event Handlers */ -chrome.storage.onChanged.addListener(interceptor._applyBlockMissingPreference); +chrome.storage.onChanged.addListener(interceptor._handleStorageChanged); diff --git a/core/main.js b/core/main.js index 1effe601a35a8c435612d23c04a635bdb5e79ebb..863d03e32e9f37d859b028b970d2f8b0eb1c8449 100644 --- a/core/main.js +++ b/core/main.js @@ -17,4 +17,8 @@ * Initializations */ -interceptor.register(); +chrome.privacy.network.networkPredictionEnabled.set({'value': false}); + +chrome.browserAction.setBadgeBackgroundColor({ + 'color': [74, 130, 108, 255] +}); diff --git a/core/request-analyzer.js b/core/request-analyzer.js index 182472d6ff1604fda1ddffcee8402102818546c7..a00d37a161869a85713a1b5ca66a84d5d4c5202d 100644 --- a/core/request-analyzer.js +++ b/core/request-analyzer.js @@ -79,7 +79,7 @@ requestAnalyzer.getLocalTarget = function (requestDetails) { } // Return either the local target's path or false. - return requestAnalyzer._findLocalTarget(resourceMappings, basePath, destinationPath); + return requestAnalyzer._findLocalTarget(resourceMappings, basePath, destinationHost, destinationPath); }; /** @@ -98,7 +98,7 @@ requestAnalyzer._matchBasePath = function (hostMappings, channelPath) { return false; }; -requestAnalyzer._findLocalTarget = function (resourceMappings, basePath, channelPath) { +requestAnalyzer._findLocalTarget = function (resourceMappings, basePath, channelHost, channelPath) { var resourcePath, versionNumber, resourcePattern; @@ -114,10 +114,14 @@ requestAnalyzer._findLocalTarget = function (resourceMappings, basePath, channel let targetPath, localPath; targetPath = resourceMappings[resourceMold].path; - return targetPath.replace(VERSION_PLACEHOLDER, versionNumber); + targetPath = targetPath.replace(VERSION_PLACEHOLDER, versionNumber); // Prepare and return a local target. - return localPath; + return { + source: channelHost, + version: versionNumber[0], + path: targetPath + }; } } diff --git a/core/state-manager.js b/core/state-manager.js new file mode 100644 index 0000000000000000000000000000000000000000..5d9db0356d6e0e9d15bd373d34e23bb246dff3f9 --- /dev/null +++ b/core/state-manager.js @@ -0,0 +1,104 @@ +/** + * State Manager + * Belongs to Decentraleyes. + * + * @author Thomas Rientjes + * @since 2017-03-10 + * @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.setBadgeText({ + tabId: tabIdentifier, + text: injectionCount.toString() + }); + + } else { + + chrome.browserAction.setBadgeText({ + tabId: tabIdentifier, + text: '' + }); + } +}; + +/** + * Private Methods + */ + +stateManager._createTab = function (tab) { + + let tabIdentifier = tab.id; + + stateManager.tabs[tabIdentifier] = { + 'injections': {} + }; + + chrome.webRequest.onBeforeRequest.addListener(function (requestDetails) { + return interceptor.handleRequest(requestDetails, tabIdentifier, tab); + }, { 'urls': ['*://*/*'], 'tabId': tabIdentifier }, ['blocking']); +}; + +stateManager._removeTab = function (tabIdentifier) { + delete stateManager.tabs[tabIdentifier]; +}; + +stateManager._updateTab = function (details) { + + let tabIdentifier = details.tabId; + + if (tabIdentifier !== -1) { + + if (stateManager.tabs[tabIdentifier]) { + stateManager.tabs[tabIdentifier].injections = {}; + } + } +}; + +/** + * Initializations + */ + +stateManager.tabs = {}; + +chrome.tabs.query({}, function (tabs) { + tabs.forEach(stateManager._createTab); +}); + +/** + * Event Handlers + */ + +chrome.tabs.onCreated.addListener(stateManager._createTab); +chrome.tabs.onRemoved.addListener(stateManager._removeTab); + +chrome.webRequest.onBeforeRequest.addListener(stateManager._updateTab, { + urls: ['<all_urls>'], types: ['main_frame'] +}); diff --git a/manifest.json b/manifest.json index fb690018de03f29c5c1abe7b38808acff02ee969..c4185f468eff4654b1b0050f6adf2c7a21119319 100644 --- a/manifest.json +++ b/manifest.json @@ -17,9 +17,10 @@ "permissions": [ "*://*/*", + "privacy", "storage", - "webRequest", "tabs", + "webRequest", "webRequestBlocking" ], diff --git a/pages/background/background.html b/pages/background/background.html index b30e9f794d62f809c7e0cccc553915f5feeca770..c87530d5ff6d2636c1559c61fbc1314c673bc7b2 100644 --- a/pages/background/background.html +++ b/pages/background/background.html @@ -12,6 +12,7 @@ <script src="../../core/files.js"></script> <script src="../../core/resources.js"></script> <script src="../../core/mappings.js"></script> + <script src="../../core/state-manager.js"></script> <script src="../../core/request-analyzer.js"></script> <script src="../../core/interceptor.js"></script> <script src="../../core/main.js"></script> diff --git a/pages/popup/popup.css b/pages/popup/popup.css index 67740c8ab5e4c71e4ce65b92f7e28431b0574646..207b1bca4654ce519b542d7f9338cded530cf41a 100644 --- a/pages/popup/popup.css +++ b/pages/popup/popup.css @@ -1,16 +1,18 @@ body { background-color: #f0f0f0; color: #555; + cursor: default; font-family: Noto Sans, Arial, sans-serif !important; font-size: 75%; margin: 0; padding: 0; + user-select: none; width: 350px; } h1 { font-size: 36px; - margin: 12px 0 0 0; + margin: 0; text-align: center; } @@ -23,14 +25,74 @@ h1 { .description { color: #777; font-style: italic; - margin-bottom: 16px; + margin-bottom: 18px; text-align: center; } .popup-content { + padding: 0; +} + +.list { + border-bottom: 1px solid #d8d8d8; + margin: 0; + padding: 10px 8px; +} + +.list-item { + background-color: #f7f7f7; + border-bottom: none !important; + border: 1px solid #e4e4e4; + color: #737373; + font-weight: 600; + list-style: none; + margin: 0; padding: 10px; } +.sub-list { + align-items: center; + background-color: #ececec; + border-bottom: none !important; + border: 1px solid #e0e0e0; + box-shadow: inset 0px 2px 10px #e2e2e2; + list-style: none; + padding-left: 8px; + padding: 0; +} + +.sub-list:last-child { + border-bottom: 1px solid #e0e0e0 !important; +} + +.sub-list-item { + border-bottom: 1px solid #e0e0e0; + color: #737373; + font-weight: bold; + padding: 10px; +} + +.sub-list-item:last-child { + border-bottom: none; +} + +.side-note { + color: #a5a5a5; + font-style: italic; + font-weight: normal; +} + +.badge { + background-color: #6bb798; + border-radius: 10px; + color: #fff; + font-family: monospace; + font-size: 13px; + font-weight: bold; + margin-right: 8px; + padding: 3px 15px; +} + .button-panel { padding: 6px; text-align: right; @@ -41,11 +103,11 @@ h1 { } .text-link { - color: #adadad; + color: #bdbdbd; float: left; font-size: 13px; padding-left: 4px; - padding-top: 5px; + padding-top: 2px; text-decoration: none; } @@ -54,10 +116,15 @@ h1 { text-decoration: underline; } +#injection-counter { + padding-top: 14px; +} + #extension-options-overlay-header { align-items: center; border-bottom: solid lightgray 1px; display: flex; + padding: 4px 0; position: relative; } diff --git a/pages/popup/popup.html b/pages/popup/popup.html index f552c493d247a9a388b0f59c6f3195742be9794c..97b84b5ba652bbd120bf98ec3af28aa7a8cced66 100644 --- a/pages/popup/popup.html +++ b/pages/popup/popup.html @@ -2,34 +2,37 @@ <html> - <head> +<head> - <title>Decentraleyes Popup</title> + <title>Decentraleyes Popup</title> - <link rel="stylesheet" type="text/css" href="popup.css"> + <link rel="stylesheet" type="text/css" href="popup.css"> - </head> +</head> - <body> +<body> - <script src="popup.js"></script> + <script src="popup.js"></script> - <div id="extension-options-overlay-header"> - <img id="extension-options-overlay-icon" src="icon.png"> - <div id="extension-options-overlay-title">Decentraleyes</div> - </div> + <div id="extension-options-overlay-header"> + <img id="extension-options-overlay-icon" src="icon.png"> + <div id="extension-options-overlay-title">Decentraleyes</div> + </div> - <section class="popup-content"> - <h1 id="injection-counter"></h1> - <div class="title" data-i18n-content="amountInjectedTitle"></div> - <div class="description" data-i18n-content="amountInjectedDescription"></div> - </section> + <section id="popup-content" class="popup-content"> - <section class="button-panel"> - <a href="https://decentraleyes.org/test" target="_blank" class="text-link">decentraleyes.org/test</a> - <button id="options-button" class="btn-sm">Options</button> - </section> + <h1 id="injection-counter">3</h1> - </body> + <div class="title" data-i18n-content="amountInjectedTitle"></div> + <div class="description" data-i18n-content="amountInjectedDescription"></div> + + </section> + + <section class="button-panel"> + <a href="https://decentraleyes.org/test" target="_blank" class="text-link">decentraleyes.org/test</a> + <button id="options-button" class="btn-sm">Options</button> + </section> + +</body> </html> diff --git a/pages/popup/popup.js b/pages/popup/popup.js index 2d03a62cf30a26f7d3dd22ec0cde2b37d0c805dd..649999c27792eafd97175c0a9d086091821c0304 100644 --- a/pages/popup/popup.js +++ b/pages/popup/popup.js @@ -33,6 +33,180 @@ document.addEventListener('DOMContentLoaded', function () { let amountInjected = items.amountInjected || 0; document.getElementById('injection-counter').innerHTML = amountInjected; + + chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { + + chrome.runtime.getBackgroundPage(function (backgroundPage) { + + let injections, injectionOverview; + + injections = backgroundPage.stateManager.tabs[tabs[0].id].injections; + injectionOverview = {}; + + for (let injection in injections) { + + let injectionSource, libraryName; + + injection = injections[injection]; + injectionSource = injection.source; + + injectionOverview[injectionSource] = injectionOverview[injectionSource] || []; + + injectionOverview[injectionSource].push({ + 'path': injection.path, + 'version': injection.version, + 'source': injection.source + }); + } + + let listElement = document.createElement('ul'); + listElement.setAttribute('class', 'list'); + + for (let injectionSource in injectionOverview) { + + let cdn, listItemElement, badgeElement, badgeTextNode, cdnName, cdnNameTextNode, subListElement; + + cdn = injectionOverview[injectionSource]; + + listItemElement = document.createElement('li'); + listItemElement.setAttribute('class', 'list-item'); + + badgeElement = document.createElement('span'); + badgeElement.setAttribute('class', 'badge'); + + badgeTextNode = document.createTextNode(cdn.length); + badgeElement.appendChild(badgeTextNode); + + switch (injectionSource) { + + case 'ajax.googleapis.com': + cdnName = 'Google Hosted Libraries'; + break; + case 'ajax.aspnetcdn.com': + cdnName = 'Microsoft Ajax CDN'; + break; + case 'ajax.microsoft.com': + cdnName = 'Microsoft Ajax CDN [Deprecated]'; + break; + case 'cdnjs.cloudflare.com': + cdnName = 'CDNJS (Cloudflare)'; + break; + case 'code.jquery.com': + cdnName = 'jQuery CDN (MaxCDN)'; + break; + case 'cdn.jsdelivr.net': + cdnName = 'jsDelivr (MaxCDN)'; + break; + case 'yastatic.net': + cdnName = 'Yandex CDN'; + break; + case 'yandex.st': + cdnName = 'Yandex CDN [Deprecated]'; + break; + case 'libs.baidu.com': + cdnName = 'Baidu CDN'; + break; + case 'lib.sinaapp.com': + cdnName = 'Sina Public Resources'; + break; + case 'upcdn.b0.upaiyun.com': + cdnName = 'UpYun Library'; + break; + } + + cdnNameTextNode = document.createTextNode(cdnName); + + listItemElement.appendChild(badgeElement); + listItemElement.appendChild(cdnNameTextNode); + + listElement.appendChild(listItemElement); + + subListElement = document.createElement('ul'); + subListElement.setAttribute('class', 'sub-list'); + + listElement.appendChild(subListElement); + + cdn.forEach(function (injection) { + + let subListItemElement, resourcePathDetails, resourceFilename, resourceName, + resourceNameTextNode, sideNoteElement, sideNoteTextNode; + + subListItemElement = document.createElement('li'); + subListItemElement.setAttribute('class', 'sub-list-item'); + + resourcePathDetails = injection.path.split('/'); + resourceFilename = resourcePathDetails[resourcePathDetails.length - 1]; + + switch (resourceFilename) { + + case 'angular.min.js.dec': + resourceName = 'AngularJS'; + break; + case 'backbone-min.js.dec': + resourceName = 'Backbone.js'; + break; + case 'dojo.js.dec': + resourceName = 'Dojo'; + break; + case 'ember.min.js.dec': + resourceName = 'Ember.js'; + break; + case 'ext-core.js.dec': + resourceName = 'Ext Core'; + break; + case 'jquery.min.js.dec': + resourceName = 'jQuery'; + break; + case 'jquery-ui.min.js.dec': + resourceName = 'jQuery UI'; + break; + case 'modernizr.min.js.dec': + resourceName = 'Modernizr'; + break; + case 'mootools-yui-compressed.js.dec': + resourceName = 'MooTools'; + break; + case 'prototype.js.dec': + resourceName = 'Prototype'; + break; + case 'scriptaculous.js.dec': + resourceName = 'Scriptaculous'; + break; + case 'swfobject.js.dec': + resourceName = 'SWFObject'; + break; + case 'underscore-min.js.dec': + resourceName = 'Underscore.js'; + break; + case 'webfont.js.dec': + resourceName = 'Web Font Loader'; + break; + } + + resourceNameTextNode = document.createTextNode('- ' + resourceName); + subListItemElement.appendChild(resourceNameTextNode); + + sideNoteElement = document.createElement('span'); + sideNoteElement.setAttribute('class', 'side-note'); + + sideNoteTextNode = document.createTextNode(' v' + injection.version); + + sideNoteElement.appendChild(sideNoteTextNode); + subListItemElement.appendChild(sideNoteElement); + + subListElement.appendChild(subListItemElement); + }); + } + + if (Object.keys(injectionOverview).length > 0) { + + let popupContentElement = document.getElementById('popup-content'); + let injectionCounterElement = document.getElementById('injection-counter'); + + popupContentElement.insertBefore(listElement, injectionCounterElement); + } + }); + }); }); document.getElementById('options-button').addEventListener('click', function () {