aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgorhill <rhill@raymondhill.net>2015-06-28 17:42:08 -0400
committergorhill <rhill@raymondhill.net>2015-06-28 17:42:08 -0400
commit39b0d719c065a042d7414690296477feaa524630 (patch)
tree545af8311f4ca13aa609869fa2ed96b1cd159cd5
parent58b655220712fc2b22e61be03b80d81fa2bb7e30 (diff)
downloaduBlock-39b0d719c065a042d7414690296477feaa524630.zip
uBlock-39b0d719c065a042d7414690296477feaa524630.tar.gz
uBlock-39b0d719c065a042d7414690296477feaa524630.tar.bz2
some more work re inspector
-rw-r--r--platform/chromium/vapi-client.js8
-rw-r--r--platform/firefox/vapi-client.js8
-rw-r--r--src/css/logger-ui-inspector.css88
-rw-r--r--src/css/logger-ui.css89
-rw-r--r--src/js/logger-ui-inspector.js671
-rw-r--r--src/js/logger-ui.js533
-rw-r--r--src/js/scriptlets/dom-inspector.js104
-rw-r--r--src/logger-ui.html14
8 files changed, 871 insertions, 644 deletions
diff --git a/platform/chromium/vapi-client.js b/platform/chromium/vapi-client.js
index 5d62a5f..763ebbb 100644
--- a/platform/chromium/vapi-client.js
+++ b/platform/chromium/vapi-client.js
@@ -100,6 +100,10 @@ MessagingListeners.prototype.remove = function(callback) {
this.listeners.splice(this.listeners.indexOf(callback), 1);
};
+MessagingListeners.prototype.removeAll = function() {
+ this.listeners = [];
+};
+
MessagingListeners.prototype.process = function(msg) {
var listeners = this.listeners;
var n = listeners.length;
@@ -209,6 +213,10 @@ MessagingChannel.prototype.removeListener = function(callback) {
this.listeners.remove(callback);
};
+MessagingChannel.prototype.removeAllListeners = function() {
+ this.listeners.removeAll();
+};
+
/******************************************************************************/
vAPI.messaging = {
diff --git a/platform/firefox/vapi-client.js b/platform/firefox/vapi-client.js
index 44bbec0..778a7c3 100644
--- a/platform/firefox/vapi-client.js
+++ b/platform/firefox/vapi-client.js
@@ -94,6 +94,10 @@ MessagingListeners.prototype.remove = function(callback) {
this.listeners.splice(this.listeners.indexOf(callback), 1);
};
+MessagingListeners.prototype.removeAll = function() {
+ this.listeners = [];
+};
+
MessagingListeners.prototype.process = function(msg) {
var listeners = this.listeners;
var n = listeners.length;
@@ -199,6 +203,10 @@ MessagingChannel.prototype.removeListener = function(callback) {
this.listeners.remove(callback);
};
+MessagingChannel.prototype.removeAllListeners = function() {
+ this.listeners.removeAll();
+};
+
/******************************************************************************/
vAPI.messaging = {
diff --git a/src/css/logger-ui-inspector.css b/src/css/logger-ui-inspector.css
new file mode 100644
index 0000000..2dc5511
--- /dev/null
+++ b/src/css/logger-ui-inspector.css
@@ -0,0 +1,88 @@
+#domInspector {
+ border-top: 1px solid #ccc;
+ display: none;
+ max-height: 40%;
+ min-height: 40%;
+ overflow: auto;
+ }
+#domInspector.enabled {
+ display: block;
+ }
+#domInspector .permatoolbar {
+ position: absolute;
+ }
+#domInspector .permatoolbar .highlightMode.invert {
+ transform: rotate(180deg);
+ }
+#domInspector > ul:first-of-type {
+ padding-left: 0.5em;
+ }
+#domInspector ul {
+ background-color: #fff;
+ margin: 0;
+ padding-left: 1em;
+ }
+#domInspector li {
+ list-style-type: none;
+ white-space: nowrap;
+ }
+#domInspector li.isCosmeticHide,
+#domInspector li.isCosmeticHide ul,
+#domInspector li.isCosmeticHide li {
+ background-color: #fee;
+ }
+#domInspector li > * {
+ margin-right: 1em;
+ }
+#domInspector li > span:first-child {
+ color: #000;
+ cursor: default;
+ display: inline-block;
+ margin-right: 0;
+ opacity: 0.5;
+ visibility: hidden;
+ width: 1em;
+ }
+#domInspector li > span:first-child:hover {
+ opacity: 1;
+ }
+#domInspector li > *:last-child {
+ margin-right: 0;
+ }
+#domInspector li > span:first-child:before {
+ content: '\a0';
+ }
+#domInspector li.branch > span:first-child:before {
+ content: '\25b8';
+ visibility: visible;
+ }
+#domInspector li.branch.show > span:first-child:before {
+ content: '\25be';
+ visibility: visible;
+ }
+#domInspector li.branch.hasCosmeticHide > span:first-child:before {
+ color: red;
+ }
+#domInspector li > code {
+ cursor: pointer;
+ font: 12px/1.4 monospace;
+ }
+#domInspector li > code.off {
+ text-decoration: line-through;
+ }
+#domInspector li > span {
+ color: #aaa;
+ }
+#domInspector li > code.filter {
+ color: red;
+ }
+#domInspector li > ul {
+ display: none;
+ }
+#domInspector li.show > ul {
+ display: block;
+ }
+
+#cosmeticFilteringDialog .dialog textarea {
+ height: 40vh;
+}
diff --git a/src/css/logger-ui.css b/src/css/logger-ui.css
index ee26f7c..449b77e 100644
--- a/src/css/logger-ui.css
+++ b/src/css/logger-ui.css
@@ -16,6 +16,12 @@ body {
input:focus {
background-color: #ffe;
}
+textarea {
+ box-sizing: border-box;
+ direction: ltr;
+ resize: none;
+ width: 100%;
+ }
.permatoolbar {
background-color: white;
border: 0;
@@ -55,85 +61,6 @@ input:focus {
padding: 0.2em 0;
}
-#domInspector {
- border-top: 1px solid #ccc;
- display: none;
- max-height: 40%;
- min-height: 40%;
- overflow: auto;
- }
-#domInspector.enabled {
- display: block;
- }
-#domInspector > ul:first-child {
- padding-left: 0;
- }
-#domInspector ul {
- background-color: #fff;
- margin: 0;
- padding-left: 1em;
- }
-#domInspector li {
- list-style-type: none;
- white-space: nowrap;
- }
-#domInspector li.isCosmeticHide,
-#domInspector li.isCosmeticHide ul,
-#domInspector li.isCosmeticHide li {
- background-color: #fee;
- }
-#domInspector li > * {
- margin-right: 1em;
- }
-#domInspector li > span:first-child {
- color: #000;
- cursor: default;
- display: inline-block;
- margin-right: 0;
- opacity: 0.5;
- visibility: hidden;
- width: 1em;
- }
-#domInspector li > span:first-child:hover {
- opacity: 1;
- }
-#domInspector li > *:last-child {
- margin-right: 0;
- }
-#domInspector li > span:first-child:before {
- content: '\a0';
- }
-#domInspector li.branch > span:first-child:before {
- content: '\25b8';
- visibility: visible;
- }
-#domInspector li.branch.show > span:first-child:before {
- content: '\25be';
- visibility: visible;
- }
-#domInspector li.branch.hasCosmeticHide > span:first-child:before {
- color: red;
- }
-#domInspector li > code {
- cursor: pointer;
- font: 12px/1.4 monospace;
- }
-#domInspector li > code.off {
- text-decoration: line-through;
- }
-#domInspector li > span {
- color: #aaa;
- }
-#domInspector li > code.filter {
- color: red;
- }
-#domInspector li > ul {
- display: none;
- }
-#domInspector li.show > ul {
- display: block;
- }
-
#events {
border-top: 1px solid #ccc;
font: 13px sans-serif;
@@ -623,11 +550,7 @@ body.colorBlind #netFilteringDialog .dialog > div.containers > div.dynamic tr.e
margin: 0.75em 0;
}
#netFilteringDialog .dialog > div.containers > div.static textarea {
- box-sizing: border-box;
- direction: ltr;
height: 6em;
- resize: none;
- width: 100%;
}
#netFilteringDialog .dialog > div.containers > div.static > p:nth-of-type(2) {
text-align: center;
diff --git a/src/js/logger-ui-inspector.js b/src/js/logger-ui-inspector.js
new file mode 100644
index 0000000..f5dcc48
--- /dev/null
+++ b/src/js/logger-ui-inspector.js
@@ -0,0 +1,671 @@
+/*******************************************************************************
+
+ uBlock Origin - a browser extension to block requests.
+ Copyright (C) 2015 Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* global vAPI, uDom */
+
+/******************************************************************************/
+
+(function() {
+
+'use strict';
+
+/******************************************************************************/
+
+// Don't bother if the browser is not modern enough.
+if ( typeof Map === undefined || typeof WeakMap === undefined ) {
+ return;
+}
+
+/******************************************************************************/
+
+var logger = self.logger;
+var messager = logger.messager;
+
+var inspectedTabId = '';
+var inspectedHostname = '';
+var pollTimer = null;
+var fingerprint = null;
+var showdomButton = uDom.nodeFromId('showdom');
+var inspector = uDom.nodeFromId('domInspector');
+var domTree = uDom.nodeFromId('domTree');
+var tabSelector = uDom.nodeFromId('pageSelector');
+
+/******************************************************************************/
+
+var nodeFromDomEntry = function(entry) {
+ var node, value;
+ var li = document.createElement('li');
+ li.setAttribute('id', entry.nid);
+ // expander/collapser
+ node = document.createElement('span');
+ li.appendChild(node);
+ // selector
+ node = document.createElement('code');
+ node.textContent = entry.sel;
+ li.appendChild(node);
+ // descendant count
+ value = entry.cnt || 0;
+ node = document.createElement('span');
+ node.textContent = value !== 0 ? value.toLocaleString() : '';
+ node.setAttribute('data-cnt', value);
+ li.appendChild(node);
+ // cosmetic filter
+ if ( entry.filter !== undefined ) {
+ node = document.createElement('code');
+ node.classList.add('filter');
+ node.textContent = entry.filter;
+ li.appendChild(node);
+ li.classList.add('isCosmeticHide');
+ }
+ return li;
+};
+
+/******************************************************************************/
+
+var appendListItem = function(ul, li) {
+ ul.appendChild(li);
+ // Ancestor nodes of a node which is affected by a cosmetic filter will
+ // be marked as "containing cosmetic filters", for user convenience.
+ if ( li.classList.contains('isCosmeticHide') === false ) {
+ return;
+ }
+ for (;;) {
+ li = li.parentElement.parentElement;
+ if ( li === null ) {
+ break;
+ }
+ li.classList.add('hasCosmeticHide');
+ }
+};
+
+/******************************************************************************/
+
+var renderDOMFull = function(response) {
+ var ul = inspector.removeChild(domTree);
+ logger.removeAllChildren(domTree);
+
+ var lvl = 0;
+ var entries = response.layout;
+ var n = entries.length;
+ var li, entry;
+ for ( var i = 0; i < n; i++ ) {
+ entry = entries[i];
+ if ( entry.lvl === lvl ) {
+ li = nodeFromDomEntry(entry);
+ appendListItem(ul, li);
+ //expandIfBlockElement(li);
+ continue;
+ }
+ if ( entry.lvl > lvl ) {
+ ul = document.createElement('ul');
+ li.appendChild(ul);
+ li.classList.add('branch');
+ li = nodeFromDomEntry(entry);
+ appendListItem(ul, li);
+ //expandIfBlockElement(li);
+ lvl = entry.lvl;
+ continue;
+ }
+ // entry.lvl < lvl
+ while ( entry.lvl < lvl ) {
+ ul = li.parentNode;
+ li = ul.parentNode;
+ ul = li.parentNode;
+ lvl -= 1;
+ }
+ li = nodeFromDomEntry(entry);
+ ul.appendChild(li);
+ }
+ while ( ul.parentNode !== null ) {
+ ul = ul.parentNode;
+ }
+ ul.firstElementChild.classList.add('show');
+
+ inspector.appendChild(domTree);
+};
+
+/******************************************************************************/
+
+var patchIncremental = function(from, delta) {
+ var span, cnt;
+ var li = from.parentElement.parentElement;
+ var patchCosmeticHide = delta >= 0 &&
+ from.classList.contains('isCosmeticFilter') &&
+ li.classList.contains('hasCosmeticFilter') === false;
+ // Include descendants count when removing a node
+ if ( delta < 0 ) {
+ delta -= countFromNode(from);
+ }
+ for ( ; li.localName === 'li'; li = li.parentElement.parentElement ) {
+ span = li.children[2];
+ if ( delta !== 0 ) {
+ cnt = countFromNode(li) + delta;
+ span.textContent = cnt !== 0 ? cnt.toLocaleString() : '';
+ span.setAttribute('data-cnt', cnt);
+ }
+ if ( patchCosmeticHide ) {
+ li.classList.add('hasCosmeticFilter');
+ }
+ }
+};
+
+/******************************************************************************/
+
+var renderDOMIncremental = function(response) {
+ // Process each journal entry:
+ // 1 = node added
+ // -1 = node removed
+ var journal = response.journal;
+ var nodes = response.nodes;
+ var entry, previous, li, ul;
+ for ( var i = 0, n = journal.length; i < n; i++ ) {
+ entry = journal[i];
+ // Remove node
+ if ( entry.what === -1 ) {
+ li = document.getElementById(entry.nid);
+ if ( li === null ) {
+ continue;
+ }
+ patchIncremental(li, -1);
+ li.parentNode.removeChild(li);
+ continue;
+ }
+ // Modify node
+ if ( entry.what === 0 ) {
+ // TODO: update selector/filter
+ continue;
+ }
+ // Add node as sibling
+ if ( entry.what === 1 && entry.l ) {
+ previous = document.getElementById(entry.l);
+ // This should not happen
+ if ( previous === null ) {
+ // throw new Error('No left sibling!?');
+ continue;
+ }
+ ul = previous.parentElement;
+ li = nodeFromDomEntry(nodes[entry.nid]);
+ ul.insertBefore(li, previous.nextElementSibling);
+ patchIncremental(li, 1);
+ continue;
+ }
+ // Add node as child
+ if ( entry.what === 1 && entry.u ) {
+ li = document.getElementById(entry.u);
+ // This should not happen
+ if ( li === null ) {
+ // throw new Error('No parent!?');
+ continue;
+ }
+ ul = li.querySelector('ul');
+ if ( ul === null ) {
+ ul = document.createElement('ul');
+ li.appendChild(ul);
+ li.classList.add('branch');
+ }
+ li = nodeFromDomEntry(nodes[entry.nid]);
+ ul.appendChild(li);
+ patchIncremental(li, 1);
+ continue;
+ }
+ }
+};
+
+/******************************************************************************/
+
+var countFromNode = function(li) {
+ var span = li.children[2];
+ var cnt = parseInt(span.getAttribute('data-cnt'), 10);
+ return isNaN(cnt) ? cnt : 0;
+};
+
+/******************************************************************************/
+
+var selectorFromNode = function(node, nth) {
+ var selector = '';
+ var code;
+ if ( nth === undefined ) {
+ nth = 1;
+ }
+ while ( node !== null ) {
+ if ( node.localName === 'li' ) {
+ code = node.querySelector('code:nth-of-type(' + nth + ')');
+ if ( code !== null ) {
+ selector = code.textContent + ' > ' + selector;
+ if ( selector.indexOf('#') !== -1 ) {
+ break;
+ }
+ nth = 1;
+ }
+ }
+ node = node.parentElement;
+ }
+ return selector.slice(0, -3);
+};
+
+/******************************************************************************/
+
+var nidFromNode = function(node) {
+ var li = node;
+ while ( li !== null ) {
+ if ( li.localName === 'li' ) {
+ return li.id || '';
+ }
+ li = li.parentElement;
+ }
+ return '';
+};
+
+/******************************************************************************/
+
+var startDialog = (function() {
+ var dialog = uDom.nodeFromId('cosmeticFilteringDialog');
+ var candidateFilters = [];
+
+ var onClick = function(ev) {
+ var target = ev.target;
+
+ // click outside the dialog proper
+ if ( target.classList.contains('modalDialog') ) {
+ return stop();
+ }
+ ev.stopPropagation();
+ };
+
+ var stop = function() {
+ dialog.removeEventListener('click', onClick, true);
+ document.body.removeChild(dialog);
+ };
+
+ var start = function() {
+ // Collect all selectors which are currently toggled
+ var node, filters = [];
+ var nodes = domTree.querySelectorAll('code.off');
+ for ( var i = 0; i < nodes.length; i++ ) {
+ node = nodes[i];
+ if ( node.classList.contains('filter') ) {
+ filters.push({
+ prefix: '#@#',
+ nid: '',
+ selector: node.textContent
+ });
+ } else {
+ filters.push({
+ prefix: '##',
+ nid: nidFromNode(node),
+ selector: node.textContent
+ });
+ }
+ }
+
+ // TODO: Send filters through dom-inspector.js for further processing.
+
+ candidateFilters = filters;
+ var taValue = [], filter;
+ for ( i = 0; i < filters.length; i++ ) {
+ filter = filters[i];
+ taValue.push(inspectedHostname + filter.prefix + filter.selector);
+ }
+ dialog.querySelector('textarea').value = taValue.join('\n');
+ document.body.appendChild(dialog);
+ dialog.addEventListener('click', onClick, true);
+ };
+
+ return start;
+})();
+
+/******************************************************************************/
+
+var onClick = function(ev) {
+ ev.stopPropagation();
+
+ if ( inspectedTabId === '' ) {
+ return;
+ }
+
+ var target = ev.target;
+ var parent = target.parentElement;
+
+ // Expand/collapse branch
+ if (
+ target.localName === 'span' &&
+ parent instanceof HTMLLIElement &&
+ parent.classList.contains('branch') &&
+ target === parent.firstElementChild
+ ) {
+ target.parentElement.classList.toggle('show');
+ return;
+ }
+
+ // Toggle selector
+ if ( target.localName === 'code' ) {
+ var original = target.classList.contains('filter') === false;
+ messager.send({
+ what: 'postMessageTo',
+ senderTabId: null,
+ senderChannel: 'logger-ui.js',
+ receiverTabId: inspectedTabId,
+ receiverChannel: 'dom-inspector.js',
+ msg: {
+ what: 'toggleNodes',
+ original: original,
+ target: original !== target.classList.toggle('off'),
+ selector: selectorFromNode(target, original ? 1 : 2),
+ nid: original ? nidFromNode(target) : ''
+ }
+ });
+ var cantCreate = inspector.querySelector('#domTree .off') === null;
+ inspector.querySelector('.permatoolbar .revert').classList.toggle('disabled', cantCreate);
+ inspector.querySelector('.permatoolbar .commit').classList.toggle('disabled', cantCreate);
+ return;
+ }
+};
+
+/******************************************************************************/
+
+var onMouseOver = (function() {
+ var mouseoverTarget = null;
+ var mouseoverTimer = null;
+
+ var timerHandler = function() {
+ mouseoverTimer = null;
+ messager.send({
+ what: 'postMessageTo',
+ senderTabId: null,
+ senderChannel: 'logger-ui.js',
+ receiverTabId: inspectedTabId,
+ receiverChannel: 'dom-inspector.js',
+ msg: {
+ what: 'highlightOne',
+ selector: selectorFromNode(mouseoverTarget),
+ nid: nidFromNode(mouseoverTarget),
+ scrollTo: true
+ }
+ });
+ };
+
+ return function(ev) {
+ if ( inspectedTabId === '' ) {
+ return;
+ }
+
+ // Find closest `li`
+ var target = ev.target;
+ while ( target !== null ) {
+ if ( target.localName === 'li' ) {
+ break;
+ }
+ target = target.parentElement;
+ }
+ if ( target === mouseoverTarget ) {
+ return;
+ }
+ mouseoverTarget = target;
+ if ( mouseoverTimer === null ) {
+ mouseoverTimer = vAPI.setTimeout(timerHandler, 50);
+ }
+ };
+})();
+
+/******************************************************************************/
+
+var currentTabId = function() {
+ if ( showdomButton.classList.contains('active') === false ) {
+ return '';
+ }
+ var tabId = logger.tabIdFromClassName(tabSelector.value) || '';
+ return tabId !== 'bts' ? tabId : '';
+};
+
+/******************************************************************************/
+
+var cancelPollTimer = function() {
+ if ( pollTimer !== null ) {
+ clearTimeout(pollTimer);
+ pollTimer = null;
+ }
+};
+
+/******************************************************************************/
+
+var onDOMFetched = function(response) {
+ if ( response === undefined || currentTabId() !== inspectedTabId ) {
+ shutdownInspector(inspectedTabId);
+ injectInspectorAsync(250);
+ return;
+ }
+
+ switch ( response.status ) {
+ case 'full':
+ renderDOMFull(response);
+ fingerprint = response.fingerprint;
+ inspectedHostname = response.hostname;
+ break;
+
+ case 'incremental':
+ renderDOMIncremental(response);
+ break;
+
+ case 'nochange':
+ case 'busy':
+ break;
+
+ default:
+ break;
+ }
+
+ fetchDOMAsync();
+};
+
+/******************************************************************************/
+
+var fetchDOM = function() {
+ messager.send({
+ what: 'postMessageTo',
+ senderTabId: null,
+ senderChannel: 'logger-ui.js',
+ receiverTabId: inspectedTabId,
+ receiverChannel: 'dom-inspector.js',
+ msg: {
+ what: 'domLayout',
+ fingerprint: fingerprint
+ }
+ });
+ pollTimer = vAPI.setTimeout(function() {
+ pollTimer = null;
+ onDOMFetched();
+ }, 1001);
+};
+
+/******************************************************************************/
+
+var fetchDOMAsync = function(delay) {
+ if ( pollTimer !== null ) {
+ return;
+ }
+ pollTimer = vAPI.setTimeout(function() {
+ pollTimer = null;
+ fetchDOM();
+ }, delay || 1001);
+};
+
+/******************************************************************************/
+
+var injectInspector = function() {
+ var tabId = currentTabId();
+ // No valid tab, go back
+ if ( tabId === '' ) {
+ injectInspectorAsync();
+ return;
+ }
+ inspectedTabId = tabId;
+ fingerprint = null;
+ messager.send({
+ what: 'scriptlet',
+ tabId: tabId,
+ scriptlet: 'dom-inspector'
+ });
+ fetchDOMAsync(250);
+};
+
+/******************************************************************************/
+
+var injectInspectorAsync = function(delay) {
+ if ( pollTimer !== null ) {
+ return;
+ }
+ if ( showdomButton.classList.contains('active') === false ) {
+ return;
+ }
+ pollTimer = vAPI.setTimeout(function() {
+ pollTimer = null;
+ injectInspector();
+ }, delay || 1001);
+};
+
+/******************************************************************************/
+
+var shutdownInspector = function(tabId) {
+ messager.send({
+ what: 'postMessageTo',
+ senderTabId: null,
+ senderChannel: 'logger-ui.js',
+ receiverTabId: tabId,
+ receiverChannel: 'dom-inspector.js',
+ msg: { what: 'shutdown', }
+ });
+ logger.removeAllChildren(domTree);
+ cancelPollTimer();
+ inspectedTabId = '';
+};
+
+/******************************************************************************/
+
+var onTabIdChanged = function() {
+ if ( inspectedTabId !== currentTabId() ) {
+ shutdownInspector();
+ injectInspectorAsync(250);
+ }
+};
+
+/******************************************************************************/
+
+var toggleHighlightMode = function() {
+ messager.send({
+ what: 'postMessageTo',
+ senderTabId: null,
+ senderChannel: 'logger-ui.js',
+ receiverTabId: inspectedTabId,
+ receiverChannel: 'dom-inspector.js',
+ msg: {
+ what: 'highlightMode',
+ invert: uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').classList.toggle('invert')
+ }
+ });
+};
+
+/******************************************************************************/
+
+var revert = function() {
+ uDom('#domTree .off').removeClass('off');
+ messager.send({
+ what: 'postMessageTo',
+ senderTabId: null,
+ senderChannel: 'logger-ui.js',
+ receiverTabId: inspectedTabId,
+ receiverChannel: 'dom-inspector.js',
+ msg: { what: 'resetToggledNodes' }
+ });
+ inspector.querySelector('.permatoolbar .revert').classList.add('disabled');
+ inspector.querySelector('.permatoolbar .commit').classList.add('disabled');
+};
+
+/******************************************************************************/
+
+var onMessage = function(request) {
+ var msg = request.what === 'postMessageTo' ? request.msg : request;
+ switch ( msg.what ) {
+ case 'domLayout':
+ cancelPollTimer();
+ onDOMFetched(msg);
+ break;
+
+ default:
+ break;
+ }
+};
+
+/******************************************************************************/
+
+var toggleOn = function() {
+ window.addEventListener('beforeunload', toggleOff);
+ tabSelector.addEventListener('change', onTabIdChanged);
+ domTree.addEventListener('click', onClick, true);
+ domTree.addEventListener('mouseover', onMouseOver, true);
+ uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').addEventListener('click', toggleHighlightMode);
+ uDom.nodeFromSelector('#domInspector .permatoolbar .revert').addEventListener('click', revert);
+ uDom.nodeFromSelector('#domInspector .permatoolbar .commit').addEventListener('click', startDialog);
+ inspector.classList.add('enabled');
+ messager.addListener(onMessage);
+ injectInspector();
+ // Adjust tree view for toolbar height
+ domTree.style.setProperty(
+ 'margin-top',
+ inspector.querySelector('.permatoolbar').clientHeight + 'px'
+ );
+};
+
+/******************************************************************************/
+
+var toggleOff = function() {
+ messager.removeListener(onMessage);
+ cancelPollTimer();
+ shutdownInspector();
+ window.removeEventListener('beforeunload', toggleOff);
+ tabSelector.removeEventListener('change', onTabIdChanged);
+ domTree.removeEventListener('click', onClick, true);
+ domTree.removeEventListener('mouseover', onMouseOver, true);
+ uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').removeEventListener('click', toggleHighlightMode);
+ uDom.nodeFromSelector('#domInspector .permatoolbar .revert').removeEventListener('click', revert);
+ uDom.nodeFromSelector('#domInspector .permatoolbar .commit').removeEventListener('click', startDialog);
+ inspectedTabId = '';
+ inspector.classList.remove('enabled');
+};
+
+/******************************************************************************/
+
+var toggle = function() {
+ if ( showdomButton.classList.toggle('active') ) {
+ toggleOn();
+ } else {
+ toggleOff();
+ }
+};
+
+/******************************************************************************/
+
+showdomButton.addEventListener('click', toggle);
+
+/******************************************************************************/
+
+})();
+
+
diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js
index f4ce468..7591286 100644
--- a/src/js/logger-ui.js
+++ b/src/js/logger-ui.js
@@ -30,6 +30,30 @@
/******************************************************************************/
+var logger = self.logger = {};
+var messager = logger.messager = vAPI.messaging.channel('logger-ui.js');
+
+/******************************************************************************/
+
+var removeAllChildren = logger.removeAllChildren = function(node) {
+ while ( node.firstChild ) {
+ node.removeChild(node.firstChild);
+ }
+};
+
+/******************************************************************************/
+
+var tabIdFromClassName = logger.tabIdFromClassName = function(className) {
+ var matches = className.match(/(?:^| )tab_([^ ]+)(?: |$)/);
+ if ( matches === null ) {
+ return '';
+ }
+ return matches[1];
+};
+
+/******************************************************************************/
+/******************************************************************************/
+
// Adjust top padding of content table, to match that of toolbar height.
(function() {
@@ -47,8 +71,6 @@
/******************************************************************************/
-var messager = vAPI.messaging.channel('logger-ui.js');
-
var tbody = document.querySelector('#events tbody');
var trJunkyard = [];
var tdJunkyard = [];
@@ -61,7 +83,6 @@ var allTabIdsToken;
var hiddenTemplate = document.querySelector('#hiddenTemplate > span');
var reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/;
var netFilteringDialog = uDom.nodeFromId('netFilteringDialog');
-var filterFinderDialog = uDom.nodeFromId('filterFinderDialog');
var prettyRequestTypes = {
'main_frame': 'doc',
@@ -98,14 +119,6 @@ var dateOptions = {
/******************************************************************************/
-var removeAllChildren = function(node) {
- while ( node.firstChild ) {
- node.removeChild(node.firstChild);
- }
-};
-
-/******************************************************************************/
-
var classNameFromTabId = function(tabId) {
if ( tabId === noTabId ) {
return 'tab_bts';
@@ -117,503 +130,6 @@ var classNameFromTabId = function(tabId) {
};
/******************************************************************************/
-
-var tabIdFromClassName = function(className) {
- var matches = className.match(/(?:^| )tab_([^ ]+)(?: |$)/);
- if ( matches === null ) {
- return '';
- }
- return matches[1];
-};
-
-/******************************************************************************/
-/******************************************************************************/
-
-// DOM inspector
-
-(function domInspector() {
- // Don't bother if the browser is not modern enough.
- if ( typeof Map === undefined || typeof WeakMap === undefined ) {
- return;
- }
-
- var inspectedTabId = '';
- var currentSelector = '';
- var showdomButton = uDom.nodeFromId('showdom');
- var inspector = uDom.nodeFromId('domInspector');
- var tabSelector = uDom.nodeFromId('pageSelector');
-
- var nodeFromDomEntry = function(entry) {
- var node, value;
- var li = document.createElement('li');
- li.setAttribute('id', entry.nid);
- // expander/collapser
- node = document.createElement('span');
- li.appendChild(node);
- // selector
- node = document.createElement('code');
- node.textContent = entry.sel;
- li.appendChild(node);
- // descendant count
- value = entry.cnt || 0;
- node = document.createElement('span');
- node.textContent = value !== 0 ? value.toLocaleString() : '';
- node.setAttribute('data-cnt', value);
- li.appendChild(node);
- // cosmetic filter
- if ( entry.filter !== undefined ) {
- node = document.createElement('code');
- node.classList.add('filter');
- node.textContent = entry.filter;
- li.appendChild(node);
- li.classList.add('isCosmeticHide');
- }
- return li;
- };
-
- var appendListItem = function(ul, li) {
- ul.appendChild(li);
- // Ancestor nodes of a node which is affected by a cosmetic filter will
- // be marked as "containing cosmetic filters", for user convenience.
- if ( li.classList.contains('isCosmeticHide') === false ) {
- return;
- }
- for (;;) {
- li = li.parentElement.parentElement;
- if ( li === null ) {
- break;
- }
- li.classList.add('hasCosmeticHide');
- }
- };
-
- var renderDOMFull = function(response) {
- var ul = document.createElement('ul');
- var lvl = 0;
- var entries = response.layout;
- var n = entries.length;
- var li, entry;
- for ( var i = 0; i < n; i++ ) {
- entry = entries[i];
- if ( entry.lvl === lvl ) {
- li = nodeFromDomEntry(entry);
- appendListItem(ul, li);
- //expandIfBlockElement(li);
- continue;
- }
- if ( entry.lvl > lvl ) {
- ul = document.createElement('ul');
- li.appendChild(ul);
- li.classList.add('branch');
- li = nodeFromDomEntry(entry);
- appendListItem(ul, li);
- //expandIfBlockElement(li);
- lvl = entry.lvl;
- continue;
- }
- // entry.lvl < lvl
- while ( entry.lvl < lvl ) {
- ul = li.parentNode;
- li = ul.parentNode;
- ul = li.parentNode;
- lvl -= 1;
- }
- li = nodeFromDomEntry(entry);
- ul.appendChild(li);
- }
- while ( ul.parentNode !== null ) {
- ul = ul.parentNode;
- }
- ul.firstElementChild.classList.add('show');
-
- removeAllChildren(inspector);
- inspector.appendChild(ul);
- };
-
- var patchIncremental = function(from, delta) {
- var span, cnt;
- var li = from.parentElement.parentElement;
- var patchCosmeticHide = delta >= 0 &&
- from.classList.contains('isCosmeticFilter') &&
- li.classList.contains('hasCosmeticFilter') === false;
- // Include descendants count when removing a node
- if ( delta < 0 ) {
- delta -= countFromNode(from);
- }
- for ( ; li.localName === 'li'; li = li.parentElement.parentElement ) {
- span = li.children[2];
- if ( delta !== 0 ) {
- cnt = countFromNode(li) + delta;
- span.textContent = cnt !== 0 ? cnt.toLocaleString() : '';
- span.setAttribute('data-cnt', cnt);
- }
- if ( patchCosmeticHide ) {
- li.classList.add('hasCosmeticFilter');
- }
- }
- };
-
- var renderDOMIncremental = function(response) {
- // Process each journal entry:
- // 1 = node added
- // -1 = node removed
- var journal = response.journal;
- var nodes = response.nodes;
- var entry, previous, li, ul;
- for ( var i = 0, n = journal.length; i < n; i++ ) {
- entry = journal[i];
- // Remove node
- if ( entry.what === -1 ) {
- li = document.getElementById(entry.nid);
- if ( li === null ) {
- continue;
- }
- patchIncremental(li, -1);
- li.parentNode.removeChild(li);
- continue;
- }
- // Modify node
- if ( entry.what === 0 ) {
- // TODO: update selector/filter
- continue;
- }
- // Add node as sibling
- if ( entry.what === 1 && entry.l ) {
- previous = document.getElementById(entry.l);
- // This should not happen
- if ( previous === null ) {
- // throw new Error('No left sibling!?');
- continue;
- }
- ul = previous.parentElement;
- li = nodeFromDomEntry(nodes[entry.nid]);
- ul.insertBefore(li, previous.nextElementSibling);
- patchIncremental(li, 1);
- continue;
- }
- // Add node as child
- if ( entry.what === 1 && entry.u ) {
- li = document.getElementById(entry.u);
- // This should not happen
- if ( li === null ) {
- // throw new Error('No parent!?');
- continue;
- }
- ul = li.querySelector('ul');
- if ( ul === null ) {
- ul = document.createElement('ul');
- li.appendChild(ul);
- li.classList.add('branch');
- }
- li = nodeFromDomEntry(nodes[entry.nid]);
- ul.appendChild(li);
- patchIncremental(li, 1);
- continue;
- }
- }
- };
-
- var countFromNode = function(li) {
- var span = li.children[2];
- var cnt = parseInt(span.getAttribute('data-cnt'), 10);
- return isNaN(cnt) ? cnt : 0;
- };
-
- var selectorFromNode = function(node, nth) {
- var selector = '';
- var code;
- if ( nth === undefined ) {
- nth = 1;
- }
- while ( node !== null ) {
- if ( node.localName === 'li' ) {
- code = node.querySelector('code:nth-of-type(' + nth + ')');
- if ( code !== null ) {
- selector = code.textContent + ' > ' + selector;
- if ( selector.indexOf('#') !== -1 ) {
- break;
- }
- nth = 1;
- }
- }
- node = node.parentElement;
- }
- return selector.slice(0, -3);
- };
-
- var onClick = function(ev) {
- ev.stopPropagation();
-
- if ( inspectedTabId === '' ) {
- return;
- }
-
- var target = ev.target;
- var parent = target.parentElement;
-
- // Expand/collapse branch
- if (
- target.localName === 'span' &&
- parent instanceof HTMLLIElement &&
- parent.classList.contains('branch') &&
- target === parent.firstElementChild
- ) {
- target.parentElement.classList.toggle('show');
- return;
- }
-
- // Toggle selector
- if ( target.localName === 'code' ) {
- var original = target.classList.contains('filter') === false;
- messager.send({
- what: 'postMessageTo',
- senderTabId: null,
- senderChannel: 'logger-ui.js',
- receiverTabId: inspectedTabId,
- receiverChannel: 'dom-inspector.js',
- msg: {
- what: 'toggleNodes',
- original: original,
- target: original !== target.classList.toggle('off'),
- selector: selectorFromNode(target, original ? 1 : 2)
- }
- });
- return;
- }
-
- // Highlight and scrollto
- if ( target.localName === 'code' ) {
- messager.send({
- what: 'postMessageTo',
- senderTabId: null,
- senderChannel: 'logger-ui.js',
- receiverTabId: inspectedTabId,
- receiverChannel: 'dom-inspector.js',
- msg: {
- what: 'highlight',
- selector: selectorFromNode(target),
- scrollTo: true
- }
- });
- return;
- }
- };
-
- var onMouseOver = (function() {
- var mouseoverTarget = null;
- var mouseoverTimer = null;
-
- var timerHandler = function() {
- mouseoverTimer = null;
- messager.send({
- what: 'postMessageTo',
- senderTabId: null,
- senderChannel: 'logger-ui.js',
- receiverTabId: inspectedTabId,
- receiverChannel: 'dom-inspector.js',
- msg: {
- what: 'highlight',
- selector: selectorFromNode(mouseoverTarget),
- scrollTo: true
- }
- });
- };
-
- return function(ev) {
- if ( inspectedTabId === '' ) {
- return;
- }
-
- // Find closest `li`
- var target = ev.target;
- while ( target !== null ) {
- if ( target.localName === 'li' ) {
- break;
- }
- target = target.parentElement;
- }
- if ( target === mouseoverTarget ) {
- return;
- }
- mouseoverTarget = target;
- if ( mouseoverTimer === null ) {
- mouseoverTimer = vAPI.setTimeout(timerHandler, 50);
- }
- };
- })();
-
- var pollTimer = null;
- var fingerprint = null;
-
- var currentTabId = function() {
- if ( showdomButton.classList.contains('active') === false ) {
- return '';
- }
- var tabId = tabIdFromClassName(tabSelector.value) || '';
- return tabId !== 'bts' ? tabId : '';
- };
-
- var cancelPollTimer = function() {
- if ( pollTimer !== null ) {
- clearTimeout(pollTimer);
- pollTimer = null;
- }
- };
-
- var onDOMFetched = function(response) {
- if ( response === undefined || currentTabId() !== inspectedTabId ) {
- shutdownInspector(inspectedTabId);
- injectInspectorAsync(250);
- return;
- }
-
- switch ( response.status ) {
- case 'full':
- renderDOMFull(response);
- fingerprint = response.fingerprint;
- break;
-
- case 'incremental':
- renderDOMIncremental(response);
- break;
-
- case 'nochange':
- case 'busy':
- break;
-
- default:
- break;
- }
-
- fetchDOMAsync();
- };
-
- var fetchDOM = function() {
- messager.send({
- what: 'postMessageTo',
- senderTabId: null,
- senderChannel: 'logger-ui.js',
- receiverTabId: inspectedTabId,
- receiverChannel: 'dom-inspector.js',
- msg: {
- what: 'domLayout',
- fingerprint: fingerprint
- }
- });
- pollTimer = vAPI.setTimeout(function() {
- pollTimer = null;
- onDOMFetched();
- }, 1001);
- };
-
- var fetchDOMAsync = function(delay) {
- if ( pollTimer !== null ) {
- return;
- }
- pollTimer = vAPI.setTimeout(function() {
- pollTimer = null;
- fetchDOM();
- }, delay || 1001);
- };
-
- var injectInspector = function() {
- var tabId = currentTabId();
- // No valid tab, go back
- if ( tabId === '' ) {
- injectInspectorAsync();
- return;
- }
- inspectedTabId = tabId;
- fingerprint = null;
- messager.send({
- what: 'scriptlet',
- tabId: tabId,
- scriptlet: 'dom-inspector'
- });
- fetchDOMAsync(250);
- };
-
- var injectInspectorAsync = function(delay) {
- if ( pollTimer !== null ) {
- return;
- }
- if ( showdomButton.classList.contains('active') === false ) {
- return;
- }
- pollTimer = vAPI.setTimeout(function() {
- pollTimer = null;
- injectInspector();
- }, delay || 1001);
- };
-
- var shutdownInspector = function(tabId) {
- messager.send({
- what: 'postMessageTo',
- senderTabId: null,
- senderChannel: 'logger-ui.js',
- receiverTabId: tabId,
- receiverChannel: 'dom-inspector.js',
- msg: { what: 'shutdown', }
- });
- removeAllChildren(inspector);
- cancelPollTimer();
- inspectedTabId = '';
- };
-
- var onTabIdChanged = function() {
- if ( inspectedTabId !== currentTabId() ) {
- shutdownInspector();
- injectInspectorAsync(250);
- }
- };
-
- var onMessage = function(request) {
- var msg = request.what === 'postMessageTo' ? request.msg : request;
- switch ( msg.what ) {
- case 'domLayout':
- cancelPollTimer();
- onDOMFetched(msg);
- break;
-
- default:
- break;
- }
- };
-
- var toggleOn = function() {
- window.addEventListener('beforeunload', toggleOff);
- inspector.addEventListener('click', onClick, true);
- inspector.addEventListener('mouseover', onMouseOver, true);
- tabSelector.addEventListener('change', onTabIdChanged);
- inspector.classList.add('enabled');
- messager.addListener(onMessage);
- injectInspector();
- };
-
- var toggleOff = function() {
- messager.removeListener(onMessage);
- cancelPollTimer();
- shutdownInspector();
- window.removeEventListener('beforeunload', toggleOff);
- inspector.removeEventListener('click', onClick, true);
- inspector.removeEventListener('mouseover', onMouseOver, true);
- tabSelector.removeEventListener('change', onTabIdChanged);
- currentSelector = inspectedTabId = '';
- inspector.classList.remove('enabled');
- };
-
- var toggle = function() {
- if ( showdomButton.classList.toggle('active') ) {
- toggleOn();
- } else {
- toggleOff();
- }
- };
-
- showdomButton.addEventListener('click', toggle);
-})();
-
-/******************************************************************************/
/******************************************************************************/
var regexFromURLFilteringResult = function(result) {
@@ -1776,6 +1292,7 @@ var netFilteringManager = (function() {
var reverseLookupManager = (function() {
var reSentence1 = /\{\{filter\}\}/g;
var sentence1Template = vAPI.i18n('loggerStaticFilteringFinderSentence1');
+ var filterFinderDialog = uDom.nodeFromId('filterFinderDialog');
var removeAllChildren = function(node) {
while ( node.firstChild ) {
diff --git a/src/js/scriptlets/dom-inspector.js b/src/js/scriptlets/dom-inspector.js
index 0d34460..c97849d 100644
--- a/src/js/scriptlets/dom-inspector.js
+++ b/src/js/scriptlets/dom-inspector.js
@@ -144,8 +144,9 @@ var svgOcean = null;
var svgIslands = null;
var svgRoot = null;
var pickerRoot = null;
-var currentSelector = '';
+var highlightedElements = [];
+var nodeToIdMap = new WeakMap(); // No need to iterate
var toggledNodes = new Map();
/******************************************************************************/
@@ -175,7 +176,6 @@ var domLayout = (function() {
};
var idGenerator = 0;
- var nodeToIdMap = new WeakMap(); // No need to iterate
// This will be used to uniquely identify nodes across process.
@@ -209,7 +209,7 @@ var domLayout = (function() {
}
return out;
})();
-
+/*
var matchesSelector = (function() {
if ( typeof Element.prototype.matches === 'function' ) {
return 'matches';
@@ -222,26 +222,7 @@ var domLayout = (function() {
}
return '';
})();
-
- var hasManyMatches = function(node, selector) {
- var fnName = matchesSelector;
- if ( fnName === '' ) {
- return true;
- }
- var child = node.firstElementChild;
- var match = false;
- while ( child !== null ) {
- if ( child[fnName](selector) ) {
- if ( match ) {
- return true;
- }
- match = true;
- }
- child = child.nextElementSibling;
- }
- return false;
- };
-
+*/
var selectorFromNode = function(node) {
var str, attr, pos, sw, i;
var tag = node.localName;
@@ -276,18 +257,6 @@ var domLayout = (function() {
selector += '[' + attr + sw + '="' + cssEscape(str) + '"]';
}
}
- // The resulting selector must cause only one element to be selected. If
- // it's not the case, further narrow using `nth-of-type` pseudo-class.
- if ( hasManyMatches(node.parentElement, selector) ) {
- i = 1;
- while ( node.previousElementSibling ) {
- node = node.previousElementSibling;
- if ( node.localName === tag ) {
- i += 1;
- }
- }
- selector += ':nth-of-type(' + i + ')';
- }
return selector;
};
@@ -543,7 +512,8 @@ var domLayout = (function() {
var response = {
what: 'domLayout',
- fingerprint: domFingerprint()
+ fingerprint: domFingerprint(),
+ hostname: window.location.hostname
};
// No mutation observer means we need to send full layout
@@ -593,7 +563,8 @@ var domLayout = (function() {
/******************************************************************************/
-var highlightElements = function(elems, scrollTo) {
+var highlightElements = function(scrollTo) {
+ var elems = highlightedElements;
var wv = pickerRoot.contentWindow.innerWidth;
var hv = pickerRoot.contentWindow.innerHeight;
var ocean = ['M0 0h' + wv + 'v' + hv + 'h-' + wv, 'z'];
@@ -684,15 +655,31 @@ var elementsFromSelector = function(filter) {
/******************************************************************************/
-var highlight = function(scrollTo) {
- var elements = elementsFromSelector(currentSelector);
- highlightElements(elements, scrollTo);
+var selectNodes = function(selector, nid) {
+ var nodes = elementsFromSelector(selector);
+ if ( nid === '' ) {
+ return nodes;
+ }
+ var i = nodes.length;
+ while ( i-- ) {
+ if ( nodeToIdMap.get(nodes[i]) === nid ) {
+ return [nodes[i]];
+ }
+ }
+ return [];
+};
+
+/******************************************************************************/
+
+var hightlightNodes = function(selector, nid, scrollTo) {
+ highlightedElements = selectNodes(selector, nid);
+ highlightElements(scrollTo);
};
/******************************************************************************/
var onScrolled = function() {
- highlight();
+ highlightElements();
};
/******************************************************************************/
@@ -703,8 +690,7 @@ var onScrolled = function() {
// hidden, any = remove display property, don't remember original state
// hidden, hidden = set display to `none`
-var toggleNodes = function(selector, originalState, targetState) {
- var nodes = document.querySelectorAll(selector);
+var toggleNodes = function(nodes, originalState, targetState) {
var i = nodes.length;
if ( i === 0 ) {
return;
@@ -759,13 +745,13 @@ var resetToggledNodes = function() {
var shutdown = function() {
resetToggledNodes();
domLayout.shutdown();
- localMessager.removeListener(onMessage);
+ localMessager.removeAllListeners();
localMessager.close();
localMessager = null;
window.removeEventListener('scroll', onScrolled, true);
document.documentElement.removeChild(pickerRoot);
pickerRoot = svgRoot = svgOcean = svgIslands = null;
- currentSelector = '';
+ highlightedElements = [];
};
/******************************************************************************/
@@ -779,15 +765,22 @@ var onMessage = function(request) {
response = domLayout.get(msg.fingerprint);
break;
- case 'highlight':
- currentSelector = msg.selector;
- highlight(msg.scrollTo);
+ case 'highlightMode':
+ svgRoot.classList.toggle('invert', msg.invert);
+ break;
+
+ case 'highlightOne':
+ hightlightNodes(msg.selector, msg.nid, msg.scrollTo);
+ break;
+
+ case 'resetToggledNodes':
+ resetToggledNodes();
break;
case 'toggleNodes':
- toggleNodes(msg.selector, msg.original, msg.target);
- currentSelector = msg.selector;
- highlight(true);
+ highlightedElements = selectNodes(msg.selector, msg.nid);
+ toggleNodes(highlightedElements, msg.original, msg.target);
+ highlightElements(true);
break;
case 'shutdown':
@@ -863,6 +856,13 @@ pickerRoot.onload = function() {
'stroke: #FFF;',
'stroke-width: 0.5px;',
'}',
+ 'svg.invert > path:first-child {',
+ 'fill: rgba(0,0,255,0.1);',
+ '}',
+ 'svg.invert > path + path {',
+ 'fill: rgba(0,0,0,0.75);',
+ 'stroke: #000;',
+ '}',
''
].join('\n');
pickerDoc.body.appendChild(style);
@@ -876,7 +876,7 @@ pickerRoot.onload = function() {
window.addEventListener('scroll', onScrolled, true);
- highlight();
+ highlightElements();
localMessager.addListener(onMessage);
};
diff --git a/src/logger-ui.html b/src/logger-ui.html
index 5f9fc1f..624ee87 100644
--- a/src/logger-ui.html
+++ b/src/logger-ui.html
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="css/common.css">
<link rel="stylesheet" type="text/css" href="css/logger-ui.css">
+<link rel="stylesheet" type="text/css" href="css/logger-ui-inspector.css">
<title data-i18n="statsPageName"></title>
</head>
<body>
@@ -20,6 +21,14 @@
</div>
<div id="domInspector">
+ <div class="permatoolbar">
+ <div>
+ <span class="button fa highlightMode">&#xf042;</span>
+ <span class="button fa revert disabled">&#xf12d;</span>
+ <span class="button fa commit disabled">&#xf0c7;</span>
+ </div>
+ </div>
+ <ul id="domTree"></ul>
</div>
<div id="events" class="compactView f">
@@ -88,7 +97,9 @@
<div class="dialog"></div>
</div>
<div id="cosmeticFilteringDialog" class="modalDialog">
- <div class="dialog"></div>
+ <div class="dialog">
+ <p><textarea class="cosmeticFilters" value=""></textarea>
+ </div>
</div>
<div id="filterFinderDialogSentence1"><span><span></span><code></code><span></span></span></div>
</div>
@@ -98,6 +109,7 @@
<script src="js/udom.js"></script>
<script src="js/i18n.js"></script>
<script src="js/logger-ui.js"></script>
+<script src="js/logger-ui-inspector.js"></script>
</body>
</html>