diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 96458eec9..4c8e6a015 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -35,6 +35,10 @@ "message":"µBlock — Network request log", "description":"Title for the network request log window" }, + "statsFilterPrompt":{ + "message":"filter log entries", + "description": "English: filter log entries" + }, "aboutPageName":{ "message":"About", "description":"appears as tab name in dashboard" diff --git a/src/css/devtool-log.css b/src/css/devtool-log.css index 36557925b..5f269284f 100644 --- a/src/css/devtool-log.css +++ b/src/css/devtool-log.css @@ -10,32 +10,35 @@ body { width: 100%; } #toolbar { - padding: 8px 0; + background-color: white; + border: 0; + box-sizing: border-box; + height: 36px; + margin: 0; + padding: 0; position: fixed; text-align: center; top: 0; - width: 4em; + width: 100%; } #toolbar .button { background-color: white; border: none; + box-sizing: border-box; cursor: pointer; - display: block; - font-size: large; + display: inline-block; + font-size: 20px; margin: 0; - padding: 0.5em 0; + padding: 8px; } #toolbar .button:hover { background-color: #eee; } +body.filterOff #toolbar #filterButton { + opacity: 0.25; + } #content { - width: calc(100% - 4em); - } -body[dir="ltr"] #content { - margin-left: 4em; - } -body[dir="rtl"] #content { - margin-right: 4em; + margin-top: 36px; } #content table { border: 0; @@ -55,6 +58,9 @@ body[dir="rtl"] #content { #content table tr.maindoc { background-color: #eee; } +body:not(.filterOff) #content table tr.hidden { + display: none; + } #content table tr td { border: 1px solid #ccc; padding: 3px; @@ -63,6 +69,7 @@ body[dir="rtl"] #content { #content table tr td:nth-of-type(1) { padding: 3px 0; text-align: center; + white-space: pre; } #content table tr td:nth-of-type(2) { white-space: normal; diff --git a/src/devtool-log.html b/src/devtool-log.html index 3b6ecd885..8733ee939 100644 --- a/src/devtool-log.html +++ b/src/devtool-log.html @@ -10,12 +10,15 @@
-
+ +
+
+ diff --git a/src/js/background.js b/src/js/background.js index d9bb7c91d..c640a3cb2 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -85,7 +85,7 @@ return { // read-only systemSettings: { - compiledMagic: 'fkaywfqahncj', + compiledMagic: 'shztbfhkfjit', selfieMagic: 'spqmeuaftfra' }, diff --git a/src/js/devtool-log.js b/src/js/devtool-log.js index 29a8ae0df..621e504e1 100644 --- a/src/js/devtool-log.js +++ b/src/js/devtool-log.js @@ -36,6 +36,7 @@ var doc = document; var body = doc.body; var tbody = doc.querySelector('#content tbody'); var rowJunkyard = []; +var reFilter = null; /******************************************************************************/ @@ -101,15 +102,15 @@ var renderLogEntry = function(entry) { var tr = createRow(); if ( entry.result.charAt(1) === 'b' ) { tr.classList.add('blocked'); - tr.cells[0].textContent = '\u2009\u2212\u2009'; + tr.cells[0].textContent = ' \u2212\u00A0'; } else if ( entry.result.charAt(1) === 'a' ) { tr.classList.add('allowed'); if ( entry.result.charAt(0) === 'm' ) { tr.classList.add('mirrored'); } - tr.cells[0].textContent = '\u2009+\u2009'; + tr.cells[0].textContent = ' +\u00A0'; } else { - tr.cells[0].textContent = '\u2009\u00A0\u2009'; + tr.cells[0].textContent = ' '; } if ( entry.type === 'main_frame' ) { tr.classList.add('maindoc'); @@ -118,9 +119,10 @@ var renderLogEntry = function(entry) { if ( entry.result.lastIndexOf('sa', 0) === 0 ) { filterText = '@@' + filterText; } - tr.cells[1].textContent = filterText; - tr.cells[2].textContent = entry.type; + tr.cells[1].textContent = filterText + ' '; + tr.cells[2].textContent = entry.type + ' '; vAPI.insertHTML(tr.cells[3], renderURL(entry.url, entry.result)); + applyFilterToRow(tr); tbody.insertBefore(tr, tbody.firstChild); }; @@ -196,6 +198,102 @@ var reloadTab = function() { /******************************************************************************/ +var applyFilterToRow = function(row) { + var re = reFilter; + if ( re === null || re.test(row.textContent) ) { + row.classList.remove('hidden'); + } else { + row.classList.add('hidden'); + } +}; + +/******************************************************************************/ + +var applyFilter = function() { + if ( reFilter === null ) { + unapplyFilter(); + return; + } + var row = document.querySelector('#content tr'); + if ( row === null ) { + return; + } + var re = reFilter; + while ( row !== null ) { + if ( re.test(row.textContent) ) { + row.classList.remove('hidden'); + } else { + row.classList.add('hidden'); + } + row = row.nextSibling; + } +}; + +/******************************************************************************/ + +var unapplyFilter = function() { + var row = document.querySelector('#content tr'); + if ( row === null ) { + return; + } + while ( row !== null ) { + row.classList.remove('hidden'); + row = row.nextSibling; + } +}; + +/******************************************************************************/ + +var onFilterButton = function() { + uDom('body').toggleClass('filterOff'); +}; + +/******************************************************************************/ + +var onFilterChanged = function() { + var filterRaw = uDom('#filterExpression').val().trim(); + + if ( filterRaw === '') { + reFilter = null; + unapplyFilter(); + return; + } + + var filterParts = filterRaw + .replace(/^\s*-(\s+|$)/, '−\xA0') + .replace(/^\s*\\+(\s+|$)/, '\\+\xA0') + .split(/\s+/); + var n = filterParts.length; + for ( var i = 0; i < n; i++ ) { + filterParts[i] = filterParts[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + reFilter = new RegExp(filterParts.join('.*\\s+.*')); + + applyFilter(); +}; + +/******************************************************************************/ + +var onFilterChangedAsync = (function() { + var timer = null; + + var commit = function() { + timer = null; + onFilterChanged(); + }; + + var changed = function() { + if ( timer !== null ) { + clearTimeout(timer); + } + timer = setTimeout(commit, 750); + }; + + return changed; +})(); + +/******************************************************************************/ + uDom.onLoad(function() { // Extract the tab id of the page we need to pull the log var matches = window.location.search.match(/[\?&]tabId=([^&]+)/); @@ -207,6 +305,8 @@ uDom.onLoad(function() { uDom('#reload').on('click', reloadTab); uDom('#clear').on('click', clearBuffer); + uDom('#filterButton').on('click', onFilterButton); + uDom('#filterExpression').on('input', onFilterChangedAsync); }); /******************************************************************************/ diff --git a/src/js/i18n.js b/src/js/i18n.js index bee0b96af..95304f51d 100644 --- a/src/js/i18n.js +++ b/src/js/i18n.js @@ -36,6 +36,12 @@ uDom.onLoad(function() { elem.attr('title', title); } }); + uDom('[placeholder]').forEach(function(elem) { + var placeholder = vAPI.i18n(elem.attr('placeholder')); + if ( placeholder ) { + elem.attr('placeholder', placeholder); + } + }); uDom('[data-i18n-tip]').forEach(function(elem) { elem.attr( 'data-tip', diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 8faf9dd28..85c3a31cf 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -921,6 +921,40 @@ FilterGenericHnAnchored.fromSelfie = function(s) { /******************************************************************************/ +var FilterGenericHnAnchoredHostname = function(s, hostname) { + FilterGenericHnAnchored.call(this, s); + this.hostname = hostname; +}; +FilterGenericHnAnchoredHostname.prototype = Object.create(FilterGenericHnAnchored.prototype); + +FilterGenericHnAnchoredHostname.prototype.match = function(url) { + if ( pageHostnameRegister.slice(-this.hostname.length) !== this.hostname ) { + return false; + } + return FilterGenericHnAnchored.prototype.match.call(this. url); +}; + +FilterGenericHnAnchoredHostname.fid = FilterGenericHnAnchoredHostname.prototype.fid = '||_h'; + +FilterGenericHnAnchoredHostname.prototype.toString = function() { + return '||' + this.s + '$domain=' + this.hostname; +}; + +FilterGenericHnAnchoredHostname.prototype.toSelfie = function() { + return this.s + '\t' + this.hostname; +}; + +FilterGenericHnAnchoredHostname.compile = function(details, hostname) { + return details.f + '\t' + hostname; +}; + +FilterGenericHnAnchoredHostname.fromSelfie = function(s) { + var pos = s.indexOf('\t'); + return new FilterGenericHnAnchoredHostname(s.slice(0, pos), s.slice(pos + 1)); +}; + +/******************************************************************************/ + // With many wildcards, a regex is best. // Ref: regex escaper taken from: @@ -1417,6 +1451,9 @@ var getHostnameBasedFilterClass = function(details) { var s = details.f; var wcOffset = s.indexOf('*'); if ( wcOffset !== -1 ) { + if ( details.hostnameAnchored ) { + return FilterGenericHnAnchoredHostname; + } if ( s.indexOf('*', wcOffset + 1) !== -1 ) { return details.anchor === 0 ? FilterManyWildcardsHostname : null; } @@ -1735,17 +1772,23 @@ var findFirstGoodToken = function(s) { reGoodToken.lastIndex = 0; var matches; while ( matches = reGoodToken.exec(s) ) { + if ( s.charAt(reGoodToken.lastIndex) === '*' ) { + continue; + } if ( badTokens.hasOwnProperty(matches[0]) ) { continue; } + return matches; + } + // No good token found, try again without minding "bad" tokens + reGoodToken.lastIndex = 0; + while ( matches = reGoodToken.exec(s) ) { if ( s.charAt(reGoodToken.lastIndex) === '*' ) { continue; } return matches; } - // No good token found, just return the first token from left - reGoodToken.lastIndex = 0; - return reGoodToken.exec(s); + return null; }; var findHostnameToken = function(s) { @@ -1753,6 +1796,8 @@ var findHostnameToken = function(s) { return reHostnameToken.exec(s); }; +/******************************************************************************/ + FilterParser.prototype.makeToken = function() { if ( this.isRegex ) { this.token = '*'; @@ -1774,7 +1819,7 @@ FilterParser.prototype.makeToken = function() { } matches = findFirstGoodToken(this.f); - if ( !matches || matches[0].length === 0 ) { + if ( matches === null || matches[0].length === 0 ) { return; } this.tokenBeg = matches.index; @@ -1864,7 +1909,8 @@ FilterContainer.prototype.factories = { '//': FilterRegex, '//h': FilterRegexHostname, '{h}': FilterHostnameDict, - '||_': FilterGenericHnAnchored + '||_': FilterGenericHnAnchored, + '||_h': FilterGenericHnAnchoredHostname }; /******************************************************************************/