diff options
author | gorhill <rhill@raymondhill.net> | 2015-01-06 08:01:15 -0500 |
---|---|---|
committer | gorhill <rhill@raymondhill.net> | 2015-01-06 08:01:15 -0500 |
commit | 1597ce7fd91880b92f25b361167b3455d891a29a (patch) | |
tree | 63d907bb0673a1b2e3e3cb7f859b3f0781f16cb6 | |
parent | 81e035589b3be6f03fa87dd47f5cd2b6ed0d9bf1 (diff) | |
download | uBlock-1597ce7fd91880b92f25b361167b3455d891a29a.zip uBlock-1597ce7fd91880b92f25b361167b3455d891a29a.tar.gz uBlock-1597ce7fd91880b92f25b361167b3455d891a29a.tar.bz2 |
lot of work related to dynamic filtering + new net requests logger
-rw-r--r-- | platform/chromium/vapi-background.js | 9 | ||||
-rw-r--r-- | src/css/devtool-log.css | 68 | ||||
-rw-r--r-- | src/css/devtools.css | 49 | ||||
-rw-r--r-- | src/css/popup.css | 105 | ||||
-rw-r--r-- | src/css/stats.css | 86 | ||||
-rw-r--r-- | src/dashboard.html | 1 | ||||
-rw-r--r-- | src/devtool-log.html | 20 | ||||
-rw-r--r-- | src/devtools.html | 25 | ||||
-rw-r--r-- | src/js/background.js | 1 | ||||
-rw-r--r-- | src/js/cosmetic-filtering.js | 10 | ||||
-rw-r--r-- | src/js/devtool-log.js | 178 | ||||
-rw-r--r-- | src/js/devtools.js | 98 | ||||
-rw-r--r-- | src/js/dynamic-net-filtering.js | 53 | ||||
-rw-r--r-- | src/js/messaging.js | 125 | ||||
-rw-r--r-- | src/js/pagestore.js | 294 | ||||
-rw-r--r-- | src/js/popup.js | 159 | ||||
-rw-r--r-- | src/js/stats.js | 255 | ||||
-rw-r--r-- | src/js/storage.js | 4 | ||||
-rw-r--r-- | src/js/tab.js | 7 | ||||
-rw-r--r-- | src/js/traffic.js | 4 | ||||
-rw-r--r-- | src/js/ublock.js | 71 | ||||
-rw-r--r-- | src/popup.html | 45 | ||||
-rw-r--r-- | src/stats.html | 40 |
23 files changed, 990 insertions, 717 deletions
diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 6d4ff02..76b25bf 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -208,6 +208,15 @@ vAPI.tabs.remove = function(tabId) { /******************************************************************************/ +vAPI.tabs.reload = function(tabId, flags) { + if ( typeof tabId === 'string' ) { + tabId = parseInt(tabId, 10); + } + chrome.tabs.reload(tabId); +}; + +/******************************************************************************/ + vAPI.tabs.injectScript = function(tabId, details, callback) { var onScriptExecuted = function() { // https://code.google.com/p/chromium/issues/detail?id=410868#c8 diff --git a/src/css/devtool-log.css b/src/css/devtool-log.css new file mode 100644 index 0000000..d4a21e0 --- /dev/null +++ b/src/css/devtool-log.css @@ -0,0 +1,68 @@ +body { + border: 0; + box-sizing: border-box; + font: 11px monospace; + margin: 0; + overflow-x: hidden; + padding: 0; + white-space: nowrap; + width: 100%; + } +#toolbar { + padding: 8px 0; + position: fixed; + text-align: center; + top: 0; + width: 4em; + } +#toolbar .button { + background-color: white; + border: none; + cursor: pointer; + display: block; + font-size: large; + margin: 0; + padding: 0.5em 0; + } +#toolbar .button:hover { + background-color: #eee; + } +#content { + margin-left: 4em; + width: calc(100% - 4em); + } +#content table { + border: 0; + border-collapse: collapse; + width: 100%; + } +#content table tr.blocked { + background-color: rgba(192, 0, 0, 0.1) + } +#content table tr.allowed { + background-color: rgba(0, 160, 0, 0.1) + } +#content table tr td { + border: 1px solid #ccc; + hyphens: none; + padding: 3px; + vertical-align: top; + white-space: normal; + word-break: break-all; + word-wrap: break-word; + } +#content table tr td:nth-of-type(1) { + width: 15%; + } +#content table tr td:nth-of-type(3) { + border-right: none; + width: 75%; + } +#content table tr.blocked td:nth-of-type(3) b { + background-color: rgba(192, 0, 0, 0.2); + font-weight: normal; + } +#content table tr.allowed td:nth-of-type(3) b { + background-color: rgba(0, 160, 0, 0.2); + font-weight: normal; + }
\ No newline at end of file diff --git a/src/css/devtools.css b/src/css/devtools.css new file mode 100644 index 0000000..f3022e7 --- /dev/null +++ b/src/css/devtools.css @@ -0,0 +1,49 @@ +body { + font-size: 13px; + margin: 0; + overflow-y: hidden; + padding: 0; + } +#toolbar { + background-color: #eee; + border: none; + box-sizing: border-box; + height: 4em; + padding: 1em; + position: fixed; + top: 0; + width: 100%; + } +#toolbar > * { + display: inline-block; + vertical-align: middle; + } +#toolbar button { + background-color: transparent; + border: none; + cursor: pointer; + font-size: 2em; + margin: 0 0 0 1em; + vertical-align: middle; + } +#toolbar #refresh { + margin-left: 4px; + } +select { + padding: 2px 0; + font-size: 14px; + min-width: 20em; + max-width: 40em; + } +select option { + max-width: 40em; + } +#content { + border: 0; + box-sizing: border-box; + height: calc(100vh - 4em); + margin-top: 4em; + overflow-y: auto; + padding: 0; + width: 100%; + } diff --git a/src/css/popup.css b/src/css/popup.css index 1dc2251..cb0ac5e 100644 --- a/src/css/popup.css +++ b/src/css/popup.css @@ -28,22 +28,41 @@ a { font-weight: normal; margin-left: 1em; } -body > div { - background-color: transparent; +body[dir="ltr"] #panes { + direction: rtl; + } +body[dir="rtl"] #panes { + direction: ltr; + } +#panes > div { display: inline-block; position: relative; vertical-align: top; } -body > div:nth-of-type(1) { - direction: rtl; /* scroll bar to the left */ +body[dir="ltr"] #panes > div { + direction: ltr; + } +body[dir="rtl"] #panes > div { + direction: rtl; + } +#panes > div:nth-of-type(2) { overflow-y: hidden; overflow-x: hidden; + width: 0; + } +body[dir="ltr"] #panes > div:nth-of-type(2) { + direction: rtl; /* scroll bar to the left */ + } +body[dir="rtl"] #panes > div:nth-of-type(2) { + direction: ltr; /* scroll bar to the right */ } -body.dynamicFilteringEnabled > div:nth-of-type(1) { +#panes.dfEnabled > div:nth-of-type(2) { overflow-y: auto; + width: 320px; } -body > div:nth-of-type(2) { - padding: 4px 12px 0 5px; + +#panes > div:nth-of-type(1) { + padding: 4px 5px 0 5px; } p { margin: 16px 0; @@ -96,17 +115,14 @@ p { color: #444; } -#dynamicFilteringToggler { - pointer-events: none; - } -#dynamicFilteringToggler::before { +#dfToggler::before { color: gray; content: '+\202F'; cursor: pointer; - font-size: 13px; - pointer-events: auto; + font-size: 14px; + line-height: 14px; } -body.dynamicFilteringEnabled #dynamicFilteringToggler::before { +#panes.dfEnabled #dfToggler::before { content: '\2212\202F'; } @@ -142,24 +158,16 @@ body.dynamicFilteringEnabled #dynamicFilteringToggler::before { margin: 0; padding: 0; text-align: right; - width: 5px; - } -body.dynamicFilteringEnabled #dynamicFilteringContainer { - display: block; - width: auto; } #dynamicFilteringContainer > div { - background-color: transparent; + background-color: #e6e6e6; border: 0; + border-bottom: 1px solid white; direction: ltr; margin: 0; padding: 0; - width: 320px; } -body.dynamicFilteringEnabled #dynamicFilteringContainer > div { - background-color: #e6e6e6; - } -body.dynamicFilteringEnabled #dynamicFilteringContainer > div:hover { +#dynamicFilteringContainer > div:hover { background-color: #f0f0f0; } #dynamicFilteringContainer > div#privacyInfo { @@ -168,30 +176,21 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer > div:hover { padding: 4px 0; text-align: center; } -#dynamicFilteringContainer > div.isDomain { - margin-top: 2px; - } #dynamicFilteringContainer > div > span { background-color: transparent; border: none; - border-bottom: 1px solid white; box-sizing: border-box; - color: transparent; + color: #000; display: inline-block; height: 24px; line-height: 24px; - pointer-events: none; + overflow: hidden; position: relative; vertical-align: top; } -body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span { - color: #000; - overflow: hidden; - pointer-events: auto; - } #dynamicFilteringContainer > div > span:nth-of-type(1) { border-right: 1px solid white; - padding-right: 4px; + padding-right: 2px; text-overflow: ellipsis; width: 70%; } @@ -201,6 +200,7 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span { } #dynamicFilteringContainer > div > span:nth-of-type(3) { border-left: 1px solid white; + color: #444; cursor: pointer; text-align: center; width: 15%; @@ -208,9 +208,14 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span { #dynamicFilteringContainer > div.isDomain > span:nth-of-type(1) { font-weight: bold; } -#dynamicFilteringContainer > div > span:nth-of-type(3) { - color: #666; - pointer-events: auto; +#dynamicFilteringContainer > div.allowed > span:nth-of-type(1) { + background-color: rgba(0, 160, 0, 0.1); + } +#dynamicFilteringContainer > div.blocked > span:nth-of-type(1) { + background-color: rgba(192, 0, 0, 0.1); + } +#dynamicFilteringContainer > div.allowed.blocked > span:nth-of-type(1) { + background-color: rgba(192, 160, 0, 0.1); } #dynamicFilteringContainer > div > span.aRule { background-color: rgba(0, 160, 0, 0.3); @@ -221,17 +226,17 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span { #dynamicFilteringContainer > div > span.nRule { background-color: rgba(96, 96, 96, 0.3); } +#dynamicFilteringContainer > div > span.ownRule { + color: white; + } #dynamicFilteringContainer > div > span.aRule.ownRule { background-color: rgba(0, 160, 0, 1); - color: white; } #dynamicFilteringContainer > div > span.bRule.ownRule { background-color: rgba(192, 0, 0, 1); - color: white; } #dynamicFilteringContainer > div > span.nRule.ownRule { background-color: rgba(108, 108, 108, 1); - color: white; } #actionSelector { @@ -264,3 +269,17 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span { #dynamicFilteringContainer span.bRule #actionSelector > span:nth-of-type(3) { visibility: hidden; } +#hotspotTip { + background-color: #ffe; + border: 1px dotted #ddb; + border-radius: 5px; + height: 50vh; + opacity: 1; + padding: 4px; + position: fixed; + right: 10px; + text-align: center; + top: 25vh; + width: 20vw; + z-index: 100; + }
\ No newline at end of file diff --git a/src/css/stats.css b/src/css/stats.css deleted file mode 100644 index 23ae170..0000000 --- a/src/css/stats.css +++ /dev/null @@ -1,86 +0,0 @@ -div { - margin: 1em 0; - } -ul { - list-style-type: none; - } -#refresh { - margin: 0 0.5em 0 4px; - display: inline-block; - vertical-align: middle; - font-size: 2em; - cursor: pointer; - } -select { - padding: 2px 0; - font-size: 14px; - min-width: 20em; - max-width: 40em; - } -select option { - max-width: 40em; - } -#requests { - margin: 2em 0 0 0; - display: none; - } -#requests.logEnabled { - display: block; - } -#requests table { - margin: 1em 0; - border: 0; - border-collapse: collapse; - min-width: 600px; - } -#requests.empty table { - display: none; - } -tr td, tr th { - border: 1px solid #aaa; - padding: 4px 6px; - white-space: pre; - } -tr.domainHeader td { - font: 16px sans-serif; - } -tr.domainHeader td span { - margin-right: 0.5em; - font-size: 14px; - color: #aaa; - cursor: pointer; - } -tr.requestEntry { - font: 12px monospace; - } -tr.requestEntry td:nth-of-type(1) { - border: 0; - background-color: white; - width: 3em; - } -tr.requestEntry td:nth-of-type(2) { - text-align: right; - } -tr.requestEntry td:nth-of-type(3), -tr.requestEntry td:nth-of-type(4) { - direction: ltr; - } -tr.logBlocked { - background-color: #fff8f8; - } -tr.logBlocked ~ tr td:nth-of-type(3) b { - padding: 2px 0; - color: #000; - background-color: rgba(255,0,0,0.1); - } -tr.logAllowed { - background-color: #f8fff8 - } -tr.logAllowed ~ tr td:nth-of-type(3) b { - padding: 2px 0; - color: #000; - background-color: rgba(0,255,0,0.2); - } -tr.logMirrored { - background-color: #ffffbb !important; - }
\ No newline at end of file diff --git a/src/dashboard.html b/src/dashboard.html index 9a172dd..4c36f62 100644 --- a/src/dashboard.html +++ b/src/dashboard.html @@ -16,7 +16,6 @@ <a class="tabButton" href="#dyna-rules.html" data-i18n="rulesPageName"></a> <a class="tabButton" href="#whitelist.html" data-i18n="whitelistPageName"></a> <a class="tabButton" href="#settings.html" data-i18n="settingsPageName"></a> - <a class="tabButton" href="#stats.html" data-i18n="statsPageName"></a> <a class="tabButton" href="#about.html" data-i18n="aboutPageName"></a> </div> </div> diff --git a/src/devtool-log.html b/src/devtool-log.html new file mode 100644 index 0000000..c8ff572 --- /dev/null +++ b/src/devtool-log.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<link rel="stylesheet" type="text/css" href="css/common.css"> +<link rel="stylesheet" type="text/css" href="css/devtool-log.css"> +<title>µBlock log</title> +</head> +<body> +<div id="toolbar"> + <span id="reload" class="button fa"></span> + <span id="clear" class="button fa"></span> + </div><!-- DO NOT REMOVE --><div id="content"> + <table><tbody></tbody></table> + </div> +<script src="js/vapi-client.js"></script> +<script src="js/udom.js"></script> +<script src="js/devtool-log.js"></script> +</body> +</html> diff --git a/src/devtools.html b/src/devtools.html new file mode 100644 index 0000000..09721d1 --- /dev/null +++ b/src/devtools.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>µBlock — Statistics</title> +<link rel="stylesheet" type="text/css" href="css/common.css"> +<link rel="stylesheet" type="text/css" href="css/devtools.css"> +</head> + +<body> + +<div id="toolbar"> + <select id="pageSelector"></select> + <button id="refresh" class="fa" type="button"></button> +</div> +<iframe id="content"></iframe> + +<script src="js/vapi-common.js"></script> +<script src="js/vapi-client.js"></script> +<script src="js/udom.js"></script> +<script src="js/i18n.js"></script> +<script src="js/devtools.js"></script> + +</body> +</html> diff --git a/src/js/background.js b/src/js/background.js index c181616..4a98ab1 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -60,7 +60,6 @@ return { dynamicFilteringEnabled: false, experimentalEnabled: false, externalLists: defaultExternalLists, - logRequests: false, parseAllABPHideFilters: true, showIconBadge: true }, diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index 0a95b0f..e8452f2 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -997,8 +997,14 @@ FilterContainer.prototype.removeFromSelectorCache = function(targetHostname, typ if ( this.selectorCache.hasOwnProperty(hostname) === false ) { continue; } - if ( targetHostname !== '*' && hostname !== targetHostname ) { - continue; + if ( targetHostname !== '*' ) { + if ( hostname.slice(0 - targetHostname.length) !== targetHostname ) { + continue; + } + if ( hostname.length !== targetHostname.length && + hostname.charAt(0 - targetHostname.length - 1) !== '.' ) { + continue; + } } this.selectorCache[hostname].remove(type); } diff --git a/src/js/devtool-log.js b/src/js/devtool-log.js new file mode 100644 index 0000000..38bd8a3 --- /dev/null +++ b/src/js/devtool-log.js @@ -0,0 +1,178 @@ +/******************************************************************************* + + sessbench - a Chromium browser extension to benchmark browser session. + Copyright (C) 2013 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/sessbench + + TODO: cleanup/refactor +*/ + +/* global vAPI, uDom */ + +/******************************************************************************/ + +(function() { + +/******************************************************************************/ + +var messager = vAPI.messaging.channel('devtool-log.js'); + +var inspectedTabId = ''; +var doc = document; +var body = doc.body; +var tbody = doc.querySelector('#content tbody'); +var rowJunkyard = []; + +/******************************************************************************/ + +var renderURL = function(url, filter) { + if ( filter.charAt(0) !== 's' ) { + return url; + } + // make a regex out of the filter + var reText = filter.slice(3); + var pos = reText.indexOf('$'); + if ( pos > 0 ) { + reText = reText.slice(0, pos); + } + if ( reText === '*' ) { + reText = '\\*'; + } else { + reText = reText + .replace(/\./g, '\\.') + .replace(/\?/g, '\\?') + .replace('||', '') + .replace(/\^/g, '.') + .replace(/\*/g, '.*') + ; + } + var re = new RegExp(reText, 'gi'); + var matches = re.exec(url); + var renderedURL = url; + + if ( matches && matches[0].length ) { + renderedURL = url.slice(0, matches.index) + + '<b>' + + url.slice(matches.index, re.lastIndex) + + '</b>' + + url.slice(re.lastIndex); + } + + return renderedURL; +}; + +/******************************************************************************/ + +var createRow = function() { + var tr = rowJunkyard.pop(); + if ( tr ) { + tr.className = ''; + return tr; + } + tr = doc.createElement('tr'); + tr.appendChild(doc.createElement('td')); + tr.appendChild(doc.createElement('td')); + tr.appendChild(doc.createElement('td')); + return tr; +}; + +/******************************************************************************/ + +var renderLogEntry = function(entry) { + var tr = createRow(); + if ( entry.result.charAt(1) === 'b' ) { + tr.classList.add('blocked'); + } else if ( entry.result.charAt(1) === 'a' ) { + tr.classList.add('allowed'); + } + tr.cells[0].textContent = entry.result.slice(3); + tr.cells[1].textContent = entry.type; + tr.cells[2].innerHTML = renderURL(entry.url, entry.result); + tbody.insertBefore(tr, tbody.firstChild); +}; + +/******************************************************************************/ + +var renderLogBuffer = function(buffer) { + // Preserve scroll position + var height = tbody.offsetHeight; + + var n = buffer.length; + for ( var i = 0; i < n; i++ ) { + renderLogEntry(buffer[i]); + } + if ( body.scrollTop !== 0 ) { + body.scrollTop += tbody.offsetHeight - height; + } +}; + +/******************************************************************************/ + +var onBufferRead = function(buffer) { + if ( Array.isArray(buffer ) ) { + renderLogBuffer(buffer); + } + setTimeout(readLogBuffer, 1000); +}; + +/******************************************************************************/ + +// This can be called only once, at init time. After that, this will be called +// automatically. If called after init time, this will be messy, and this would +// require a bit more code to ensure no multi time out events. + +var readLogBuffer = function() { + messager.send({ what: 'readLogBuffer', tabId: inspectedTabId }, onBufferRead); +}; + +/******************************************************************************/ + +var clearBuffer = function() { + var rows = tbody.rows; + var row; + var i = rows.length; + while ( i-- ) { + row = rows[i]; + row.parentNode.removeChild(row); + rowJunkyard.push(row); + } +}; + +/******************************************************************************/ + +var reloadTab = function() { + messager.send({ what: 'reloadTab', tabId: inspectedTabId }); +}; + +/******************************************************************************/ + +uDom.onLoad(function() { + // Extract the tab id of the page we need to pull the log + var matches = window.location.search.match(/[\?&]tabId=([^&]+)/); + if ( matches && matches.length === 2 ) { + inspectedTabId = matches[1]; + } + + readLogBuffer(); + + uDom('#reload').on('click', reloadTab); + uDom('#clear').on('click', clearBuffer); +}); + +/******************************************************************************/ + +})(); diff --git a/src/js/devtools.js b/src/js/devtools.js new file mode 100644 index 0000000..809eae9 --- /dev/null +++ b/src/js/devtools.js @@ -0,0 +1,98 @@ +/******************************************************************************* + + µBlock - a Chromium browser extension to block requests. + Copyright (C) 2014 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/uBlock +*/ + +/* jshint bitwise: false */ +/* global vAPI, uDom */ + +/******************************************************************************/ + +(function() { + +'use strict'; + +/******************************************************************************/ + +var messager = vAPI.messaging.channel('stats.js'); + +/******************************************************************************/ + +var renderPageSelector = function(targetTabId) { + var selectedTabId = targetTabId || uDom('#pageSelector').val(); + var onTabReceived = function(tabId, tabTitle) { + uDom('#pageSelector').append('<option value="' + tabId + '">' + tabTitle); + if ( tabId.toString() === selectedTabId ) { + uDom('#pageSelector').val(tabId); + } + }; + var onDataReceived = function(pageDetails) { + uDom('#pageSelector option').remove(); + if ( pageDetails.hasOwnProperty(selectedTabId) === false ) { + selectedTabId = pageDetails[0]; + } + for ( var tabId in pageDetails ) { + if ( pageDetails.hasOwnProperty(tabId) ) { + onTabReceived(tabId, pageDetails[tabId]); + } + } + selectPage(); + }; + messager.send({ what: 'getPageDetails' }, onDataReceived); +}; + +/******************************************************************************/ + +var pageSelectorChanged = function() { + selectPage(); +}; + +/******************************************************************************/ + +var selectPage = function() { + var tabId = uDom('#pageSelector').val() || ''; + var inspector = uDom('#content'); + var currentSrc = inspector.attr('src'); + var targetSrc = 'devtool-log.html?tabId=' + tabId; + if ( targetSrc !== currentSrc ) { + inspector.attr('src', targetSrc); + } +}; + +/******************************************************************************/ + +uDom.onLoad(function() { + var tabId; + + // Extract the tab id of the page we need to pull the log + var matches = window.location.search.match(/[\?&]tabId=([^&]+)/); + if ( matches && matches.length === 2 ) { + tabId = matches[1]; + } + + renderPageSelector(tabId); + + uDom('#pageSelector').on('change', pageSelectorChanged); + uDom('#refresh').on('click', function() { renderPageSelector(); } ); +}); + +/******************************************************************************/ + +})(); + diff --git a/src/js/dynamic-net-filtering.js b/src/js/dynamic-net-filtering.js index 4f31696..635b91e 100644 --- a/src/js/dynamic-net-filtering.js +++ b/src/js/dynamic-net-filtering.js @@ -38,13 +38,25 @@ var Matrix = function() { /******************************************************************************/ +var supportedTypes = { + '*': true, +'inline-script': true, + 'script': true, + '1p-script': true, + '3p-script': true, + 'sub_frame': true, + '3p-frame': true, + 'image': true +}; + var typeBitOffsets = { '*': 0, 'inline-script': 2, '1p-script': 4, '3p-script': 6, '3p-frame': 8, - 'image': 10 + 'image': 10, + '3p-any': 12 }; var actionToNameMap = { @@ -192,6 +204,19 @@ Matrix.prototype.clearRegisters = function() { /******************************************************************************/ +var isFirstParty = function(srcHostname, desHostname) { + if ( desHostname.slice(0 - srcHostname.length) !== srcHostname ) { + return false; + } + // Be sure to not confuse 'example.com' with 'anotherexample.com' + if ( desHostname.lenght === srcHostname.lenght ) { + return true; + } + return desHostname.charAt(desHostname.length - srcHostname.length - 1) === '.'; +}; + +/******************************************************************************/ + Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) { var bitOffset = typeBitOffsets[type]; var s = srcHostname; @@ -217,32 +242,42 @@ Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) { /******************************************************************************/ Matrix.prototype.evaluateCellZY = function(srcHostname, desHostname, type) { - if ( typeBitOffsets.hasOwnProperty(type) === false ) { + this.r = 0; + + if ( supportedTypes.hasOwnProperty(type) === false ) { this.type = ''; - this.r = 0; return this; } - this.type = type; - // Specific-hostname specific-type cell + + this.type = '*'; + + // Specific-destination + any type this.y = desHostname; this.r = this.evaluateCellZ(srcHostname, desHostname, type); if ( this.r !== 0 ) { return this; } - var d = desHostname; for (;;) { d = toBroaderHostname(d); if ( d === '*' ) { break; } - // specific-hostname specific-type cell this.y = d; this.r = this.evaluateCellZ(srcHostname, d, type); if ( this.r !== 0 ) { return this; } } - // Any-hostname specific-type cells + // Any destination + specific-type this.y = '*'; + + if ( type === 'script' ) { + type = isFirstParty(srcHostname, desHostname) ? '1p-script' : '3p-script'; + } else if ( type === 'sub-frame' && isFirstParty(srcHostname, desHostname) === false ) { + type = '3p-frame'; + } + + this.type = type; this.r = this.evaluateCellZ(srcHostname, '*', type); + return this; }; @@ -447,7 +482,7 @@ Matrix.prototype.fromSelfie = function(selfie) { /******************************************************************************/ -return new Matrix; +return new Matrix(); /******************************************************************************/ diff --git a/src/js/messaging.js b/src/js/messaging.js index 53c1ab9..aa95a7e 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -71,6 +71,10 @@ var onMessage = function(request, sender, callback) { µb.reloadPresetBlacklists(request.switches, request.update); break; + case 'reloadTab': + vAPI.tabs.reload(request.tabId); + break; + case 'userSettings': response = µb.changeUserSettings(request.name, request.value); break; @@ -177,8 +181,7 @@ var getStats = function(tabId) { pageAllowedRequestCount: 0, netFilteringSwitch: false, cosmeticFilteringSwitch: false, - logRequests: µb.userSettings.logRequests, - dynamicFilteringEnabled: µb.userSettings.dynamicFilteringEnabled + dfEnabled: µb.userSettings.dynamicFilteringEnabled }; var pageStore = µb.pageStoreFromTabId(tabId); if ( pageStore ) { @@ -749,50 +752,30 @@ var µb = µBlock; /******************************************************************************/ -var getPageDetails = function(µb, tabId) { - var r = { - blockedRequests: [], - allowedRequests: [], - hash: '' - }; - var pageStore = µb.pageStores[tabId]; - if ( !pageStore ) { - return r; +var getPageDetails = function(callback) { + var out = {}; + var tabIds = Object.keys(µb.pageStores); + + var countdown = tabIds.length; + if ( countdown === 0 ) { + callback(out); + return; } - var prepareRequests = function(wantBlocked, hasher) { - var µburi = µb.URI; - var dict = pageStore.netFilteringCache.fetchAll(); - var r = []; - var details, hostname, domain; - for ( var url in dict ) { - if ( dict.hasOwnProperty(url) === false ) { - continue; - } - details = dict[url]; - if ( wantBlocked !== pageStore.boolFromResult(details.result) ) { - continue; - } - hasher.appendStr(url); - hasher.appendStr(details.result); - hostname = µburi.hostnameFromURI(url); - domain = µburi.domainFromHostname(hostname) || hostname; - r.push({ - url: url, - domain: domain, - reason: details.result, - type: details.type, - flags: details.flags - }); + + var onTabDetails = function(tab) { + if ( tab ) { + out[tab.id] = tab.title; + } + countdown -= 1; + if ( countdown === 0 ) { + callback(out); } - return r; }; - var hasher = new YaMD5(); - if ( µb.userSettings.logRequests ) { - r.blockedRequests = prepareRequests(true, hasher); - r.allowedRequests = prepareRequests(false, hasher); + + var i = countdown; + while ( i-- ) { + vAPI.tabs.get(tabIds[i], onTabDetails); } - r.hash = hasher.end(); - return r; }; /******************************************************************************/ @@ -800,8 +783,8 @@ var getPageDetails = function(µb, tabId) { var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { - case 'getTabForStats': - vAPI.tabs.get(request.tabId, callback); + case 'getPageDetails': + getPageDetails(callback); return; default: @@ -812,14 +795,6 @@ var onMessage = function(request, sender, callback) { var response; switch ( request.what ) { - case 'getPageSelectors': - response = Object.keys(µb.pageStores); - break; - - case 'getPageDetails': - response = getPageDetails(µb, request.tabId); - break; - default: return vAPI.messaging.UNHANDLED; } @@ -933,6 +908,52 @@ vAPI.messaging.listen('about.js', onMessage); })(); +/******************************************************************************/ +/******************************************************************************/ + +// devtool-log.js + +(function() { + +'use strict'; + +/******************************************************************************/ + +var µb = µBlock; + +/******************************************************************************/ + +var onMessage = function(request, sender, callback) { + // Async + switch ( request.what ) { + default: + break; + } + + // Sync + var response; + + switch ( request.what ) { + case 'readLogBuffer': + var pageStore = µb.pageStoreFromTabId(request.tabId); + if ( pageStore ) { + response = pageStore.logBuffer.readAll(); + } + break; + + default: + return vAPI.messaging.UNHANDLED; + } + + callback(response); +}; + +vAPI.messaging.listen('devtool-log.js', onMessage); + +/******************************************************************************/ + +})(); + // https://www.youtube.com/watch?v=3_WcygKJP1k /******************************************************************************/ diff --git a/src/js/pagestore.js b/src/js/pagestore.js index ad70f42..ec887cd 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -45,22 +45,194 @@ var µb = µBlock; /******************************************************************************/ /******************************************************************************/ +var LogEntry = function(details, result) { + this.init(details, result); +}; + +/******************************************************************************/ + +var logEntryFactory = function(details, result) { + var entry = logEntryJunkyard.pop(); + if ( entry ) { + return entry.init(details, result); + } + return new LogEntry(details, result); +}; + +var logEntryJunkyard = []; +var logEntryJunkyardMax = 100; + +/******************************************************************************/ + +LogEntry.prototype.init = function(details, result) { + this.tstamp = Date.now(); + this.url = details.requestURL; + this.domain = details.requestDomain; + this.hostname = details.requestHostname; + this.type = details.requestType; + this.result = result; + return this; +}; + +/******************************************************************************/ + +LogEntry.prototype.dispose = function() { + this.url = this.domain = this.hostname = this.type = this.result = ''; + if ( logEntryJunkyard.length < logEntryJunkyardMax ) { + logEntryJunkyard.push(this); + } +}; + +/******************************************************************************/ + +var LogBuffer = function() { + this.lastReadTime = 0; + this.size = 25; + this.buffer = null; + this.readPtr = 0; + this.writePtr = 0; +}; + +/******************************************************************************/ + +var logBufferFactory = function() { + return new LogBuffer(); +}; + +var liveLogBuffers = []; + +/******************************************************************************/ + +LogBuffer.prototype.dispose = function() { + if ( this.buffer === null ) { + return null; + } + var entry; + var i = this.buffer.length; + while ( i-- ) { + entry = this.buffer[i]; + if ( entry instanceof LogEntry ) { + entry.dispose(); + } + } + this.buffer = null; + return null; +}; + +/******************************************************************************/ + +LogBuffer.prototype.start = function() { + if ( this.buffer === null ) { + this.buffer = new Array(this.size); + this.readPtr = 0; + this.writePtr = 0; + liveLogBuffers.push(this); + } +}; + +/******************************************************************************/ + +LogBuffer.prototype.stop = function() { + this.dispose(); + this.buffer = null; + // The janitor will remove us from the live pool eventually. +}; + +/******************************************************************************/ + +LogBuffer.prototype.writeOne = function(details, result) { + if ( this.buffer === null ) { + return; + } + // Reusing log entry = less memory churning + var entry = this.buffer[this.writePtr]; + if ( entry instanceof LogEntry === false ) { + this.buffer[this.writePtr] = logEntryFactory(details, result); + } else { + entry.init(details, result); + } + this.writePtr += 1; + if ( this.writePtr === this.size ) { + this.writePtr = 0; + } + // Grow the buffer between 1.5x-2x the current size + if ( this.writePtr === this.readPtr ) { + var toMove = this.buffer.slice(0, this.writePtr); + var minSize = Math.ceil(this.size * 1.5); + this.size += this.writePtr; + if ( this.size < minSize ) { + this.buffer = this.buffer.concat(toMove, new Array(minSize - this.size)); + this.writePtr = this.size; + } else { + this.buffer = this.buffer.concat(toMove); + this.writePtr = 0; + } + this.size = this.buffer.length; + } +}; + +/******************************************************************************/ + +LogBuffer.prototype.readAll = function() { + var out; + if ( this.buffer === null ) { + this.start(); + out = []; + } else if ( this.readPtr < this.writePtr ) { + out = this.buffer.slice(this.readPtr, this.writePtr); + } else if ( this.writePtr < this.readPtr ) { + out = this.buffer.slice(this.readPtr).concat(this.buffer.slice(0, this.writePtr)); + } else { + out = []; + } + this.readPtr = this.writePtr; + this.lastReadTime = Date.now(); + return out; +}; + +/******************************************************************************/ + +var logBufferJanitor = function() { + var logBuffer; + var obsolete = Date.now() - logBufferObsoleteAfter; + var i = liveLogBuffers.length; + while ( i-- ) { + logBuffer = liveLogBuffers[i]; + if ( logBuffer.lastReadTime < obsolete ) { + logBuffer.stop(); + liveLogBuffers.splice(i, 1); + } + } + setTimeout(logBufferJanitor, logBufferJanitorPeriod); +}; + +// The janitor will look for stale log buffer every 2 minutes. +var logBufferJanitorPeriod = 2 * 60 * 1000; + +// After 30 seconds without being read, a buffer will be considered unused, and +// thus removed from memory. +var logBufferObsoleteAfter = 30 * 1000; + +setTimeout(logBufferJanitor, logBufferJanitorPeriod); + +/******************************************************************************/ +/******************************************************************************/ + // To mitigate memory churning var netFilteringResultCacheEntryJunkyard = []; var netFilteringResultCacheEntryJunkyardMax = 200; /******************************************************************************/ -var NetFilteringResultCacheEntry = function(result, type, flags) { - this.init(result, type, flags); +var NetFilteringResultCacheEntry = function(result, type) { + this.init(result, type); }; /******************************************************************************/ -NetFilteringResultCacheEntry.prototype.init = function(result, type, flags) { +NetFilteringResultCacheEntry.prototype.init = function(result, type) { this.result = result; this.type = type; - this.flags = flags; this.time = Date.now(); }; @@ -76,12 +248,12 @@ NetFilteringResultCacheEntry.prototype.dispose = function() { /******************************************************************************/ -NetFilteringResultCacheEntry.factory = function(result, type, flags) { +NetFilteringResultCacheEntry.factory = function(result, type) { var entry = netFilteringResultCacheEntryJunkyard.pop(); if ( entry === undefined ) { - entry = new NetFilteringResultCacheEntry(result, type, flags); + entry = new NetFilteringResultCacheEntry(result, type); } else { - entry.init(result, type, flags); + entry.init(result, type); } return entry; }; @@ -115,10 +287,11 @@ NetFilteringResultCache.factory = function() { /******************************************************************************/ NetFilteringResultCache.prototype.init = function() { - this.uname = 'NetFilteringResultCache:' + uidGenerator++; this.urls = {}; this.count = 0; this.shelfLife = 60 * 1000; + this.timer = null; + this.boundPruneAsyncCallback = this.pruneAsyncCallback.bind(this); }; /******************************************************************************/ @@ -130,10 +303,12 @@ NetFilteringResultCache.prototype.dispose = function() { } this.urls[key].dispose(); } - µBlock.asyncJobs.remove(this.uname); - this.uname = ''; this.urls = {}; this.count = 0; + if ( this.timer !== null ) { + clearTimeout(this.timer); + this.timer = null; + } if ( netFilteringCacheJunkyard.length < netFilteringCacheJunkyardMax ) { netFilteringCacheJunkyard.push(this); } @@ -142,16 +317,17 @@ NetFilteringResultCache.prototype.dispose = function() { /******************************************************************************/ -NetFilteringResultCache.prototype.add = function(url, result, type, flags) { +NetFilteringResultCache.prototype.add = function(context, result) { + var url = context.requestURL; + var type = context.requestType; var entry = this.urls[url]; if ( entry !== undefined ) { entry.result = result; entry.type = type; - entry.flags = flags; entry.time = Date.now(); return; } - this.urls[url] = NetFilteringResultCacheEntry.factory(result, type, flags); + this.urls[url] = NetFilteringResultCacheEntry.factory(result, type); if ( this.count === 0 ) { this.pruneAsync(); } @@ -197,13 +373,14 @@ NetFilteringResultCache.prototype.prune = function() { /******************************************************************************/ NetFilteringResultCache.prototype.pruneAsync = function() { - µBlock.asyncJobs.add( - this.uname, - null, - this.prune.bind(this), - this.shelfLife + 120000, - false - ); + if ( this.timer === null ) { + this.timer = setTimeout(this.boundPruneAsyncCallback, this.shelfLife * 2); + } +}; + +NetFilteringResultCache.prototype.pruneAsyncCallback = function() { + this.timer = null; + this.prune(); }; /******************************************************************************/ @@ -272,12 +449,6 @@ var pageStoreJunkyardMax = 10; /******************************************************************************/ -// Cache only what is worth it if logging is disabled -// http://jsperf.com/string-indexof-vs-object -var collapsibleRequestTypes = 'image sub_frame object'; - -/******************************************************************************/ - var PageStore = function(tabId, pageURL) { this.init(tabId, pageURL); }; @@ -318,10 +489,12 @@ PageStore.prototype.init = function(tabId, pageURL) { this.perLoadBlockedRequestCount = 0; this.perLoadAllowedRequestCount = 0; this.skipLocalMirroring = false; - this.netFilteringCache = NetFilteringResultCache.factory(); - if ( µb.userSettings.logRequests ) { - this.netFilteringCache.shelfLife = 30 * 60 * 1000; + + // Preserve old buffer if there is one already, it may be in use, and + // overwritting it would required another read to restart it. + if ( this.logBuffer instanceof LogBuffer === false ) { + this.logBuffer = logBufferFactory(); } return this; @@ -374,6 +547,7 @@ PageStore.prototype.dispose = function() { this.hostnameToCountMap = null; this.disposeFrameStores(); this.netFilteringCache = this.netFilteringCache.dispose(); + this.logBuffer = this.logBuffer.dispose(); if ( pageStoreJunkyard.length < pageStoreJunkyardMax ) { pageStoreJunkyard.push(this); } @@ -422,27 +596,46 @@ PageStore.prototype.getNetFilteringSwitch = function() { /******************************************************************************/ PageStore.prototype.filterRequest = function(context) { - var requestURL = context.requestURL; - if ( this.getNetFilteringSwitch() === false ) { - this.recordResult(context.requestType, requestURL, ''); + this.cacheResult(context, ''); return ''; } - var entry = this.netFilteringCache.lookup(requestURL); + var entry = this.netFilteringCache.lookup(context.requestURL); if ( entry !== undefined ) { - //console.debug('cache HIT: PageStore.filterRequest("%s")', requestURL); + //console.debug('cache HIT: PageStore.filterRequest("%s")', context.requestURL); return entry.result; } - var result = µb.filterRequest(context); + var result = ''; + + // Given that: + // - Dynamic filtering override static filtering + // - Evaluating dynamic filtering is much faster than static filtering + // We evaluate dynamic filtering first, and hopefully we can skip + // evaluation of static filtering. + var df = µb.dynamicNetFilteringEngine.clearRegisters(); + df.evaluateCellZY(context.rootHostname, context.requestHostname, context.requestType); + if ( df.mustBlockOrAllow() ) { + result = df.toFilterString(); + } + + // Static filtering never override dynamic filtering + if ( result === '' ) { + result = µb.staticNetFilteringEngine.matchString(context); + } + + //console.debug('cache MISS: PageStore.filterRequest("%s")', context.requestURL); + this.cacheResult(context, result); - //console.debug('cache MISS: PageStore.filterRequest("%s")', requestURL); - this.recordResult(context.requestType, requestURL, result); + // console.debug('[%s, %s] = "%s"', context.requestHostname, context.requestType, result); - // TODO: send this to a dev-panel tool - //console.debug('[%s, %s] = "%s"', context.requestHostname, context.requestType, result); + return result; +}; + +/******************************************************************************/ +PageStore.prototype.cacheResult = function(context, result) { var requestHostname = context.requestHostname; if ( this.hostnameToCountMap.hasOwnProperty(requestHostname) === false ) { this.hostnameToCountMap[requestHostname] = 0; @@ -453,25 +646,14 @@ PageStore.prototype.filterRequest = function(context) { } else /* if ( c === 'b' ) */ { this.hostnameToCountMap[requestHostname] += 0x00000001; } - return result; -}; - -/******************************************************************************/ - -PageStore.prototype.setRequestFlags = function(requestURL, targetBits, valueBits) { - var entry = this.netFilteringCache.lookup(requestURL); - if ( entry !== undefined ) { - entry.flags = (entry.flags & ~targetBits) | (valueBits & targetBits); + if ( collapsibleRequestTypes.indexOf(context.requestType) !== -1 ) { + this.netFilteringCache.add(context, result); } }; -/******************************************************************************/ - -PageStore.prototype.recordResult = function(requestType, requestURL, result) { - if ( collapsibleRequestTypes.indexOf(requestType) !== -1 || µb.userSettings.logRequests ) { - this.netFilteringCache.add(requestURL, result, requestType, 0); - } -}; +// Cache only what is worth it if logging is disabled +// http://jsperf.com/string-indexof-vs-object +var collapsibleRequestTypes = 'image sub_frame object'; /******************************************************************************/ @@ -493,7 +675,7 @@ PageStore.prototype.updateBadge = function() { var iconStr = ''; if ( µb.userSettings.showIconBadge && netFiltering && this.perLoadBlockedRequestCount ) { // Safari can't show formatted strings, only integers. - if (vAPI.safari) { + if ( vAPI.safari ) { iconStr = this.perLoadBlockedRequestCount; } else { diff --git a/src/js/popup.js b/src/js/popup.js index 91dab68..0d706bb 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -29,8 +29,9 @@ /******************************************************************************/ -var stats; -var dynaTypes = [ +var popupData; +var dfPaneBuilt = false; +var dfTypes = [ 'image', 'inline-script', '1p-script', @@ -48,7 +49,7 @@ var scopeToSrcHostnameMap = { var threePlus = '+++'; var threeMinus = '−−−'; var sixSpace = '\u2007\u2007\u2007\u2007\u2007\u2007'; -var dynaHotspots = null; +var dfHotspots = null; var hostnameToSortableTokenMap = {}; /******************************************************************************/ @@ -60,15 +61,15 @@ var messager = vAPI.messaging.channel('popup.js'); /******************************************************************************/ var cachePopupData = function(data) { - stats = {}; + popupData = {}; scopeToSrcHostnameMap['.'] = ''; hostnameToSortableTokenMap = {}; if ( typeof data !== 'object' ) { - return stats; + return popupData; } - stats = data; - scopeToSrcHostnameMap['.'] = stats.pageHostname || ''; - var hostnameDict = stats.hostnameDict; + popupData = data; + scopeToSrcHostnameMap['.'] = popupData.pageHostname || ''; + var hostnameDict = popupData.hostnameDict; if ( typeof hostnameDict === 'object' ) { var domain, prefix; for ( var hostname in hostnameDict ) { @@ -76,14 +77,14 @@ var cachePopupData = function(data) { continue; } domain = hostnameDict[hostname].domain; - if ( domain === stats.pageDomain ) { + if ( domain === popupData.pageDomain ) { domain = '\u0020'; } prefix = hostname.slice(0, 0 - domain.length); hostnameToSortableTokenMap[hostname] = domain + prefix.split('.').reverse().join('.'); } } - return stats; + return popupData; }; /******************************************************************************/ @@ -118,12 +119,15 @@ var addDynamicFilterRow = function(des) { row.descendants('[data-des]').attr('data-des', des); row.descendants('div > span:nth-of-type(1)').text(des); - var hnDetails = stats.hostnameDict[des] || {}; + var hnDetails = popupData.hostnameDict[des] || {}; var isDomain = des === hnDetails.domain; row.toggleClass('isDomain', isDomain); if ( hnDetails.allowCount !== 0 ) { touchedDomains[hnDetails.domain] = true; - row.addClass('wasTouched'); + row.addClass('allowed'); + } + if ( hnDetails.blockCount !== 0 ) { + row.addClass('blocked'); } row.appendTo('#dynamicFilteringContainer'); @@ -134,8 +138,8 @@ var addDynamicFilterRow = function(des) { // of the popup, and the left pane will have a scrollbar if ever its // height is larger than what is available. if ( popupHeight === undefined ) { - popupHeight = uDom('body > div:nth-of-type(2)').nodeAt(0).offsetHeight; - uDom('body > div:nth-of-type(1)').css('height', popupHeight + 'px'); + popupHeight = uDom('#panes > div:nth-of-type(1)').nodeAt(0).offsetHeight; + uDom('#panes > div:nth-of-type(2)').css('height', popupHeight + 'px'); } return row; }; @@ -169,10 +173,10 @@ var syncDynamicFilterCell = function(scope, des, type, result) { if ( scope !== '.' || type !== '*' ) { return; } - if ( stats.hostnameDict.hasOwnProperty(des) === false ) { + if ( popupData.hostnameDict.hasOwnProperty(des) === false ) { return; } - var hnDetails = stats.hostnameDict[des]; + var hnDetails = popupData.hostnameDict[des]; var aCount = hnDetails.allowCount; var bCount = hnDetails.blockCount; if ( aCount === 0 && bCount === 0 ) { @@ -192,9 +196,9 @@ var syncDynamicFilterCell = function(scope, des, type, result) { var syncAllDynamicFilters = function() { var hasRule = false; - var rules = stats.dynamicFilterRules; + var rules = popupData.dynamicFilterRules; var type, result; - var types = dynaTypes; + var types = dfTypes; var i = types.length; while ( i-- ) { type = types[i]; @@ -220,28 +224,30 @@ var syncAllDynamicFilters = function() { } uDom('#privacyInfo').text(vAPI.i18n('popupHitDomainCountPrompt').replace('{{count}}', Object.keys(touchedDomains).length)); + + if ( dfPaneBuilt !== true ) { + uDom('#dynamicFilteringContainer') + .on('click', 'span[data-src]', unsetDynamicFilterHandler) + .on('mouseenter', '[data-src]', mouseenterCellHandler) + .on('mouseleave', '[data-src]', mouseleaveCellHandler); + dfHotspots = uDom('#actionSelector') + .on('click', 'span', setDynamicFilterHandler) + .detach(); + dfPaneBuilt = true; + } }; /******************************************************************************/ -var renderPopup = function(details) { - if ( !cachePopupData(details) ) { - return; - } - - var hdr = uDom('#version'); - hdr.nodes[0].previousSibling.textContent = details.appName; - hdr.html(hdr.html() + 'v' + details.appVersion); +var renderPopup = function() { + uDom('#appname').text(popupData.appName); + uDom('#version').text(popupData.appVersion); - var isHTTP = /^https?:\/\/[0-9a-z]/.test(stats.pageURL); + var isHTTP = /^https?:\/\/[0-9a-z]/.test(popupData.pageURL); // Conditions for request log: // - `http` or `https` scheme - // - logging of requests enabled - uDom('#gotoLog').toggleClass( - 'enabled', - isHTTP && stats.logRequests - ); + uDom('#gotoLog').toggleClass('enabled', isHTTP); // Conditions for element picker: // - `http` or `https` scheme @@ -251,8 +257,8 @@ var renderPopup = function(details) { ); var or = vAPI.i18n('popupOr'); - var blocked = stats.pageBlockedRequestCount; - var total = stats.pageAllowedRequestCount + blocked; + var blocked = popupData.pageBlockedRequestCount; + var total = popupData.pageAllowedRequestCount + blocked; var html = []; if ( total === 0 ) { html.push('0'); @@ -268,8 +274,8 @@ var renderPopup = function(details) { } uDom('#page-blocked').html(html.join('')); - blocked = stats.globalBlockedRequestCount; - total = stats.globalAllowedRequestCount + blocked; + blocked = popupData.globalBlockedRequestCount; + total = popupData.globalAllowedRequestCount + blocked; html = []; if ( total === 0 ) { html.push('0'); @@ -284,27 +290,28 @@ var renderPopup = function(details) { ); } - //if ( stats.dynamicFilteringEnabled ) { + // Build dynamic filtering pane only if in use + if ( popupData.dfEnabled ) { syncAllDynamicFilters(); - //} + } uDom('#total-blocked').html(html.join('')); - uDom('#switch .fa').toggleClass('off', stats.pageURL === '' || !stats.netFilteringSwitch); - uDom('body').toggleClass('dynamicFilteringEnabled', stats.dynamicFilteringEnabled); + uDom('#switch .fa').toggleClass('off', popupData.pageURL === '' || !popupData.netFilteringSwitch); + uDom('#panes').toggleClass('dfEnabled', popupData.dfEnabled); }; /******************************************************************************/ var toggleNetFilteringSwitch = function(ev) { - if ( !stats || !stats.pageURL ) { + if ( !popupData || !popupData.pageURL ) { return; } messager.send({ what: 'toggleNetFiltering', - url: stats.pageURL, + url: popupData.pageURL, scope: ev.ctrlKey || ev.metaKey ? 'page' : '', state: !uDom(this).toggleClass('off').hasClass('off'), - tabId: stats.tabId + tabId: popupData.tabId }); }; @@ -323,11 +330,11 @@ var gotoDashboard = function() { /******************************************************************************/ -var gotoStats = function() { +var gotoDevTools = function() { messager.send({ what: 'gotoURL', details: { - url: 'dashboard.html?tab=stats&which=' + stats.tabId, + url: 'devtools.html?tabId=' + popupData.tabId, select: true, index: -1 } @@ -339,7 +346,7 @@ var gotoStats = function() { var gotoPick = function() { messager.send({ what: 'gotoPick', - tabId: stats.tabId + tabId: popupData.tabId }); window.open('','_self').close(); }; @@ -366,42 +373,42 @@ var gotoLink = function(ev) { /******************************************************************************/ var toggleDynamicFiltering = function(ev) { - var el = uDom('body'); - el.toggleClass('dynamicFilteringEnabled'); + var el = uDom('#panes'); + popupData.dfEnabled = !popupData.dfEnabled; messager.send({ what: 'userSettings', name: 'dynamicFilteringEnabled', - value: el.hasClass('dynamicFilteringEnabled') - }); + value: popupData.dfEnabled + }, renderPopup); }; /******************************************************************************/ var mouseenterCellHandler = function() { if ( uDom(this).hasClass('ownRule') === false ) { - dynaHotspots.appendTo(this); + dfHotspots.appendTo(this); } }; var mouseleaveCellHandler = function() { - dynaHotspots.detach(); + dfHotspots.detach(); }; /******************************************************************************/ var setDynamicFilter = function(src, des, type, action) { // This can happen on pages where uBlock does not work - if ( typeof stats.pageHostname !== 'string' || stats.pageHostname === '' ) { + if ( typeof popupData.pageHostname !== 'string' || popupData.pageHostname === '' ) { return; } - var onDynamicFilterChanged = function(details) { - cachePopupData(details); + var onDynamicFilterChanged = function(response) { + cachePopupData(response); syncAllDynamicFilters(); }; messager.send({ what: 'toggleDynamicFilter', - tabId: stats.tabId, - pageHostname: stats.pageHostname, + tabId: popupData.tabId, + pageHostname: popupData.pageHostname, srcHostname: src, desHostname: des, requestType: type, @@ -414,12 +421,12 @@ var setDynamicFilter = function(src, des, type, action) { var unsetDynamicFilterHandler = function() { var cell = uDom(this); setDynamicFilter( - cell.attr('data-src') === '/' ? '*' : stats.pageHostname, + cell.attr('data-src') === '/' ? '*' : popupData.pageHostname, cell.attr('data-des'), cell.attr('data-type'), 0 ); - dynaHotspots.appendTo(cell); + dfHotspots.appendTo(cell); }; /******************************************************************************/ @@ -440,39 +447,31 @@ var setDynamicFilterHandler = function() { action = 1; } setDynamicFilter( - cell.attr('data-src') === '/' ? '*' : stats.pageHostname, + cell.attr('data-src') === '/' ? '*' : popupData.pageHostname, cell.attr('data-des'), cell.attr('data-type'), action ); - dynaHotspots.detach(); + dfHotspots.detach(); }; /******************************************************************************/ -var installEventHandlers = function() { +// Make menu only when popup html is fully loaded + +uDom.onLoad(function() { + messager.send({ what: 'activeTabStats' }, function(response) { + if ( !cachePopupData(response) ) { + return; + } + renderPopup(); + }); uDom('h1,h2,h3,h4').on('click', gotoDashboard); uDom('#switch .fa').on('click', toggleNetFilteringSwitch); - uDom('#gotoLog').on('click', gotoStats); + uDom('#gotoLog').on('click', gotoDevTools); uDom('#gotoPick').on('click', gotoPick); uDom('a[href^=http]').on('click', gotoLink); - uDom('#dynamicFilteringToggler').on('click', toggleDynamicFiltering); - - uDom('#dynamicFilteringContainer').on('click', 'span[data-src]', unsetDynamicFilterHandler); - uDom('#dynamicFilteringContainer') - .on('mouseenter', '[data-src]', mouseenterCellHandler) - .on('mouseleave', '[data-src]', mouseleaveCellHandler); - dynaHotspots = uDom('#actionSelector'); - dynaHotspots.on('click', 'span', setDynamicFilterHandler).detach(); -}; - -/******************************************************************************/ - -// Make menu only when popup html is fully loaded - -uDom.onLoad(function() { - messager.send({ what: 'activeTabStats' }, renderPopup); - installEventHandlers(); + uDom('#dfToggler').on('click', toggleDynamicFiltering); }); /******************************************************************************/ diff --git a/src/js/stats.js b/src/js/stats.js deleted file mode 100644 index df5a924..0000000 --- a/src/js/stats.js +++ /dev/null @@ -1,255 +0,0 @@ -/******************************************************************************* - - µBlock - a Chromium browser extension to block requests. - Copyright (C) 2014 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/uBlock -*/ - -/* jshint bitwise: false */ -/* global uDom */ - -/******************************************************************************/ - -(function() { - -'use strict'; - -/******************************************************************************/ - -var messager = vAPI.messaging.channel('stats.js'); - -/******************************************************************************/ - -var logSettingChanged = function() { - messager.send({ - what: 'userSettings', - name: 'logRequests', - value: this.checked - }); - uDom('#requests').toggleClass('logEnabled', this.checked); - renderPageSelector(); -}; - -/******************************************************************************/ - -var cachedPageSelectors = {}; -var cachedPageHash = ''; - -var toPrettyTypeNames = { - 'stylesheet': 'css', - 'sub_frame': 'frame', - 'object': 'plugin', - 'xmlhttprequest': 'XHR' -}; - -/******************************************************************************/ - -var chunkify = function(s) { - var chunkSize = 50; - var chunks = []; - while ( s.length ) { - chunks.push(s.slice(0, chunkSize)); - s = s.slice(chunkSize); - } - return chunks; -}; - -/******************************************************************************/ - -var renderURL = function(url, filter) { - var chunkSize = 50; - // make a regex out of the filter - var reText = filter; - var pos = reText.indexOf('$'); - if ( pos > 0 ) { - reText = reText.slice(0, pos); - } - if ( reText.charAt(0) === 's' ) { - reText = reText.slice(3); - } - if ( reText === '*' ) { - reText = '\\*'; - } else { - reText = reText - .replace(/\./g, '\\.') - .replace(/\?/g, '\\?') - .replace('||', '') - .replace(/\^/g, '.') - .replace(/\*/g, '.*') - ; - } - var re = new RegExp(reText, 'gi'); - var matches = re.exec(url); - var renderedURL = chunkify(url); - - if ( matches && matches[0].length ) { - var index = (re.lastIndex / chunkSize) | 0; - var offset = re.lastIndex % chunkSize; - if ( index > 0 && offset === 0 ) { - offset = chunkSize; - index -= 1; - } - var segment = renderedURL[index]; - renderedURL[index] = segment.slice(0, offset) + '</b>' + segment.slice(offset); - - index = (matches.index / chunkSize) | 0; - offset = matches.index % chunkSize; - if ( index > 0 && offset === 0 ) { - offset = chunkSize; - index -= 1; - } - segment = renderedURL[index]; - renderedURL[index] = segment.slice(0, offset) + '<b>' + segment.slice(offset); - } - - return renderedURL.join('\n'); -}; - -/******************************************************************************/ - -var renderPageDetails = function(tabId) { - if ( !cachedPageSelectors[tabId] ) { - return; - } - - var onDataReceived = function(details) { - if ( details.hash === cachedPageHash ) { - return; - } - cachedPageHash = details.hash; - var renderRequests = function(requests, className) { - requests.sort(function(a, b) { - var r = a.domain.localeCompare(b.domain); - if ( r ) { return r; } - r = a.reason.localeCompare(b.reason); - if ( r ) { return r; } - r = a.type.localeCompare(b.type); - if ( r ) { return r; } - return a.url.localeCompare(b.url); - }); - var html = [], request; - html.push( - '<tr class="header ', className, '">', - '<td colspan="4"><h3>', - vAPI.i18n(className + (requests.length ? 'RequestsHeader' : 'RequestsEmpty')), - '</h3>' - ); - var currentDomain = ''; - for ( var i = 0; i < requests.length; i++ ) { - request = requests[i]; - if ( request.domain !== currentDomain ) { - currentDomain = request.domain; - html.push( - '<tr class="', className, ' domainHeader">', - '<td colspan="4">', currentDomain - ); - } - html.push( - '<tr class="', className, request.flags & 0x01 ? ' logMirrored': '', ' requestEntry">', - '<td>', - '<td>', toPrettyTypeNames[request.type] || request.type, - '<td>', renderURL(request.url, request.reason), - '<td>', chunkify(request.reason).join('\n') - ); - } - return html; - }; - uDom('#requests .tableHeader ~ tr').remove(); - var htmlBlocked = renderRequests(details.blockedRequests || [], 'logBlocked'); - var htmlAllowed = renderRequests(details.allowedRequests || [], 'logAllowed'); - uDom('#requests .tableHeader').insertAfter(htmlBlocked.concat(htmlAllowed).join('')); - }; - - messager.send({ what: 'getPageDetails', tabId: tabId }, onDataReceived); -}; - -/******************************************************************************/ - -var pageSelectorChanged = function() { - renderPageDetails(this.value); -}; - -/******************************************************************************/ - -var renderPageSelector = function(targetTabId) { - if ( !uDom('#logRequests').prop('checked') ) { - return; - } - var selectedTabId = targetTabId || parseInt(uDom('#pageSelector').val(), 10); - var onTabReceived = function(tab) { - if ( !tab ) { - return; - } - var html = [ - '<option value="', - tab.id, - '">', - tab.title - ]; - uDom('#pageSelector').append(html.join('')); - if ( tab.id === selectedTabId ) { - uDom('#pageSelector').val(tab.id); - } - }; - var onDataReceived = function(pageSelectors) { - uDom('#requests').toggleClass('empty', pageSelectors.length === 0); - uDom('#pageSelector option').remove(); - cachedPageSelectors = {}; - pageSelectors.sort().map(function(tabId) { - cachedPageSelectors[tabId] = true; - }); - if ( !cachedPageSelectors[selectedTabId] ) { - selectedTabId = pageSelectors[0]; - } - for ( var i = 0; i < pageSelectors.length; i++ ) { - messager.send({ - what: 'getTabForStats', - tabId: parseInt(pageSelectors[i], 10) - }, onTabReceived); - } - if ( pageSelectors.length > 0 ) { - renderPageDetails(selectedTabId); - } - }; - messager.send({ what: 'getPageSelectors' }, onDataReceived); -}; - -/******************************************************************************/ - -var onUserSettingsReceived = function(details) { - uDom('#logRequests').prop('checked', details.logRequests); - uDom('#requests').toggleClass('logEnabled', details.logRequests); - - var matches = window.location.search.slice(1).match(/(?:^|&)which=(\d+)/); - var tabId = matches && matches.length === 2 ? parseInt(matches[1], 10) : 0; - renderPageSelector(tabId); - - uDom('#logRequests').on('change', logSettingChanged); - uDom('#refresh').on('click', function() { renderPageSelector(); }); - uDom('#pageSelector').on('change', pageSelectorChanged); -}; - -/******************************************************************************/ - -uDom.onLoad(function() { - messager.send({ what: 'userSettings' }, onUserSettingsReceived); -}); - -/******************************************************************************/ - -})(); - diff --git a/src/js/storage.js b/src/js/storage.js index d90a1d1..044ec86 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -661,6 +661,10 @@ µb.mirrors.toggle(settings.experimentalEnabled); µb.contextMenu.toggle(settings.contextMenuEnabled); + // Remove obsolete setting + delete µb.userSettings.logRequests; + µb.XAL.keyvalRemoveOne('logRequests'); + if ( typeof settings.dynamicFilteringSelfie === 'string' ) { if ( settings.dynamicFilteringString === '' && settings.dynamicFilteringSelfie !== '' ) { µb.dynamicNetFilteringEngine.fromObsoleteSelfie(settings.dynamicFilteringSelfie); diff --git a/src/js/tab.js b/src/js/tab.js index 9584a1e..fd035f2 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -83,7 +83,12 @@ vAPI.tabs.onPopup = function(details) { // https://github.com/gorhill/uBlock/issues/91 if ( result !== '' ) { - pageStore.recordResult('popup', requestURL, result); + var context = { + requestURL: requestURL, + requestHostname: µb.URI.hostnameFromURI(requestURL), + requestType: 'popup' + }; + pageStore.logBuffer.writeOne(context, result); } // Not blocked diff --git a/src/js/traffic.js b/src/js/traffic.js index 7b50dcf..785becd 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -95,6 +95,9 @@ var onBeforeRequest = function(details) { var result = pageStore.filterRequest(requestContext); + // Log result + pageStore.logBuffer.writeOne(requestContext, result); + // Not blocked if ( pageStore.boolFromResult(result) === false ) { //console.debug('onBeforeRequest()> ALLOW "%s" (%o) because "%s"', details.url, details, result); @@ -125,7 +128,6 @@ var onBeforeRequest = function(details) { // Not all redirects will succeed, until bug above is fixed. var redirectURL = µb.mirrors.toURL(requestURL, true); if ( redirectURL !== '' ) { - pageStore.setRequestFlags(requestURL, 0x01, 0x01); //console.debug('"%s" redirected to "%s..."', requestURL.slice(0, 50), redirectURL.slice(0, 50)); return { redirectUrl: redirectURL }; } diff --git a/src/js/ublock.js b/src/js/ublock.js index 26ab7f8..a0be4c0 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -321,75 +321,7 @@ var matchWhitelistDirective = function(url, hostname, directive) { this.XAL.keyvalSetOne('dynamicFilteringString', this.userSettings.dynamicFilteringString); // https://github.com/gorhill/uBlock/issues/420 - if ( details.requestType === '3p-frame' && !details.block ) { - this.cosmeticFilteringEngine.removeFromSelectorCache(details.hostname, 'net'); - } -}; - -/******************************************************************************/ - -µBlock.isFirstParty = function(firstPartyDomain, hostname) { - if ( hostname.slice(0 - firstPartyDomain.length) !== firstPartyDomain ) { - return false; - } - // Be sure to not confuse 'example.com' with 'anotherexample.com' - var c = hostname.charAt(hostname.length - firstPartyDomain.length - 1); - return c === '.' || c === ''; -}; - -/******************************************************************************/ - -// The core logic to evaluate requests through dynamic/static filtering -// is here. - -µBlock.filterRequest = function(context) { - // Given that: - // - Dynamic filtering override static filtering - // - Evaluating dynamic filtering is much faster than static filtering - // We evaluate dynamic filtering first, and hopefully we can skip - // evaluation of static filtering. - // Dynamic filtering evaluation is ordered from most-specific to least- - // specific. - var df = this.dynamicNetFilteringEngine.clearRegisters(); - - var rootHostname = context.rootHostname; - var requestHostname = context.requestHostname; - var requestType = context.requestType; - - // Dynamic filters: - // 1. specific source, specific destination, any type, allow/block - // 2. any source, specific destination, any type, allow/block - df.evaluateCellZY(rootHostname, requestHostname, '*'); - if ( df.mustBlockOrAllow() ) { - return df.toFilterString(); - } - - // Dynamic filters: - // 3. specific source, any destination, specific type, allow/block - // 4. any source, any destination, specific type, allow/block - if ( df.mustAbort() === false ) { - if ( requestType === 'script' ) { - df.evaluateCellZY(rootHostname, requestHostname, this.isFirstParty(rootHostname, requestHostname) ? '1p-script' : '3p-script'); - if ( df.mustBlockOrAllow() ) { - return df.toFilterString(); - } - } else if ( requestType === 'sub_frame' ) { - if ( this.isFirstParty(rootHostname, requestHostname) === false ) { - df.evaluateCellZY(rootHostname, requestHostname, '3p-frame'); - if ( df.mustBlockOrAllow() ) { - return df.toFilterString(); - } - } - } else { - df.evaluateCellZY(rootHostname, requestHostname, requestType); - if ( df.mustBlockOrAllow() ) { - return df.toFilterString(); - } - } - } - - // 5. Static filtering never override dynamic filtering - return this.staticNetFilteringEngine.matchString(context); + this.cosmeticFilteringEngine.removeFromSelectorCache(details.srcHostname, 'net'); }; /******************************************************************************/ @@ -403,6 +335,7 @@ var matchWhitelistDirective = function(url, hostname, directive) { µBlock.isAllowResult = function(result) { return typeof result !== 'string' || result.charAt(1) !== 'b'; }; + /******************************************************************************/ })();
\ No newline at end of file diff --git a/src/popup.html b/src/popup.html index 1fb6725..312f2b2 100644 --- a/src/popup.html +++ b/src/popup.html @@ -9,33 +9,36 @@ </head> <body> -<h4 title="popupTipDashboard">v<span id="version"></span></h4> -<div> - <div id="dynamicFilteringContainer"> - <div><span data-i18n="popupImageRulePrompt"></span><span data-src="/" data-des="*" data-type="image"> </span><span data-src="." data-des="*" data-type="image"></span></div> - <div><span data-i18n="popupInlineScriptRulePrompt"></span><span data-src="/" data-des="*" data-type="inline-script"> </span><span data-src="." data-des="*" data-type="inline-script"> </span></div> - <div><span data-i18n="popup1pScriptRulePrompt"></span><span data-src="/" data-des="*" data-type="1p-script"> </span><span data-src="." data-des="*" data-type="1p-script"> </span></div> - <div><span data-i18n="popup3pScriptRulePrompt"></span><span data-src="/" data-des="*" data-type="3p-script"> </span><span data-src="." data-des="*" data-type="3p-script"> </span></div> - <div><span data-i18n="popup3pFrameRulePrompt"></span><span data-src="/" data-des="*" data-type="3p-frame"> </span><span data-src="." data-des="*" data-type="3p-frame"> </span></div> - <div id="privacyInfo"></div> +<h4 title="popupTipDashboard"><span id="appname"></span><span id="version"></span></h4> +<div id="panes"> + <div> + <p id="switch" data-i18n-tip="popupPowerSwitchInfo"><span class="fa"></span></p> + <p id="switch-hint"></p> + <p id="dfToggler" data-i18n="popupBlockedRequestPrompt"></p> + <p class="stats"> + <span data-i18n="popupBlockedOnThisPagePrompt"></span>  + <span id="gotoPick" class="fa tool" data-i18n-tip="popupTipPicker" data-tip-anchor="top"></span>  + <span id="gotoLog" class="fa tool" data-i18n-tip="popupTipLog" data-tip-anchor="top"></span> + </p> + <p id="page-blocked">?</p> + <p class="stats" data-i18n="popupBlockedSinceInstallPrompt"></p> + <p id="total-blocked">?</p> + </div><!-- DO NOT REMOVE --><div> + <div id="dynamicFilteringContainer"> + <div><span data-i18n="popupImageRulePrompt"></span><span data-src="/" data-des="*" data-type="image"> </span><span data-src="." data-des="*" data-type="image"></span></div> + <div><span data-i18n="popupInlineScriptRulePrompt"></span><span data-src="/" data-des="*" data-type="inline-script"> </span><span data-src="." data-des="*" data-type="inline-script"> </span></div> + <div><span data-i18n="popup1pScriptRulePrompt"></span><span data-src="/" data-des="*" data-type="1p-script"> </span><span data-src="." data-des="*" data-type="1p-script"> </span></div> + <div><span data-i18n="popup3pScriptRulePrompt"></span><span data-src="/" data-des="*" data-type="3p-script"> </span><span data-src="." data-des="*" data-type="3p-script"> </span></div> + <div><span data-i18n="popup3pFrameRulePrompt"></span><span data-src="/" data-des="*" data-type="3p-frame"> </span><span data-src="." data-des="*" data-type="3p-frame"> </span></div> + <div id="privacyInfo"></div> + </div> </div> - </div><!-- DO NOT REMOVE --><div> - <p id="switch" data-i18n-tip="popupPowerSwitchInfo"><span class="fa"></span></p> - <p id="switch-hint"></p> - <p id="dynamicFilteringToggler" data-i18n="popupBlockedRequestPrompt"></p> - <p class="stats"> - <span data-i18n="popupBlockedOnThisPagePrompt"></span>  - <span id="gotoPick" class="fa tool" data-i18n-tip="popupTipPicker" data-tip-anchor="top"></span>  - <span id="gotoLog" class="fa tool" data-i18n-tip="popupTipLog" data-tip-anchor="top"></span> - </p> - <p id="page-blocked">?</p> - <p class="stats" data-i18n="popupBlockedSinceInstallPrompt"></p> - <p id="total-blocked">?</p> </div> <div id="templates" style="display: none"> <div><span></span><span data-src="/" data-des="" data-type="*"> </span><span data-src="." data-des="" data-type="*"> </span></div> <div id='actionSelector'><span id="dynaAllow"></span><span id="dynaNoop"></span><span id="dynaBlock"></span></div> + <div id=hotspotTip></div> </div> <script src="js/vapi-common.js"></script> diff --git a/src/stats.html b/src/stats.html deleted file mode 100644 index ab3187e..0000000 --- a/src/stats.html +++ /dev/null @@ -1,40 +0,0 @@ -<!DOCTYPE html> -<html> -<head> -<meta charset="utf-8"> -<title>µBlock — Statistics</title> -<link rel="stylesheet" type="text/css" href="css/common.css"> -<link rel="stylesheet" type="text/css" href="css/dashboard-common.css"> -<link rel="stylesheet" type="text/css" href="css/stats.css"> -</head> - -<body> - -<ul> - <li><input id="logRequests" type="checkbox" data-range="bool" /><label data-i18n="logNetRequestsPrompt" for="logRequests"></label> - <button class="whatisthis"></button> - <div class="whatisthis-expandable para" data-i18n="logNetRequestsHelp"></div> -</ul> - -<div id="requests"> -<span id="refresh" class="fa"></span><select id="pageSelector"></select> - -<table> -<tr class="tableHeader"> -<th> -<th data-i18n="logRequestsHeaderType"> -<th data-i18n="logRequestsHeaderURL"> -<th data-i18n="logRequestsHeaderFilter"> -</table> - -</div> - -<script src="js/vapi-common.js"></script> -<script src="js/vapi-client.js"></script> -<script src="js/udom.js"></script> -<script src="js/i18n.js"></script> -<script src="js/dashboard-common.js"></script> -<script src="js/stats.js"></script> - -</body> -</html> |