mirror of https://github.com/gorhill/uBlock.git
Isolate DOM inspector layers from page context
Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/1411 Additionally, refactored communication mechanism between content script contexts and uBO contexts by using MessageChannel/BroadcastChannel web APIs.
This commit is contained in:
parent
2c495487f1
commit
ee83a4304a
|
@ -967,71 +967,11 @@ vAPI.messaging = {
|
|||
}
|
||||
},
|
||||
|
||||
broadcast: function(message) {
|
||||
const messageWrapper = { broadcast: true, msg: message };
|
||||
for ( const { port } of this.ports.values() ) {
|
||||
try {
|
||||
port.postMessage(messageWrapper);
|
||||
} catch(ex) {
|
||||
this.onPortDisconnect(port);
|
||||
}
|
||||
}
|
||||
if ( this.defaultHandler ) {
|
||||
this.defaultHandler(message, null, ( ) => { });
|
||||
}
|
||||
},
|
||||
|
||||
onFrameworkMessage: function(request, port, callback) {
|
||||
const portDetails = this.ports.get(port.name) || {};
|
||||
const tabId = portDetails.tabId;
|
||||
const msg = request.msg;
|
||||
switch ( msg.what ) {
|
||||
case 'connectionAccepted':
|
||||
case 'connectionRefused': {
|
||||
const toPort = this.ports.get(msg.fromToken);
|
||||
if ( toPort !== undefined ) {
|
||||
msg.tabId = tabId;
|
||||
toPort.port.postMessage(request);
|
||||
} else {
|
||||
msg.what = 'connectionBroken';
|
||||
port.postMessage(request);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'connectionRequested':
|
||||
msg.tabId = tabId;
|
||||
for ( const { port: toPort } of this.ports.values() ) {
|
||||
if ( toPort === port ) { continue; }
|
||||
try {
|
||||
toPort.postMessage(request);
|
||||
} catch (ex) {
|
||||
this.onPortDisconnect(toPort);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'connectionBroken':
|
||||
case 'connectionCheck':
|
||||
case 'connectionMessage': {
|
||||
const toPort = this.ports.get(
|
||||
port.name === msg.fromToken ? msg.toToken : msg.fromToken
|
||||
);
|
||||
if ( toPort !== undefined ) {
|
||||
msg.tabId = tabId;
|
||||
toPort.port.postMessage(request);
|
||||
} else {
|
||||
msg.what = 'connectionBroken';
|
||||
port.postMessage(request);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'extendClient':
|
||||
vAPI.tabs.executeScript(tabId, {
|
||||
file: '/js/vapi-client-extra.js',
|
||||
frameId: portDetails.frameId,
|
||||
}).then(( ) => {
|
||||
callback();
|
||||
});
|
||||
break;
|
||||
case 'localStorage': {
|
||||
if ( portDetails.privileged !== true ) { break; }
|
||||
const args = msg.args || [];
|
||||
|
|
|
@ -1,264 +0,0 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2019-present 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
|
||||
*/
|
||||
|
||||
// For non-background page
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Direct messaging connection ability
|
||||
|
||||
(( ) => {
|
||||
// >>>>>>>> start of private namespace
|
||||
|
||||
if ( typeof vAPI !== 'object' ) { return; }
|
||||
if ( vAPI.messaging instanceof Object === false ) { return; }
|
||||
if ( vAPI.MessagingConnection instanceof Function ) { return; }
|
||||
|
||||
const listeners = new Set();
|
||||
const connections = new Map();
|
||||
|
||||
vAPI.MessagingConnection = class {
|
||||
constructor(handler, details) {
|
||||
this.messaging = vAPI.messaging;
|
||||
this.handler = handler;
|
||||
this.id = details.id;
|
||||
this.to = details.to;
|
||||
this.toToken = details.toToken;
|
||||
this.from = details.from;
|
||||
this.fromToken = details.fromToken;
|
||||
this.checkTimer = undefined;
|
||||
// On Firefox it appears ports are not automatically disconnected
|
||||
// when navigating to another page.
|
||||
const ctor = vAPI.MessagingConnection;
|
||||
if ( ctor.pagehide !== undefined ) { return; }
|
||||
ctor.pagehide = ( ) => {
|
||||
for ( const connection of connections.values() ) {
|
||||
connection.disconnect();
|
||||
connection.handler(
|
||||
connection.toDetails('connectionBroken')
|
||||
);
|
||||
}
|
||||
};
|
||||
window.addEventListener('pagehide', ctor.pagehide);
|
||||
}
|
||||
toDetails(what, payload) {
|
||||
return {
|
||||
what: what,
|
||||
id: this.id,
|
||||
from: this.from,
|
||||
fromToken: this.fromToken,
|
||||
to: this.to,
|
||||
toToken: this.toToken,
|
||||
payload: payload
|
||||
};
|
||||
}
|
||||
disconnect() {
|
||||
if ( this.checkTimer !== undefined ) {
|
||||
clearTimeout(this.checkTimer);
|
||||
this.checkTimer = undefined;
|
||||
}
|
||||
connections.delete(this.id);
|
||||
const port = this.messaging.getPort();
|
||||
if ( port === null ) { return; }
|
||||
port.postMessage({
|
||||
channel: 'vapi',
|
||||
msg: this.toDetails('connectionBroken'),
|
||||
});
|
||||
}
|
||||
checkAsync() {
|
||||
if ( this.checkTimer !== undefined ) {
|
||||
clearTimeout(this.checkTimer);
|
||||
}
|
||||
this.checkTimer = vAPI.setTimeout(
|
||||
( ) => { this.check(); },
|
||||
499
|
||||
);
|
||||
}
|
||||
check() {
|
||||
this.checkTimer = undefined;
|
||||
if ( connections.has(this.id) === false ) { return; }
|
||||
const port = this.messaging.getPort();
|
||||
if ( port === null ) { return; }
|
||||
port.postMessage({
|
||||
channel: 'vapi',
|
||||
msg: this.toDetails('connectionCheck'),
|
||||
});
|
||||
this.checkAsync();
|
||||
}
|
||||
receive(details) {
|
||||
switch ( details.what ) {
|
||||
case 'connectionAccepted':
|
||||
this.toToken = details.toToken;
|
||||
this.handler(details);
|
||||
this.checkAsync();
|
||||
break;
|
||||
case 'connectionBroken':
|
||||
connections.delete(this.id);
|
||||
this.handler(details);
|
||||
break;
|
||||
case 'connectionMessage':
|
||||
this.handler(details);
|
||||
this.checkAsync();
|
||||
break;
|
||||
case 'connectionCheck':
|
||||
const port = this.messaging.getPort();
|
||||
if ( port === null ) { return; }
|
||||
if ( connections.has(this.id) ) {
|
||||
this.checkAsync();
|
||||
} else {
|
||||
details.what = 'connectionBroken';
|
||||
port.postMessage({ channel: 'vapi', msg: details });
|
||||
}
|
||||
break;
|
||||
case 'connectionRefused':
|
||||
connections.delete(this.id);
|
||||
this.handler(details);
|
||||
break;
|
||||
}
|
||||
}
|
||||
send(payload) {
|
||||
const port = this.messaging.getPort();
|
||||
if ( port === null ) { return; }
|
||||
port.postMessage({
|
||||
channel: 'vapi',
|
||||
msg: this.toDetails('connectionMessage', payload),
|
||||
});
|
||||
}
|
||||
|
||||
static addListener(listener) {
|
||||
listeners.add(listener);
|
||||
vAPI.messaging.getPort(); // Ensure a port instance exists
|
||||
}
|
||||
static removeListener(listener) {
|
||||
listeners.delete(listener);
|
||||
}
|
||||
static connectTo(from, to, handler) {
|
||||
const port = vAPI.messaging.getPort();
|
||||
if ( port === null ) { return; }
|
||||
const connection = new vAPI.MessagingConnection(handler, {
|
||||
id: `${from}-${to}-${vAPI.sessionId}`,
|
||||
to: to,
|
||||
from: from,
|
||||
fromToken: port.name
|
||||
});
|
||||
connections.set(connection.id, connection);
|
||||
port.postMessage({
|
||||
channel: 'vapi',
|
||||
msg: {
|
||||
what: 'connectionRequested',
|
||||
id: connection.id,
|
||||
from: from,
|
||||
fromToken: port.name,
|
||||
to: to,
|
||||
}
|
||||
});
|
||||
return connection.id;
|
||||
}
|
||||
static disconnectFrom(connectionId) {
|
||||
const connection = connections.get(connectionId);
|
||||
if ( connection === undefined ) { return; }
|
||||
connection.disconnect();
|
||||
}
|
||||
static sendTo(connectionId, payload) {
|
||||
const connection = connections.get(connectionId);
|
||||
if ( connection === undefined ) { return; }
|
||||
connection.send(payload);
|
||||
}
|
||||
static canDestroyPort() {
|
||||
return listeners.length === 0 && connections.size === 0;
|
||||
}
|
||||
static mustDestroyPort() {
|
||||
if ( connections.size === 0 ) { return; }
|
||||
for ( const connection of connections.values() ) {
|
||||
connection.receive({ what: 'connectionBroken' });
|
||||
}
|
||||
connections.clear();
|
||||
}
|
||||
static canProcessMessage(details) {
|
||||
if ( details.channel !== 'vapi' ) { return; }
|
||||
switch ( details.msg.what ) {
|
||||
case 'connectionAccepted':
|
||||
case 'connectionBroken':
|
||||
case 'connectionCheck':
|
||||
case 'connectionMessage':
|
||||
case 'connectionRefused': {
|
||||
const connection = connections.get(details.msg.id);
|
||||
if ( connection === undefined ) { break; }
|
||||
connection.receive(details.msg);
|
||||
return true;
|
||||
}
|
||||
case 'connectionRequested':
|
||||
if ( listeners.length === 0 ) { return; }
|
||||
const port = vAPI.messaging.getPort();
|
||||
if ( port === null ) { break; }
|
||||
let listener, result;
|
||||
for ( listener of listeners ) {
|
||||
result = listener(details.msg);
|
||||
if ( result !== undefined ) { break; }
|
||||
}
|
||||
if ( result === undefined ) { break; }
|
||||
if ( result === true ) {
|
||||
details.msg.what = 'connectionAccepted';
|
||||
details.msg.toToken = port.name;
|
||||
const connection = new vAPI.MessagingConnection(
|
||||
listener,
|
||||
details.msg
|
||||
);
|
||||
connections.set(connection.id, connection);
|
||||
} else {
|
||||
details.msg.what = 'connectionRefused';
|
||||
}
|
||||
port.postMessage(details);
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
vAPI.messaging.extensions.push(vAPI.MessagingConnection);
|
||||
|
||||
// <<<<<<<< end of private namespace
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
|
||||
DO NOT:
|
||||
- Remove the following code
|
||||
- Add code beyond the following code
|
||||
Reason:
|
||||
- https://github.com/gorhill/uBlock/pull/3721
|
||||
- uBO never uses the return value from injected content scripts
|
||||
|
||||
**/
|
||||
|
||||
void 0;
|
|
@ -83,8 +83,6 @@ vAPI.messaging = {
|
|||
port: null,
|
||||
portTimer: null,
|
||||
portTimerDelay: 10000,
|
||||
extended: undefined,
|
||||
extensions: [],
|
||||
msgIdGenerator: 1,
|
||||
pending: new Map(),
|
||||
shuttingDown: false,
|
||||
|
@ -127,23 +125,11 @@ vAPI.messaging = {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Unhandled messages
|
||||
this.extensions.every(ext => ext.canProcessMessage(details) !== true);
|
||||
},
|
||||
messageListenerBound: null,
|
||||
|
||||
canDestroyPort: function() {
|
||||
return this.pending.size === 0 && (
|
||||
this.extensions.length === 0 ||
|
||||
this.extensions.every(e => e.canDestroyPort())
|
||||
);
|
||||
},
|
||||
|
||||
mustDestroyPort: function() {
|
||||
if ( this.extensions.length === 0 ) { return; }
|
||||
this.extensions.forEach(e => e.mustDestroyPort());
|
||||
this.extensions.length = 0;
|
||||
return this.pending.size === 0;
|
||||
},
|
||||
|
||||
portPoller: function() {
|
||||
|
@ -168,7 +154,6 @@ vAPI.messaging = {
|
|||
port.onDisconnect.removeListener(this.disconnectListenerBound);
|
||||
this.port = null;
|
||||
}
|
||||
this.mustDestroyPort();
|
||||
// service pending callbacks
|
||||
if ( this.pending.size !== 0 ) {
|
||||
const pending = this.pending;
|
||||
|
@ -232,22 +217,6 @@ vAPI.messaging = {
|
|||
port.postMessage({ channel, msgId, msg });
|
||||
return promise;
|
||||
},
|
||||
|
||||
// Dynamically extend capabilities.
|
||||
//
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/1571
|
||||
// Don't use `self` to access `vAPI`.
|
||||
extend: function() {
|
||||
if ( this.extended === undefined ) {
|
||||
this.extended = vAPI.messaging.send('vapi', {
|
||||
what: 'extendClient'
|
||||
}).then(( ) =>
|
||||
typeof vAPI === 'object' && this.extensions.length !== 0
|
||||
).catch(( ) => {
|
||||
});
|
||||
}
|
||||
return this.extended;
|
||||
},
|
||||
};
|
||||
|
||||
vAPI.shutdown.add(( ) => {
|
||||
|
|
|
@ -53,7 +53,6 @@
|
|||
<script src="js/vapi.js"></script>
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/vapi-client-extra.js"></script>
|
||||
|
||||
<script src="js/codemirror/search.js" type="module"></script>
|
||||
<script src="js/codemirror/search-thread.js"></script>
|
||||
|
|
|
@ -108,7 +108,6 @@
|
|||
<script src="js/vapi.js"></script>
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/vapi-client-extra.js"></script>
|
||||
<script src="js/theme.js" type="module"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js" type="module"></script>
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
html#ublock0-inspector,
|
||||
#ublock0-inspector body {
|
||||
background: transparent;
|
||||
box-sizing: border-box;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
width: 100vw;
|
||||
}
|
||||
#ublock0-inspector :focus {
|
||||
outline: none;
|
||||
}
|
||||
#ublock0-inspector svg {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
#ublock0-inspector svg > path:nth-of-type(1) {
|
||||
fill: rgba(255,0,0,0.2);
|
||||
stroke: #F00;
|
||||
}
|
||||
#ublock0-inspector svg > path:nth-of-type(2) {
|
||||
fill: rgba(0,255,0,0.2);
|
||||
stroke: #0F0;
|
||||
}
|
||||
#ublock0-inspector svg > path:nth-of-type(3) {
|
||||
fill: rgba(255,0,0,0.2);
|
||||
stroke: #F00;
|
||||
}
|
||||
#ublock0-inspector svg > path:nth-of-type(4) {
|
||||
fill: rgba(0,0,255,0.1);
|
||||
stroke: #FFF;
|
||||
stroke-width: 0.5px;
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2014-present 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
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
const svgRoot = document.querySelector('svg');
|
||||
|
||||
const quit = ( ) => {
|
||||
inspectorContentPort.postMessage({ what: 'quitInspector' });
|
||||
inspectorContentPort.close();
|
||||
inspectorContentPort.onmessage = inspectorContentPort.onmessageerror = null;
|
||||
inspectorContentPort = undefined;
|
||||
loggerPort.postMessage({ what: 'quitInspector' });
|
||||
loggerPort.close();
|
||||
loggerPort.onmessage = loggerPort.onmessageerror = null;
|
||||
loggerPort = undefined;
|
||||
};
|
||||
|
||||
const onMessage = (msg, fromLogger) => {
|
||||
switch ( msg.what ) {
|
||||
case 'quitInspector': {
|
||||
quit();
|
||||
break;
|
||||
}
|
||||
case 'svgPaths': {
|
||||
const paths = svgRoot.children;
|
||||
paths[0].setAttribute('d', msg.paths[0]);
|
||||
paths[1].setAttribute('d', msg.paths[1]);
|
||||
paths[2].setAttribute('d', msg.paths[2]);
|
||||
paths[3].setAttribute('d', msg.paths[3]);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if ( typeof fromLogger !== 'boolean' ) { return; }
|
||||
if ( fromLogger ) {
|
||||
inspectorContentPort.postMessage(msg);
|
||||
} else {
|
||||
loggerPort.postMessage(msg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Wait for the content script to establish communication
|
||||
|
||||
let inspectorContentPort;
|
||||
|
||||
let loggerPort = new globalThis.BroadcastChannel('loggerInspector');
|
||||
loggerPort.onmessage = ev => {
|
||||
const msg = ev.data || {};
|
||||
onMessage(msg, true);
|
||||
};
|
||||
loggerPort.onmessageerror = ( ) => {
|
||||
quit();
|
||||
};
|
||||
|
||||
globalThis.addEventListener('message', ev => {
|
||||
const msg = ev.data || {};
|
||||
if ( msg.what !== 'startInspector' ) { return; }
|
||||
if ( Array.isArray(ev.ports) === false ) { return; }
|
||||
if ( ev.ports.length === 0 ) { return; }
|
||||
inspectorContentPort = ev.ports[0];
|
||||
inspectorContentPort.onmessage = ev => {
|
||||
const msg = ev.data || {};
|
||||
onMessage(msg, false);
|
||||
};
|
||||
inspectorContentPort.onmessageerror = ( ) => {
|
||||
quit();
|
||||
};
|
||||
inspectorContentPort.postMessage({ what: 'startInspector' });
|
||||
}, { once: true });
|
|
@ -53,18 +53,15 @@ const NoPaths = 'M0 0';
|
|||
|
||||
const reCosmeticAnchor = /^#(\$|\?|\$\?)?#/;
|
||||
|
||||
const epickerId = (( ) => {
|
||||
{
|
||||
const url = new URL(self.location.href);
|
||||
if ( url.searchParams.has('zap') ) {
|
||||
pickerRoot.classList.add('zap');
|
||||
}
|
||||
return url.searchParams.get('epid');
|
||||
})();
|
||||
if ( epickerId === null ) { return; }
|
||||
}
|
||||
|
||||
const docURL = new URL(vAPI.getURL(''));
|
||||
|
||||
let epickerConnectionId;
|
||||
let resultsetOpt;
|
||||
|
||||
let netFilterCandidates = [];
|
||||
|
@ -305,7 +302,7 @@ const cosmeticCandidatesFromFilterChoice = function(filterChoice) {
|
|||
candidates.push(paths);
|
||||
}
|
||||
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
pickerContentPort.postMessage({
|
||||
what: 'optimizeCandidates',
|
||||
candidates,
|
||||
slot,
|
||||
|
@ -333,7 +330,7 @@ const onSvgClicked = function(ev) {
|
|||
// If zap mode, highlight element under mouse, this makes the zapper usable
|
||||
// on touch screens.
|
||||
if ( pickerRoot.classList.contains('zap') ) {
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
pickerContentPort.postMessage({
|
||||
what: 'zapElementAtPoint',
|
||||
mx: ev.clientX,
|
||||
my: ev.clientY,
|
||||
|
@ -358,7 +355,7 @@ const onSvgClicked = function(ev) {
|
|||
if ( ev.type === 'touch' ) {
|
||||
pickerRoot.classList.add('show');
|
||||
}
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
pickerContentPort.postMessage({
|
||||
what: 'filterElementAtPoint',
|
||||
mx: ev.clientX,
|
||||
my: ev.clientY,
|
||||
|
@ -432,7 +429,7 @@ const onSvgTouch = (( ) => {
|
|||
pickerRoot.classList.contains('zap') &&
|
||||
svgIslands.getAttribute('d') !== NoPaths
|
||||
) {
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
pickerContentPort.postMessage({
|
||||
what: 'unhighlight'
|
||||
});
|
||||
return;
|
||||
|
@ -463,7 +460,7 @@ const onCandidateChanged = function() {
|
|||
$id('resultsetModifiers').classList.toggle(
|
||||
'hide', text === '' || text !== computedCandidate
|
||||
);
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
pickerContentPort.postMessage({
|
||||
what: 'dialogSetFilter',
|
||||
filter,
|
||||
compiled: reCosmeticAnchor.test(filter)
|
||||
|
@ -476,7 +473,7 @@ const onCandidateChanged = function() {
|
|||
|
||||
const onPreviewClicked = function() {
|
||||
const state = pickerRoot.classList.toggle('preview');
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
pickerContentPort.postMessage({
|
||||
what: 'togglePreview',
|
||||
state,
|
||||
});
|
||||
|
@ -496,7 +493,7 @@ const onCreateClicked = function() {
|
|||
killCache: reCosmeticAnchor.test(candidate) === false,
|
||||
});
|
||||
}
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
pickerContentPort.postMessage({
|
||||
what: 'dialogCreate',
|
||||
filter: candidate,
|
||||
compiled: reCosmeticAnchor.test(candidate)
|
||||
|
@ -578,7 +575,7 @@ const onKeyPressed = function(ev) {
|
|||
(ev.key === 'Delete' || ev.key === 'Backspace') &&
|
||||
pickerRoot.classList.contains('zap')
|
||||
) {
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
pickerContentPort.postMessage({
|
||||
what: 'zapElementAtPoint',
|
||||
options: { stay: true },
|
||||
});
|
||||
|
@ -678,7 +675,7 @@ const svgListening = (( ) => {
|
|||
|
||||
const onTimer = ( ) => {
|
||||
timer = undefined;
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
pickerContentPort.postMessage({
|
||||
what: 'highlightElementAtPoint',
|
||||
mx,
|
||||
my,
|
||||
|
@ -798,7 +795,7 @@ const pausePicker = function() {
|
|||
|
||||
const unpausePicker = function() {
|
||||
pickerRoot.classList.remove('paused', 'preview');
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
pickerContentPort.postMessage({
|
||||
what: 'togglePreview',
|
||||
state: false,
|
||||
});
|
||||
|
@ -838,8 +835,9 @@ const startPicker = function() {
|
|||
/******************************************************************************/
|
||||
|
||||
const quitPicker = function() {
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, { what: 'quitPicker' });
|
||||
vAPI.MessagingConnection.disconnectFrom(epickerConnectionId);
|
||||
pickerContentPort.postMessage({ what: 'quitPicker' });
|
||||
pickerContentPort.close();
|
||||
pickerContentPort = undefined;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -876,49 +874,27 @@ const onPickerMessage = function(msg) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
const onConnectionMessage = function(msg) {
|
||||
switch ( msg.what ) {
|
||||
case 'connectionBroken':
|
||||
break;
|
||||
case 'connectionMessage':
|
||||
onPickerMessage(msg.payload);
|
||||
break;
|
||||
case 'connectionAccepted':
|
||||
epickerConnectionId = msg.id;
|
||||
startPicker();
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
what: 'start',
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
// Wait for the content script to establish communication
|
||||
|
||||
vAPI.MessagingConnection.connectTo(
|
||||
`epickerDialog-${epickerId}`,
|
||||
`epicker-${epickerId}`,
|
||||
onConnectionMessage
|
||||
);
|
||||
let pickerContentPort;
|
||||
|
||||
globalThis.addEventListener('message', ev => {
|
||||
const msg = ev.data || {};
|
||||
if ( msg.what !== 'epickerStart' ) { return; }
|
||||
if ( Array.isArray(ev.ports) === false ) { return; }
|
||||
if ( ev.ports.length === 0 ) { return; }
|
||||
pickerContentPort = ev.ports[0];
|
||||
pickerContentPort.onmessage = ev => {
|
||||
const msg = ev.data || {};
|
||||
onPickerMessage(msg);
|
||||
};
|
||||
pickerContentPort.onmessageerror = ( ) => {
|
||||
quitPicker();
|
||||
};
|
||||
startPicker();
|
||||
pickerContentPort.postMessage({ what: 'start' });
|
||||
}, { once: true });
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
|
||||
DO NOT:
|
||||
- Remove the following code
|
||||
- Add code beyond the following code
|
||||
Reason:
|
||||
- https://github.com/gorhill/uBlock/pull/3721
|
||||
- uBO never uses the return value from injected content scripts
|
||||
|
||||
**/
|
||||
|
||||
void 0;
|
||||
|
|
|
@ -44,64 +44,44 @@ if (
|
|||
/******************************************************************************/
|
||||
|
||||
const logger = self.logger;
|
||||
var inspectorConnectionId;
|
||||
var inspectedTabId = 0;
|
||||
var inspectedURL = '';
|
||||
var inspectedHostname = '';
|
||||
var inspector = qs$('#domInspector');
|
||||
var domTree = qs$('#domTree');
|
||||
var uidGenerator = 1;
|
||||
var filterToIdMap = new Map();
|
||||
const inspector = qs$('#domInspector');
|
||||
const domTree = qs$('#domTree');
|
||||
const filterToIdMap = new Map();
|
||||
|
||||
let inspectedTabId = 0;
|
||||
let inspectedURL = '';
|
||||
let inspectedHostname = '';
|
||||
let uidGenerator = 1;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const messaging = vAPI.messaging;
|
||||
|
||||
vAPI.MessagingConnection.addListener(function(msg) {
|
||||
if ( msg.from !== 'domInspector' || msg.to !== 'loggerUI' ) { return; }
|
||||
switch ( msg.what ) {
|
||||
case 'connectionBroken':
|
||||
if ( inspectorConnectionId === msg.id ) {
|
||||
filterToIdMap.clear();
|
||||
logger.removeAllChildren(domTree);
|
||||
inspectorConnectionId = undefined;
|
||||
}
|
||||
injectInspector();
|
||||
break;
|
||||
case 'connectionMessage':
|
||||
if ( msg.payload.what === 'domLayoutFull' ) {
|
||||
inspectedURL = msg.payload.url;
|
||||
inspectedHostname = msg.payload.hostname;
|
||||
renderDOMFull(msg.payload);
|
||||
} else if ( msg.payload.what === 'domLayoutIncremental' ) {
|
||||
renderDOMIncremental(msg.payload);
|
||||
}
|
||||
break;
|
||||
case 'connectionRequested':
|
||||
if ( msg.tabId === undefined || msg.tabId !== inspectedTabId ) {
|
||||
return;
|
||||
}
|
||||
filterToIdMap.clear();
|
||||
logger.removeAllChildren(domTree);
|
||||
inspectorConnectionId = msg.id;
|
||||
return true;
|
||||
const inspectorFramePort = new globalThis.BroadcastChannel('loggerInspector');
|
||||
inspectorFramePort.onmessage = ev => {
|
||||
const msg = ev.data || {};
|
||||
if ( msg.what === 'domLayoutFull' ) {
|
||||
inspectedURL = msg.url;
|
||||
inspectedHostname = msg.hostname;
|
||||
renderDOMFull(msg);
|
||||
} else if ( msg.what === 'domLayoutIncremental' ) {
|
||||
renderDOMIncremental(msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
inspectorFramePort.onmessageerror = ( ) => {
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const nodeFromDomEntry = function(entry) {
|
||||
var node, value;
|
||||
const li = document.createElement('li');
|
||||
dom.attr(li, 'id', entry.nid);
|
||||
// expander/collapser
|
||||
li.appendChild(document.createElement('span'));
|
||||
// selector
|
||||
node = document.createElement('code');
|
||||
let node = document.createElement('code');
|
||||
node.textContent = entry.sel;
|
||||
li.appendChild(node);
|
||||
// descendant count
|
||||
value = entry.cnt || 0;
|
||||
let value = entry.cnt || 0;
|
||||
node = document.createElement('span');
|
||||
node.textContent = value !== 0 ? value.toLocaleString() : '';
|
||||
dom.attr(node, 'data-cnt', value);
|
||||
|
@ -114,7 +94,7 @@ const nodeFromDomEntry = function(entry) {
|
|||
dom.cl.add(node, 'filter');
|
||||
value = filterToIdMap.get(entry.filter);
|
||||
if ( value === undefined ) {
|
||||
value = uidGenerator.toString();
|
||||
value = `${uidGenerator}`;
|
||||
filterToIdMap.set(entry.filter, value);
|
||||
uidGenerator += 1;
|
||||
}
|
||||
|
@ -142,18 +122,15 @@ const appendListItem = function(ul, li) {
|
|||
/******************************************************************************/
|
||||
|
||||
const renderDOMFull = function(response) {
|
||||
var domTreeParent = domTree.parentElement;
|
||||
var ul = domTreeParent.removeChild(domTree);
|
||||
const domTreeParent = domTree.parentElement;
|
||||
let ul = domTreeParent.removeChild(domTree);
|
||||
logger.removeAllChildren(domTree);
|
||||
|
||||
filterToIdMap.clear();
|
||||
|
||||
var lvl = 0;
|
||||
var entries = response.layout;
|
||||
var n = entries.length;
|
||||
var li, entry;
|
||||
for ( var i = 0; i < n; i++ ) {
|
||||
entry = entries[i];
|
||||
let lvl = 0;
|
||||
let li;
|
||||
for ( const entry of response.layout ) {
|
||||
if ( entry.lvl === lvl ) {
|
||||
li = nodeFromDomEntry(entry);
|
||||
appendListItem(ul, li);
|
||||
|
@ -186,24 +163,21 @@ const renderDOMFull = function(response) {
|
|||
domTreeParent.appendChild(domTree);
|
||||
};
|
||||
|
||||
// https://www.youtube.com/watch?v=IDGNA83mxDo
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const patchIncremental = function(from, delta) {
|
||||
var span, cnt;
|
||||
var li = from.parentElement.parentElement;
|
||||
var patchCosmeticHide = delta >= 0 &&
|
||||
dom.cl.has(from, 'isCosmeticHide') &&
|
||||
dom.cl.has(li, 'hasCosmeticHide') === false;
|
||||
let li = from.parentElement.parentElement;
|
||||
const patchCosmeticHide = delta >= 0 &&
|
||||
dom.cl.has(from, 'isCosmeticHide') &&
|
||||
dom.cl.has(li, 'hasCosmeticHide') === false;
|
||||
// Include descendants count when removing a node
|
||||
if ( delta < 0 ) {
|
||||
delta -= countFromNode(from);
|
||||
}
|
||||
for ( ; li.localName === 'li'; li = li.parentElement.parentElement ) {
|
||||
span = li.children[2];
|
||||
const span = li.children[2];
|
||||
if ( delta !== 0 ) {
|
||||
cnt = countFromNode(li) + delta;
|
||||
const cnt = countFromNode(li) + delta;
|
||||
span.textContent = cnt !== 0 ? cnt.toLocaleString() : '';
|
||||
dom.attr(span, 'data-cnt', cnt);
|
||||
}
|
||||
|
@ -219,11 +193,10 @@ const renderDOMIncremental = function(response) {
|
|||
// Process each journal entry:
|
||||
// 1 = node added
|
||||
// -1 = node removed
|
||||
var journal = response.journal;
|
||||
var nodes = new Map(response.nodes);
|
||||
var entry, previous, li, ul;
|
||||
for ( var i = 0, n = journal.length; i < n; i++ ) {
|
||||
entry = journal[i];
|
||||
const nodes = new Map(response.nodes);
|
||||
let li = null;
|
||||
let ul = null;
|
||||
for ( const entry of response.journal ) {
|
||||
// Remove node
|
||||
if ( entry.what === -1 ) {
|
||||
li = qs$(`#${entry.nid}`);
|
||||
|
@ -239,7 +212,7 @@ const renderDOMIncremental = function(response) {
|
|||
}
|
||||
// Add node as sibling
|
||||
if ( entry.what === 1 && entry.l ) {
|
||||
previous = qs$(`#${entry.l}`);
|
||||
const previous = qs$(`#${entry.l}`);
|
||||
// This should not happen
|
||||
if ( previous === null ) {
|
||||
// throw new Error('No left sibling!?');
|
||||
|
@ -276,24 +249,21 @@ const renderDOMIncremental = function(response) {
|
|||
/******************************************************************************/
|
||||
|
||||
const countFromNode = function(li) {
|
||||
var span = li.children[2];
|
||||
var cnt = parseInt(dom.attr(span, 'data-cnt'), 10);
|
||||
const span = li.children[2];
|
||||
const cnt = parseInt(dom.attr(span, 'data-cnt'), 10);
|
||||
return isNaN(cnt) ? 0 : cnt;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const selectorFromNode = function(node) {
|
||||
var selector = '';
|
||||
var code;
|
||||
let selector = '';
|
||||
while ( node !== null ) {
|
||||
if ( node.localName === 'li' ) {
|
||||
code = qs$(node, 'code');
|
||||
const code = qs$(node, 'code');
|
||||
if ( code !== null ) {
|
||||
selector = code.textContent + ' > ' + selector;
|
||||
if ( selector.indexOf('#') !== -1 ) {
|
||||
break;
|
||||
}
|
||||
selector = `${code.textContent} > ${selector}`;
|
||||
if ( selector.includes('#') ) { break; }
|
||||
}
|
||||
}
|
||||
node = node.parentElement;
|
||||
|
@ -306,7 +276,7 @@ const selectorFromNode = function(node) {
|
|||
const selectorFromFilter = function(node) {
|
||||
while ( node !== null ) {
|
||||
if ( node.localName === 'li' ) {
|
||||
var code = qs$(node, 'code:nth-of-type(2)');
|
||||
const code = qs$(node, 'code:nth-of-type(2)');
|
||||
if ( code !== null ) {
|
||||
return code.textContent;
|
||||
}
|
||||
|
@ -319,7 +289,7 @@ const selectorFromFilter = function(node) {
|
|||
/******************************************************************************/
|
||||
|
||||
const nidFromNode = function(node) {
|
||||
var li = node;
|
||||
let li = node;
|
||||
while ( li !== null ) {
|
||||
if ( li.localName === 'li' ) {
|
||||
return li.id || '';
|
||||
|
@ -367,17 +337,17 @@ const startDialog = (function() {
|
|||
};
|
||||
|
||||
const onClicked = function(ev) {
|
||||
var target = ev.target;
|
||||
const target = ev.target;
|
||||
|
||||
ev.stopPropagation();
|
||||
|
||||
if ( target.id === 'createCosmeticFilters' ) {
|
||||
messaging.send('loggerUI', {
|
||||
vAPI.messaging.send('loggerUI', {
|
||||
what: 'createUserFilter',
|
||||
filters: textarea.value,
|
||||
});
|
||||
// Force a reload for the new cosmetic filter(s) to take effect
|
||||
messaging.send('loggerUI', {
|
||||
vAPI.messaging.send('loggerUI', {
|
||||
what: 'reloadTab',
|
||||
tabId: inspectedTabId,
|
||||
});
|
||||
|
@ -386,7 +356,7 @@ const startDialog = (function() {
|
|||
};
|
||||
|
||||
const showCommitted = function() {
|
||||
vAPI.MessagingConnection.sendTo(inspectorConnectionId, {
|
||||
inspectorFramePort.postMessage({
|
||||
what: 'showCommitted',
|
||||
hide: hideSelectors.join(',\n'),
|
||||
unhide: unhideSelectors.join(',\n')
|
||||
|
@ -394,7 +364,7 @@ const startDialog = (function() {
|
|||
};
|
||||
|
||||
const showInteractive = function() {
|
||||
vAPI.MessagingConnection.sendTo(inspectorConnectionId, {
|
||||
inspectorFramePort.postMessage({
|
||||
what: 'showInteractive',
|
||||
hide: hideSelectors.join(',\n'),
|
||||
unhide: unhideSelectors.join(',\n')
|
||||
|
@ -449,8 +419,8 @@ const onClicked = function(ev) {
|
|||
|
||||
if ( inspectedTabId === 0 ) { return; }
|
||||
|
||||
var target = ev.target;
|
||||
var parent = target.parentElement;
|
||||
const target = ev.target;
|
||||
const parent = target.parentElement;
|
||||
|
||||
// Expand/collapse branch
|
||||
if (
|
||||
|
@ -473,7 +443,7 @@ const onClicked = function(ev) {
|
|||
|
||||
// Toggle cosmetic filter
|
||||
if ( dom.cl.has(target, 'filter') ) {
|
||||
vAPI.MessagingConnection.sendTo(inspectorConnectionId, {
|
||||
inspectorFramePort.postMessage({
|
||||
what: 'toggleFilter',
|
||||
original: false,
|
||||
target: dom.cl.toggle(target, 'off'),
|
||||
|
@ -489,7 +459,7 @@ const onClicked = function(ev) {
|
|||
}
|
||||
// Toggle node
|
||||
else {
|
||||
vAPI.MessagingConnection.sendTo(inspectorConnectionId, {
|
||||
inspectorFramePort.postMessage({
|
||||
what: 'toggleNodes',
|
||||
original: true,
|
||||
target: dom.cl.toggle(target, 'off') === false,
|
||||
|
@ -509,7 +479,7 @@ const onMouseOver = (function() {
|
|||
let mouseoverTarget = null;
|
||||
|
||||
const timerHandler = ( ) => {
|
||||
vAPI.MessagingConnection.sendTo(inspectorConnectionId, {
|
||||
inspectorFramePort.postMessage({
|
||||
what: 'highlightOne',
|
||||
selector: selectorFromNode(mouseoverTarget),
|
||||
nid: nidFromNode(mouseoverTarget),
|
||||
|
@ -544,7 +514,7 @@ const injectInspector = function() {
|
|||
const tabId = currentTabId();
|
||||
if ( tabId <= 0 ) { return; }
|
||||
inspectedTabId = tabId;
|
||||
messaging.send('loggerUI', {
|
||||
vAPI.messaging.send('loggerUI', {
|
||||
what: 'scriptlet',
|
||||
tabId,
|
||||
scriptlet: 'dom-inspector',
|
||||
|
@ -554,9 +524,8 @@ const injectInspector = function() {
|
|||
/******************************************************************************/
|
||||
|
||||
const shutdownInspector = function() {
|
||||
if ( inspectorConnectionId !== undefined ) {
|
||||
vAPI.MessagingConnection.disconnectFrom(inspectorConnectionId);
|
||||
inspectorConnectionId = undefined;
|
||||
if ( inspectorFramePort !== undefined ) {
|
||||
inspectorFramePort.postMessage({ what: 'quitInspector' });
|
||||
}
|
||||
logger.removeAllChildren(domTree);
|
||||
dom.cl.remove(inspector, 'vExpanded');
|
||||
|
@ -594,10 +563,7 @@ const toggleHCompactView = function() {
|
|||
|
||||
const revert = function() {
|
||||
dom.cl.remove('#domTree .off', 'off');
|
||||
vAPI.MessagingConnection.sendTo(
|
||||
inspectorConnectionId,
|
||||
{ what: 'resetToggledNodes' }
|
||||
);
|
||||
inspectorFramePort.postMessage({ what: 'resetToggledNodes' });
|
||||
dom.cl.add(qs$(inspector, '.permatoolbar .revert'), 'disabled');
|
||||
dom.cl.add(qs$(inspector, '.permatoolbar .commit'), 'disabled');
|
||||
};
|
||||
|
|
|
@ -1846,6 +1846,48 @@ vAPI.messaging.listen({
|
|||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// Channel:
|
||||
// domInspectorContent
|
||||
// unprivileged
|
||||
|
||||
{
|
||||
// >>>>> start of local scope
|
||||
|
||||
const onMessage = (request, sender, callback) => {
|
||||
// Async
|
||||
switch ( request.what ) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Sync
|
||||
let response;
|
||||
switch ( request.what ) {
|
||||
case 'getInspectorArgs':
|
||||
response = {
|
||||
inspectorURL: vAPI.getURL(
|
||||
`/web_accessible_resources/dom-inspector.html?secret=${vAPI.warSecret.short()}`
|
||||
),
|
||||
};
|
||||
break;
|
||||
default:
|
||||
return vAPI.messaging.UNHANDLED;
|
||||
}
|
||||
|
||||
callback(response);
|
||||
};
|
||||
|
||||
vAPI.messaging.listen({
|
||||
name: 'domInspectorContent',
|
||||
listener: onMessage,
|
||||
privileged: false,
|
||||
});
|
||||
|
||||
// <<<<< end of local scope
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// Channel:
|
||||
// documentBlocked
|
||||
// privileged
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2015-2018 Raymond Hill
|
||||
Copyright (C) 2015-present 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
|
||||
|
@ -24,30 +24,21 @@
|
|||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
(( ) => {
|
||||
(async ( ) => {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) { return; }
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var sessionId = vAPI.sessionId;
|
||||
|
||||
if ( document.querySelector('iframe.dom-inspector.' + sessionId) !== null ) {
|
||||
return;
|
||||
}
|
||||
if ( typeof vAPI !== 'object' ) { return; }
|
||||
if ( vAPI.domFilterer instanceof Object === false ) { return; }
|
||||
if ( document.querySelector(`iframe[${vAPI.sessionId}]`) !== null ) { return; }
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
let loggerConnectionId;
|
||||
|
||||
// Highlighter-related
|
||||
let svgRoot = null;
|
||||
let pickerRoot = null;
|
||||
let inspectorRoot = null;
|
||||
|
||||
let nodeToIdMap = new WeakMap(); // No need to iterate
|
||||
const nodeToIdMap = new WeakMap(); // No need to iterate
|
||||
|
||||
let blueNodes = [];
|
||||
const roRedNodes = new Map(); // node => current cosmetic filter
|
||||
|
@ -59,7 +50,11 @@ const reHasCSSCombinators = /[ >+~]/;
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
const domLayout = (function() {
|
||||
//const getNodeId = node => nodeToIdMap.get(node) || 0;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const domLayout = (( ) => {
|
||||
const skipTagNames = new Set([
|
||||
'br', 'head', 'link', 'meta', 'script', 'style', 'title'
|
||||
]);
|
||||
|
@ -70,87 +65,81 @@ const domLayout = (function() {
|
|||
[ 'object', 'data' ]
|
||||
]);
|
||||
|
||||
var idGenerator = 0;
|
||||
let idGenerator = 1;
|
||||
|
||||
// This will be used to uniquely identify nodes across process.
|
||||
|
||||
const newNodeId = function(node) {
|
||||
var nid = 'n' + (idGenerator++).toString(36);
|
||||
const newNodeId = node => {
|
||||
const nid = `n${(idGenerator++).toString(36)}`;
|
||||
nodeToIdMap.set(node, nid);
|
||||
return nid;
|
||||
};
|
||||
|
||||
const selectorFromNode = function(node) {
|
||||
var str, attr, pos, sw, i;
|
||||
var tag = node.localName;
|
||||
var selector = CSS.escape(tag);
|
||||
const selectorFromNode = node => {
|
||||
const tag = node.localName;
|
||||
let selector = CSS.escape(tag);
|
||||
// Id
|
||||
if ( typeof node.id === 'string' ) {
|
||||
str = node.id.trim();
|
||||
let str = node.id.trim();
|
||||
if ( str !== '' ) {
|
||||
selector += '#' + CSS.escape(str);
|
||||
selector += `#${CSS.escape(str)}`;
|
||||
}
|
||||
}
|
||||
// Class
|
||||
var cl = node.classList;
|
||||
const cl = node.classList;
|
||||
if ( cl ) {
|
||||
for ( i = 0; i < cl.length; i++ ) {
|
||||
selector += '.' + CSS.escape(cl[i]);
|
||||
for ( let i = 0; i < cl.length; i++ ) {
|
||||
selector += `.${CSS.escape(cl[i])}`;
|
||||
}
|
||||
}
|
||||
// Tag-specific attributes
|
||||
attr = resourceAttrNames.get(tag);
|
||||
const attr = resourceAttrNames.get(tag);
|
||||
if ( attr !== undefined ) {
|
||||
str = node.getAttribute(attr) || '';
|
||||
let str = node.getAttribute(attr) || '';
|
||||
str = str.trim();
|
||||
if ( str.startsWith('data:') ) {
|
||||
pos = 5;
|
||||
} else {
|
||||
pos = str.search(/[#?]/);
|
||||
}
|
||||
const pos = str.startsWith('data:') ? 5 : str.search(/[#?]/);
|
||||
let sw = '';
|
||||
if ( pos !== -1 ) {
|
||||
str = str.slice(0, pos);
|
||||
sw = '^';
|
||||
} else {
|
||||
sw = '';
|
||||
}
|
||||
if ( str !== '' ) {
|
||||
selector += '[' + attr + sw + '="' + CSS.escape(str, true) + '"]';
|
||||
selector += `[${attr}${sw}="${CSS.escape(str, true)}"]`;
|
||||
}
|
||||
}
|
||||
return selector;
|
||||
};
|
||||
|
||||
const DomRoot = function() {
|
||||
function DomRoot() {
|
||||
this.nid = newNodeId(document.body);
|
||||
this.lvl = 0;
|
||||
this.sel = 'body';
|
||||
this.cnt = 0;
|
||||
this.filter = roRedNodes.get(document.body);
|
||||
};
|
||||
}
|
||||
|
||||
const DomNode = function(node, level) {
|
||||
function DomNode(node, level) {
|
||||
this.nid = newNodeId(node);
|
||||
this.lvl = level;
|
||||
this.sel = selectorFromNode(node);
|
||||
this.cnt = 0;
|
||||
this.filter = roRedNodes.get(node);
|
||||
};
|
||||
}
|
||||
|
||||
const domNodeFactory = function(level, node) {
|
||||
const domNodeFactory = (level, node) => {
|
||||
const localName = node.localName;
|
||||
if ( skipTagNames.has(localName) ) { return null; }
|
||||
// skip uBlock's own nodes
|
||||
if ( node.classList.contains(sessionId) ) { return null; }
|
||||
if ( node === inspectorRoot ) { return null; }
|
||||
if ( level === 0 && localName === 'body' ) {
|
||||
return new DomRoot();
|
||||
}
|
||||
return new DomNode(node, level);
|
||||
};
|
||||
|
||||
// Collect layout data.
|
||||
// Collect layout data
|
||||
|
||||
const getLayoutData = function() {
|
||||
const getLayoutData = ( ) => {
|
||||
const layout = [];
|
||||
const stack = [];
|
||||
let lvl = 0;
|
||||
|
@ -188,14 +177,14 @@ const domLayout = (function() {
|
|||
|
||||
// Descendant count for each node.
|
||||
|
||||
const patchLayoutData = function(layout) {
|
||||
var stack = [], ptr;
|
||||
var lvl = 0;
|
||||
var domNode, cnt;
|
||||
var i = layout.length;
|
||||
const patchLayoutData = layout => {
|
||||
const stack = [];
|
||||
let ptr;
|
||||
let lvl = 0;
|
||||
let i = layout.length;
|
||||
|
||||
while ( i-- ) {
|
||||
domNode = layout[i];
|
||||
const domNode = layout[i];
|
||||
if ( domNode.lvl === lvl ) {
|
||||
stack[ptr] += 1;
|
||||
continue;
|
||||
|
@ -210,7 +199,7 @@ const domLayout = (function() {
|
|||
continue;
|
||||
}
|
||||
// domNode.lvl < lvl
|
||||
cnt = stack.pop();
|
||||
const cnt = stack.pop();
|
||||
domNode.cnt = cnt;
|
||||
lvl -= 1;
|
||||
ptr = lvl - 1;
|
||||
|
@ -221,13 +210,13 @@ const domLayout = (function() {
|
|||
|
||||
// Track and report mutations of the DOM
|
||||
|
||||
var mutationObserver = null;
|
||||
var mutationTimer;
|
||||
var addedNodelists = [];
|
||||
var removedNodelist = [];
|
||||
let mutationObserver = null;
|
||||
let mutationTimer;
|
||||
let addedNodelists = [];
|
||||
let removedNodelist = [];
|
||||
|
||||
const previousElementSiblingId = function(node) {
|
||||
var sibling = node;
|
||||
const previousElementSiblingId = node => {
|
||||
let sibling = node;
|
||||
for (;;) {
|
||||
sibling = sibling.previousElementSibling;
|
||||
if ( sibling === null ) { return null; }
|
||||
|
@ -236,11 +225,10 @@ const domLayout = (function() {
|
|||
}
|
||||
};
|
||||
|
||||
const journalFromBranch = function(root, newNodes, newNodeToIdMap) {
|
||||
var domNode;
|
||||
var node = root.firstElementChild;
|
||||
const journalFromBranch = (root, newNodes, newNodeToIdMap) => {
|
||||
let node = root.firstElementChild;
|
||||
while ( node !== null ) {
|
||||
domNode = domNodeFactory(undefined, node);
|
||||
const domNode = domNodeFactory(undefined, node);
|
||||
if ( domNode !== null ) {
|
||||
newNodeToIdMap.set(domNode.nid, domNode);
|
||||
newNodes.push(node);
|
||||
|
@ -267,22 +255,21 @@ const domLayout = (function() {
|
|||
}
|
||||
};
|
||||
|
||||
const journalFromMutations = function() {
|
||||
var nodelist, node, domNode, nid;
|
||||
const journalFromMutations = ( ) => {
|
||||
mutationTimer = undefined;
|
||||
|
||||
// This is used to temporarily hold all added nodes, before resolving
|
||||
// their node id and relative position.
|
||||
var newNodes = [];
|
||||
var journalEntries = [];
|
||||
var newNodeToIdMap = new Map();
|
||||
const newNodes = [];
|
||||
const journalEntries = [];
|
||||
const newNodeToIdMap = new Map();
|
||||
|
||||
for ( nodelist of addedNodelists ) {
|
||||
for ( node of nodelist ) {
|
||||
for ( const nodelist of addedNodelists ) {
|
||||
for ( const node of nodelist ) {
|
||||
if ( node.nodeType !== 1 ) { continue; }
|
||||
if ( node.parentElement === null ) { continue; }
|
||||
cosmeticFilterMapper.incremental(node);
|
||||
domNode = domNodeFactory(undefined, node);
|
||||
const domNode = domNodeFactory(undefined, node);
|
||||
if ( domNode !== null ) {
|
||||
newNodeToIdMap.set(domNode.nid, domNode);
|
||||
newNodes.push(node);
|
||||
|
@ -291,19 +278,16 @@ const domLayout = (function() {
|
|||
}
|
||||
}
|
||||
addedNodelists = [];
|
||||
for ( nodelist of removedNodelist ) {
|
||||
for ( node of nodelist ) {
|
||||
for ( const nodelist of removedNodelist ) {
|
||||
for ( const node of nodelist ) {
|
||||
if ( node.nodeType !== 1 ) { continue; }
|
||||
nid = nodeToIdMap.get(node);
|
||||
const nid = nodeToIdMap.get(node);
|
||||
if ( nid === undefined ) { continue; }
|
||||
journalEntries.push({
|
||||
what: -1,
|
||||
nid: nid
|
||||
});
|
||||
journalEntries.push({ what: -1, nid });
|
||||
}
|
||||
}
|
||||
removedNodelist = [];
|
||||
for ( node of newNodes ) {
|
||||
for ( const node of newNodes ) {
|
||||
journalEntries.push({
|
||||
what: 1,
|
||||
nid: nodeToIdMap.get(node),
|
||||
|
@ -314,7 +298,7 @@ const domLayout = (function() {
|
|||
|
||||
if ( journalEntries.length === 0 ) { return; }
|
||||
|
||||
vAPI.MessagingConnection.sendTo(loggerConnectionId, {
|
||||
inspectorFramePort.postMessage({
|
||||
what: 'domLayoutIncremental',
|
||||
url: window.location.href,
|
||||
hostname: window.location.hostname,
|
||||
|
@ -323,8 +307,8 @@ const domLayout = (function() {
|
|||
});
|
||||
};
|
||||
|
||||
const onMutationObserved = function(mutationRecords) {
|
||||
for ( var record of mutationRecords ) {
|
||||
const onMutationObserved = mutationRecords => {
|
||||
for ( const record of mutationRecords ) {
|
||||
if ( record.addedNodes.length !== 0 ) {
|
||||
addedNodelists.push(record.addedNodes);
|
||||
}
|
||||
|
@ -339,7 +323,7 @@ const domLayout = (function() {
|
|||
|
||||
// API
|
||||
|
||||
const getLayout = function() {
|
||||
const getLayout = ( ) => {
|
||||
cosmeticFilterMapper.reset();
|
||||
mutationObserver = new MutationObserver(onMutationObserved);
|
||||
mutationObserver.observe(document.body, {
|
||||
|
@ -355,11 +339,11 @@ const domLayout = (function() {
|
|||
};
|
||||
};
|
||||
|
||||
const reset = function() {
|
||||
const reset = ( ) => {
|
||||
shutdown();
|
||||
};
|
||||
|
||||
const shutdown = function() {
|
||||
const shutdown = ( ) => {
|
||||
if ( mutationTimer !== undefined ) {
|
||||
clearTimeout(mutationTimer);
|
||||
mutationTimer = undefined;
|
||||
|
@ -370,35 +354,20 @@ const domLayout = (function() {
|
|||
}
|
||||
addedNodelists = [];
|
||||
removedNodelist = [];
|
||||
nodeToIdMap = new WeakMap();
|
||||
};
|
||||
|
||||
return {
|
||||
get: getLayout,
|
||||
reset: reset,
|
||||
shutdown: shutdown
|
||||
reset,
|
||||
shutdown,
|
||||
};
|
||||
})();
|
||||
|
||||
// https://www.youtube.com/watch?v=qo8zKhd4Cf0
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// For browsers not supporting `:scope`, it's not the end of the world: the
|
||||
// suggested CSS selectors may just end up being more verbose.
|
||||
|
||||
let cssScope = ':scope > ';
|
||||
try {
|
||||
document.querySelector(':scope *');
|
||||
} catch (e) {
|
||||
cssScope = '';
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const cosmeticFilterMapper = (function() {
|
||||
const nodesFromStyleTag = function(rootNode) {
|
||||
const cosmeticFilterMapper = (( ) => {
|
||||
const nodesFromStyleTag = rootNode => {
|
||||
const filterMap = roRedNodes;
|
||||
const details = vAPI.domFilterer.getAllSelectors();
|
||||
|
||||
|
@ -434,25 +403,25 @@ const cosmeticFilterMapper = (function() {
|
|||
}
|
||||
};
|
||||
|
||||
const incremental = function(rootNode) {
|
||||
const incremental = rootNode => {
|
||||
nodesFromStyleTag(rootNode);
|
||||
};
|
||||
|
||||
const reset = function() {
|
||||
const reset = ( ) => {
|
||||
roRedNodes.clear();
|
||||
if ( document.documentElement !== null ) {
|
||||
incremental(document.documentElement);
|
||||
}
|
||||
};
|
||||
|
||||
const shutdown = function() {
|
||||
const shutdown = ( ) => {
|
||||
vAPI.domFilterer.toggle(true);
|
||||
};
|
||||
|
||||
return {
|
||||
incremental: incremental,
|
||||
reset: reset,
|
||||
shutdown: shutdown
|
||||
incremental,
|
||||
reset,
|
||||
shutdown,
|
||||
};
|
||||
})();
|
||||
|
||||
|
@ -475,21 +444,18 @@ const elementsFromSelector = function(selector, context) {
|
|||
};
|
||||
|
||||
const elementsFromSpecialSelector = function(selector) {
|
||||
var out = [], i;
|
||||
var matches = /^(.+?):has\((.+?)\)$/.exec(selector);
|
||||
const out = [];
|
||||
let matches = /^(.+?):has\((.+?)\)$/.exec(selector);
|
||||
if ( matches !== null ) {
|
||||
var nodes;
|
||||
let nodes;
|
||||
try {
|
||||
nodes = document.querySelectorAll(matches[1]);
|
||||
} catch(ex) {
|
||||
nodes = [];
|
||||
}
|
||||
i = nodes.length;
|
||||
while ( i-- ) {
|
||||
var node = nodes[i];
|
||||
if ( node.querySelector(matches[2]) !== null ) {
|
||||
out.push(node);
|
||||
}
|
||||
for ( const node of nodes ) {
|
||||
if ( node.querySelector(matches[2]) === null ) { continue; }
|
||||
out.push(node);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
@ -503,7 +469,7 @@ const elementsFromSpecialSelector = function(selector) {
|
|||
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
|
||||
null
|
||||
);
|
||||
i = xpr.snapshotLength;
|
||||
let i = xpr.snapshotLength;
|
||||
while ( i-- ) {
|
||||
out.push(xpr.snapshotItem(i));
|
||||
}
|
||||
|
@ -512,128 +478,114 @@ const elementsFromSpecialSelector = function(selector) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
const getSvgRootChildren = function() {
|
||||
if ( svgRoot.children ) {
|
||||
return svgRoot.children;
|
||||
} else {
|
||||
const childNodes = Array.prototype.slice.apply(svgRoot.childNodes);
|
||||
return childNodes.filter(function(node) {
|
||||
return node.nodeType === Node.ELEMENT_NODE;
|
||||
});
|
||||
}
|
||||
};
|
||||
const highlightElements = ( ) => {
|
||||
const paths = [];
|
||||
|
||||
const highlightElements = function() {
|
||||
var islands;
|
||||
var elem, rect, poly;
|
||||
var xl, xr, yt, yb, w, h, ws;
|
||||
var svgRootChildren = getSvgRootChildren();
|
||||
|
||||
islands = [];
|
||||
for ( elem of rwRedNodes.keys() ) {
|
||||
if ( elem === pickerRoot ) { continue; }
|
||||
const path = [];
|
||||
for ( const elem of rwRedNodes.keys() ) {
|
||||
if ( elem === inspectorRoot ) { continue; }
|
||||
if ( rwGreenNodes.has(elem) ) { 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) +
|
||||
const rect = elem.getBoundingClientRect();
|
||||
const xl = rect.left;
|
||||
const w = rect.width;
|
||||
const yt = rect.top;
|
||||
const h = rect.height;
|
||||
const ws = w.toFixed(1);
|
||||
const poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
|
||||
'h' + ws +
|
||||
'v' + h.toFixed(1) +
|
||||
'h-' + ws +
|
||||
'z';
|
||||
islands.push(poly);
|
||||
path.push(poly);
|
||||
}
|
||||
svgRootChildren[0].setAttribute('d', islands.join('') || 'M0 0');
|
||||
paths.push(path.join('') || 'M0 0');
|
||||
|
||||
islands = [];
|
||||
for ( elem of rwGreenNodes ) {
|
||||
path.length = 0;
|
||||
for ( const elem of rwGreenNodes ) {
|
||||
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) +
|
||||
const rect = elem.getBoundingClientRect();
|
||||
const xl = rect.left;
|
||||
const w = rect.width;
|
||||
const yt = rect.top;
|
||||
const h = rect.height;
|
||||
const ws = w.toFixed(1);
|
||||
const poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
|
||||
'h' + ws +
|
||||
'v' + h.toFixed(1) +
|
||||
'h-' + ws +
|
||||
'z';
|
||||
islands.push(poly);
|
||||
path.push(poly);
|
||||
}
|
||||
svgRootChildren[1].setAttribute('d', islands.join('') || 'M0 0');
|
||||
paths.push(path.join('') || 'M0 0');
|
||||
|
||||
islands = [];
|
||||
for ( elem of roRedNodes.keys() ) {
|
||||
if ( elem === pickerRoot ) { continue; }
|
||||
path.length = 0;
|
||||
for ( const elem of roRedNodes.keys() ) {
|
||||
if ( elem === inspectorRoot ) { continue; }
|
||||
if ( rwGreenNodes.has(elem) ) { 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) +
|
||||
const rect = elem.getBoundingClientRect();
|
||||
const xl = rect.left;
|
||||
const w = rect.width;
|
||||
const yt = rect.top;
|
||||
const h = rect.height;
|
||||
const ws = w.toFixed(1);
|
||||
const poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
|
||||
'h' + ws +
|
||||
'v' + h.toFixed(1) +
|
||||
'h-' + ws +
|
||||
'z';
|
||||
islands.push(poly);
|
||||
path.push(poly);
|
||||
}
|
||||
svgRootChildren[2].setAttribute('d', islands.join('') || 'M0 0');
|
||||
paths.push(path.join('') || 'M0 0');
|
||||
|
||||
islands = [];
|
||||
for ( elem of blueNodes ) {
|
||||
if ( elem === pickerRoot ) { continue; }
|
||||
path.length = 0;
|
||||
for ( const elem of blueNodes ) {
|
||||
if ( elem === inspectorRoot ) { 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) +
|
||||
const rect = elem.getBoundingClientRect();
|
||||
const xl = rect.left;
|
||||
const w = rect.width;
|
||||
const yt = rect.top;
|
||||
const h = rect.height;
|
||||
const ws = w.toFixed(1);
|
||||
const poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
|
||||
'h' + ws +
|
||||
'v' + h.toFixed(1) +
|
||||
'h-' + ws +
|
||||
'z';
|
||||
islands.push(poly);
|
||||
path.push(poly);
|
||||
}
|
||||
svgRootChildren[3].setAttribute('d', islands.join('') || 'M0 0');
|
||||
paths.push(path.join('') || 'M0 0');
|
||||
|
||||
inspectorFramePort.postMessage({
|
||||
what: 'svgPaths',
|
||||
paths,
|
||||
});
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const onScrolled = (function() {
|
||||
let buffered = false;
|
||||
const timerHandler = function() {
|
||||
buffered = false;
|
||||
highlightElements();
|
||||
};
|
||||
return function() {
|
||||
if ( buffered === false ) {
|
||||
window.requestAnimationFrame(timerHandler);
|
||||
buffered = true;
|
||||
}
|
||||
const onScrolled = (( ) => {
|
||||
let timer;
|
||||
return ( ) => {
|
||||
if ( timer ) { return; }
|
||||
timer = window.requestAnimationFrame(( ) => {
|
||||
timer = undefined;
|
||||
highlightElements();
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
||||
const onMouseOver = ( ) => {
|
||||
if ( blueNodes.length === 0 ) { return; }
|
||||
blueNodes = [];
|
||||
highlightElements();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const selectNodes = function(selector, nid) {
|
||||
const selectNodes = (selector, nid) => {
|
||||
const nodes = elementsFromSelector(selector);
|
||||
if ( nid === '' ) { return nodes; }
|
||||
for ( const node of nodes ) {
|
||||
|
@ -646,7 +598,7 @@ const selectNodes = function(selector, nid) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
const nodesFromFilter = function(selector) {
|
||||
const nodesFromFilter = selector => {
|
||||
const out = [];
|
||||
for ( const entry of roRedNodes ) {
|
||||
if ( entry[1] === selector ) {
|
||||
|
@ -658,7 +610,7 @@ const nodesFromFilter = function(selector) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
const toggleExceptions = function(nodes, targetState) {
|
||||
const toggleExceptions = (nodes, targetState) => {
|
||||
for ( const node of nodes ) {
|
||||
if ( targetState ) {
|
||||
rwGreenNodes.add(node);
|
||||
|
@ -668,7 +620,7 @@ const toggleExceptions = function(nodes, targetState) {
|
|||
}
|
||||
};
|
||||
|
||||
const toggleFilter = function(nodes, targetState) {
|
||||
const toggleFilter = (nodes, targetState) => {
|
||||
for ( const node of nodes ) {
|
||||
if ( targetState ) {
|
||||
rwRedNodes.delete(node);
|
||||
|
@ -678,23 +630,28 @@ const toggleFilter = function(nodes, targetState) {
|
|||
}
|
||||
};
|
||||
|
||||
const resetToggledNodes = function() {
|
||||
const resetToggledNodes = ( ) => {
|
||||
rwGreenNodes.clear();
|
||||
rwRedNodes.clear();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const start = function() {
|
||||
const onReady = function(ev) {
|
||||
if ( ev ) {
|
||||
document.removeEventListener(ev.type, onReady);
|
||||
}
|
||||
vAPI.MessagingConnection.sendTo(loggerConnectionId, domLayout.get());
|
||||
const start = ( ) => {
|
||||
const onReady = ( ) => {
|
||||
window.addEventListener('scroll', onScrolled, {
|
||||
capture: true,
|
||||
passive: true,
|
||||
});
|
||||
window.addEventListener('mouseover', onMouseOver, {
|
||||
capture: true,
|
||||
passive: true,
|
||||
});
|
||||
inspectorFramePort.postMessage(domLayout.get());
|
||||
vAPI.domFilterer.toggle(false, highlightElements);
|
||||
};
|
||||
if ( document.readyState === 'loading' ) {
|
||||
document.addEventListener('DOMContentLoaded', onReady);
|
||||
document.addEventListener('DOMContentLoaded', onReady, { once: true });
|
||||
} else {
|
||||
onReady();
|
||||
}
|
||||
|
@ -702,34 +659,49 @@ const start = function() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
const shutdown = function() {
|
||||
const shutdown = ( ) => {
|
||||
cosmeticFilterMapper.shutdown();
|
||||
domLayout.shutdown();
|
||||
vAPI.MessagingConnection.disconnectFrom(loggerConnectionId);
|
||||
window.removeEventListener('scroll', onScrolled, true);
|
||||
pickerRoot.remove();
|
||||
pickerRoot = svgRoot = null;
|
||||
window.removeEventListener('scroll', onScrolled, {
|
||||
capture: true,
|
||||
passive: true,
|
||||
});
|
||||
window.removeEventListener('mouseover', onMouseOver, {
|
||||
capture: true,
|
||||
passive: true,
|
||||
});
|
||||
inspectorFramePort.close();
|
||||
inspectorFramePort = undefined;
|
||||
vAPI.userStylesheet.remove(inspectorCSS);
|
||||
vAPI.userStylesheet.apply();
|
||||
if ( inspectorRoot === null ) { return; }
|
||||
inspectorRoot.remove();
|
||||
inspectorRoot = null;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
const onMessage = function(request) {
|
||||
var response,
|
||||
nodes;
|
||||
|
||||
const onMessage = request => {
|
||||
switch ( request.what ) {
|
||||
case 'startInspector':
|
||||
start();
|
||||
break;
|
||||
|
||||
case 'quitInspector':
|
||||
shutdown();
|
||||
break;
|
||||
|
||||
case 'commitFilters':
|
||||
highlightElements();
|
||||
break;
|
||||
|
||||
case 'domLayout':
|
||||
response = domLayout.get();
|
||||
domLayout.get();
|
||||
highlightElements();
|
||||
break;
|
||||
|
||||
case 'highlightMode':
|
||||
//svgRoot.classList.toggle('invert', request.invert);
|
||||
break;
|
||||
|
||||
case 'highlightOne':
|
||||
|
@ -753,110 +725,47 @@ const onMessage = function(request) {
|
|||
highlightElements();
|
||||
break;
|
||||
|
||||
case 'toggleFilter':
|
||||
nodes = selectNodes(request.selector, request.nid);
|
||||
if ( nodes.length !== 0 ) { nodes[0].scrollIntoView(); }
|
||||
case 'toggleFilter': {
|
||||
const nodes = selectNodes(request.selector, request.nid);
|
||||
if ( nodes.length !== 0 ) {
|
||||
nodes[0].scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'center',
|
||||
});
|
||||
}
|
||||
toggleExceptions(nodesFromFilter(request.filter), request.target);
|
||||
highlightElements();
|
||||
break;
|
||||
|
||||
case 'toggleNodes':
|
||||
nodes = selectNodes(request.selector, request.nid);
|
||||
if ( nodes.length !== 0 ) { nodes[0].scrollIntoView(); }
|
||||
}
|
||||
case 'toggleNodes': {
|
||||
const nodes = selectNodes(request.selector, request.nid);
|
||||
if ( nodes.length !== 0 ) {
|
||||
nodes[0].scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'center',
|
||||
});
|
||||
}
|
||||
toggleFilter(nodes, request.target);
|
||||
highlightElements();
|
||||
break;
|
||||
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Install DOM inspector widget
|
||||
let inspectorArgs = await vAPI.messaging.send('domInspectorContent', {
|
||||
what: 'getInspectorArgs',
|
||||
});
|
||||
if ( typeof inspectorArgs !== 'object' ) { return; }
|
||||
if ( inspectorArgs === null ) { return; }
|
||||
|
||||
const bootstrap = function(ev) {
|
||||
if ( ev ) {
|
||||
pickerRoot.removeEventListener(ev.type, bootstrap);
|
||||
}
|
||||
const pickerDoc = ev.target.contentDocument;
|
||||
|
||||
pickerDoc.documentElement.style.setProperty(
|
||||
'color-scheme',
|
||||
'dark light',
|
||||
'important'
|
||||
);
|
||||
|
||||
const style = pickerDoc.createElement('style');
|
||||
style.textContent = [
|
||||
'body {',
|
||||
'background-color: transparent;',
|
||||
'}',
|
||||
'svg {',
|
||||
'height: 100%;',
|
||||
'left: 0;',
|
||||
'position: fixed;',
|
||||
'top: 0;',
|
||||
'width: 100%;',
|
||||
'}',
|
||||
'svg > path:nth-of-type(1) {',
|
||||
'fill: rgba(255,0,0,0.2);',
|
||||
'stroke: #F00;',
|
||||
'}',
|
||||
'svg > path:nth-of-type(2) {',
|
||||
'fill: rgba(0,255,0,0.2);',
|
||||
'stroke: #0F0;',
|
||||
'}',
|
||||
'svg > path:nth-of-type(3) {',
|
||||
'fill: rgba(255,0,0,0.2);',
|
||||
'stroke: #F00;',
|
||||
'}',
|
||||
'svg > path:nth-of-type(4) {',
|
||||
'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');
|
||||
svgRoot.appendChild(pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'path'));
|
||||
svgRoot.appendChild(pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'path'));
|
||||
svgRoot.appendChild(pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'path'));
|
||||
svgRoot.appendChild(pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'path'));
|
||||
pickerDoc.body.appendChild(svgRoot);
|
||||
|
||||
window.addEventListener('scroll', onScrolled, true);
|
||||
|
||||
// Dynamically add direct connection abilities so that we can establish
|
||||
// a direct, fast messaging connection to the logger.
|
||||
vAPI.messaging.extend().then(extended => {
|
||||
if ( extended !== true ) { return; }
|
||||
vAPI.MessagingConnection.connectTo('domInspector', 'loggerUI', msg => {
|
||||
switch ( msg.what ) {
|
||||
case 'connectionAccepted':
|
||||
loggerConnectionId = msg.id;
|
||||
start();
|
||||
break;
|
||||
case 'connectionBroken':
|
||||
shutdown();
|
||||
break;
|
||||
case 'connectionMessage':
|
||||
onMessage(msg.payload);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
pickerRoot = document.createElement('iframe');
|
||||
pickerRoot.classList.add(sessionId);
|
||||
pickerRoot.classList.add('dom-inspector');
|
||||
pickerRoot.style.cssText = [
|
||||
const inspectorCSSStyle = [
|
||||
'background: transparent',
|
||||
'border: 0',
|
||||
'border-radius: 0',
|
||||
|
@ -878,8 +787,43 @@ pickerRoot.style.cssText = [
|
|||
''
|
||||
].join(' !important;\n');
|
||||
|
||||
pickerRoot.addEventListener('load', ev => { bootstrap(ev); });
|
||||
(document.documentElement || document).appendChild(pickerRoot);
|
||||
const inspectorCSS = `
|
||||
:root > [${vAPI.sessionId}] {
|
||||
${inspectorCSSStyle}
|
||||
}
|
||||
:root > [${vAPI.sessionId}-loaded] {
|
||||
visibility: visible !important;
|
||||
}
|
||||
`;
|
||||
|
||||
vAPI.userStylesheet.add(inspectorCSS);
|
||||
vAPI.userStylesheet.apply();
|
||||
|
||||
inspectorRoot = document.createElement('iframe');
|
||||
inspectorRoot.setAttribute(vAPI.sessionId, '');
|
||||
document.documentElement.append(inspectorRoot);
|
||||
|
||||
let inspectorFramePort;
|
||||
|
||||
inspectorRoot.addEventListener('load', ( ) => {
|
||||
const channel = new MessageChannel();
|
||||
inspectorFramePort = channel.port1;
|
||||
inspectorFramePort.onmessage = ev => {
|
||||
const msg = ev.data || {};
|
||||
onMessage(msg);
|
||||
};
|
||||
inspectorFramePort.onmessageerror = ( ) => {
|
||||
shutdown();
|
||||
};
|
||||
inspectorRoot.setAttribute(`${vAPI.sessionId}-loaded`, '');
|
||||
inspectorRoot.contentWindow.postMessage(
|
||||
{ what: 'startInspector' },
|
||||
inspectorArgs.inspectorURL,
|
||||
[ channel.port2 ]
|
||||
);
|
||||
}, { once: true });
|
||||
|
||||
inspectorRoot.contentWindow.location = inspectorArgs.inspectorURL;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ if ( typeof vAPI !== 'object' || vAPI === null ) {
|
|||
/******************************************************************************/
|
||||
|
||||
const epickerId = vAPI.randomToken();
|
||||
let epickerConnectionId;
|
||||
|
||||
let pickerRoot = document.querySelector(`[${vAPI.sessionId}]`);
|
||||
if ( pickerRoot !== null ) { return; }
|
||||
|
@ -144,7 +143,7 @@ const highlightElements = function(elems, force) {
|
|||
);
|
||||
}
|
||||
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
pickerFramePort.postMessage({
|
||||
what: 'svgPaths',
|
||||
ocean: `M0 0h${ow}v${oh}h-${ow}z`,
|
||||
islands: islands.join(''),
|
||||
|
@ -900,7 +899,7 @@ const onOptimizeCandidates = function(details) {
|
|||
if ( r !== 0 ) { return r; }
|
||||
return a.selector.length - b.selector.length;
|
||||
});
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
pickerFramePort.postMessage({
|
||||
what: 'candidatesOptimized',
|
||||
candidates: results.map(a => a.selector),
|
||||
slot: details.slot,
|
||||
|
@ -910,7 +909,7 @@ const onOptimizeCandidates = function(details) {
|
|||
/******************************************************************************/
|
||||
|
||||
const showDialog = function(options) {
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
pickerFramePort.postMessage({
|
||||
what: 'showDialog',
|
||||
url: self.location.href,
|
||||
netFilters: netFilterCandidates,
|
||||
|
@ -1141,16 +1140,13 @@ const quitPicker = function() {
|
|||
self.removeEventListener('resize', onViewportChanged, { passive: true });
|
||||
self.removeEventListener('keydown', onKeyPressed, true);
|
||||
vAPI.shutdown.remove(quitPicker);
|
||||
vAPI.MessagingConnection.disconnectFrom(epickerConnectionId);
|
||||
vAPI.MessagingConnection.removeListener(onConnectionMessage);
|
||||
pickerFramePort.close();
|
||||
pickerFramePort = undefined;
|
||||
vAPI.userStylesheet.remove(pickerCSS);
|
||||
vAPI.userStylesheet.apply();
|
||||
|
||||
if ( pickerRoot === null ) { return; }
|
||||
|
||||
pickerRoot.remove();
|
||||
pickerRoot = null;
|
||||
|
||||
self.focus();
|
||||
};
|
||||
|
||||
|
@ -1176,7 +1172,7 @@ const onDialogMessage = function(msg) {
|
|||
const resultset = filterToDOMInterface.queryAll(msg) || [];
|
||||
highlightElements(resultset.map(a => a.elem), true);
|
||||
if ( msg.filter === '!' ) { break; }
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
pickerFramePort.postMessage({
|
||||
what: 'resultsetDetails',
|
||||
count: resultset.length,
|
||||
opt: resultset.length !== 0 ? resultset[0].opt : undefined,
|
||||
|
@ -1215,23 +1211,6 @@ const onDialogMessage = function(msg) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
const onConnectionMessage = function(msg) {
|
||||
if ( msg.from !== `epickerDialog-${epickerId}` ) { return; }
|
||||
switch ( msg.what ) {
|
||||
case 'connectionRequested':
|
||||
epickerConnectionId = msg.id;
|
||||
return true;
|
||||
case 'connectionBroken':
|
||||
quitPicker();
|
||||
break;
|
||||
case 'connectionMessage':
|
||||
onDialogMessage(msg.payload);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// epicker-ui.html will be injected in the page through an iframe, and
|
||||
// is a sandboxed so as to prevent the page from interfering with its
|
||||
// content and behavior.
|
||||
|
@ -1249,17 +1228,13 @@ const onConnectionMessage = function(msg) {
|
|||
// of the iframe, and cannot interfere with its style properties. However the
|
||||
// page can remove the iframe.
|
||||
|
||||
// We need extra messaging capabilities + fetch/process picker arguments.
|
||||
// fetch/process picker arguments.
|
||||
{
|
||||
const results = await Promise.all([
|
||||
vAPI.messaging.extend(),
|
||||
vAPI.messaging.send('elementPicker', { what: 'elementPickerArguments' }),
|
||||
]);
|
||||
if ( results[0] !== true ) { return; }
|
||||
pickerBootArgs = results[1];
|
||||
if ( typeof pickerBootArgs !== 'object' || pickerBootArgs === null ) {
|
||||
return;
|
||||
}
|
||||
pickerBootArgs = await vAPI.messaging.send('elementPicker', {
|
||||
what: 'elementPickerArguments',
|
||||
});
|
||||
if ( typeof pickerBootArgs !== 'object' ) { return; }
|
||||
if ( pickerBootArgs === null ) { return; }
|
||||
// Restore net filter union data if origin is the same.
|
||||
const eprom = pickerBootArgs.eprom || null;
|
||||
if ( eprom !== null && eprom.lastNetFilterSession === lastNetFilterSession ) {
|
||||
|
@ -1302,12 +1277,12 @@ const pickerCSSStyle = [
|
|||
'width: 100%',
|
||||
'z-index: 2147483647',
|
||||
''
|
||||
];
|
||||
].join(' !important;\n');
|
||||
|
||||
|
||||
const pickerCSS = `
|
||||
:root > [${vAPI.sessionId}] {
|
||||
${pickerCSSStyle.join(' !important;')}
|
||||
${pickerCSSStyle}
|
||||
}
|
||||
:root > [${vAPI.sessionId}-loaded] {
|
||||
visibility: visible !important;
|
||||
|
@ -1326,7 +1301,7 @@ document.documentElement.append(pickerRoot);
|
|||
|
||||
vAPI.shutdown.add(quitPicker);
|
||||
|
||||
vAPI.MessagingConnection.addListener(onConnectionMessage);
|
||||
let pickerFramePort;
|
||||
|
||||
{
|
||||
const url = new URL(pickerBootArgs.pickerURL);
|
||||
|
@ -1334,10 +1309,24 @@ vAPI.MessagingConnection.addListener(onConnectionMessage);
|
|||
if ( pickerBootArgs.zap ) {
|
||||
url.searchParams.set('zap', '1');
|
||||
}
|
||||
pickerRoot.addEventListener("load", function() {
|
||||
pickerRoot.setAttribute(`${vAPI.sessionId}-loaded`, "");
|
||||
});
|
||||
pickerRoot.src = url.href;
|
||||
pickerRoot.addEventListener('load', ( ) => {
|
||||
const channel = new MessageChannel();
|
||||
pickerFramePort = channel.port1;
|
||||
pickerFramePort.onmessage = ev => {
|
||||
const msg = ev.data || {};
|
||||
onDialogMessage(msg);
|
||||
};
|
||||
pickerFramePort.onmessageerror = ( ) => {
|
||||
quitPicker();
|
||||
};
|
||||
pickerRoot.setAttribute(`${vAPI.sessionId}-loaded`, '');
|
||||
pickerRoot.contentWindow.postMessage(
|
||||
{ what: 'epickerStart' },
|
||||
url.href,
|
||||
[ channel.port2 ]
|
||||
);
|
||||
}, { once: true });
|
||||
pickerRoot.contentWindow.location = url.href;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -222,7 +222,6 @@
|
|||
<script src="js/vapi.js"></script>
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/vapi-client-extra.js"></script>
|
||||
<script src="js/theme.js" type="module"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/logger-ui.js" type="module"></script>
|
||||
|
|
|
@ -121,7 +121,6 @@
|
|||
<script src="js/vapi.js"></script>
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/vapi-client-extra.js"></script>
|
||||
<script src="js/theme.js" type="module"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js" type="module"></script>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html id="ublock0-inspector">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>uBlock Origin Inspector</title>
|
||||
<link rel="stylesheet" href="../css/dom-inspector.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<svg>
|
||||
<path d></path>
|
||||
<path d></path>
|
||||
<path d></path>
|
||||
<path d></path>
|
||||
</svg>
|
||||
|
||||
<script src="../js/vapi.js"></script>
|
||||
<script src="../js/vapi-common.js"></script>
|
||||
<script src="../js/vapi-client.js"></script>
|
||||
<script src="../js/dom-inspector.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -67,7 +67,6 @@
|
|||
<script src="../js/vapi.js"></script>
|
||||
<script src="../js/vapi-common.js"></script>
|
||||
<script src="../js/vapi-client.js"></script>
|
||||
<script src="../js/vapi-client-extra.js"></script>
|
||||
<script src="../js/theme.js" type="module"></script>
|
||||
<script src="../js/i18n.js" type="module"></script>
|
||||
<script src="../js/epicker-ui.js" type="module"></script>
|
||||
|
|
Loading…
Reference in New Issue