mirror of
https://github.com/gorhill/uBlock.git
synced 2024-10-04 16:47:15 +02:00
some refactoring of new DOM inspector code
This commit is contained in:
parent
631443768f
commit
7d2855180c
@ -603,7 +603,7 @@ vAPI.messaging.onPortMessage = function(request, port) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error('µBlock> messaging > unknown request: %o', request);
|
console.error('uBlock> messaging > unknown request: %o', request);
|
||||||
|
|
||||||
// Unhandled:
|
// Unhandled:
|
||||||
// Need to callback anyways in case caller expected an answer, or
|
// 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 port;
|
||||||
var chromiumTabId = toChromiumTabId(tabId);
|
var chromiumTabId = toChromiumTabId(message.receiverTabId);
|
||||||
for ( var portName in this.ports ) {
|
for ( var portName in this.ports ) {
|
||||||
if ( this.ports.hasOwnProperty(portName) === false ) {
|
if ( this.ports.hasOwnProperty(portName) === false ) {
|
||||||
continue;
|
continue;
|
||||||
@ -673,7 +690,7 @@ vAPI.messaging.send = function(tabId, channelName, message) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
port.postMessage({
|
port.postMessage({
|
||||||
channelName: channelName,
|
channelName: message.receiverChannel,
|
||||||
msg: message
|
msg: message
|
||||||
});
|
});
|
||||||
if ( chromiumTabId !== 0 ) {
|
if ( chromiumTabId !== 0 ) {
|
||||||
|
@ -1239,10 +1239,30 @@ vAPI.messaging.setup = function(defaultHandler) {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.messaging.send = function(tabId, channelName, message) {
|
// "Auxiliary process": any process other than main process.
|
||||||
var ffTabId = tabId || '';
|
//
|
||||||
|
// 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 targetId = location.host + ':broadcast';
|
||||||
var payload = JSON.stringify({ channelName: channelName, msg: message });
|
var payload = JSON.stringify({
|
||||||
|
channelName: message.receiverChannel,
|
||||||
|
msg: message
|
||||||
|
});
|
||||||
|
|
||||||
if ( ffTabId === '' ) {
|
if ( ffTabId === '' ) {
|
||||||
this.globalMessageManager.broadcastAsyncMessage(targetId, payload);
|
this.globalMessageManager.broadcastAsyncMessage(targetId, payload);
|
||||||
|
@ -133,80 +133,16 @@ var tabIdFromClassName = function(className) {
|
|||||||
|
|
||||||
(function domInspector() {
|
(function domInspector() {
|
||||||
// Don't bother if the browser is not modern enough.
|
// Don't bother if the browser is not modern enough.
|
||||||
if ( typeof Map === undefined ) {
|
if ( typeof Map === undefined || typeof WeakMap === undefined ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var enabled = false;
|
var inspectedTabId = '';
|
||||||
var state = '';
|
|
||||||
var fingerprint = '';
|
|
||||||
var fingerprintTimer = null;
|
|
||||||
var currentTabId = '';
|
|
||||||
var currentSelector = '';
|
var currentSelector = '';
|
||||||
|
var showdomButton = uDom.nodeFromId('showdom');
|
||||||
var inspector = uDom.nodeFromId('domInspector');
|
var inspector = uDom.nodeFromId('domInspector');
|
||||||
var tabSelector = uDom.nodeFromId('pageSelector');
|
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 nodeFromDomEntry = function(entry) {
|
||||||
var node;
|
var node;
|
||||||
var li = document.createElement('li');
|
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 renderDOM = function(response) {
|
||||||
var ul = document.createElement('ul');
|
var ul = document.createElement('ul');
|
||||||
var lvl = 0;
|
var lvl = 0;
|
||||||
@ -314,9 +227,6 @@ var tabIdFromClassName = function(className) {
|
|||||||
|
|
||||||
removeAllChildren(inspector);
|
removeAllChildren(inspector);
|
||||||
inspector.appendChild(ul);
|
inspector.appendChild(ul);
|
||||||
|
|
||||||
// Check for change at regular interval.
|
|
||||||
fingerprint = entries.length !== 0 ? entries[0].fp : '';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var selectorFromNode = function(node, nth) {
|
var selectorFromNode = function(node, nth) {
|
||||||
@ -344,7 +254,7 @@ var tabIdFromClassName = function(className) {
|
|||||||
var onClick = function(ev) {
|
var onClick = function(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
if ( currentTabId === '' ) {
|
if ( inspectedTabId === '' ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,12 +276,13 @@ var tabIdFromClassName = function(className) {
|
|||||||
if ( target.localName === 'code' ) {
|
if ( target.localName === 'code' ) {
|
||||||
var original = target.classList.contains('filter') === false;
|
var original = target.classList.contains('filter') === false;
|
||||||
messager.send({
|
messager.send({
|
||||||
what: 'sendMessageTo',
|
what: 'postMessageTo',
|
||||||
tabId: currentTabId,
|
senderTabId: null,
|
||||||
channelName: 'scriptlets',
|
senderChannel: 'logger-ui.js',
|
||||||
|
receiverTabId: inspectedTabId,
|
||||||
|
receiverChannel: 'dom-inspector.js',
|
||||||
msg: {
|
msg: {
|
||||||
what: 'dom-highlight',
|
what: 'toggleNodes',
|
||||||
action: 'toggleNodes',
|
|
||||||
original: original,
|
original: original,
|
||||||
target: original !== target.classList.toggle('off'),
|
target: original !== target.classList.toggle('off'),
|
||||||
selector: selectorFromNode(target, original ? 1 : 2)
|
selector: selectorFromNode(target, original ? 1 : 2)
|
||||||
@ -383,12 +294,13 @@ var tabIdFromClassName = function(className) {
|
|||||||
// Highlight and scrollto
|
// Highlight and scrollto
|
||||||
if ( target.localName === 'code' ) {
|
if ( target.localName === 'code' ) {
|
||||||
messager.send({
|
messager.send({
|
||||||
what: 'sendMessageTo',
|
what: 'postMessageTo',
|
||||||
tabId: currentTabId,
|
senderTabId: null,
|
||||||
channelName: 'scriptlets',
|
senderChannel: 'logger-ui.js',
|
||||||
|
receiverTabId: inspectedTabId,
|
||||||
|
receiverChannel: 'dom-inspector.js',
|
||||||
msg: {
|
msg: {
|
||||||
what: 'dom-highlight',
|
what: 'highlight',
|
||||||
action: 'highlight',
|
|
||||||
selector: selectorFromNode(target),
|
selector: selectorFromNode(target),
|
||||||
scrollTo: true
|
scrollTo: true
|
||||||
}
|
}
|
||||||
@ -404,12 +316,13 @@ var tabIdFromClassName = function(className) {
|
|||||||
var timerHandler = function() {
|
var timerHandler = function() {
|
||||||
mouseoverTimer = null;
|
mouseoverTimer = null;
|
||||||
messager.send({
|
messager.send({
|
||||||
what: 'sendMessageTo',
|
what: 'postMessageTo',
|
||||||
tabId: currentTabId,
|
senderTabId: null,
|
||||||
channelName: 'scriptlets',
|
senderChannel: 'logger-ui.js',
|
||||||
|
receiverTabId: inspectedTabId,
|
||||||
|
receiverChannel: 'dom-inspector.js',
|
||||||
msg: {
|
msg: {
|
||||||
what: 'dom-highlight',
|
what: 'highlight',
|
||||||
action: 'highlight',
|
|
||||||
selector: selectorFromNode(mouseoverTarget),
|
selector: selectorFromNode(mouseoverTarget),
|
||||||
scrollTo: true
|
scrollTo: true
|
||||||
}
|
}
|
||||||
@ -417,6 +330,10 @@ var tabIdFromClassName = function(className) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return function(ev) {
|
return function(ev) {
|
||||||
|
if ( inspectedTabId === '' ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Find closest `li`
|
// Find closest `li`
|
||||||
var target = ev.target;
|
var target = ev.target;
|
||||||
while ( target !== null ) {
|
while ( target !== null ) {
|
||||||
@ -435,115 +352,165 @@ var tabIdFromClassName = function(className) {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
var onFingerprintFetched = function(response) {
|
var pollTimer = null;
|
||||||
if ( state !== 'fetchingFingerprint' ) {
|
var fingerprint = null;
|
||||||
return;
|
|
||||||
|
var currentTabId = function() {
|
||||||
|
if ( showdomButton.classList.contains('active') === false ) {
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
state = '';
|
var tabId = tabIdFromClassName(tabSelector.value) || '';
|
||||||
fingerprintTimer = null;
|
return tabId !== 'bts' ? tabId : '';
|
||||||
if ( !enabled ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( response === fingerprint ) {
|
|
||||||
startTimer();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fingerprint = response || '';
|
|
||||||
fetchDOM();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var fetchFingerprint = function() {
|
var cancelPollTimer = function() {
|
||||||
messager.send({
|
if ( pollTimer !== null ) {
|
||||||
what: 'scriptlet',
|
clearTimeout(pollTimer);
|
||||||
tabId: currentTabId,
|
pollTimer = null;
|
||||||
scriptlet: 'dom-fingerprint'
|
}
|
||||||
}, onFingerprintFetched);
|
|
||||||
state = 'fetchingFingerprint';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var onDOMFetched = function(response) {
|
var onDOMFetched = function(response) {
|
||||||
if ( state !== 'fetchingDOM' ) {
|
if ( response === undefined || currentTabId() !== inspectedTabId ) {
|
||||||
|
shutdownInspector(inspectedTabId);
|
||||||
|
injectInspectorAsync(250);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state = '';
|
|
||||||
if ( !enabled ) {
|
if ( response.layout === 'NOCHANGE' ) {
|
||||||
|
fetchDOMAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ( Array.isArray(response) && response.length !== 0 ) {
|
|
||||||
renderDOM(response);
|
renderDOM(response.layout);
|
||||||
injectHighlighter(currentTabId);
|
fingerprint = response.fingerprint;
|
||||||
} else {
|
|
||||||
fingerprint = '';
|
fetchDOMAsync();
|
||||||
}
|
|
||||||
startTimer();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var fetchDOM = function() {
|
var fetchDOM = function() {
|
||||||
if ( currentTabId === '' ) {
|
messager.send({
|
||||||
removeAllChildren(inspector);
|
what: 'postMessageTo',
|
||||||
startTimer();
|
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;
|
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({
|
messager.send({
|
||||||
what: 'scriptlet',
|
what: 'scriptlet',
|
||||||
tabId: currentTabId,
|
tabId: tabId,
|
||||||
scriptlet: 'dom-layout'
|
scriptlet: 'dom-inspector'
|
||||||
}, onDOMFetched);
|
});
|
||||||
state = 'fetchingDOM';
|
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() {
|
var onTabIdChanged = function() {
|
||||||
if ( !enabled ) {
|
if ( inspectedTabId !== currentTabId() ) {
|
||||||
return;
|
shutdownInspector();
|
||||||
|
injectInspectorAsync(250);
|
||||||
}
|
}
|
||||||
var previousTabId = currentTabId;
|
};
|
||||||
var tabId = tabIdFromClassName(tabSelector.value) || '';
|
|
||||||
currentTabId = tabId !== 'bts' && tabId !== '' ? tabId : '';
|
var onMessage = function(request) {
|
||||||
if ( currentTabId !== previousTabId ) {
|
var msg = request.what === 'postMessageTo' ? request.msg : request;
|
||||||
removeHighlighter(previousTabId);
|
switch ( msg.what ) {
|
||||||
|
case 'domLayout':
|
||||||
|
cancelPollTimer();
|
||||||
|
onDOMFetched(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if ( state === 'fetchingDOM' ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fetchDOM();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var toggleOn = function() {
|
var toggleOn = function() {
|
||||||
if ( enabled ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
enabled = true;
|
|
||||||
window.addEventListener('beforeunload', toggleOff);
|
window.addEventListener('beforeunload', toggleOff);
|
||||||
inspector.addEventListener('click', onClick, true);
|
inspector.addEventListener('click', onClick, true);
|
||||||
inspector.addEventListener('mouseover', onMouseOver, true);
|
inspector.addEventListener('mouseover', onMouseOver, true);
|
||||||
tabSelector.addEventListener('change', onTabIdChanged);
|
tabSelector.addEventListener('change', onTabIdChanged);
|
||||||
onTabIdChanged();
|
|
||||||
inspector.classList.add('enabled');
|
inspector.classList.add('enabled');
|
||||||
|
messager.addListener(onMessage);
|
||||||
|
injectInspector();
|
||||||
};
|
};
|
||||||
|
|
||||||
var toggleOff = function() {
|
var toggleOff = function() {
|
||||||
removeHighlighter(currentTabId);
|
messager.removeListener(onMessage);
|
||||||
|
cancelPollTimer();
|
||||||
|
shutdownInspector();
|
||||||
window.removeEventListener('beforeunload', toggleOff);
|
window.removeEventListener('beforeunload', toggleOff);
|
||||||
inspector.removeEventListener('click', onClick, true);
|
inspector.removeEventListener('click', onClick, true);
|
||||||
inspector.removeEventListener('mouseover', onMouseOver, true);
|
inspector.removeEventListener('mouseover', onMouseOver, true);
|
||||||
tabSelector.removeEventListener('change', onTabIdChanged);
|
tabSelector.removeEventListener('change', onTabIdChanged);
|
||||||
removeAllChildren(inspector);
|
currentSelector = inspectedTabId = '';
|
||||||
stopTimer();
|
|
||||||
currentTabId = currentSelector = fingerprint = '';
|
|
||||||
enabled = false;
|
|
||||||
inspector.classList.remove('enabled');
|
inspector.classList.remove('enabled');
|
||||||
};
|
};
|
||||||
|
|
||||||
var toggle = function() {
|
var toggle = function() {
|
||||||
if ( uDom.nodeFromId('showdom').classList.toggle('active') ) {
|
if ( showdomButton.classList.toggle('active') ) {
|
||||||
toggleOn();
|
toggleOn();
|
||||||
} else {
|
} else {
|
||||||
toggleOff();
|
toggleOff();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
uDom('#showdom').on('click', toggle);
|
showdomButton.addEventListener('click', toggle);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -141,6 +141,12 @@ var onMessage = function(request, sender, callback) {
|
|||||||
vAPI.tabs.open(request.details);
|
vAPI.tabs.open(request.details);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// Passthrough for auxiliary script to auxiliary script messaging
|
||||||
|
case 'postMessageTo':
|
||||||
|
request.senderTabId = tabId;
|
||||||
|
vAPI.messaging.post(request);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'reloadTab':
|
case 'reloadTab':
|
||||||
if ( vAPI.isBehindTheSceneTabId(request.tabId) === false ) {
|
if ( vAPI.isBehindTheSceneTabId(request.tabId) === false ) {
|
||||||
vAPI.tabs.reload(request.tabId);
|
vAPI.tabs.reload(request.tabId);
|
||||||
@ -158,10 +164,6 @@ var onMessage = function(request, sender, callback) {
|
|||||||
µb.selectFilterLists(request.switches);
|
µb.selectFilterLists(request.switches);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'sendMessageTo':
|
|
||||||
vAPI.messaging.send(request.tabId, request.channelName, request.msg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'toggleHostnameSwitch':
|
case 'toggleHostnameSwitch':
|
||||||
µb.toggleHostnameSwitch(request);
|
µb.toggleHostnameSwitch(request);
|
||||||
break;
|
break;
|
||||||
|
@ -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();
|
|
||||||
});
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
@ -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);
|
|
||||||
})();
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
691
src/js/scriptlets/dom-inspector.js
Normal file
691
src/js/scriptlets/dom-inspector.js
Normal file
@ -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);
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
/******************************************************************************/
|
@ -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;
|
|
||||||
});
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
Loading…
Reference in New Issue
Block a user