You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

867 lines
25 KiB

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