diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index a8495990b..c4fb03fd6 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -403,6 +403,10 @@ "message":"No non-blocked requests logged for this page", "description":"English: No non-blocked requests logged for this page" }, + "logAll":{ + "message":"All", + "description":"Appears in the logger's tab selector" + }, "logBehindTheScene":{ "message":"Behind the scene", "description":"Pretty name for behind-the-scene network requests" diff --git a/src/css/logger-ui.css b/src/css/logger-ui.css index 13de2dc5d..00b8f9b62 100644 --- a/src/css/logger-ui.css +++ b/src/css/logger-ui.css @@ -7,7 +7,6 @@ body { margin: 0; overflow-x: hidden; padding: 0; - white-space: nowrap; width: 100%; } #toolbar { @@ -16,7 +15,7 @@ body { box-sizing: border-box; left: 0; margin: 0; - padding: 0 1em; + padding: 0.5em 1em; position: fixed; top: 0; width: 100%; @@ -28,7 +27,7 @@ body { box-sizing: border-box; cursor: pointer; display: inline-block; - font-size: 20px; + font-size: 150%; margin: 0; padding: 8px; } @@ -39,6 +38,19 @@ body { #toolbar .button:hover { background-color: #eee; } +#toolbar > div { + white-space: nowrap; + } +#toolbar > div:first-of-type { + font-size: 120%; + } +#toolbar > div > * { + vertical-align: middle; + } +#pageSelector { + width: 28em; + padding: 0.2em 0; + } body #compactViewToggler.button:before { content: '\f102'; } @@ -55,14 +67,13 @@ body.f #filterButton { background-color: #fee; } #maxEntries { - margin-left: 3em; + margin: 0 2em; } input:focus { background-color: #ffe; } #content { font: 13px sans-serif; - margin-top: 3.5em; width: 100%; } diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index 84e6c5078..2690762c9 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -30,6 +30,15 @@ /******************************************************************************/ +// Adjust top padding of content table, to match that of toolbar height. + +document.getElementById('content').style.setProperty( + 'margin-top', + document.getElementById('toolbar').offsetHeight + 'px' +); + +/******************************************************************************/ + var messager = vAPI.messaging.channel('logger-ui.js'); var tbody = document.querySelector('#content tbody'); var trJunkyard = []; @@ -40,6 +49,8 @@ var maxEntries = 5000; var noTabId = ''; var allTabIds = {}; +var hiddenTemplate = document.querySelector('#hiddenTemplate > span'); + var prettyRequestTypes = { 'main_frame': 'doc', 'stylesheet': 'css', @@ -61,6 +72,18 @@ var dateOptions = { /******************************************************************************/ +var classNameFromTabId = function(tabId) { + if ( tabId === noTabId ) { + return 'tab_bts'; + } + if ( tabId !== '' ) { + return 'tab_' + tabId; + } + return ''; +}; + +/******************************************************************************/ + // Emphasize hostname in URL, as this is what matters in uMatrix's rules. var nodeFromURL = function(url, filter) { @@ -165,6 +188,14 @@ var createRow = function(layout) { /******************************************************************************/ +var createHiddenTextNode = function(text) { + var node = hiddenTemplate.cloneNode(true); + node.textContent = text; + return node; +}; + +/******************************************************************************/ + var createGap = function(tabId, url) { var tr = createRow('1'); tr.classList.add('tab'); @@ -246,11 +277,9 @@ var renderLogEntry = function(entry) { tr.cells[0].title = time.toLocaleDateString('fullwide', dateOptions); if ( entry.tab ) { - tr.classList.add('tab'); + tr.classList.add('tab', classNameFromTabId(entry.tab)); if ( entry.tab === noTabId ) { - tr.classList.add('tab_bts'); - } else if ( entry.tab !== '' ) { - tr.classList.add('tab_' + entry.tab); + tr.cells[1].appendChild(createHiddenTextNode('bts')); } } if ( entry.cat !== '' ) { @@ -316,6 +345,78 @@ var renderLogEntries = function(response) { /******************************************************************************/ +var synchronizeTabIds = function(newTabIds) { + var oldTabIds = allTabIds; + + // Neuter rows for which a tab does not exist anymore + // TODO: sort to avoid using indexOf + + var autoDeleteVoidRows = !!vAPI.localStorage.getItem('loggerAutoDeleteVoidRows'); + var rowVoided = false; + var trs; + for ( var tabId in oldTabIds ) { + if ( oldTabIds.hasOwnProperty(tabId) === false ) { + continue; + } + if ( newTabIds.hasOwnProperty(tabId) ) { + continue; + } + // Mark or remove voided rows + trs = uDom('.tab_' + tabId); + if ( autoDeleteVoidRows ) { + toJunkyard(trs); + } else { + trs.removeClass('canMtx'); + rowVoided = true; + } + // Remove popup if it is currently bound to a removed tab. + if ( tabId === popupManager.tabId ) { + popupManager.toggleOff(); + } + } + + var select = document.getElementById('pageSelector'); + var selectValue = select.value; + var tabIds = Object.keys(newTabIds).sort(function(a, b) { + return newTabIds[a].localeCompare(newTabIds[b]); + }); + var option; + for ( var i = 0, j = 2; i < tabIds.length; i++ ) { + tabId = tabIds[i]; + if ( tabId === noTabId ) { + continue; + } + option = select.options[j]; + j += 1; + if ( !option ) { + option = document.createElement('option'); + select.appendChild(option); + } + option.textContent = newTabIds[tabId]; + option.value = classNameFromTabId(tabId); + if ( option.value === selectValue ) { + option.setAttribute('selected', ''); + } else { + option.removeAttribute('selected'); + } + } + while ( j < select.options.length ) { + select.removeChild(select.options[j]); + } + if ( select.value !== selectValue ) { + select.selectedIndex = 0; + select.value = ''; + select.options[0].setAttribute('selected', ''); + pageSelectorChanged(); + } + + allTabIds = newTabIds; + + return rowVoided; +}; + +/******************************************************************************/ + var truncateLog = function(size) { if ( size === 0 ) { size = 5000; @@ -343,28 +444,7 @@ var onLogBufferRead = function(response) { // Neuter rows for which a tab does not exist anymore // TODO: sort to avoid using indexOf - var autoDeleteVoidRows = vAPI.localStorage.getItem('loggerAutoDeleteVoidRows'); - var rowVoided = false, trs; - for ( var tabId in allTabIds ) { - if ( allTabIds.hasOwnProperty(tabId) === false ) { - continue; - } - if ( response.tabIds.hasOwnProperty(tabId) ) { - continue; - } - trs = uDom('.tab_' + tabId); - if ( autoDeleteVoidRows ) { - toJunkyard(trs); - } else { - trs.removeClass('canMtx'); - rowVoided = true; - } - if ( tabId === popupManager.tabId ) { - popupManager.toggleOff(); - } - } - allTabIds = response.tabIds; - + var rowVoided = synchronizeTabIds(response.tabIds); renderLogEntries(response); if ( rowVoided ) { @@ -395,6 +475,41 @@ var readLogBuffer = function() { /******************************************************************************/ +var pageSelectorChanged = function() { + var style = document.getElementById('tabFilterer'); + var tabClass = document.getElementById('pageSelector').value; + var sheet = style.sheet; + while ( sheet.cssRules.length !== 0 ) { + sheet.deleteRule(0); + } + if ( tabClass !== '' ) { + sheet.insertRule( + '#content table tr:not(.' + tabClass + ') { display: none; }', + 0 + ); + } + uDom('#refresh').toggleClass( + 'disabled', + tabClass === '' || tabClass === 'tab_bts' + ); +}; + +/******************************************************************************/ + +var reloadTab = function() { + var tabClass = document.getElementById('pageSelector').value; + var matches = tabClass.match(/^tab_(.+)$/); + if ( matches === null ) { + return; + } + if ( matches[1] === 'bts' ) { + return; + } + messager.send({ what: 'reloadTab', tabId: matches[1] }); +}; + +/******************************************************************************/ + var onMaxEntriesChanged = function() { var raw = uDom(this).val(); try { @@ -649,7 +764,7 @@ var popupManager = (function() { popupObserver = new MutationObserver(resizePopup); container.appendChild(popup); - style = document.querySelector('#content > style'); + style = document.getElementById('popupFilterer'); style.textContent = styleTemplate.replace('{{tabId}}', localTabId); document.body.classList.add('popupOn'); @@ -701,6 +816,8 @@ var popupManager = (function() { uDom.onLoad(function() { readLogBuffer(); + uDom('#pageSelector').on('change', pageSelectorChanged); + uDom('#refresh').on('click', reloadTab); uDom('#compactViewToggler').on('click', toggleCompactView); uDom('#clean').on('click', cleanBuffer); uDom('#clear').on('click', clearBuffer); diff --git a/src/js/messaging.js b/src/js/messaging.js index c5d0fc1a5..874980944 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -1193,11 +1193,17 @@ var onMessage = function(request, sender, callback) { switch ( request.what ) { case 'readAll': - var tabIds = {}; + var tabIds = {}, pageStore; + var loggerURL = vAPI.getURL('logger-ui.html'); for ( var tabId in µb.pageStores ) { - if ( µb.pageStores.hasOwnProperty(tabId) ) { - tabIds[tabId] = true; + pageStore = µb.pageStoreFromTabId(tabId); + if ( pageStore === null ) { + continue; } + if ( pageStore.rawURL.lastIndexOf(loggerURL, 0) === 0 ) { + continue; + } + tabIds[tabId] = pageStore.title; } response = { colorBlind: µb.userSettings.colorBlindFriendly, diff --git a/src/js/pagestore.js b/src/js/pagestore.js index 5a7d64bb3..a2378e30a 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -295,6 +295,8 @@ PageStore.prototype.init = function(tabId) { var tabContext = µb.tabContextManager.lookup(tabId); this.tabId = tabId; this.tabHostname = tabContext.rootHostname; + this.title = tabContext.rawURL; + this.rawURL = tabContext.rawURL; this.hostnameToCountMap = {}; this.contentLastModified = 0; this.frames = {}; @@ -334,6 +336,7 @@ PageStore.prototype.reuse = function(context) { if ( context === 'tabUpdated' ) { // As part of https://github.com/chrisaljoudi/uBlock/issues/405 // URL changed, force a re-evaluation of filtering switch + this.rawURL = tabContext.rawURL; this.netFilteringReadTime = 0; return this; } @@ -354,6 +357,9 @@ PageStore.prototype.dispose = function() { // need to release the memory taken by these, which can amount to // sizeable enough chunks (especially requests, through the request URL // used as a key). + this.tabHostname = ''; + this.title = ''; + this.rawURL = ''; this.hostnameToCountMap = null; this.disposeFrameStores(); this.netFilteringCache = this.netFilteringCache.dispose(); diff --git a/src/js/tab.js b/src/js/tab.js index 010cfae24..c07ac0512 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -510,9 +510,7 @@ vAPI.tabs.registerListeners(); // Create an entry for the tab if it doesn't exist. µb.bindTabToPageStats = function(tabId, context) { - if ( vAPI.isBehindTheSceneTabId(tabId) === false ) { - this.updateBadgeAsync(tabId); - } + this.updateBadgeAsync(tabId); // Do not create a page store for URLs which are of no interests if ( µb.tabContextManager.exists(tabId) === false ) { @@ -540,6 +538,8 @@ vAPI.tabs.registerListeners(); return pageStore; } + this.updateTitle(tabId); + // Rebind according to context. We rebind even if the URL did not change, // as maybe the tab was force-reloaded, in which case the page stats must // be all reset. @@ -570,8 +570,65 @@ vAPI.tabs.registerListeners(); // Permanent page store for behind-the-scene requests. Must never be removed. µb.pageStores[vAPI.noTabId] = µb.PageStore.factory(vAPI.noTabId); +µb.pageStores[vAPI.noTabId].title = vAPI.i18n('logBehindTheScene'); /******************************************************************************/ + +µb.updateTitle = (function() { + var tabIdToTimer = Object.create(null); + var tabIdToTryCount = Object.create(null); + var delay = 499; + + var tryNoMore = function(tabId) { + delete tabIdToTryCount[tabId]; + }; + + var tryAgain = function(tabId) { + var count = tabIdToTryCount[tabId]; + if ( count === undefined ) { + return false; + } + if ( count === 1 ) { + delete tabIdToTryCount[tabId]; + return false; + } + tabIdToTryCount[tabId] = count - 1; + tabIdToTimer[tabId] = setTimeout(updateTitle.bind(µb, tabId), delay); + return true; + }; + + var onTabReady = function(tabId, tab) { + if ( !tab ) { + return tryNoMore(tabId); + } + var pageStore = this.pageStoreFromTabId(tabId); + if ( pageStore === null ) { + return tryNoMore(tabId); + } + if ( !tab.title && tryAgain(tabId) ) { + return; + } + tryNoMore(tabId); + pageStore.title = tab.title || tab.url || ''; + }; + + var updateTitle = function(tabId) { + delete tabIdToTimer[tabId]; + vAPI.tabs.get(tabId, onTabReady.bind(this, tabId)); + }; + + return function(tabId) { + if ( vAPI.isBehindTheSceneTabId(tabId) ) { + return; + } + if ( tabIdToTimer[tabId] ) { + clearTimeout(tabIdToTimer[tabId]); + } + tabIdToTimer[tabId] = setTimeout(updateTitle.bind(this, tabId), delay); + tabIdToTryCount[tabId] = 5; + }; +})(); + /******************************************************************************/ // Stale page store entries janitor @@ -612,7 +669,6 @@ var pageStoreJanitor = function() { setTimeout(pageStoreJanitor, pageStoreJanitorPeriod); -/******************************************************************************/ /******************************************************************************/ })(); diff --git a/src/logger-ui.html b/src/logger-ui.html index 2e59050c8..8ac913c27 100644 --- a/src/logger-ui.html +++ b/src/logger-ui.html @@ -9,15 +9,25 @@