aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgorhill <rhill@raymondhill.net>2015-06-26 15:45:54 -0400
committergorhill <rhill@raymondhill.net>2015-06-26 15:45:54 -0400
commit7d2855180c5560ebc1d850261edc5a2f5bdf3b0c (patch)
tree594357fdb9d32485f746fab72d2bc519c6399d57
parent631443768f2359e6fdb0bc6a3e457a0951e0a9e9 (diff)
downloaduBlock-7d2855180c5560ebc1d850261edc5a2f5bdf3b0c.zip
uBlock-7d2855180c5560ebc1d850261edc5a2f5bdf3b0c.tar.gz
uBlock-7d2855180c5560ebc1d850261edc5a2f5bdf3b0c.tar.bz2
some refactoring of new DOM inspector code
-rw-r--r--platform/chromium/vapi-background.js25
-rw-r--r--platform/firefox/vapi-background.js26
-rw-r--r--src/js/logger-ui.js303
-rw-r--r--src/js/messaging.js10
-rw-r--r--src/js/scriptlets/dom-fingerprint.js66
-rw-r--r--src/js/scriptlets/dom-highlight.js339
-rw-r--r--src/js/scriptlets/dom-inspector.js691
-rw-r--r--src/js/scriptlets/dom-layout.js406
8 files changed, 876 insertions, 990 deletions
diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js
index 8e67b3d..ed66264 100644
--- a/platform/chromium/vapi-background.js
+++ b/platform/chromium/vapi-background.js
@@ -603,7 +603,7 @@ vAPI.messaging.onPortMessage = function(request, port) {
return;
}
- console.error('µBlock> messaging > unknown request: %o', request);
+ console.error('uBlock> messaging > unknown request: %o', request);
// Unhandled:
// Need to callback anyways in case caller expected an answer, or
@@ -661,9 +661,26 @@ vAPI.messaging.broadcast = function(message) {
/******************************************************************************/
-vAPI.messaging.send = function(tabId, channelName, message) {
+// "Auxiliary process": any process other than main process.
+//
+// Main process to auxiliary processes messaging. The approach is that of
+// smoke-signal messaging, so emitters have to be ready to deal with no
+// response at all (i.e. use timeout where needed).
+//
+// Mandatory:
+// - receiverTabId: Which tab to send the message.
+// No target tab id means sends to all tabs.
+// - receiverChannel: Which channel to send the message.
+//
+// Optional:
+// - senderTabId: From which tab the message originates.
+// - senderChannel: From which channel the message originates.
+// These optional fields are useful for the target, and may be used
+// to send back a response to the sender.
+
+vAPI.messaging.post = function(message) {
var port;
- var chromiumTabId = toChromiumTabId(tabId);
+ var chromiumTabId = toChromiumTabId(message.receiverTabId);
for ( var portName in this.ports ) {
if ( this.ports.hasOwnProperty(portName) === false ) {
continue;
@@ -673,7 +690,7 @@ vAPI.messaging.send = function(tabId, channelName, message) {
continue;
}
port.postMessage({
- channelName: channelName,
+ channelName: message.receiverChannel,
msg: message
});
if ( chromiumTabId !== 0 ) {
diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js
index f7a4d72..67bda4f 100644
--- a/platform/firefox/vapi-background.js
+++ b/platform/firefox/vapi-background.js
@@ -1239,10 +1239,30 @@ vAPI.messaging.setup = function(defaultHandler) {
/******************************************************************************/
-vAPI.messaging.send = function(tabId, channelName, message) {
- var ffTabId = tabId || '';
+// "Auxiliary process": any process other than main process.
+//
+// Main process to auxiliary processes messaging. The approach is that of
+// smoke-signal messaging, so emitters have to be ready to deal with no
+// response at all (i.e. use timeout where needed).
+//
+// Mandatory:
+// - receiverTabId: Which tab to send the message.
+// No target tab id means sends to all tabs.
+// - receiverChannel: Which channel to send the message.
+//
+// Optional:
+// - senderTabId: From which tab the message originates.
+// - senderChannel: From which channel the message originates.
+// These optional fields are useful for the target, and may be used
+// to send back a response to the sender.
+
+vAPI.messaging.post = function(message) {
+ var ffTabId = message.receiverTabId || '';
var targetId = location.host + ':broadcast';
- var payload = JSON.stringify({ channelName: channelName, msg: message });
+ var payload = JSON.stringify({
+ channelName: message.receiverChannel,
+ msg: message
+ });
if ( ffTabId === '' ) {
this.globalMessageManager.broadcastAsyncMessage(targetId, payload);
diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js
index 576360a..fe07b1a 100644
--- a/src/js/logger-ui.js
+++ b/src/js/logger-ui.js
@@ -133,80 +133,16 @@ var tabIdFromClassName = function(className) {
(function domInspector() {
// Don't bother if the browser is not modern enough.
- if ( typeof Map === undefined ) {
+ if ( typeof Map === undefined || typeof WeakMap === undefined ) {
return;
}
- var enabled = false;
- var state = '';
- var fingerprint = '';
- var fingerprintTimer = null;
- var currentTabId = '';
+ var inspectedTabId = '';
var currentSelector = '';
+ var showdomButton = uDom.nodeFromId('showdom');
var inspector = uDom.nodeFromId('domInspector');
var tabSelector = uDom.nodeFromId('pageSelector');
- var inlineElementTags = [
- 'a', 'abbr', 'acronym',
- 'b', 'bdo', 'big', 'br', 'button',
- 'cite', 'code',
- 'del', 'dfn',
- 'em',
- 'font',
- 'i', 'img', 'input', 'ins',
- 'kbd',
- 'label',
- 'map',
- 'object',
- 'q',
- 'samp', 'select', 'small', 'span', 'strong', 'sub', 'sup',
- 'textarea', 'tt',
- 'u',
- 'var'
- ].reduce(function(p, c) {
- p[c] = true;
- return p;
- }, Object.create(null));
-
- var startTimer = function() {
- if ( fingerprintTimer === null ) {
- fingerprintTimer = vAPI.setTimeout(fetchFingerprint, 2000);
- }
- };
-
- var stopTimer = function() {
- if ( fingerprintTimer !== null ) {
- clearTimeout(fingerprintTimer);
- fingerprintTimer = null;
- }
- };
-
- var injectHighlighter = function(tabId) {
- if ( tabId === '' ) {
- return;
- }
- messager.send({
- what: 'scriptlet',
- tabId: tabId,
- scriptlet: 'dom-highlight'
- });
- };
-
- var removeHighlighter = function(tabId) {
- if ( tabId === '' ) {
- return;
- }
- messager.send({
- what: 'sendMessageTo',
- tabId: tabId,
- channelName: 'scriptlets',
- msg: {
- what: 'dom-highlight',
- action: 'shutdown'
- }
- });
- };
-
var nodeFromDomEntry = function(entry) {
var node;
var li = document.createElement('li');
@@ -250,29 +186,6 @@ var tabIdFromClassName = function(className) {
}
};
- var expandIfBlockElement = function(node) {
- var ul = node.parentElement;
- if ( ul === null ) {
- return;
- }
- var li = ul.parentElement;
- if ( li === null ) {
- return;
- }
- if ( li.classList.contains('show') ) {
- return;
- }
- var tag = node.firstElementChild.textContent;
- var pos = tag.search(/[^a-z0-9]/);
- if ( pos !== -1 ) {
- tag = tag.slice(0, pos);
- }
- if ( inlineElementTags[tag] !== undefined ) {
- return;
- }
- li.classList.add('show');
- };
-
var renderDOM = function(response) {
var ul = document.createElement('ul');
var lvl = 0;
@@ -314,9 +227,6 @@ var tabIdFromClassName = function(className) {
removeAllChildren(inspector);
inspector.appendChild(ul);
-
- // Check for change at regular interval.
- fingerprint = entries.length !== 0 ? entries[0].fp : '';
};
var selectorFromNode = function(node, nth) {
@@ -344,7 +254,7 @@ var tabIdFromClassName = function(className) {
var onClick = function(ev) {
ev.stopPropagation();
- if ( currentTabId === '' ) {
+ if ( inspectedTabId === '' ) {
return;
}
@@ -366,12 +276,13 @@ var tabIdFromClassName = function(className) {
if ( target.localName === 'code' ) {
var original = target.classList.contains('filter') === false;
messager.send({
- what: 'sendMessageTo',
- tabId: currentTabId,
- channelName: 'scriptlets',
+ what: 'postMessageTo',
+ senderTabId: null,
+ senderChannel: 'logger-ui.js',
+ receiverTabId: inspectedTabId,
+ receiverChannel: 'dom-inspector.js',
msg: {
- what: 'dom-highlight',
- action: 'toggleNodes',
+ what: 'toggleNodes',
original: original,
target: original !== target.classList.toggle('off'),
selector: selectorFromNode(target, original ? 1 : 2)
@@ -383,12 +294,13 @@ var tabIdFromClassName = function(className) {
// Highlight and scrollto
if ( target.localName === 'code' ) {
messager.send({
- what: 'sendMessageTo',
- tabId: currentTabId,
- channelName: 'scriptlets',
+ what: 'postMessageTo',
+ senderTabId: null,
+ senderChannel: 'logger-ui.js',
+ receiverTabId: inspectedTabId,
+ receiverChannel: 'dom-inspector.js',
msg: {
- what: 'dom-highlight',
- action: 'highlight',
+ what: 'highlight',
selector: selectorFromNode(target),
scrollTo: true
}
@@ -404,12 +316,13 @@ var tabIdFromClassName = function(className) {
var timerHandler = function() {
mouseoverTimer = null;
messager.send({
- what: 'sendMessageTo',
- tabId: currentTabId,
- channelName: 'scriptlets',
+ what: 'postMessageTo',
+ senderTabId: null,
+ senderChannel: 'logger-ui.js',
+ receiverTabId: inspectedTabId,
+ receiverChannel: 'dom-inspector.js',
msg: {
- what: 'dom-highlight',
- action: 'highlight',
+ what: 'highlight',
selector: selectorFromNode(mouseoverTarget),
scrollTo: true
}
@@ -417,6 +330,10 @@ var tabIdFromClassName = function(className) {
};
return function(ev) {
+ if ( inspectedTabId === '' ) {
+ return;
+ }
+
// Find closest `li`
var target = ev.target;
while ( target !== null ) {
@@ -435,115 +352,165 @@ var tabIdFromClassName = function(className) {
};
})();
- var onFingerprintFetched = function(response) {
- if ( state !== 'fetchingFingerprint' ) {
- return;
+ var pollTimer = null;
+ var fingerprint = null;
+
+ var currentTabId = function() {
+ if ( showdomButton.classList.contains('active') === false ) {
+ return '';
}
- state = '';
- fingerprintTimer = null;
- if ( !enabled ) {
+ 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;
}
- if ( response === fingerprint ) {
- startTimer();
+
+ if ( response.layout === 'NOCHANGE' ) {
+ fetchDOMAsync();
return;
}
- fingerprint = response || '';
- fetchDOM();
+
+ renderDOM(response.layout);
+ fingerprint = response.fingerprint;
+
+ fetchDOMAsync();
};
- var fetchFingerprint = function() {
+ var fetchDOM = function() {
messager.send({
- what: 'scriptlet',
- tabId: currentTabId,
- scriptlet: 'dom-fingerprint'
- }, onFingerprintFetched);
- state = 'fetchingFingerprint';
+ 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 onDOMFetched = function(response) {
- if ( state !== 'fetchingDOM' ) {
+ var fetchDOMAsync = function(delay) {
+ if ( pollTimer !== null ) {
return;
}
- state = '';
- if ( !enabled ) {
+ pollTimer = vAPI.setTimeout(function() {
+ pollTimer = null;
+ fetchDOM();
+ }, delay || 1001);
+ };
+
+ var injectInspector = function() {
+ var tabId = currentTabId();
+ // No valid tab, go back
+ if ( tabId === '' ) {
+ injectInspectorAsync();
return;
}
- if ( Array.isArray(response) && response.length !== 0 ) {
- renderDOM(response);
- injectHighlighter(currentTabId);
- } else {
- fingerprint = '';
- }
- startTimer();
+ inspectedTabId = tabId;
+ fingerprint = null;
+ messager.send({
+ what: 'scriptlet',
+ tabId: tabId,
+ scriptlet: 'dom-inspector'
+ });
+ fetchDOMAsync(250);
};
- var fetchDOM = function() {
- if ( currentTabId === '' ) {
- removeAllChildren(inspector);
- startTimer();
+ 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: 'scriptlet',
- tabId: currentTabId,
- scriptlet: 'dom-layout'
- }, onDOMFetched);
- state = 'fetchingDOM';
+ 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 ( !enabled ) {
- return;
- }
- var previousTabId = currentTabId;
- var tabId = tabIdFromClassName(tabSelector.value) || '';
- currentTabId = tabId !== 'bts' && tabId !== '' ? tabId : '';
- if ( currentTabId !== previousTabId ) {
- removeHighlighter(previousTabId);
+ if ( inspectedTabId !== currentTabId() ) {
+ shutdownInspector();
+ injectInspectorAsync(250);
}
- if ( state === 'fetchingDOM' ) {
- return;
+ };
+
+ var onMessage = function(request) {
+ var msg = request.what === 'postMessageTo' ? request.msg : request;
+ switch ( msg.what ) {
+ case 'domLayout':
+ cancelPollTimer();
+ onDOMFetched(msg);
+ break;
+
+ default:
+ break;
}
- fetchDOM();
};
var toggleOn = function() {
- if ( enabled ) {
- return;
- }
- enabled = true;
window.addEventListener('beforeunload', toggleOff);
inspector.addEventListener('click', onClick, true);
inspector.addEventListener('mouseover', onMouseOver, true);
tabSelector.addEventListener('change', onTabIdChanged);
- onTabIdChanged();
inspector.classList.add('enabled');
+ messager.addListener(onMessage);
+ injectInspector();
};
var toggleOff = function() {
- removeHighlighter(currentTabId);
+ messager.removeListener(onMessage);
+ cancelPollTimer();
+ shutdownInspector();
window.removeEventListener('beforeunload', toggleOff);
inspector.removeEventListener('click', onClick, true);
inspector.removeEventListener('mouseover', onMouseOver, true);
tabSelector.removeEventListener('change', onTabIdChanged);
- removeAllChildren(inspector);
- stopTimer();
- currentTabId = currentSelector = fingerprint = '';
- enabled = false;
+ currentSelector = inspectedTabId = '';
inspector.classList.remove('enabled');
};
var toggle = function() {
- if ( uDom.nodeFromId('showdom').classList.toggle('active') ) {
+ if ( showdomButton.classList.toggle('active') ) {
toggleOn();
} else {
toggleOff();
}
};
- uDom('#showdom').on('click', toggle);
+ showdomButton.addEventListener('click', toggle);
})();
/******************************************************************************/
diff --git a/src/js/messaging.js b/src/js/messaging.js
index 41e563b..daf0b83 100644
--- a/src/js/messaging.js
+++ b/src/js/messaging.js
@@ -141,6 +141,12 @@ var onMessage = function(request, sender, callback) {
vAPI.tabs.open(request.details);
break;
+ // Passthrough for auxiliary script to auxiliary script messaging
+ case 'postMessageTo':
+ request.senderTabId = tabId;
+ vAPI.messaging.post(request);
+ break;
+
case 'reloadTab':
if ( vAPI.isBehindTheSceneTabId(request.tabId) === false ) {
vAPI.tabs.reload(request.tabId);
@@ -158,10 +164,6 @@ var onMessage = function(request, sender, callback) {
µb.selectFilterLists(request.switches);
break;
- case 'sendMessageTo':
- vAPI.messaging.send(request.tabId, request.channelName, request.msg);
- break;
-
case 'toggleHostnameSwitch':
µb.toggleHostnameSwitch(request);
break;
diff --git a/src/js/scriptlets/dom-fingerprint.js b/src/js/scriptlets/dom-fingerprint.js
deleted file mode 100644
index 05a9fb9..0000000
--- a/src/js/scriptlets/dom-fingerprint.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/*******************************************************************************
-
- 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, HTMLDocument */
-
-/******************************************************************************/
-
-(function() {
-
-'use strict';
-
-/******************************************************************************/
-
-// https://github.com/gorhill/uBlock/issues/464
-if ( document instanceof HTMLDocument === false ) {
- return;
-}
-
-// This can happen
-if ( typeof vAPI !== 'object' ) {
- return;
-}
-
-/******************************************************************************/
-
-// Some kind of fingerprint for the DOM, without incurring too much overhead.
-
-var url = window.location.href;
-var pos = url.indexOf('#');
-if ( pos !== -1 ) {
- url = url.slice(0, pos);
-}
-var fingerprint = url + '{' + document.getElementsByTagName('*').length.toString() + '}';
-
-var localMessager = vAPI.messaging.channel('scriptlets');
-localMessager.send({
- what: 'scriptletResponse',
- scriptlet: 'dom-fingerprint',
- response: fingerprint
-}, function() {
- localMessager.close();
-});
-
-/******************************************************************************/
-
-})();
-
-/******************************************************************************/
diff --git a/src/js/scriptlets/dom-highlight.js b/src/js/scriptlets/dom-highlight.js
deleted file mode 100644
index 1b4c981..0000000
--- a/src/js/scriptlets/dom-highlight.js
+++ /dev/null
@@ -1,339 +0,0 @@
-/*******************************************************************************
-
- 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, HTMLDocument */
-
-/******************************************************************************/
-
-(function() {
-
-'use strict';
-
-/******************************************************************************/
-
-// https://github.com/gorhill/uBlock/issues/464
-if ( document instanceof HTMLDocument === false ) {
- return;
-}
-
-// This can happen
-if ( typeof vAPI !== 'object' ) {
- return;
-}
-
-/******************************************************************************/
-
-if ( document.querySelector('iframe.dom-highlight.' + vAPI.sessionId) !== null ) {
- return;
-}
-
-/******************************************************************************/
-
-var localMessager = null;
-var svgOcean = null;
-var svgIslands = null;
-var svgRoot = null;
-var pickerRoot = null;
-var currentSelector = '';
-
-var toggledNodes = new Map();
-
-/******************************************************************************/
-
-var highlightElements = function(elems, scrollTo) {
- var wv = pickerRoot.contentWindow.innerWidth;
- var hv = pickerRoot.contentWindow.innerHeight;
- var ocean = ['M0 0h' + wv + 'v' + hv + 'h-' + wv, 'z'];
- var islands = [];
- var elem, rect, poly;
- var xl, xr, yt, yb, w, h, ws;
- var xlu = Number.MAX_VALUE, xru = 0, ytu = Number.MAX_VALUE, ybu = 0;
-
- for ( var i = 0; i < elems.length; i++ ) {
- elem = elems[i];
- if ( elem === pickerRoot ) {
- continue;
- }
- if ( typeof elem.getBoundingClientRect !== 'function' ) {
- continue;
- }
-
- rect = elem.getBoundingClientRect();
- xl = rect.left;
- xr = rect.right;
- w = rect.width;
- yt = rect.top;
- yb = rect.bottom;
- h = rect.height;
-
- ws = w.toFixed(1);
- poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
- 'h' + ws +
- 'v' + h.toFixed(1) +
- 'h-' + ws +
- 'z';
- ocean.push(poly);
- islands.push(poly);
-
- if ( !scrollTo ) {
- continue;
- }
-
- if ( xl < xlu ) { xlu = xl; }
- if ( xr > xru ) { xru = xr; }
- if ( yt < ytu ) { ytu = yt; }
- if ( yb > ybu ) { ybu = yb; }
- }
- svgOcean.setAttribute('d', ocean.join(''));
- svgIslands.setAttribute('d', islands.join('') || 'M0 0');
-
- if ( !scrollTo ) {
- return;
- }
-
- // Highlighted area completely within viewport
- if ( xlu >= 0 && xru <= wv && ytu >= 0 && ybu <= hv ) {
- return;
- }
-
- var dx = 0, dy = 0;
-
- if ( xru > wv ) {
- dx = xru - wv;
- xlu -= dx;
- }
- if ( xlu < 0 ) {
- dx += xlu;
- }
- if ( ybu > hv ) {
- dy = ybu - hv;
- ytu -= dy;
- }
- if ( ytu < 0 ) {
- dy += ytu;
- }
-
- if ( dx !== 0 || dy !== 0 ) {
- window.scrollBy(dx, dy);
- }
-};
-
-/******************************************************************************/
-
-var elementsFromSelector = function(filter) {
- var out = [];
- try {
- out = document.querySelectorAll(filter);
- } catch (ex) {
- }
- return out;
-};
-
-/******************************************************************************/
-
-var highlight = function(scrollTo) {
- var elements = elementsFromSelector(currentSelector);
- highlightElements(elements, scrollTo);
-};
-
-/******************************************************************************/
-
-var onScrolled = function() {
- highlight();
-};
-
-/******************************************************************************/
-
-// original, target = what to do
-// any, any = restore saved display property
-// any, hidden = set display to `none`, remember original state
-// 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 i = nodes.length;
- if ( i === 0 ) {
- return;
- }
- var node, value;
- while ( i-- ) {
- node = nodes[i];
- if ( originalState ) { // any, ?
- if ( targetState ) { // any, any
- value = toggledNodes.get(node);
- if ( value === undefined ) {
- continue;
- }
- if ( value !== null ) {
- node.style.removeProperty('display');
- } else {
- node.style.setProperty('display', value);
- }
- toggledNodes.delete(node);
- } else { // any, hidden
- toggledNodes.set(node, node.style.getPropertyValue('display') || null);
- node.style.setProperty('display', 'none');
- }
- } else { // hidden, ?
- if ( targetState ) { // hidden, any
- node.style.setProperty('display', 'initial', 'important');
- } else { // hidden, hidden
- node.style.setProperty('display', 'none', 'important');
- }
- }
- }
-};
-
-/******************************************************************************/
-
-var resetToggledNodes = function() {
- // Chromium does not support destructuring as of v43.
- for ( var node of toggledNodes.keys() ) {
- value = toggledNodes.get(node);
- if ( value !== null ) {
- node.style.removeProperty('display');
- } else {
- node.style.setProperty('display', value);
- }
- }
- toggledNodes.clear();
-};
-
-/******************************************************************************/
-
-var shutdown = function() {
- resetToggledNodes();
- localMessager.removeListener(onMessage);
- localMessager.close();
- localMessager = null;
- window.removeEventListener('scroll', onScrolled, true);
- document.documentElement.removeChild(pickerRoot);
- pickerRoot = svgRoot = svgOcean = svgIslands = null;
- currentSelector = '';
-};
-
-/******************************************************************************/
-
-var onMessage = function(msg) {
- if ( msg.what !== 'dom-highlight' ) {
- return;
- }
- switch ( msg.action ) {
- case 'highlight':
- currentSelector = msg.selector;
- highlight(msg.scrollTo);
- break;
-
- case 'toggleNodes':
- toggleNodes(msg.selector, msg.original, msg.target);
- currentSelector = msg.selector;
- highlight(true);
- break;
-
- case 'shutdown':
- shutdown();
- break;
-
- default:
- break;
- }
-};
-
-/******************************************************************************/
-
-(function() {
- pickerRoot = document.createElement('iframe');
- pickerRoot.classList.add(vAPI.sessionId);
- pickerRoot.classList.add('dom-highlight');
- pickerRoot.style.cssText = [
- 'background: transparent',
- 'border: 0',
- 'border-radius: 0',
- 'box-shadow: none',
- 'display: block',
- 'height: 100%',
- 'left: 0',
- 'margin: 0',
- 'opacity: 1',
- 'position: fixed',
- 'outline: 0',
- 'padding: 0',
- 'top: 0',
- 'visibility: visible',
- 'width: 100%',
- 'z-index: 2147483647',
- ''
- ].join(' !important;\n');
-
- pickerRoot.onload = function() {
- pickerRoot.onload = null;
- var pickerDoc = this.contentDocument;
-
- var style = pickerDoc.createElement('style');
- style.textContent = [
- 'body {',
- 'background-color: transparent;',
- 'cursor: crosshair;',
- '}',
- 'svg {',
- 'height: 100%;',
- 'left: 0;',
- 'position: fixed;',
- 'top: 0;',
- 'width: 100%;',
- '}',
- 'svg > path:first-child {',
- 'fill: rgba(0,0,0,0.75);',
- 'fill-rule: evenodd;',
- '}',
- 'svg > path + path {',
- 'fill: rgba(0,0,255,0.1);',
- 'stroke: #FFF;',
- 'stroke-width: 0.5px;',
- '}',
- ''
- ].join('\n');
- pickerDoc.body.appendChild(style);
-
- svgRoot = pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'svg');
- svgOcean = pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'path');
- svgRoot.appendChild(svgOcean);
- svgIslands = pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'path');
- svgRoot.appendChild(svgIslands);
- pickerDoc.body.appendChild(svgRoot);
-
- window.addEventListener('scroll', onScrolled, true);
-
- localMessager = vAPI.messaging.channel('scriptlets');
- localMessager.addListener(onMessage);
-
- highlight();
- };
-
- document.documentElement.appendChild(pickerRoot);
-})();
-
-/******************************************************************************/
-
-})();
-
-/******************************************************************************/
diff --git a/src/js/scriptlets/dom-inspector.js b/src/js/scriptlets/dom-inspector.js
new file mode 100644
index 0000000..3127799
--- /dev/null
+++ b/src/js/scriptlets/dom-inspector.js
@@ -0,0 +1,691 @@
+/*******************************************************************************
+
+ 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, HTMLDocument */
+
+/******************************************************************************/
+/******************************************************************************/
+
+(function() {
+
+'use strict';
+
+/******************************************************************************/
+
+// https://github.com/gorhill/uBlock/issues/464
+if ( document instanceof HTMLDocument === false ) {
+ return;
+}
+
+// This can happen
+if ( typeof vAPI !== 'object' ) {
+ return;
+}
+
+/******************************************************************************/
+
+if ( document.querySelector('iframe.dom-inspector.' + vAPI.sessionId) !== null ) {
+ return;
+}
+
+/******************************************************************************/
+/******************************************************************************/
+
+// Modified to avoid installing as a global shim -- so the scriptlet can be
+// flushed from memory once no longer in use.
+
+/*! http://mths.be/cssescape v0.2.1 by @mathias | MIT license */
+var cssEscape = (function(root) {
+
+ var css = root.CSS || {};
+ if ( css.escape ) {
+ return css.escape;
+ }
+
+ var InvalidCharacterError = function(message) {
+ this.message = message;
+ };
+ InvalidCharacterError.prototype = new Error();
+ InvalidCharacterError.prototype.name = 'InvalidCharacterError';
+
+ // http://dev.w3.org/csswg/cssom/#serialize-an-identifier
+ return function(value) {
+ var string = String(value);
+ var length = string.length;
+ var index = -1;
+ var codeUnit;
+ var result = '';
+ var firstCodeUnit = string.charCodeAt(0);
+ while (++index < length) {
+ codeUnit = string.charCodeAt(index);
+ // Note: there’s no need to special-case astral symbols, surrogate
+ // pairs, or lone surrogates.
+
+ // If the character is NULL (U+0000), then throw an
+ // `InvalidCharacterError` exception and terminate these steps.
+ if (codeUnit === 0x0000) {
+ throw new InvalidCharacterError(
+ 'Invalid character: the input contains U+0000.'
+ );
+ }
+
+ if (
+ // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
+ // U+007F, […]
+ (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
+ // If the character is the first character and is in the range [0-9]
+ // (U+0030 to U+0039), […]
+ (index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
+ // If the character is the second character and is in the range [0-9]
+ // (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
+ (
+ index == 1 &&
+ codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
+ firstCodeUnit == 0x002D
+ )
+ ) {
+ // http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point
+ result += '\\' + codeUnit.toString(16) + ' ';
+ continue;
+ }
+
+ // If the character is not handled by one of the above rules and is
+ // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
+ // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
+ // U+005A), or [a-z] (U+0061 to U+007A), […]
+ if (
+ codeUnit >= 0x0080 ||
+ codeUnit == 0x002D ||
+ codeUnit == 0x005F ||
+ codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
+ codeUnit >= 0x0041 && codeUnit <= 0x005A ||
+ codeUnit >= 0x0061 && codeUnit <= 0x007A
+ ) {
+ // the character itself
+ result += string.charAt(index);
+ continue;
+ }
+
+ // Otherwise, the escaped character.
+ // http://dev.w3.org/csswg/cssom/#escape-a-character
+ result += '\\' + string.charAt(index);
+
+ }
+ return result;
+ };
+
+}(self));
+
+/******************************************************************************/
+/******************************************************************************/
+
+var localMessager = vAPI.messaging.channel('dom-inspector.js');
+
+var svgOcean = null;
+var svgIslands = null;
+var svgRoot = null;
+var pickerRoot = null;
+var currentSelector = '';
+
+var toggledNodes = new Map();
+
+/******************************************************************************/
+
+var domLayout = (function() {
+ var skipTagNames = {
+ 'br': true,
+ 'link': true,
+ 'meta': true,
+ 'script': true,
+ 'style': true
+ };
+
+ var resourceAttrNames = {
+ 'a': 'href',
+ 'iframe': 'src',
+ 'img': 'src',
+ 'object': 'data'
+ };
+
+ // Collect all nodes which are directly affected by cosmetic filters: these
+ // will be reported in the layout data.
+
+ var nodeToCosmeticFilterMap = (function() {
+ var out = new WeakMap();
+ var styleTags = vAPI.styles || [];
+ var i = styleTags.length;
+ var selectors, styleText, j, selector, nodes, k;
+ while ( i-- ) {
+ styleText = styleTags[i].textContent;
+ selectors = styleText.slice(0, styleText.lastIndexOf('\n')).split(/,\n/);
+ j = selectors.length;
+ while ( j-- ) {
+ selector = selectors[j];
+ nodes = document.querySelectorAll(selector);
+ k = nodes.length;
+ while ( k-- ) {
+ out.set(nodes[k], selector);
+ }
+ }
+ }
+ return out;
+ })();
+
+ var DomRoot = function() {
+ this.lvl = 0;
+ this.sel = 'body';
+ var url = window.location.href;
+ var pos = url.indexOf('#');
+ if ( pos !== -1 ) {
+ url = url.slice(0, pos);
+ }
+ this.src = url;
+ this.top = window === window.top;
+ this.cnt = 0;
+ };
+
+ var DomNode = function(level, selector, filter) {
+ this.lvl = level;
+ this.sel = selector;
+ this.cnt = 0;
+ this.filter = filter;
+ };
+
+ 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 matchesSelector = (function() {
+ if ( typeof Element.prototype.matches === 'function' ) {
+ return 'matches';
+ }
+ if ( typeof Element.prototype.mozMatchesSelector === 'function' ) {
+ return 'mozMatchesSelector';
+ }
+ if ( typeof Element.prototype.webkitMatchesSelector === 'function' ) {
+ return 'webkitMatchesSelector';
+ }
+ return '';
+ })();
+
+ var selectorFromNode = function(node) {
+ var str, attr, pos, sw, i;
+ var tag = node.localName;
+ var selector = cssEscape(tag);
+ // Id
+ if ( typeof node.id === 'string' ) {
+ str = node.id.trim();
+ if ( str !== '' ) {
+ selector += '#' + cssEscape(str);
+ }
+ }
+ // Class
+ var cl = node.classList;
+ if ( cl ) {
+ for ( i = 0; i < cl.length; i++ ) {
+ selector += '.' + cssEscape(cl[i]);
+ }
+ }
+ // Tag-specific attributes
+ if ( resourceAttrNames.hasOwnProperty(tag) ) {
+ attr = resourceAttrNames[tag];
+ str = node.getAttribute(attr) || '';
+ str = str.trim();
+ pos = str.indexOf('#');
+ if ( pos !== -1 ) {
+ str = str.slice(0, pos);
+ sw = '^';
+ } else {
+ sw = '';
+ }
+ if ( str !== '' ) {
+ 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;
+ };
+
+ var domNodeFactory = function(level, node) {
+ var localName = node.localName;
+ if ( skipTagNames.hasOwnProperty(localName) ) {
+ return null;
+ }
+ // skip uBlock's own nodes
+ if ( node.classList.contains(vAPI.sessionId) ) {
+ return null;
+ }
+ if ( level === 0 && localName === 'body' ) {
+ return new DomRoot();
+ }
+ var selector = selectorFromNode(node);
+ var filter = nodeToCosmeticFilterMap.get(node);
+ return new DomNode(level, selector, filter);
+ };
+
+ // Collect layout data.
+
+ var getLayoutData = function() {
+ var domLayout = [];
+ var stack = [];
+ var node = document.body;
+ var domNode;
+ var lvl = 0;
+
+ for (;;) {
+ domNode = domNodeFactory(lvl, node);
+ if ( domNode !== null ) {
+ domLayout.push(domNode);
+ }
+ // children
+ if ( node.firstElementChild !== null ) {
+ stack.push(node);
+ lvl += 1;
+ node = node.firstElementChild;
+ continue;
+ }
+ // sibling
+ if ( node.nextElementSibling === null ) {
+ do {
+ node = stack.pop();
+ if ( !node ) { break; }
+ lvl -= 1;
+ } while ( node.nextElementSibling === null );
+ if ( !node ) { break; }
+ }
+ node = node.nextElementSibling;
+ }
+ return domLayout;
+ };
+
+ // Descendant count for each node.
+
+ var patchLayoutData = function(domLayout) {
+ var stack = [], ptr;
+ var lvl = 0;
+ var domNode, cnt;
+ var i = domLayout.length;
+
+ while ( i-- ) {
+ domNode = domLayout[i];
+ if ( domNode.lvl === lvl ) {
+ stack[ptr] += 1;
+ continue;
+ }
+ if ( domNode.lvl > lvl ) {
+ while ( lvl < domNode.lvl ) {
+ stack.push(0);
+ lvl += 1;
+ }
+ ptr = lvl - 1;
+ stack[ptr] += 1;
+ continue;
+ }
+ // domNode.lvl < lvl
+ cnt = stack.pop();
+ domNode.cnt = cnt;
+ lvl -= 1;
+ ptr = lvl - 1;
+ stack[ptr] += cnt + 1;
+ }
+ return domLayout;
+ };
+
+ return function() {
+ return patchLayoutData(getLayoutData());
+ };
+})();
+
+/******************************************************************************/
+
+// Some kind of fingerprint for the DOM, without incurring too much overhead.
+
+var domFingerprint = function() {
+ return vAPI.sessionId + '{' + document.getElementsByTagName('*').length + '}';
+};
+
+/******************************************************************************/
+
+var highlightElements = function(elems, scrollTo) {
+ var wv = pickerRoot.contentWindow.innerWidth;
+ var hv = pickerRoot.contentWindow.innerHeight;
+ var ocean = ['M0 0h' + wv + 'v' + hv + 'h-' + wv, 'z'];
+ var islands = [];
+ var elem, rect, poly;
+ var xl, xr, yt, yb, w, h, ws;
+ var xlu = Number.MAX_VALUE, xru = 0, ytu = Number.MAX_VALUE, ybu = 0;
+
+ for ( var i = 0; i < elems.length; i++ ) {
+ elem = elems[i];
+ if ( elem === pickerRoot ) {
+ continue;
+ }
+ if ( typeof elem.getBoundingClientRect !== 'function' ) {
+ continue;
+ }
+
+ rect = elem.getBoundingClientRect();
+ xl = rect.left;
+ xr = rect.right;
+ w = rect.width;
+ yt = rect.top;
+ yb = rect.bottom;
+ h = rect.height;
+
+ ws = w.toFixed(1);
+ poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
+ 'h' + ws +
+ 'v' + h.toFixed(1) +
+ 'h-' + ws +
+ 'z';
+ ocean.push(poly);
+ islands.push(poly);
+
+ if ( !scrollTo ) {
+ continue;
+ }
+
+ if ( xl < xlu ) { xlu = xl; }
+ if ( xr > xru ) { xru = xr; }
+ if ( yt < ytu ) { ytu = yt; }
+ if ( yb > ybu ) { ybu = yb; }
+ }
+ svgOcean.setAttribute('d', ocean.join(''));
+ svgIslands.setAttribute('d', islands.join('') || 'M0 0');
+
+ if ( !scrollTo ) {
+ return;
+ }
+
+ // Highlighted area completely within viewport
+ if ( xlu >= 0 && xru <= wv && ytu >= 0 && ybu <= hv ) {
+ return;
+ }
+
+ var dx = 0, dy = 0;
+
+ if ( xru > wv ) {
+ dx = xru - wv;
+ xlu -= dx;
+ }
+ if ( xlu < 0 ) {
+ dx += xlu;
+ }
+ if ( ybu > hv ) {
+ dy = ybu - hv;
+ ytu -= dy;
+ }
+ if ( ytu < 0 ) {
+ dy += ytu;
+ }
+
+ if ( dx !== 0 || dy !== 0 ) {
+ window.scrollBy(dx, dy);
+ }
+};
+
+/******************************************************************************/
+
+var elementsFromSelector = function(filter) {
+ var out = [];
+ try {
+ out = document.querySelectorAll(filter);
+ } catch (ex) {
+ }
+ return out;
+};
+
+/******************************************************************************/
+
+var highlight = function(scrollTo) {
+ var elements = elementsFromSelector(currentSelector);
+ highlightElements(elements, scrollTo);
+};
+
+/******************************************************************************/
+
+var onScrolled = function() {
+ highlight();
+};
+
+/******************************************************************************/
+
+// original, target = what to do
+// any, any = restore saved display property
+// any, hidden = set display to `none`, remember original state
+// 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 i = nodes.length;
+ if ( i === 0 ) {
+ return;
+ }
+ var node, value;
+ while ( i-- ) {
+ node = nodes[i];
+ if ( originalState ) { // any, ?
+ if ( targetState ) { // any, any
+ value = toggledNodes.get(node);
+ if ( value === undefined ) {
+ continue;
+ }
+ if ( value !== null ) {
+ node.style.removeProperty('display');
+ } else {
+ node.style.setProperty('display', value);
+ }
+ toggledNodes.delete(node);
+ } else { // any, hidden
+ toggledNodes.set(node, node.style.getPropertyValue('display') || null);
+ node.style.setProperty('display', 'none');
+ }
+ } else { // hidden, ?
+ if ( targetState ) { // hidden, any
+ node.style.setProperty('display', 'initial', 'important');
+ } else { // hidden, hidden
+ node.style.setProperty('display', 'none', 'important');
+ }
+ }
+ }
+};
+
+/******************************************************************************/
+
+var resetToggledNodes = function() {
+ var value;
+ // Chromium does not support destructuring as of v43.
+ for ( var node of toggledNodes.keys() ) {
+ value = toggledNodes.get(node);
+ if ( value !== null ) {
+ node.style.removeProperty('display');
+ } else {
+ node.style.setProperty('display', value);
+ }
+ }
+ toggledNodes.clear();
+};
+
+/******************************************************************************/
+
+var shutdown = function() {
+ resetToggledNodes();
+ localMessager.removeListener(onMessage);
+ localMessager.close();
+ localMessager = null;
+ window.removeEventListener('scroll', onScrolled, true);
+ document.documentElement.removeChild(pickerRoot);
+ pickerRoot = svgRoot = svgOcean = svgIslands = null;
+ currentSelector = '';
+};
+
+/******************************************************************************/
+
+var onMessage = function(request) {
+ var msg = request.what === 'postMessageTo' ? request.msg : request;
+ var response;
+
+ switch ( msg.what ) {
+ case 'domLayout':
+ var fingerprint = domFingerprint();
+ response = {
+ what: 'domLayout',
+ layout: msg.fingerprint !== fingerprint ? domLayout() : 'NOCHANGE',
+ fingerprint: fingerprint
+ };
+ break;
+
+ case 'highlight':
+ currentSelector = msg.selector;
+ highlight(msg.scrollTo);
+ break;
+
+ case 'toggleNodes':
+ toggleNodes(msg.selector, msg.original, msg.target);
+ currentSelector = msg.selector;
+ highlight(true);
+ break;
+
+ case 'shutdown':
+ shutdown();
+ break;
+
+ default:
+ break;
+ }
+
+ if ( response !== undefined && request.what === 'postMessageTo' ) {
+ localMessager.send({
+ what: 'postMessageTo',
+ senderTabId: null,
+ senderChannel: 'dom-inspector.js',
+ receiverTabId: request.senderTabId,
+ receiverChannel: request.senderChannel,
+ msg: response
+ });
+ }
+};
+
+/******************************************************************************/
+
+// Install DOM inspector widget
+
+pickerRoot = document.createElement('iframe');
+pickerRoot.classList.add(vAPI.sessionId);
+pickerRoot.classList.add('dom-inspector');
+pickerRoot.style.cssText = [
+ 'background: transparent',
+ 'border: 0',
+ 'border-radius: 0',
+ 'box-shadow: none',
+ 'display: block',
+ 'height: 100%',
+ 'left: 0',
+ 'margin: 0',
+ 'opacity: 1',
+ 'position: fixed',
+ 'outline: 0',
+ 'padding: 0',
+ 'top: 0',
+ 'visibility: visible',
+ 'width: 100%',
+ 'z-index: 2147483647',
+ ''
+].join(' !important;\n');
+
+pickerRoot.onload = function() {
+ pickerRoot.onload = null;
+ var pickerDoc = this.contentDocument;
+
+ var style = pickerDoc.createElement('style');
+ style.textContent = [
+ 'body {',
+ 'background-color: transparent;',
+ 'cursor: crosshair;',
+ '}',
+ 'svg {',
+ 'height: 100%;',
+ 'left: 0;',
+ 'position: fixed;',
+ 'top: 0;',
+ 'width: 100%;',
+ '}',
+ 'svg > path:first-child {',
+ 'fill: rgba(0,0,0,0.75);',
+ 'fill-rule: evenodd;',
+ '}',
+ 'svg > path + path {',
+ 'fill: rgba(0,0,255,0.1);',
+ 'stroke: #FFF;',
+ 'stroke-width: 0.5px;',
+ '}',
+ ''
+ ].join('\n');
+ pickerDoc.body.appendChild(style);
+
+ svgRoot = pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ svgOcean = pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'path');
+ svgRoot.appendChild(svgOcean);
+ svgIslands = pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'path');
+ svgRoot.appendChild(svgIslands);
+ pickerDoc.body.appendChild(svgRoot);
+
+ window.addEventListener('scroll', onScrolled, true);
+
+ highlight();
+
+ localMessager.addListener(onMessage);
+};
+
+document.documentElement.appendChild(pickerRoot);
+
+/******************************************************************************/
+
+})();
+
+/******************************************************************************/
diff --git a/src/js/scriptlets/dom-layout.js b/src/js/scriptlets/dom-layout.js
deleted file mode 100644
index 1c22de8..0000000
--- a/src/js/scriptlets/dom-layout.js
+++ /dev/null
@@ -1,406 +0,0 @@
-/*******************************************************************************
-
- 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, HTMLDocument */
-
-/******************************************************************************/
-/******************************************************************************/
-
-/*! http://mths.be/cssescape v0.2.1 by @mathias | MIT license */
-;(function(root) {
-
- 'use strict';
-
- if (!root.CSS) {
- root.CSS = {};
- }
-
- var CSS = root.CSS;
-
- var InvalidCharacterError = function(message) {
- this.message = message;
- };
- InvalidCharacterError.prototype = new Error();
- InvalidCharacterError.prototype.name = 'InvalidCharacterError';
-
- if (!CSS.escape) {
- // http://dev.w3.org/csswg/cssom/#serialize-an-identifier
- CSS.escape = function(value) {
- var string = String(value);
- var length = string.length;
- var index = -1;
- var codeUnit;
- var result = '';
- var firstCodeUnit = string.charCodeAt(0);
- while (++index < length) {
- codeUnit = string.charCodeAt(index);
- // Note: there’s no need to special-case astral symbols, surrogate
- // pairs, or lone surrogates.
-
- // If the character is NULL (U+0000), then throw an
- // `InvalidCharacterError` exception and terminate these steps.
- if (codeUnit === 0x0000) {
- throw new InvalidCharacterError(
- 'Invalid character: the input contains U+0000.'
- );
- }
-
- if (
- // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
- // U+007F, […]
- (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
- // If the character is the first character and is in the range [0-9]
- // (U+0030 to U+0039), […]
- (index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
- // If the character is the second character and is in the range [0-9]
- // (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
- (
- index == 1 &&
- codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
- firstCodeUnit == 0x002D
- )
- ) {
- // http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point
- result += '\\' + codeUnit.toString(16) + ' ';
- continue;
- }
-
- // If the character is not handled by one of the above rules and is
- // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
- // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
- // U+005A), or [a-z] (U+0061 to U+007A), […]
- if (
- codeUnit >= 0x0080 ||
- codeUnit == 0x002D ||
- codeUnit == 0x005F ||
- codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
- codeUnit >= 0x0041 && codeUnit <= 0x005A ||
- codeUnit >= 0x0061 && codeUnit <= 0x007A
- ) {
- // the character itself
- result += string.charAt(index);
- continue;
- }
-
- // Otherwise, the escaped character.
- // http://dev.w3.org/csswg/cssom/#escape-a-character
- result += '\\' + string.charAt(index);
-
- }
- return result;
- };
- }
-
-}(self));
-
-/******************************************************************************/
-/******************************************************************************/
-
-(function() {
-
-'use strict';
-
-/******************************************************************************/
-
-// https://github.com/gorhill/uBlock/issues/464
-if ( document instanceof HTMLDocument === false ) {
- return;
-}
-
-// This can happen
-if ( typeof vAPI !== 'object' ) {
- return;
-}
-
-/******************************************************************************/
-
-var skipTagNames = {
- 'br': true,
- 'link': true,
- 'meta': true,
- 'script': true,
- 'style': true
-};
-
-var resourceAttrNames = {
- 'a': 'href',
- 'iframe': 'src',
- 'img': 'src',
- 'object': 'data'
-};
-
-/******************************************************************************/
-
-// Collect all nodes which are directly affected by cosmetic filters: these
-// will be reported in the layout data.
-
-var nodeToCosmeticFilterMap = (function() {
- var out = new WeakMap();
- var styleTags = vAPI.styles || [];
- var i = styleTags.length;
- var selectors, styleText, j, selector, nodes, k;
- while ( i-- ) {
- styleText = styleTags[i].textContent;
- selectors = styleText.slice(0, styleText.lastIndexOf('\n')).split(/,\n/);
- j = selectors.length;
- while ( j-- ) {
- selector = selectors[j];
- nodes = document.querySelectorAll(selector);
- k = nodes.length;
- while ( k-- ) {
- out.set(nodes[k], selector);
- }
- }
- }
- return out;
-})();
-
-/******************************************************************************/
-
-var DomRoot = function() {
- this.lvl = 0;
- this.sel = 'body';
- var url = window.location.href;
- var pos = url.indexOf('#');
- if ( pos !== -1 ) {
- url = url.slice(0, pos);
- }
- this.src = url;
- this.top = window === window.top;
- this.cnt = 0;
- this.fp = fingerprint();
-};
-
-var DomNode = function(level, selector, filter) {
- this.lvl = level;
- this.sel = selector;
- this.cnt = 0;
- this.filter = filter;
-};
-
-/******************************************************************************/
-
-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 matchesSelector = (function() {
- if ( typeof Element.prototype.matches === 'function' ) {
- return 'matches';
- }
- if ( typeof Element.prototype.mozMatchesSelector === 'function' ) {
- return 'mozMatchesSelector';
- }
- if ( typeof Element.prototype.webkitMatchesSelector === 'function' ) {
- return 'webkitMatchesSelector';
- }
- return '';
-})();
-
-/******************************************************************************/
-
-var selectorFromNode = function(node) {
- var str, attr, pos, sw, i;
- var tag = node.localName;
- var selector = CSS.escape(tag);
- // Id
- if ( typeof node.id === 'string' ) {
- str = node.id.trim();
- if ( str !== '' ) {
- selector += '#' + CSS.escape(str);
- }
- }
- // Class
- var cl = node.classList;
- if ( cl ) {
- for ( i = 0; i < cl.length; i++ ) {
- selector += '.' + CSS.escape(cl[i]);
- }
- }
- // Tag-specific attributes
- if ( resourceAttrNames.hasOwnProperty(tag) ) {
- attr = resourceAttrNames[tag];
- str = node.getAttribute(attr) || '';
- str = str.trim();
- pos = str.indexOf('#');
- if ( pos !== -1 ) {
- str = str.slice(0, pos);
- sw = '^';
- } else {
- sw = '';
- }
- if ( str !== '' ) {
- selector += '[' + attr + sw + '="' + CSS.escape(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;
-};
-
-/******************************************************************************/
-
-var domNodeFactory = function(level, node) {
- var localName = node.localName;
- if ( skipTagNames.hasOwnProperty(localName) ) {
- return null;
- }
- // skip uBlock's own nodes
- if ( node.classList.contains(vAPI.sessionId) ) {
- return null;
- }
- if ( level === 0 && localName === 'body' ) {
- return new DomRoot();
- }
- var selector = selectorFromNode(node);
- var filter = nodeToCosmeticFilterMap.get(node);
- return new DomNode(level, selector, filter);
-};
-
-/******************************************************************************/
-
-// Some kind of fingerprint for the DOM, without incurring too much
-// overhead.
-
-var fingerprint = function() {
- var url = window.location.href;
- var pos = url.indexOf('#');
- if ( pos !== -1 ) {
- url = url.slice(0, pos);
- }
- return url + '{' + document.getElementsByTagName('*').length.toString() + '}';
-};
-
-/******************************************************************************/
-
-// Collect layout data.
-
-var domLayout = [];
-
-(function() {
- var dom = domLayout;
- var stack = [];
- var node = document.body;
- var domNode;
- var lvl = 0;
-
- for (;;) {
- domNode = domNodeFactory(lvl, node);
- if ( domNode !== null ) {
- dom.push(domNode);
- }
- // children
- if ( node.firstElementChild !== null ) {
- stack.push(node);
- lvl += 1;
- node = node.firstElementChild;
- continue;
- }
- // sibling
- if ( node.nextElementSibling === null ) {
- do {
- node = stack.pop();
- if ( !node ) { break; }
- lvl -= 1;
- } while ( node.nextElementSibling === null );
- if ( !node ) { break; }
- }
- node = node.nextElementSibling;
- }
-})();
-
-/******************************************************************************/
-
-// Descendant count for each node.
-
-(function() {
- var dom = domLayout;
- var stack = [], ptr;
- var lvl = 0;
- var domNode, cnt;
- var i = dom.length;
-
- while ( i-- ) {
- domNode = dom[i];
- if ( domNode.lvl === lvl ) {
- stack[ptr] += 1;
- continue;
- }
- if ( domNode.lvl > lvl ) {
- while ( lvl < domNode.lvl ) {
- stack.push(0);
- lvl += 1;
- }
- ptr = lvl - 1;
- stack[ptr] += 1;
- continue;
- }
- // domNode.lvl < lvl
- cnt = stack.pop();
- domNode.cnt = cnt;
- lvl -= 1;
- ptr = lvl - 1;
- stack[ptr] += cnt + 1;
- }
-})();
-
-/******************************************************************************/
-
-var localMessager = vAPI.messaging.channel('scriptlets');
-localMessager.send({
- what: 'scriptletResponse',
- scriptlet: 'dom-layout',
- response: domLayout
-}, function() {
- localMessager.close();
- localMessager = null;
-});
-
-/******************************************************************************/
-
-})();
-
-/******************************************************************************/