/******************************************************************************* ηMatrix - a browser extension to black/white list requests. Copyright (C) 2015-2019 Raymond Hill Copyright (C) 2019-2022 Alessio Vanni 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://gitlab.com/vannilla/ematrix uMatrix Home: https://github.com/gorhill/sessbench */ 'use strict'; (function () { let tbody = document.querySelector('#content tbody'); let trJunkyard = []; let tdJunkyard = []; let firstVarDataCol = 2; // currently, column 2 (0-based index) let lastVarDataIndex = 3; // currently, d0-d3 let maxEntries = 0; let noTabId = ''; let allTabIds = {}; let allTabIdsToken; let ownerId = Date.now(); let emphasizeTemplate = document.querySelector('#emphasizeTemplate > span'); let hiddenTemplate = document.querySelector('#hiddenTemplate > span'); let prettyRequestTypes = { 'main_frame': 'doc', 'stylesheet': 'css', 'sub_frame': 'frame', 'xmlhttprequest': 'xhr' }; let dontEmphasizeSet = new Set([ 'COOKIE', 'CSP', 'REFERER' ]); // Adjust top padding of content table, to match that of toolbar height. document .getElementById('content') .style .setProperty('margin-top', document.getElementById('toolbar').clientHeight + 'px'); let classNameFromTabId = function (tabId) { if (tabId === noTabId) { return 'tab_bts'; } if (tabId !== '') { return 'tab_' + tabId; } return ''; }; // Emphasize hostname and cookie name. let emphasizeCookie = function (s) { let pnode = emphasizeHostname(s); if (pnode.childNodes.length !== 3) { return pnode; } let prefix = '-cookie:'; let text = pnode.childNodes[2].textContent; let beg = text.indexOf(prefix); if (beg === -1) { return pnode; } beg += prefix.length; let end = text.indexOf('}', beg); if (end === -1) { return pnode; } let cnode = emphasizeTemplate.cloneNode(true); cnode.childNodes[0].textContent = text.slice(0, beg); cnode.childNodes[1].textContent = text.slice(beg, end); cnode.childNodes[2].textContent = text.slice(end); pnode.replaceChild(cnode.childNodes[0], pnode.childNodes[2]); pnode.appendChild(cnode.childNodes[0]); pnode.appendChild(cnode.childNodes[0]); return pnode; }; // Emphasize hostname in URL. let emphasizeHostname = function (url) { let hnbeg = url.indexOf('://'); if (hnbeg === -1) { return document.createTextNode(url); } hnbeg += 3; let hnend = url.indexOf('/', hnbeg); if (hnend === -1) { hnend = url.slice(hnbeg).search(/\?#/); if (hnend !== -1) { hnend += hnbeg; } else { hnend = url.length; } } let node = emphasizeTemplate.cloneNode(true); node.childNodes[0].textContent = url.slice(0, hnbeg); node.childNodes[1].textContent = url.slice(hnbeg, hnend); node.childNodes[2].textContent = url.slice(hnend); return node; }; let createCellAt = function (tr, index) { let td = tr.cells[index]; let mustAppend = !td; if (mustAppend) { td = tdJunkyard.pop(); } if (td) { td.removeAttribute('colspan'); td.textContent = ''; } else { td = document.createElement('td'); } if (mustAppend) { tr.appendChild(td); } return td; }; let createRow = function (layout) { let tr = trJunkyard.pop(); if (tr) { tr.className = ''; } else { tr = document.createElement('tr'); } let index; for (index=0; index size) { let tr = tbody.lastElementChild; trJunkyard.push(tbody.removeChild(tr)); } }; let onLogBufferRead = function (response) { if (!response || response.unavailable) { readLogBufferAsync(); return; } // This tells us the behind-the-scene tab id noTabId = response.noTabId; // This may have changed meanwhile if (response.maxLoggedRequests !== maxEntries) { maxEntries = response.maxLoggedRequests; uDom('#maxEntries').val(maxEntries || ''); } // Neuter rows for which a tab does not exist anymore let rowVoided = false; if (response.tabIdsToken !== allTabIdsToken) { rowVoided = synchronizeTabIds(response.tabIds); allTabIdsToken = response.tabIdsToken; } renderLogEntries(response); if (rowVoided) { uDom('#clean') .toggleClass('disabled', tbody .querySelector('tr.tab:not(.canMtx)') === null); } // Synchronize toolbar with content of log uDom('#clear').toggleClass('disabled', tbody.querySelector('tr') === null); readLogBufferAsync(); }; // 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. let readLogBuffer = function () { if (ownerId === undefined) { return; } vAPI.messaging.send('logger-ui.js', { what: 'readMany', ownerId: ownerId }, onLogBufferRead); }; let readLogBufferAsync = function () { if (ownerId === undefined) { return; } vAPI.setTimeout(readLogBuffer, 1200); }; let pageSelectorChanged = function () { let style = document.getElementById('tabFilterer'); let tabClass = document.getElementById('pageSelector').value; let 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'); }; let refreshTab = function () { let tabClass = document.getElementById('pageSelector').value; let matches = tabClass.match(/^tab_(.+)$/); if (matches === null) { return; } if (matches[1] === 'bts') { return; } vAPI.messaging.send('logger-ui.js', { what: 'forceReloadTab', tabId: matches[1] }); }; let onMaxEntriesChanged = function () { let raw = uDom(this).val(); try { maxEntries = parseInt(raw, 10); if (isNaN(maxEntries)) { maxEntries = 0; } } catch (e) { maxEntries = 0; } vAPI.messaging.send('logger-ui.js', { what: 'userSettings', name: 'maxLoggedRequests', value: maxEntries }); truncateLog(maxEntries); }; let rowFilterer = (function () { let filters = []; let parseInput = function () { filters = []; let rawPart, hardBeg, hardEnd; let raw = uDom('#filterInput').val().trim(); let rawParts = raw.split(/\s+/); let reStr, reStrs = [], not = false; let n = rawParts.length; for (let i=0; i=0; --i) { filterOne(rows[i]); } }; let onFilterChangedAsync = (function () { let timer = null; let commit = function () { timer = null; parseInput(); filterAll(); }; return function () { if (timer !== null) { clearTimeout(timer); } timer = vAPI.setTimeout(commit, 750); }; })(); let onFilterButton = function () { let cl = document.body.classList; cl.toggle('f', cl.contains('f') === false); }; uDom('#filterButton').on('click', onFilterButton); uDom('#filterInput').on('input', onFilterChangedAsync); return { filterOne: filterOne, filterAll: filterAll, }; })(); let toJunkyard = function (trs) { trs.remove(); for (let i=trs.length-1; i>=0; --i) { trJunkyard.push(trs.nodeAt(i)); } }; let clearBuffer = function () { let tbody = document.querySelector('#content tbody'); let tr; while (tbody.firstChild !== null) { tr = tbody.lastElementChild; trJunkyard.push(tbody.removeChild(tr)); } uDom('#clear').addClass('disabled'); uDom('#clean').addClass('disabled'); }; let cleanBuffer = function () { let rows = uDom('#content tr.tab:not(.canMtx)').remove(); for (let i=rows.length-1; i>=0; --i) { trJunkyard.push(rows.nodeAt(i)); } uDom('#clean').addClass('disabled'); }; let toggleCompactView = function () { document.body.classList.toggle('compactView'); uDom('#content table .vExpanded').removeClass('vExpanded'); }; let toggleCompactRow = function (ev) { ev.target.parentElement.classList.toggle('vExpanded'); }; let popupManager = (function () { let realTabId = null; let localTabId = null; let container = null; let popup = null; let popupObserver = null; let style = null; let styleTemplate = [ 'tr:not(.tab_{{tabId}}) {', 'cursor: not-allowed;', 'opacity: 0.2;', '}' ].join('\n'); let resizePopup = function () { if (popup === null) { return; } let popupBody = popup.contentWindow.document.body; if (popupBody.clientWidth !== 0 && container.clientWidth !== popupBody.clientWidth) { container.style.setProperty('width', popupBody.clientWidth + 'px'); } popup.style.removeProperty('height'); if (popupBody.clientHeight !== 0 && popup.clientHeight !== popupBody.clientHeight) { popup.style.setProperty('height', popupBody.clientHeight + 'px'); } let ph = document.documentElement.clientHeight; let crect = container.getBoundingClientRect(); if (crect.height > ph) { popup.style.setProperty('height', 'calc(' + ph + 'px - 1.8em)'); } // Adjust width for presence/absence of vertical scroll bar which may // have appeared as a result of last operation. let cw = container.clientWidth; let dw = popup.contentWindow.document.documentElement.clientWidth; if (cw !== dw) { container.style.setProperty('width', (2 * cw - dw) + 'px'); } }; let toggleSize = function () { container.classList.toggle('hide'); }; let onResizeRequested = function () { let popupBody = popup.contentWindow.document.body; if (popupBody.hasAttribute('data-resize-popup') === false) { return; } popupBody.removeAttribute('data-resize-popup'); resizePopup(); }; let onLoad = function () { resizePopup(); let popupBody = popup.contentDocument.body; popupBody.removeAttribute('data-resize-popup'); popupObserver.observe(popupBody, { attributes: true, attributesFilter: [ 'data-resize-popup' ] }); }; let toggleOn = function (td) { let tr = td.parentNode; let matches = tr.className.match(/(?:^| )tab_([^ ]+)/); if (matches === null) { return; } realTabId = localTabId = matches[1]; if (localTabId === 'bts') { realTabId = noTabId; } container = document.getElementById('popupContainer'); container .querySelector('div > span:nth-of-type(1)') .addEventListener('click', toggleSize); container .querySelector('div > span:nth-of-type(2)') .addEventListener('click', toggleOff); popup = document.createElement('iframe'); popup.addEventListener('load', onLoad); popup.setAttribute('src', 'popup.html?tabId=' + realTabId); popupObserver = new MutationObserver(onResizeRequested); container.appendChild(popup); style = document.getElementById('popupFilterer'); style.textContent = styleTemplate.replace('{{tabId}}', localTabId); document.body.classList.add('popupOn'); }; let toggleOff = function () { document.body.classList.remove('popupOn'); container .querySelector('div > span:nth-of-type(1)') .removeEventListener('click', toggleSize); container .querySelector('div > span:nth-of-type(2)') .removeEventListener('click', toggleOff); container.classList.remove('hide'); popup.removeEventListener('load', onLoad); popupObserver.disconnect(); popupObserver = null; popup.setAttribute('src', ''); container.removeChild(popup); popup = null; style.textContent = ''; style = null; container = null; realTabId = null; }; let exports = { toggleOn: function (ev) { if (realTabId === null) { toggleOn(ev.target); } }, toggleOff: function () { if (realTabId !== null) { toggleOff(); } } }; Object.defineProperty(exports, 'tabId', { get: function () { return realTabId || 0; }, }); return exports; })(); let grabView = function () { if (ownerId === undefined) { ownerId = Date.now(); } readLogBufferAsync(); }; let releaseView = function () { if (ownerId === undefined) { return; } vAPI.messaging.send('logger-ui.js', { what: 'releaseView', ownerId: ownerId }); ownerId = undefined; }; window.addEventListener('pagehide', releaseView); window.addEventListener('pageshow', grabView); // https://bugzilla.mozilla.org/show_bug.cgi?id=1398625 window.addEventListener('beforeunload', releaseView); readLogBuffer(); uDom('#pageSelector').on('change', pageSelectorChanged); uDom('#refresh').on('click', refreshTab); uDom('#compactViewToggler').on('click', toggleCompactView); uDom('#clean').on('click', cleanBuffer); uDom('#clear').on('click', clearBuffer); uDom('#maxEntries').on('change', onMaxEntriesChanged); uDom('#content table').on('click', 'tr > td:nth-of-type(1)', toggleCompactRow); uDom('#content table').on('click', 'tr.canMtx > td:nth-of-type(2)', popupManager.toggleOn); })();