logger-ui.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. /*******************************************************************************
  2. ηMatrix - a browser extension to black/white list requests.
  3. Copyright (C) 2015-2019 Raymond Hill
  4. Copyright (C) 2019 Alessio Vanni
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see {http://www.gnu.org/licenses/}.
  15. Home: https://libregit.org/heckyel/ematrix
  16. uMatrix Home: https://github.com/gorhill/sessbench
  17. */
  18. /* global uDom */
  19. 'use strict';
  20. /******************************************************************************/
  21. (function() {
  22. /******************************************************************************/
  23. var tbody = document.querySelector('#content tbody');
  24. var trJunkyard = [];
  25. var tdJunkyard = [];
  26. var firstVarDataCol = 2; // currently, column 2 (0-based index)
  27. var lastVarDataIndex = 3; // currently, d0-d3
  28. var maxEntries = 0;
  29. var noTabId = '';
  30. var allTabIds = {};
  31. var allTabIdsToken;
  32. var ownerId = Date.now();
  33. var emphasizeTemplate = document.querySelector('#emphasizeTemplate > span');
  34. var hiddenTemplate = document.querySelector('#hiddenTemplate > span');
  35. var prettyRequestTypes = {
  36. 'main_frame': 'doc',
  37. 'stylesheet': 'css',
  38. 'sub_frame': 'frame',
  39. 'xmlhttprequest': 'xhr'
  40. };
  41. var dontEmphasizeSet = new Set([
  42. 'COOKIE',
  43. 'CSP',
  44. 'REFERER'
  45. ]);
  46. /******************************************************************************/
  47. // Adjust top padding of content table, to match that of toolbar height.
  48. document.getElementById('content').style.setProperty(
  49. 'margin-top',
  50. document.getElementById('toolbar').clientHeight + 'px'
  51. );
  52. /******************************************************************************/
  53. var classNameFromTabId = function(tabId) {
  54. if ( tabId === noTabId ) {
  55. return 'tab_bts';
  56. }
  57. if ( tabId !== '' ) {
  58. return 'tab_' + tabId;
  59. }
  60. return '';
  61. };
  62. /******************************************************************************/
  63. // Emphasize hostname and cookie name.
  64. var emphasizeCookie = function(s) {
  65. var pnode = emphasizeHostname(s);
  66. if ( pnode.childNodes.length !== 3 ) {
  67. return pnode;
  68. }
  69. var prefix = '-cookie:';
  70. var text = pnode.childNodes[2].textContent;
  71. var beg = text.indexOf(prefix);
  72. if ( beg === -1 ) {
  73. return pnode;
  74. }
  75. beg += prefix.length;
  76. var end = text.indexOf('}', beg);
  77. if ( end === -1 ) {
  78. return pnode;
  79. }
  80. var cnode = emphasizeTemplate.cloneNode(true);
  81. cnode.childNodes[0].textContent = text.slice(0, beg);
  82. cnode.childNodes[1].textContent = text.slice(beg, end);
  83. cnode.childNodes[2].textContent = text.slice(end);
  84. pnode.replaceChild(cnode.childNodes[0], pnode.childNodes[2]);
  85. pnode.appendChild(cnode.childNodes[0]);
  86. pnode.appendChild(cnode.childNodes[0]);
  87. return pnode;
  88. };
  89. /******************************************************************************/
  90. // Emphasize hostname in URL.
  91. var emphasizeHostname = function(url) {
  92. var hnbeg = url.indexOf('://');
  93. if ( hnbeg === -1 ) {
  94. return document.createTextNode(url);
  95. }
  96. hnbeg += 3;
  97. var hnend = url.indexOf('/', hnbeg);
  98. if ( hnend === -1 ) {
  99. hnend = url.slice(hnbeg).search(/\?#/);
  100. if ( hnend !== -1 ) {
  101. hnend += hnbeg;
  102. } else {
  103. hnend = url.length;
  104. }
  105. }
  106. var node = emphasizeTemplate.cloneNode(true);
  107. node.childNodes[0].textContent = url.slice(0, hnbeg);
  108. node.childNodes[1].textContent = url.slice(hnbeg, hnend);
  109. node.childNodes[2].textContent = url.slice(hnend);
  110. return node;
  111. };
  112. /******************************************************************************/
  113. var createCellAt = function(tr, index) {
  114. var td = tr.cells[index];
  115. var mustAppend = !td;
  116. if ( mustAppend ) {
  117. td = tdJunkyard.pop();
  118. }
  119. if ( td ) {
  120. td.removeAttribute('colspan');
  121. td.textContent = '';
  122. } else {
  123. td = document.createElement('td');
  124. }
  125. if ( mustAppend ) {
  126. tr.appendChild(td);
  127. }
  128. return td;
  129. };
  130. /******************************************************************************/
  131. var createRow = function(layout) {
  132. var tr = trJunkyard.pop();
  133. if ( tr ) {
  134. tr.className = '';
  135. } else {
  136. tr = document.createElement('tr');
  137. }
  138. for ( var index = 0; index < firstVarDataCol; index++ ) {
  139. createCellAt(tr, index);
  140. }
  141. var i = 1, span = 1, td;
  142. for (;;) {
  143. td = createCellAt(tr, index);
  144. if ( i === lastVarDataIndex ) {
  145. break;
  146. }
  147. if ( layout.charAt(i) !== '1' ) {
  148. span += 1;
  149. } else {
  150. if ( span !== 1 ) {
  151. td.setAttribute('colspan', span);
  152. }
  153. index += 1;
  154. span = 1;
  155. }
  156. i += 1;
  157. }
  158. if ( span !== 1 ) {
  159. td.setAttribute('colspan', span);
  160. }
  161. index += 1;
  162. while ( (td = tr.cells[index]) ) {
  163. tdJunkyard.push(tr.removeChild(td));
  164. }
  165. return tr;
  166. };
  167. /******************************************************************************/
  168. var createHiddenTextNode = function(text) {
  169. var node = hiddenTemplate.cloneNode(true);
  170. node.textContent = text;
  171. return node;
  172. };
  173. /******************************************************************************/
  174. var padTo2 = function(v) {
  175. return v < 10 ? '0' + v : v;
  176. };
  177. /******************************************************************************/
  178. var createGap = function(tabId, url) {
  179. var tr = createRow('1');
  180. tr.classList.add('doc');
  181. tr.classList.add('tab');
  182. tr.classList.add('canMtx');
  183. tr.classList.add('tab_' + tabId);
  184. tr.cells[firstVarDataCol].textContent = url;
  185. tbody.insertBefore(tr, tbody.firstChild);
  186. };
  187. /******************************************************************************/
  188. var renderLogEntry = function(entry) {
  189. var tr;
  190. var fvdc = firstVarDataCol;
  191. switch ( entry.cat ) {
  192. case 'error':
  193. case 'info':
  194. tr = createRow('1');
  195. if ( entry.d0 === 'cookie' ) {
  196. tr.cells[fvdc].appendChild(emphasizeCookie(entry.d1));
  197. } else {
  198. tr.cells[fvdc].textContent = entry.d0;
  199. }
  200. break;
  201. case 'net':
  202. tr = createRow('111');
  203. tr.classList.add('canMtx');
  204. // If the request is that of a root frame, insert a gap in the table
  205. // in order to visually separate entries for different documents.
  206. if ( entry.d2 === 'doc' && entry.tab !== noTabId ) {
  207. createGap(entry.tab, entry.d1);
  208. }
  209. if ( entry.d3 ) {
  210. tr.classList.add('blocked');
  211. tr.cells[fvdc].textContent = '--';
  212. } else {
  213. tr.cells[fvdc].textContent = '';
  214. }
  215. tr.cells[fvdc+1].textContent = (prettyRequestTypes[entry.d2] || entry.d2);
  216. if ( dontEmphasizeSet.has(entry.d2) ) {
  217. tr.cells[fvdc+2].textContent = entry.d1;
  218. } else if ( entry.d2 === 'cookie' ) {
  219. tr.cells[fvdc+2].appendChild(emphasizeCookie(entry.d1));
  220. } else {
  221. tr.cells[fvdc+2].appendChild(emphasizeHostname(entry.d1));
  222. }
  223. break;
  224. default:
  225. tr = createRow('1');
  226. tr.cells[fvdc].textContent = entry.d0;
  227. break;
  228. }
  229. // Fields common to all rows.
  230. var time = logDate;
  231. time.setTime(entry.tstamp - logDateTimezoneOffset);
  232. tr.cells[0].textContent = padTo2(time.getUTCHours()) + ':' +
  233. padTo2(time.getUTCMinutes()) + ':' +
  234. padTo2(time.getSeconds());
  235. if ( entry.tab ) {
  236. tr.classList.add('tab');
  237. tr.classList.add(classNameFromTabId(entry.tab));
  238. if ( entry.tab === noTabId ) {
  239. tr.cells[1].appendChild(createHiddenTextNode('bts'));
  240. }
  241. }
  242. if ( entry.cat !== '' ) {
  243. tr.classList.add('cat_' + entry.cat);
  244. }
  245. rowFilterer.filterOne(tr, true);
  246. tbody.insertBefore(tr, tbody.firstChild);
  247. };
  248. // Reuse date objects.
  249. var logDate = new Date(),
  250. logDateTimezoneOffset = logDate.getTimezoneOffset() * 60000;
  251. /******************************************************************************/
  252. var renderLogEntries = function(response) {
  253. var entries = response.entries;
  254. if ( entries.length === 0 ) {
  255. return;
  256. }
  257. // Preserve scroll position
  258. var height = tbody.offsetHeight;
  259. var tabIds = response.tabIds;
  260. var n = entries.length;
  261. var entry;
  262. for ( var i = 0; i < n; i++ ) {
  263. entry = entries[i];
  264. // Unlikely, but it may happen
  265. if ( entry.tab && tabIds.hasOwnProperty(entry.tab) === false ) {
  266. continue;
  267. }
  268. renderLogEntry(entries[i]);
  269. }
  270. // Prevent logger from growing infinitely and eating all memory. For
  271. // instance someone could forget that it is left opened for some
  272. // dynamically refreshed pages.
  273. truncateLog(maxEntries);
  274. var yDelta = tbody.offsetHeight - height;
  275. if ( yDelta === 0 ) {
  276. return;
  277. }
  278. // Chromium:
  279. // body.scrollTop = good value
  280. // body.parentNode.scrollTop = 0
  281. if ( document.body.scrollTop !== 0 ) {
  282. document.body.scrollTop += yDelta;
  283. return;
  284. }
  285. // Firefox:
  286. // body.scrollTop = 0
  287. // body.parentNode.scrollTop = good value
  288. var parentNode = document.body.parentNode;
  289. if ( parentNode && parentNode.scrollTop !== 0 ) {
  290. parentNode.scrollTop += yDelta;
  291. }
  292. };
  293. /******************************************************************************/
  294. var synchronizeTabIds = function(newTabIds) {
  295. var oldTabIds = allTabIds;
  296. var autoDeleteVoidRows = !!vAPI.localStorage.getItem('loggerAutoDeleteVoidRows');
  297. var rowVoided = false;
  298. var trs;
  299. for ( var tabId in oldTabIds ) {
  300. if ( oldTabIds.hasOwnProperty(tabId) === false ) {
  301. continue;
  302. }
  303. if ( newTabIds.hasOwnProperty(tabId) ) {
  304. continue;
  305. }
  306. // Mark or remove voided rows
  307. trs = uDom('.tab_' + tabId);
  308. if ( autoDeleteVoidRows ) {
  309. toJunkyard(trs);
  310. } else {
  311. trs.removeClass('canMtx');
  312. rowVoided = true;
  313. }
  314. // Remove popup if it is currently bound to a removed tab.
  315. if ( tabId === popupManager.tabId ) {
  316. popupManager.toggleOff();
  317. }
  318. }
  319. var select = document.getElementById('pageSelector');
  320. var selectValue = select.value;
  321. var tabIds = Object.keys(newTabIds).sort(function(a, b) {
  322. return newTabIds[a].localeCompare(newTabIds[b]);
  323. });
  324. var option;
  325. for ( var i = 0, j = 2; i < tabIds.length; i++ ) {
  326. tabId = tabIds[i];
  327. if ( tabId === noTabId ) {
  328. continue;
  329. }
  330. option = select.options[j];
  331. j += 1;
  332. if ( !option ) {
  333. option = document.createElement('option');
  334. select.appendChild(option);
  335. }
  336. option.textContent = newTabIds[tabId];
  337. option.value = classNameFromTabId(tabId);
  338. if ( option.value === selectValue ) {
  339. option.setAttribute('selected', '');
  340. } else {
  341. option.removeAttribute('selected');
  342. }
  343. }
  344. while ( j < select.options.length ) {
  345. select.removeChild(select.options[j]);
  346. }
  347. if ( select.value !== selectValue ) {
  348. select.selectedIndex = 0;
  349. select.value = '';
  350. select.options[0].setAttribute('selected', '');
  351. pageSelectorChanged();
  352. }
  353. allTabIds = newTabIds;
  354. return rowVoided;
  355. };
  356. /******************************************************************************/
  357. var truncateLog = function(size) {
  358. if ( size === 0 ) {
  359. size = 5000;
  360. }
  361. var tbody = document.querySelector('#content tbody');
  362. size = Math.min(size, 10000);
  363. var tr;
  364. while ( tbody.childElementCount > size ) {
  365. tr = tbody.lastElementChild;
  366. trJunkyard.push(tbody.removeChild(tr));
  367. }
  368. };
  369. /******************************************************************************/
  370. var onLogBufferRead = function(response) {
  371. if ( !response || response.unavailable ) {
  372. readLogBufferAsync();
  373. return;
  374. }
  375. // This tells us the behind-the-scene tab id
  376. noTabId = response.noTabId;
  377. // This may have changed meanwhile
  378. if ( response.maxLoggedRequests !== maxEntries ) {
  379. maxEntries = response.maxLoggedRequests;
  380. uDom('#maxEntries').val(maxEntries || '');
  381. }
  382. // Neuter rows for which a tab does not exist anymore
  383. var rowVoided = false;
  384. if ( response.tabIdsToken !== allTabIdsToken ) {
  385. rowVoided = synchronizeTabIds(response.tabIds);
  386. allTabIdsToken = response.tabIdsToken;
  387. }
  388. renderLogEntries(response);
  389. if ( rowVoided ) {
  390. uDom('#clean').toggleClass(
  391. 'disabled',
  392. tbody.querySelector('tr.tab:not(.canMtx)') === null
  393. );
  394. }
  395. // Synchronize toolbar with content of log
  396. uDom('#clear').toggleClass(
  397. 'disabled',
  398. tbody.querySelector('tr') === null
  399. );
  400. readLogBufferAsync();
  401. };
  402. /******************************************************************************/
  403. // This can be called only once, at init time. After that, this will be called
  404. // automatically. If called after init time, this will be messy, and this would
  405. // require a bit more code to ensure no multi time out events.
  406. var readLogBuffer = function() {
  407. if ( ownerId === undefined ) { return; }
  408. vAPI.messaging.send(
  409. 'logger-ui.js',
  410. { what: 'readMany', ownerId: ownerId },
  411. onLogBufferRead
  412. );
  413. };
  414. var readLogBufferAsync = function() {
  415. if ( ownerId === undefined ) { return; }
  416. vAPI.setTimeout(readLogBuffer, 1200);
  417. };
  418. /******************************************************************************/
  419. var pageSelectorChanged = function() {
  420. var style = document.getElementById('tabFilterer');
  421. var tabClass = document.getElementById('pageSelector').value;
  422. var sheet = style.sheet;
  423. while ( sheet.cssRules.length !== 0 ) {
  424. sheet.deleteRule(0);
  425. }
  426. if ( tabClass !== '' ) {
  427. sheet.insertRule(
  428. '#content table tr:not(.' + tabClass + ') { display: none; }',
  429. 0
  430. );
  431. }
  432. uDom('#refresh').toggleClass(
  433. 'disabled',
  434. tabClass === '' || tabClass === 'tab_bts'
  435. );
  436. };
  437. /******************************************************************************/
  438. var refreshTab = function() {
  439. var tabClass = document.getElementById('pageSelector').value;
  440. var matches = tabClass.match(/^tab_(.+)$/);
  441. if ( matches === null ) {
  442. return;
  443. }
  444. if ( matches[1] === 'bts' ) {
  445. return;
  446. }
  447. vAPI.messaging.send(
  448. 'logger-ui.js',
  449. { what: 'forceReloadTab', tabId: matches[1] }
  450. );
  451. };
  452. /******************************************************************************/
  453. var onMaxEntriesChanged = function() {
  454. var raw = uDom(this).val();
  455. try {
  456. maxEntries = parseInt(raw, 10);
  457. if ( isNaN(maxEntries) ) {
  458. maxEntries = 0;
  459. }
  460. } catch (e) {
  461. maxEntries = 0;
  462. }
  463. vAPI.messaging.send('logger-ui.js', {
  464. what: 'userSettings',
  465. name: 'maxLoggedRequests',
  466. value: maxEntries
  467. });
  468. truncateLog(maxEntries);
  469. };
  470. /******************************************************************************/
  471. var rowFilterer = (function() {
  472. var filters = [];
  473. var parseInput = function() {
  474. filters = [];
  475. var rawPart, hardBeg, hardEnd;
  476. var raw = uDom('#filterInput').val().trim();
  477. var rawParts = raw.split(/\s+/);
  478. var reStr, reStrs = [], not = false;
  479. var n = rawParts.length;
  480. for ( var i = 0; i < n; i++ ) {
  481. rawPart = rawParts[i];
  482. if ( rawPart.charAt(0) === '!' ) {
  483. if ( reStrs.length === 0 ) {
  484. not = true;
  485. }
  486. rawPart = rawPart.slice(1);
  487. }
  488. hardBeg = rawPart.charAt(0) === '|';
  489. if ( hardBeg ) {
  490. rawPart = rawPart.slice(1);
  491. }
  492. hardEnd = rawPart.slice(-1) === '|';
  493. if ( hardEnd ) {
  494. rawPart = rawPart.slice(0, -1);
  495. }
  496. if ( rawPart === '' ) {
  497. continue;
  498. }
  499. // https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions
  500. reStr = rawPart.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  501. if ( hardBeg ) {
  502. reStr = '(?:^|\\s)' + reStr;
  503. }
  504. if ( hardEnd ) {
  505. reStr += '(?:\\s|$)';
  506. }
  507. reStrs.push(reStr);
  508. if ( i < (n - 1) && rawParts[i + 1] === '||' ) {
  509. i += 1;
  510. continue;
  511. }
  512. reStr = reStrs.length === 1 ? reStrs[0] : reStrs.join('|');
  513. filters.push({
  514. re: new RegExp(reStr, 'i'),
  515. r: !not
  516. });
  517. reStrs = [];
  518. not = false;
  519. }
  520. };
  521. var filterOne = function(tr, clean) {
  522. var ff = filters;
  523. var fcount = ff.length;
  524. if ( fcount === 0 && clean === true ) {
  525. return;
  526. }
  527. // do not filter out doc boundaries, they help separate important
  528. // section of log.
  529. var cl = tr.classList;
  530. if ( cl.contains('doc') ) {
  531. return;
  532. }
  533. if ( fcount === 0 ) {
  534. cl.remove('f');
  535. return;
  536. }
  537. var cc = tr.cells;
  538. var ccount = cc.length;
  539. var hit, j, f;
  540. // each filter expression must hit (implicit and-op)
  541. // if...
  542. // positive filter expression = there must one hit on any field
  543. // negative filter expression = there must be no hit on all fields
  544. for ( var i = 0; i < fcount; i++ ) {
  545. f = ff[i];
  546. hit = !f.r;
  547. for ( j = 0; j < ccount; j++ ) {
  548. if ( f.re.test(cc[j].textContent) ) {
  549. hit = f.r;
  550. break;
  551. }
  552. }
  553. if ( !hit ) {
  554. cl.add('f');
  555. return;
  556. }
  557. }
  558. cl.remove('f');
  559. };
  560. var filterAll = function() {
  561. // Special case: no filter
  562. if ( filters.length === 0 ) {
  563. uDom('#content tr').removeClass('f');
  564. return;
  565. }
  566. var tbody = document.querySelector('#content tbody');
  567. var rows = tbody.rows;
  568. var i = rows.length;
  569. while ( i-- ) {
  570. filterOne(rows[i]);
  571. }
  572. };
  573. var onFilterChangedAsync = (function() {
  574. var timer = null;
  575. var commit = function() {
  576. timer = null;
  577. parseInput();
  578. filterAll();
  579. };
  580. return function() {
  581. if ( timer !== null ) {
  582. clearTimeout(timer);
  583. }
  584. timer = vAPI.setTimeout(commit, 750);
  585. };
  586. })();
  587. var onFilterButton = function() {
  588. var cl = document.body.classList;
  589. cl.toggle('f', cl.contains('f') === false);
  590. };
  591. uDom('#filterButton').on('click', onFilterButton);
  592. uDom('#filterInput').on('input', onFilterChangedAsync);
  593. return {
  594. filterOne: filterOne,
  595. filterAll: filterAll
  596. };
  597. })();
  598. /******************************************************************************/
  599. var toJunkyard = function(trs) {
  600. trs.remove();
  601. var i = trs.length;
  602. while ( i-- ) {
  603. trJunkyard.push(trs.nodeAt(i));
  604. }
  605. };
  606. /******************************************************************************/
  607. var clearBuffer = function() {
  608. var tbody = document.querySelector('#content tbody');
  609. var tr;
  610. while ( tbody.firstChild !== null ) {
  611. tr = tbody.lastElementChild;
  612. trJunkyard.push(tbody.removeChild(tr));
  613. }
  614. uDom('#clear').addClass('disabled');
  615. uDom('#clean').addClass('disabled');
  616. };
  617. /******************************************************************************/
  618. var cleanBuffer = function() {
  619. var rows = uDom('#content tr.tab:not(.canMtx)').remove();
  620. var i = rows.length;
  621. while ( i-- ) {
  622. trJunkyard.push(rows.nodeAt(i));
  623. }
  624. uDom('#clean').addClass('disabled');
  625. };
  626. /******************************************************************************/
  627. var toggleCompactView = function() {
  628. document.body.classList.toggle('compactView');
  629. uDom('#content table .vExpanded').removeClass('vExpanded');
  630. };
  631. var toggleCompactRow = function(ev) {
  632. ev.target.parentElement.classList.toggle('vExpanded');
  633. };
  634. /******************************************************************************/
  635. var popupManager = (function() {
  636. var realTabId = null;
  637. var localTabId = null;
  638. var container = null;
  639. var popup = null;
  640. var popupObserver = null;
  641. var style = null;
  642. var styleTemplate = [
  643. 'tr:not(.tab_{{tabId}}) {',
  644. 'cursor: not-allowed;',
  645. 'opacity: 0.2;',
  646. '}'
  647. ].join('\n');
  648. var resizePopup = function() {
  649. if ( popup === null ) {
  650. return;
  651. }
  652. var popupBody = popup.contentWindow.document.body;
  653. if ( popupBody.clientWidth !== 0 && container.clientWidth !== popupBody.clientWidth ) {
  654. container.style.setProperty('width', popupBody.clientWidth + 'px');
  655. }
  656. popup.style.removeProperty('height');
  657. if ( popupBody.clientHeight !== 0 && popup.clientHeight !== popupBody.clientHeight ) {
  658. popup.style.setProperty('height', popupBody.clientHeight + 'px');
  659. }
  660. var ph = document.documentElement.clientHeight;
  661. var crect = container.getBoundingClientRect();
  662. if ( crect.height > ph ) {
  663. popup.style.setProperty('height', 'calc(' + ph + 'px - 1.8em)');
  664. }
  665. // Adjust width for presence/absence of vertical scroll bar which may
  666. // have appeared as a result of last operation.
  667. var cw = container.clientWidth;
  668. var dw = popup.contentWindow.document.documentElement.clientWidth;
  669. if ( cw !== dw ) {
  670. container.style.setProperty('width', (2 * cw - dw) + 'px');
  671. }
  672. };
  673. var toggleSize = function() {
  674. container.classList.toggle('hide');
  675. };
  676. var onResizeRequested = function() {
  677. var popupBody = popup.contentWindow.document.body;
  678. if ( popupBody.hasAttribute('data-resize-popup') === false ) {
  679. return;
  680. }
  681. popupBody.removeAttribute('data-resize-popup');
  682. resizePopup();
  683. };
  684. var onLoad = function() {
  685. resizePopup();
  686. var popupBody = popup.contentDocument.body;
  687. popupBody.removeAttribute('data-resize-popup');
  688. popupObserver.observe(popupBody, {
  689. attributes: true,
  690. attributesFilter: [ 'data-resize-popup' ]
  691. });
  692. };
  693. var toggleOn = function(td) {
  694. var tr = td.parentNode;
  695. var matches = tr.className.match(/(?:^| )tab_([^ ]+)/);
  696. if ( matches === null ) {
  697. return;
  698. }
  699. realTabId = localTabId = matches[1];
  700. if ( localTabId === 'bts' ) {
  701. realTabId = noTabId;
  702. }
  703. container = document.getElementById('popupContainer');
  704. container.querySelector('div > span:nth-of-type(1)').addEventListener('click', toggleSize);
  705. container.querySelector('div > span:nth-of-type(2)').addEventListener('click', toggleOff);
  706. popup = document.createElement('iframe');
  707. popup.addEventListener('load', onLoad);
  708. popup.setAttribute('src', 'popup.html?tabId=' + realTabId);
  709. popupObserver = new MutationObserver(onResizeRequested);
  710. container.appendChild(popup);
  711. style = document.getElementById('popupFilterer');
  712. style.textContent = styleTemplate.replace('{{tabId}}', localTabId);
  713. document.body.classList.add('popupOn');
  714. };
  715. var toggleOff = function() {
  716. document.body.classList.remove('popupOn');
  717. container.querySelector('div > span:nth-of-type(1)').removeEventListener('click', toggleSize);
  718. container.querySelector('div > span:nth-of-type(2)').removeEventListener('click', toggleOff);
  719. container.classList.remove('hide');
  720. popup.removeEventListener('load', onLoad);
  721. popupObserver.disconnect();
  722. popupObserver = null;
  723. popup.setAttribute('src', '');
  724. container.removeChild(popup);
  725. popup = null;
  726. style.textContent = '';
  727. style = null;
  728. container = null;
  729. realTabId = null;
  730. };
  731. var exports = {
  732. toggleOn: function(ev) {
  733. if ( realTabId === null ) {
  734. toggleOn(ev.target);
  735. }
  736. },
  737. toggleOff: function() {
  738. if ( realTabId !== null ) {
  739. toggleOff();
  740. }
  741. }
  742. };
  743. Object.defineProperty(exports, 'tabId', {
  744. get: function() { return realTabId || 0; }
  745. });
  746. return exports;
  747. })();
  748. /******************************************************************************/
  749. var grabView = function() {
  750. if ( ownerId === undefined ) {
  751. ownerId = Date.now();
  752. }
  753. readLogBufferAsync();
  754. };
  755. var releaseView = function() {
  756. if ( ownerId === undefined ) { return; }
  757. vAPI.messaging.send(
  758. 'logger-ui.js',
  759. { what: 'releaseView', ownerId: ownerId }
  760. );
  761. ownerId = undefined;
  762. };
  763. window.addEventListener('pagehide', releaseView);
  764. window.addEventListener('pageshow', grabView);
  765. // https://bugzilla.mozilla.org/show_bug.cgi?id=1398625
  766. window.addEventListener('beforeunload', releaseView);
  767. /******************************************************************************/
  768. readLogBuffer();
  769. uDom('#pageSelector').on('change', pageSelectorChanged);
  770. uDom('#refresh').on('click', refreshTab);
  771. uDom('#compactViewToggler').on('click', toggleCompactView);
  772. uDom('#clean').on('click', cleanBuffer);
  773. uDom('#clear').on('click', clearBuffer);
  774. uDom('#maxEntries').on('change', onMaxEntriesChanged);
  775. uDom('#content table').on('click', 'tr > td:nth-of-type(1)', toggleCompactRow);
  776. uDom('#content table').on('click', 'tr.canMtx > td:nth-of-type(2)', popupManager.toggleOn);
  777. /******************************************************************************/
  778. })();