aboutsummaryrefslogtreecommitdiffstats
path: root/js/logger-ui.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/logger-ui.js')
-rw-r--r--js/logger-ui.js908
1 files changed, 908 insertions, 0 deletions
diff --git a/js/logger-ui.js b/js/logger-ui.js
new file mode 100644
index 0000000..a3e9d9b
--- /dev/null
+++ b/js/logger-ui.js
@@ -0,0 +1,908 @@
+/*******************************************************************************
+
+ ηMatrix - a browser extension to black/white list requests.
+ Copyright (C) 2015-2019 Raymond Hill
+ Copyright (C) 2019 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/}.
+
+ uMatrix Home: https://github.com/gorhill/sessbench
+*/
+
+/* global uDom */
+
+'use strict';
+
+/******************************************************************************/
+
+(function() {
+
+/******************************************************************************/
+
+var tbody = document.querySelector('#content tbody');
+var trJunkyard = [];
+var tdJunkyard = [];
+var firstVarDataCol = 2; // currently, column 2 (0-based index)
+var lastVarDataIndex = 3; // currently, d0-d3
+var maxEntries = 0;
+var noTabId = '';
+var allTabIds = {};
+var allTabIdsToken;
+var ownerId = Date.now();
+
+var emphasizeTemplate = document.querySelector('#emphasizeTemplate > span');
+var hiddenTemplate = document.querySelector('#hiddenTemplate > span');
+
+var prettyRequestTypes = {
+ 'main_frame': 'doc',
+ 'stylesheet': 'css',
+ 'sub_frame': 'frame',
+ 'xmlhttprequest': 'xhr'
+};
+
+var 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'
+);
+
+/******************************************************************************/
+
+var classNameFromTabId = function(tabId) {
+ if ( tabId === noTabId ) {
+ return 'tab_bts';
+ }
+ if ( tabId !== '' ) {
+ return 'tab_' + tabId;
+ }
+ return '';
+};
+
+/******************************************************************************/
+
+// Emphasize hostname and cookie name.
+
+var emphasizeCookie = function(s) {
+ var pnode = emphasizeHostname(s);
+ if ( pnode.childNodes.length !== 3 ) {
+ return pnode;
+ }
+ var prefix = '-cookie:';
+ var text = pnode.childNodes[2].textContent;
+ var beg = text.indexOf(prefix);
+ if ( beg === -1 ) {
+ return pnode;
+ }
+ beg += prefix.length;
+ var end = text.indexOf('}', beg);
+ if ( end === -1 ) {
+ return pnode;
+ }
+ var 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.
+
+var emphasizeHostname = function(url) {
+ var hnbeg = url.indexOf('://');
+ if ( hnbeg === -1 ) {
+ return document.createTextNode(url);
+ }
+ hnbeg += 3;
+
+ var hnend = url.indexOf('/', hnbeg);
+ if ( hnend === -1 ) {
+ hnend = url.slice(hnbeg).search(/\?#/);
+ if ( hnend !== -1 ) {
+ hnend += hnbeg;
+ } else {
+ hnend = url.length;
+ }
+ }
+
+ var 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;
+};
+
+/******************************************************************************/
+
+var createCellAt = function(tr, index) {
+ var td = tr.cells[index];
+ var 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;
+};
+
+/******************************************************************************/
+
+var createRow = function(layout) {
+ var tr = trJunkyard.pop();
+ if ( tr ) {
+ tr.className = '';
+ } else {
+ tr = document.createElement('tr');
+ }
+ for ( var index = 0; index < firstVarDataCol; index++ ) {
+ createCellAt(tr, index);
+ }
+ var i = 1, span = 1, td;
+ for (;;) {
+ td = createCellAt(tr, index);
+ if ( i === lastVarDataIndex ) {
+ break;
+ }
+ if ( layout.charAt(i) !== '1' ) {
+ span += 1;
+ } else {
+ if ( span !== 1 ) {
+ td.setAttribute('colspan', span);
+ }
+ index += 1;
+ span = 1;
+ }
+ i += 1;
+ }
+ if ( span !== 1 ) {
+ td.setAttribute('colspan', span);
+ }
+ index += 1;
+ while ( (td = tr.cells[index]) ) {
+ tdJunkyard.push(tr.removeChild(td));
+ }
+ return tr;
+};
+
+/******************************************************************************/
+
+var createHiddenTextNode = function(text) {
+ var node = hiddenTemplate.cloneNode(true);
+ node.textContent = text;
+ return node;
+};
+
+/******************************************************************************/
+
+var padTo2 = function(v) {
+ return v < 10 ? '0' + v : v;
+};
+
+/******************************************************************************/
+
+var createGap = function(tabId, url) {
+ var tr = createRow('1');
+ tr.classList.add('doc');
+ tr.classList.add('tab');
+ tr.classList.add('canMtx');
+ tr.classList.add('tab_' + tabId);
+ tr.cells[firstVarDataCol].textContent = url;
+ tbody.insertBefore(tr, tbody.firstChild);
+};
+
+/******************************************************************************/
+
+var renderLogEntry = function(entry) {
+ var tr;
+ var fvdc = firstVarDataCol;
+
+ switch ( entry.cat ) {
+ case 'error':
+ case 'info':
+ tr = createRow('1');
+ if ( entry.d0 === 'cookie' ) {
+ tr.cells[fvdc].appendChild(emphasizeCookie(entry.d1));
+ } else {
+ tr.cells[fvdc].textContent = entry.d0;
+ }
+ break;
+
+ case 'net':
+ tr = createRow('111');
+ tr.classList.add('canMtx');
+ // If the request is that of a root frame, insert a gap in the table
+ // in order to visually separate entries for different documents.
+ if ( entry.d2 === 'doc' && entry.tab !== noTabId ) {
+ createGap(entry.tab, entry.d1);
+ }
+ if ( entry.d3 ) {
+ tr.classList.add('blocked');
+ tr.cells[fvdc].textContent = '--';
+ } else {
+ tr.cells[fvdc].textContent = '';
+ }
+ tr.cells[fvdc+1].textContent = (prettyRequestTypes[entry.d2] || entry.d2);
+ if ( dontEmphasizeSet.has(entry.d2) ) {
+ tr.cells[fvdc+2].textContent = entry.d1;
+ } else if ( entry.d2 === 'cookie' ) {
+ tr.cells[fvdc+2].appendChild(emphasizeCookie(entry.d1));
+ } else {
+ tr.cells[fvdc+2].appendChild(emphasizeHostname(entry.d1));
+ }
+ break;
+
+ default:
+ tr = createRow('1');
+ tr.cells[fvdc].textContent = entry.d0;
+ break;
+ }
+
+ // Fields common to all rows.
+ var time = logDate;
+ time.setTime(entry.tstamp - logDateTimezoneOffset);
+ tr.cells[0].textContent = padTo2(time.getUTCHours()) + ':' +
+ padTo2(time.getUTCMinutes()) + ':' +
+ padTo2(time.getSeconds());
+
+ if ( entry.tab ) {
+ tr.classList.add('tab');
+ tr.classList.add(classNameFromTabId(entry.tab));
+ if ( entry.tab === noTabId ) {
+ tr.cells[1].appendChild(createHiddenTextNode('bts'));
+ }
+ }
+ if ( entry.cat !== '' ) {
+ tr.classList.add('cat_' + entry.cat);
+ }
+
+ rowFilterer.filterOne(tr, true);
+
+ tbody.insertBefore(tr, tbody.firstChild);
+};
+
+// Reuse date objects.
+var logDate = new Date(),
+ logDateTimezoneOffset = logDate.getTimezoneOffset() * 60000;
+
+/******************************************************************************/
+
+var renderLogEntries = function(response) {
+ var entries = response.entries;
+ if ( entries.length === 0 ) {
+ return;
+ }
+
+ // Preserve scroll position
+ var height = tbody.offsetHeight;
+
+ var tabIds = response.tabIds;
+ var n = entries.length;
+ var entry;
+ for ( var i = 0; i < n; i++ ) {
+ entry = entries[i];
+ // Unlikely, but it may happen
+ if ( entry.tab && tabIds.hasOwnProperty(entry.tab) === false ) {
+ continue;
+ }
+ renderLogEntry(entries[i]);
+ }
+
+ // Prevent logger from growing infinitely and eating all memory. For
+ // instance someone could forget that it is left opened for some
+ // dynamically refreshed pages.
+ truncateLog(maxEntries);
+
+ var yDelta = tbody.offsetHeight - height;
+ if ( yDelta === 0 ) {
+ return;
+ }
+
+ // Chromium:
+ // body.scrollTop = good value
+ // body.parentNode.scrollTop = 0
+ if ( document.body.scrollTop !== 0 ) {
+ document.body.scrollTop += yDelta;
+ return;
+ }
+
+ // Firefox:
+ // body.scrollTop = 0
+ // body.parentNode.scrollTop = good value
+ var parentNode = document.body.parentNode;
+ if ( parentNode && parentNode.scrollTop !== 0 ) {
+ parentNode.scrollTop += yDelta;
+ }
+};
+
+/******************************************************************************/
+
+var synchronizeTabIds = function(newTabIds) {
+ var oldTabIds = allTabIds;
+ 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;
+ }
+ var tbody = document.querySelector('#content tbody');
+ size = Math.min(size, 10000);
+ var tr;
+ while ( tbody.childElementCount > size ) {
+ tr = tbody.lastElementChild;
+ trJunkyard.push(tbody.removeChild(tr));
+ }
+};
+
+/******************************************************************************/
+
+var 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
+ var 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.
+
+var readLogBuffer = function() {
+ if ( ownerId === undefined ) { return; }
+ vAPI.messaging.send(
+ 'logger-ui.js',
+ { what: 'readMany', ownerId: ownerId },
+ onLogBufferRead
+ );
+};
+
+var readLogBufferAsync = function() {
+ if ( ownerId === undefined ) { return; }
+ vAPI.setTimeout(readLogBuffer, 1200);
+};
+
+/******************************************************************************/
+
+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 refreshTab = function() {
+ var tabClass = document.getElementById('pageSelector').value;
+ var matches = tabClass.match(/^tab_(.+)$/);
+ if ( matches === null ) {
+ return;
+ }
+ if ( matches[1] === 'bts' ) {
+ return;
+ }
+ vAPI.messaging.send(
+ 'logger-ui.js',
+ { what: 'forceReloadTab', tabId: matches[1] }
+ );
+};
+
+/******************************************************************************/
+
+var onMaxEntriesChanged = function() {
+ var 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);
+};
+
+/******************************************************************************/
+
+var rowFilterer = (function() {
+ var filters = [];
+
+ var parseInput = function() {
+ filters = [];
+
+ var rawPart, hardBeg, hardEnd;
+ var raw = uDom('#filterInput').val().trim();
+ var rawParts = raw.split(/\s+/);
+ var reStr, reStrs = [], not = false;
+ var n = rawParts.length;
+ for ( var i = 0; i < n; i++ ) {
+ rawPart = rawParts[i];
+ if ( rawPart.charAt(0) === '!' ) {
+ if ( reStrs.length === 0 ) {
+ not = true;
+ }
+ rawPart = rawPart.slice(1);
+ }
+ hardBeg = rawPart.charAt(0) === '|';
+ if ( hardBeg ) {
+ rawPart = rawPart.slice(1);
+ }
+ hardEnd = rawPart.slice(-1) === '|';
+ if ( hardEnd ) {
+ rawPart = rawPart.slice(0, -1);
+ }
+ if ( rawPart === '' ) {
+ continue;
+ }
+ // https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions
+ reStr = rawPart.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ if ( hardBeg ) {
+ reStr = '(?:^|\\s)' + reStr;
+ }
+ if ( hardEnd ) {
+ reStr += '(?:\\s|$)';
+ }
+ reStrs.push(reStr);
+ if ( i < (n - 1) && rawParts[i + 1] === '||' ) {
+ i += 1;
+ continue;
+ }
+ reStr = reStrs.length === 1 ? reStrs[0] : reStrs.join('|');
+ filters.push({
+ re: new RegExp(reStr, 'i'),
+ r: !not
+ });
+ reStrs = [];
+ not = false;
+ }
+ };
+
+ var filterOne = function(tr, clean) {
+ var ff = filters;
+ var fcount = ff.length;
+ if ( fcount === 0 && clean === true ) {
+ return;
+ }
+ // do not filter out doc boundaries, they help separate important
+ // section of log.
+ var cl = tr.classList;
+ if ( cl.contains('doc') ) {
+ return;
+ }
+ if ( fcount === 0 ) {
+ cl.remove('f');
+ return;
+ }
+ var cc = tr.cells;
+ var ccount = cc.length;
+ var hit, j, f;
+ // each filter expression must hit (implicit and-op)
+ // if...
+ // positive filter expression = there must one hit on any field
+ // negative filter expression = there must be no hit on all fields
+ for ( var i = 0; i < fcount; i++ ) {
+ f = ff[i];
+ hit = !f.r;
+ for ( j = 0; j < ccount; j++ ) {
+ if ( f.re.test(cc[j].textContent) ) {
+ hit = f.r;
+ break;
+ }
+ }
+ if ( !hit ) {
+ cl.add('f');
+ return;
+ }
+ }
+ cl.remove('f');
+ };
+
+ var filterAll = function() {
+ // Special case: no filter
+ if ( filters.length === 0 ) {
+ uDom('#content tr').removeClass('f');
+ return;
+ }
+ var tbody = document.querySelector('#content tbody');
+ var rows = tbody.rows;
+ var i = rows.length;
+ while ( i-- ) {
+ filterOne(rows[i]);
+ }
+ };
+
+ var onFilterChangedAsync = (function() {
+ var timer = null;
+ var commit = function() {
+ timer = null;
+ parseInput();
+ filterAll();
+ };
+ return function() {
+ if ( timer !== null ) {
+ clearTimeout(timer);
+ }
+ timer = vAPI.setTimeout(commit, 750);
+ };
+ })();
+
+ var onFilterButton = function() {
+ var 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
+ };
+})();
+
+/******************************************************************************/
+
+var toJunkyard = function(trs) {
+ trs.remove();
+ var i = trs.length;
+ while ( i-- ) {
+ trJunkyard.push(trs.nodeAt(i));
+ }
+};
+
+/******************************************************************************/
+
+var clearBuffer = function() {
+ var tbody = document.querySelector('#content tbody');
+ var tr;
+ while ( tbody.firstChild !== null ) {
+ tr = tbody.lastElementChild;
+ trJunkyard.push(tbody.removeChild(tr));
+ }
+ uDom('#clear').addClass('disabled');
+ uDom('#clean').addClass('disabled');
+};
+
+/******************************************************************************/
+
+var cleanBuffer = function() {
+ var rows = uDom('#content tr.tab:not(.canMtx)').remove();
+ var i = rows.length;
+ while ( i-- ) {
+ trJunkyard.push(rows.nodeAt(i));
+ }
+ uDom('#clean').addClass('disabled');
+};
+
+/******************************************************************************/
+
+var toggleCompactView = function() {
+ document.body.classList.toggle('compactView');
+ uDom('#content table .vExpanded').removeClass('vExpanded');
+};
+
+var toggleCompactRow = function(ev) {
+ ev.target.parentElement.classList.toggle('vExpanded');
+};
+
+/******************************************************************************/
+
+var popupManager = (function() {
+ var realTabId = null;
+ var localTabId = null;
+ var container = null;
+ var popup = null;
+ var popupObserver = null;
+ var style = null;
+ var styleTemplate = [
+ 'tr:not(.tab_{{tabId}}) {',
+ 'cursor: not-allowed;',
+ 'opacity: 0.2;',
+ '}'
+ ].join('\n');
+
+ var resizePopup = function() {
+ if ( popup === null ) {
+ return;
+ }
+ var 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');
+ }
+ var ph = document.documentElement.clientHeight;
+ var 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.
+ var cw = container.clientWidth;
+ var dw = popup.contentWindow.document.documentElement.clientWidth;
+ if ( cw !== dw ) {
+ container.style.setProperty('width', (2 * cw - dw) + 'px');
+ }
+ };
+
+ var toggleSize = function() {
+ container.classList.toggle('hide');
+ };
+
+ var onResizeRequested = function() {
+ var popupBody = popup.contentWindow.document.body;
+ if ( popupBody.hasAttribute('data-resize-popup') === false ) {
+ return;
+ }
+ popupBody.removeAttribute('data-resize-popup');
+ resizePopup();
+ };
+
+ var onLoad = function() {
+ resizePopup();
+ var popupBody = popup.contentDocument.body;
+ popupBody.removeAttribute('data-resize-popup');
+ popupObserver.observe(popupBody, {
+ attributes: true,
+ attributesFilter: [ 'data-resize-popup' ]
+ });
+ };
+
+ var toggleOn = function(td) {
+ var tr = td.parentNode;
+ var 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');
+ };
+
+ var 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;
+ };
+
+ var 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;
+})();
+
+/******************************************************************************/
+
+var grabView = function() {
+ if ( ownerId === undefined ) {
+ ownerId = Date.now();
+ }
+ readLogBufferAsync();
+};
+
+var 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);
+
+/******************************************************************************/
+
+})();