logger-ui.js 23 KB

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