1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-07-08 12:57:57 +02:00

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:
Raymond Hill 2023-12-03 16:21:32 -05:00
parent 2c495487f1
commit ee83a4304a
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
16 changed files with 586 additions and 875 deletions

View File

@ -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) { onFrameworkMessage: function(request, port, callback) {
const portDetails = this.ports.get(port.name) || {}; const portDetails = this.ports.get(port.name) || {};
const tabId = portDetails.tabId; const tabId = portDetails.tabId;
const msg = request.msg; const msg = request.msg;
switch ( msg.what ) { 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': { case 'localStorage': {
if ( portDetails.privileged !== true ) { break; } if ( portDetails.privileged !== true ) { break; }
const args = msg.args || []; const args = msg.args || [];

View File

@ -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;

View File

@ -83,8 +83,6 @@ vAPI.messaging = {
port: null, port: null,
portTimer: null, portTimer: null,
portTimerDelay: 10000, portTimerDelay: 10000,
extended: undefined,
extensions: [],
msgIdGenerator: 1, msgIdGenerator: 1,
pending: new Map(), pending: new Map(),
shuttingDown: false, shuttingDown: false,
@ -127,23 +125,11 @@ vAPI.messaging = {
return; return;
} }
} }
// Unhandled messages
this.extensions.every(ext => ext.canProcessMessage(details) !== true);
}, },
messageListenerBound: null, messageListenerBound: null,
canDestroyPort: function() { canDestroyPort: function() {
return this.pending.size === 0 && ( 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;
}, },
portPoller: function() { portPoller: function() {
@ -168,7 +154,6 @@ vAPI.messaging = {
port.onDisconnect.removeListener(this.disconnectListenerBound); port.onDisconnect.removeListener(this.disconnectListenerBound);
this.port = null; this.port = null;
} }
this.mustDestroyPort();
// service pending callbacks // service pending callbacks
if ( this.pending.size !== 0 ) { if ( this.pending.size !== 0 ) {
const pending = this.pending; const pending = this.pending;
@ -232,22 +217,6 @@ vAPI.messaging = {
port.postMessage({ channel, msgId, msg }); port.postMessage({ channel, msgId, msg });
return promise; 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(( ) => { vAPI.shutdown.add(( ) => {

View File

@ -53,7 +53,6 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.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.js" type="module"></script>
<script src="js/codemirror/search-thread.js"></script> <script src="js/codemirror/search-thread.js"></script>

View File

@ -108,7 +108,6 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.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/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js" type="module"></script> <script src="js/dashboard-common.js" type="module"></script>

38
src/css/dom-inspector.css Normal file
View File

@ -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;
}

92
src/js/dom-inspector.js Normal file
View File

@ -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 });

View File

@ -53,18 +53,15 @@ const NoPaths = 'M0 0';
const reCosmeticAnchor = /^#(\$|\?|\$\?)?#/; const reCosmeticAnchor = /^#(\$|\?|\$\?)?#/;
const epickerId = (( ) => { {
const url = new URL(self.location.href); const url = new URL(self.location.href);
if ( url.searchParams.has('zap') ) { if ( url.searchParams.has('zap') ) {
pickerRoot.classList.add('zap'); pickerRoot.classList.add('zap');
} }
return url.searchParams.get('epid'); }
})();
if ( epickerId === null ) { return; }
const docURL = new URL(vAPI.getURL('')); const docURL = new URL(vAPI.getURL(''));
let epickerConnectionId;
let resultsetOpt; let resultsetOpt;
let netFilterCandidates = []; let netFilterCandidates = [];
@ -305,7 +302,7 @@ const cosmeticCandidatesFromFilterChoice = function(filterChoice) {
candidates.push(paths); candidates.push(paths);
} }
vAPI.MessagingConnection.sendTo(epickerConnectionId, { pickerContentPort.postMessage({
what: 'optimizeCandidates', what: 'optimizeCandidates',
candidates, candidates,
slot, slot,
@ -333,7 +330,7 @@ const onSvgClicked = function(ev) {
// If zap mode, highlight element under mouse, this makes the zapper usable // If zap mode, highlight element under mouse, this makes the zapper usable
// on touch screens. // on touch screens.
if ( pickerRoot.classList.contains('zap') ) { if ( pickerRoot.classList.contains('zap') ) {
vAPI.MessagingConnection.sendTo(epickerConnectionId, { pickerContentPort.postMessage({
what: 'zapElementAtPoint', what: 'zapElementAtPoint',
mx: ev.clientX, mx: ev.clientX,
my: ev.clientY, my: ev.clientY,
@ -358,7 +355,7 @@ const onSvgClicked = function(ev) {
if ( ev.type === 'touch' ) { if ( ev.type === 'touch' ) {
pickerRoot.classList.add('show'); pickerRoot.classList.add('show');
} }
vAPI.MessagingConnection.sendTo(epickerConnectionId, { pickerContentPort.postMessage({
what: 'filterElementAtPoint', what: 'filterElementAtPoint',
mx: ev.clientX, mx: ev.clientX,
my: ev.clientY, my: ev.clientY,
@ -432,7 +429,7 @@ const onSvgTouch = (( ) => {
pickerRoot.classList.contains('zap') && pickerRoot.classList.contains('zap') &&
svgIslands.getAttribute('d') !== NoPaths svgIslands.getAttribute('d') !== NoPaths
) { ) {
vAPI.MessagingConnection.sendTo(epickerConnectionId, { pickerContentPort.postMessage({
what: 'unhighlight' what: 'unhighlight'
}); });
return; return;
@ -463,7 +460,7 @@ const onCandidateChanged = function() {
$id('resultsetModifiers').classList.toggle( $id('resultsetModifiers').classList.toggle(
'hide', text === '' || text !== computedCandidate 'hide', text === '' || text !== computedCandidate
); );
vAPI.MessagingConnection.sendTo(epickerConnectionId, { pickerContentPort.postMessage({
what: 'dialogSetFilter', what: 'dialogSetFilter',
filter, filter,
compiled: reCosmeticAnchor.test(filter) compiled: reCosmeticAnchor.test(filter)
@ -476,7 +473,7 @@ const onCandidateChanged = function() {
const onPreviewClicked = function() { const onPreviewClicked = function() {
const state = pickerRoot.classList.toggle('preview'); const state = pickerRoot.classList.toggle('preview');
vAPI.MessagingConnection.sendTo(epickerConnectionId, { pickerContentPort.postMessage({
what: 'togglePreview', what: 'togglePreview',
state, state,
}); });
@ -496,7 +493,7 @@ const onCreateClicked = function() {
killCache: reCosmeticAnchor.test(candidate) === false, killCache: reCosmeticAnchor.test(candidate) === false,
}); });
} }
vAPI.MessagingConnection.sendTo(epickerConnectionId, { pickerContentPort.postMessage({
what: 'dialogCreate', what: 'dialogCreate',
filter: candidate, filter: candidate,
compiled: reCosmeticAnchor.test(candidate) compiled: reCosmeticAnchor.test(candidate)
@ -578,7 +575,7 @@ const onKeyPressed = function(ev) {
(ev.key === 'Delete' || ev.key === 'Backspace') && (ev.key === 'Delete' || ev.key === 'Backspace') &&
pickerRoot.classList.contains('zap') pickerRoot.classList.contains('zap')
) { ) {
vAPI.MessagingConnection.sendTo(epickerConnectionId, { pickerContentPort.postMessage({
what: 'zapElementAtPoint', what: 'zapElementAtPoint',
options: { stay: true }, options: { stay: true },
}); });
@ -678,7 +675,7 @@ const svgListening = (( ) => {
const onTimer = ( ) => { const onTimer = ( ) => {
timer = undefined; timer = undefined;
vAPI.MessagingConnection.sendTo(epickerConnectionId, { pickerContentPort.postMessage({
what: 'highlightElementAtPoint', what: 'highlightElementAtPoint',
mx, mx,
my, my,
@ -798,7 +795,7 @@ const pausePicker = function() {
const unpausePicker = function() { const unpausePicker = function() {
pickerRoot.classList.remove('paused', 'preview'); pickerRoot.classList.remove('paused', 'preview');
vAPI.MessagingConnection.sendTo(epickerConnectionId, { pickerContentPort.postMessage({
what: 'togglePreview', what: 'togglePreview',
state: false, state: false,
}); });
@ -838,8 +835,9 @@ const startPicker = function() {
/******************************************************************************/ /******************************************************************************/
const quitPicker = function() { const quitPicker = function() {
vAPI.MessagingConnection.sendTo(epickerConnectionId, { what: 'quitPicker' }); pickerContentPort.postMessage({ what: 'quitPicker' });
vAPI.MessagingConnection.disconnectFrom(epickerConnectionId); pickerContentPort.close();
pickerContentPort = undefined;
}; };
/******************************************************************************/ /******************************************************************************/
@ -876,49 +874,27 @@ const onPickerMessage = function(msg) {
/******************************************************************************/ /******************************************************************************/
const onConnectionMessage = function(msg) { // Wait for the content script to establish communication
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;
}
};
vAPI.MessagingConnection.connectTo( let pickerContentPort;
`epickerDialog-${epickerId}`,
`epicker-${epickerId}`, globalThis.addEventListener('message', ev => {
onConnectionMessage 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;

View File

@ -44,64 +44,44 @@ if (
/******************************************************************************/ /******************************************************************************/
const logger = self.logger; const logger = self.logger;
var inspectorConnectionId; const inspector = qs$('#domInspector');
var inspectedTabId = 0; const domTree = qs$('#domTree');
var inspectedURL = ''; const filterToIdMap = new Map();
var inspectedHostname = '';
var inspector = qs$('#domInspector'); let inspectedTabId = 0;
var domTree = qs$('#domTree'); let inspectedURL = '';
var uidGenerator = 1; let inspectedHostname = '';
var filterToIdMap = new Map(); let uidGenerator = 1;
/******************************************************************************/ /******************************************************************************/
const messaging = vAPI.messaging; const inspectorFramePort = new globalThis.BroadcastChannel('loggerInspector');
inspectorFramePort.onmessage = ev => {
vAPI.MessagingConnection.addListener(function(msg) { const msg = ev.data || {};
if ( msg.from !== 'domInspector' || msg.to !== 'loggerUI' ) { return; } if ( msg.what === 'domLayoutFull' ) {
switch ( msg.what ) { inspectedURL = msg.url;
case 'connectionBroken': inspectedHostname = msg.hostname;
if ( inspectorConnectionId === msg.id ) { renderDOMFull(msg);
filterToIdMap.clear(); } else if ( msg.what === 'domLayoutIncremental' ) {
logger.removeAllChildren(domTree); renderDOMIncremental(msg);
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;
} }
}); };
inspectorFramePort.onmessageerror = ( ) => {
};
/******************************************************************************/ /******************************************************************************/
const nodeFromDomEntry = function(entry) { const nodeFromDomEntry = function(entry) {
var node, value;
const li = document.createElement('li'); const li = document.createElement('li');
dom.attr(li, 'id', entry.nid); dom.attr(li, 'id', entry.nid);
// expander/collapser // expander/collapser
li.appendChild(document.createElement('span')); li.appendChild(document.createElement('span'));
// selector // selector
node = document.createElement('code'); let node = document.createElement('code');
node.textContent = entry.sel; node.textContent = entry.sel;
li.appendChild(node); li.appendChild(node);
// descendant count // descendant count
value = entry.cnt || 0; let value = entry.cnt || 0;
node = document.createElement('span'); node = document.createElement('span');
node.textContent = value !== 0 ? value.toLocaleString() : ''; node.textContent = value !== 0 ? value.toLocaleString() : '';
dom.attr(node, 'data-cnt', value); dom.attr(node, 'data-cnt', value);
@ -114,7 +94,7 @@ const nodeFromDomEntry = function(entry) {
dom.cl.add(node, 'filter'); dom.cl.add(node, 'filter');
value = filterToIdMap.get(entry.filter); value = filterToIdMap.get(entry.filter);
if ( value === undefined ) { if ( value === undefined ) {
value = uidGenerator.toString(); value = `${uidGenerator}`;
filterToIdMap.set(entry.filter, value); filterToIdMap.set(entry.filter, value);
uidGenerator += 1; uidGenerator += 1;
} }
@ -142,18 +122,15 @@ const appendListItem = function(ul, li) {
/******************************************************************************/ /******************************************************************************/
const renderDOMFull = function(response) { const renderDOMFull = function(response) {
var domTreeParent = domTree.parentElement; const domTreeParent = domTree.parentElement;
var ul = domTreeParent.removeChild(domTree); let ul = domTreeParent.removeChild(domTree);
logger.removeAllChildren(domTree); logger.removeAllChildren(domTree);
filterToIdMap.clear(); filterToIdMap.clear();
var lvl = 0; let lvl = 0;
var entries = response.layout; let li;
var n = entries.length; for ( const entry of response.layout ) {
var li, entry;
for ( var i = 0; i < n; i++ ) {
entry = entries[i];
if ( entry.lvl === lvl ) { if ( entry.lvl === lvl ) {
li = nodeFromDomEntry(entry); li = nodeFromDomEntry(entry);
appendListItem(ul, li); appendListItem(ul, li);
@ -186,24 +163,21 @@ const renderDOMFull = function(response) {
domTreeParent.appendChild(domTree); domTreeParent.appendChild(domTree);
}; };
// https://www.youtube.com/watch?v=IDGNA83mxDo
/******************************************************************************/ /******************************************************************************/
const patchIncremental = function(from, delta) { const patchIncremental = function(from, delta) {
var span, cnt; let li = from.parentElement.parentElement;
var li = from.parentElement.parentElement; const patchCosmeticHide = delta >= 0 &&
var patchCosmeticHide = delta >= 0 && dom.cl.has(from, 'isCosmeticHide') &&
dom.cl.has(from, 'isCosmeticHide') && dom.cl.has(li, 'hasCosmeticHide') === false;
dom.cl.has(li, 'hasCosmeticHide') === false;
// Include descendants count when removing a node // Include descendants count when removing a node
if ( delta < 0 ) { if ( delta < 0 ) {
delta -= countFromNode(from); delta -= countFromNode(from);
} }
for ( ; li.localName === 'li'; li = li.parentElement.parentElement ) { for ( ; li.localName === 'li'; li = li.parentElement.parentElement ) {
span = li.children[2]; const span = li.children[2];
if ( delta !== 0 ) { if ( delta !== 0 ) {
cnt = countFromNode(li) + delta; const cnt = countFromNode(li) + delta;
span.textContent = cnt !== 0 ? cnt.toLocaleString() : ''; span.textContent = cnt !== 0 ? cnt.toLocaleString() : '';
dom.attr(span, 'data-cnt', cnt); dom.attr(span, 'data-cnt', cnt);
} }
@ -219,11 +193,10 @@ const renderDOMIncremental = function(response) {
// Process each journal entry: // Process each journal entry:
// 1 = node added // 1 = node added
// -1 = node removed // -1 = node removed
var journal = response.journal; const nodes = new Map(response.nodes);
var nodes = new Map(response.nodes); let li = null;
var entry, previous, li, ul; let ul = null;
for ( var i = 0, n = journal.length; i < n; i++ ) { for ( const entry of response.journal ) {
entry = journal[i];
// Remove node // Remove node
if ( entry.what === -1 ) { if ( entry.what === -1 ) {
li = qs$(`#${entry.nid}`); li = qs$(`#${entry.nid}`);
@ -239,7 +212,7 @@ const renderDOMIncremental = function(response) {
} }
// Add node as sibling // Add node as sibling
if ( entry.what === 1 && entry.l ) { if ( entry.what === 1 && entry.l ) {
previous = qs$(`#${entry.l}`); const previous = qs$(`#${entry.l}`);
// This should not happen // This should not happen
if ( previous === null ) { if ( previous === null ) {
// throw new Error('No left sibling!?'); // throw new Error('No left sibling!?');
@ -276,24 +249,21 @@ const renderDOMIncremental = function(response) {
/******************************************************************************/ /******************************************************************************/
const countFromNode = function(li) { const countFromNode = function(li) {
var span = li.children[2]; const span = li.children[2];
var cnt = parseInt(dom.attr(span, 'data-cnt'), 10); const cnt = parseInt(dom.attr(span, 'data-cnt'), 10);
return isNaN(cnt) ? 0 : cnt; return isNaN(cnt) ? 0 : cnt;
}; };
/******************************************************************************/ /******************************************************************************/
const selectorFromNode = function(node) { const selectorFromNode = function(node) {
var selector = ''; let selector = '';
var code;
while ( node !== null ) { while ( node !== null ) {
if ( node.localName === 'li' ) { if ( node.localName === 'li' ) {
code = qs$(node, 'code'); const code = qs$(node, 'code');
if ( code !== null ) { if ( code !== null ) {
selector = code.textContent + ' > ' + selector; selector = `${code.textContent} > ${selector}`;
if ( selector.indexOf('#') !== -1 ) { if ( selector.includes('#') ) { break; }
break;
}
} }
} }
node = node.parentElement; node = node.parentElement;
@ -306,7 +276,7 @@ const selectorFromNode = function(node) {
const selectorFromFilter = function(node) { const selectorFromFilter = function(node) {
while ( node !== null ) { while ( node !== null ) {
if ( node.localName === 'li' ) { 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 ) { if ( code !== null ) {
return code.textContent; return code.textContent;
} }
@ -319,7 +289,7 @@ const selectorFromFilter = function(node) {
/******************************************************************************/ /******************************************************************************/
const nidFromNode = function(node) { const nidFromNode = function(node) {
var li = node; let li = node;
while ( li !== null ) { while ( li !== null ) {
if ( li.localName === 'li' ) { if ( li.localName === 'li' ) {
return li.id || ''; return li.id || '';
@ -367,17 +337,17 @@ const startDialog = (function() {
}; };
const onClicked = function(ev) { const onClicked = function(ev) {
var target = ev.target; const target = ev.target;
ev.stopPropagation(); ev.stopPropagation();
if ( target.id === 'createCosmeticFilters' ) { if ( target.id === 'createCosmeticFilters' ) {
messaging.send('loggerUI', { vAPI.messaging.send('loggerUI', {
what: 'createUserFilter', what: 'createUserFilter',
filters: textarea.value, filters: textarea.value,
}); });
// Force a reload for the new cosmetic filter(s) to take effect // Force a reload for the new cosmetic filter(s) to take effect
messaging.send('loggerUI', { vAPI.messaging.send('loggerUI', {
what: 'reloadTab', what: 'reloadTab',
tabId: inspectedTabId, tabId: inspectedTabId,
}); });
@ -386,7 +356,7 @@ const startDialog = (function() {
}; };
const showCommitted = function() { const showCommitted = function() {
vAPI.MessagingConnection.sendTo(inspectorConnectionId, { inspectorFramePort.postMessage({
what: 'showCommitted', what: 'showCommitted',
hide: hideSelectors.join(',\n'), hide: hideSelectors.join(',\n'),
unhide: unhideSelectors.join(',\n') unhide: unhideSelectors.join(',\n')
@ -394,7 +364,7 @@ const startDialog = (function() {
}; };
const showInteractive = function() { const showInteractive = function() {
vAPI.MessagingConnection.sendTo(inspectorConnectionId, { inspectorFramePort.postMessage({
what: 'showInteractive', what: 'showInteractive',
hide: hideSelectors.join(',\n'), hide: hideSelectors.join(',\n'),
unhide: unhideSelectors.join(',\n') unhide: unhideSelectors.join(',\n')
@ -449,8 +419,8 @@ const onClicked = function(ev) {
if ( inspectedTabId === 0 ) { return; } if ( inspectedTabId === 0 ) { return; }
var target = ev.target; const target = ev.target;
var parent = target.parentElement; const parent = target.parentElement;
// Expand/collapse branch // Expand/collapse branch
if ( if (
@ -473,7 +443,7 @@ const onClicked = function(ev) {
// Toggle cosmetic filter // Toggle cosmetic filter
if ( dom.cl.has(target, 'filter') ) { if ( dom.cl.has(target, 'filter') ) {
vAPI.MessagingConnection.sendTo(inspectorConnectionId, { inspectorFramePort.postMessage({
what: 'toggleFilter', what: 'toggleFilter',
original: false, original: false,
target: dom.cl.toggle(target, 'off'), target: dom.cl.toggle(target, 'off'),
@ -489,7 +459,7 @@ const onClicked = function(ev) {
} }
// Toggle node // Toggle node
else { else {
vAPI.MessagingConnection.sendTo(inspectorConnectionId, { inspectorFramePort.postMessage({
what: 'toggleNodes', what: 'toggleNodes',
original: true, original: true,
target: dom.cl.toggle(target, 'off') === false, target: dom.cl.toggle(target, 'off') === false,
@ -509,7 +479,7 @@ const onMouseOver = (function() {
let mouseoverTarget = null; let mouseoverTarget = null;
const timerHandler = ( ) => { const timerHandler = ( ) => {
vAPI.MessagingConnection.sendTo(inspectorConnectionId, { inspectorFramePort.postMessage({
what: 'highlightOne', what: 'highlightOne',
selector: selectorFromNode(mouseoverTarget), selector: selectorFromNode(mouseoverTarget),
nid: nidFromNode(mouseoverTarget), nid: nidFromNode(mouseoverTarget),
@ -544,7 +514,7 @@ const injectInspector = function() {
const tabId = currentTabId(); const tabId = currentTabId();
if ( tabId <= 0 ) { return; } if ( tabId <= 0 ) { return; }
inspectedTabId = tabId; inspectedTabId = tabId;
messaging.send('loggerUI', { vAPI.messaging.send('loggerUI', {
what: 'scriptlet', what: 'scriptlet',
tabId, tabId,
scriptlet: 'dom-inspector', scriptlet: 'dom-inspector',
@ -554,9 +524,8 @@ const injectInspector = function() {
/******************************************************************************/ /******************************************************************************/
const shutdownInspector = function() { const shutdownInspector = function() {
if ( inspectorConnectionId !== undefined ) { if ( inspectorFramePort !== undefined ) {
vAPI.MessagingConnection.disconnectFrom(inspectorConnectionId); inspectorFramePort.postMessage({ what: 'quitInspector' });
inspectorConnectionId = undefined;
} }
logger.removeAllChildren(domTree); logger.removeAllChildren(domTree);
dom.cl.remove(inspector, 'vExpanded'); dom.cl.remove(inspector, 'vExpanded');
@ -594,10 +563,7 @@ const toggleHCompactView = function() {
const revert = function() { const revert = function() {
dom.cl.remove('#domTree .off', 'off'); dom.cl.remove('#domTree .off', 'off');
vAPI.MessagingConnection.sendTo( inspectorFramePort.postMessage({ what: 'resetToggledNodes' });
inspectorConnectionId,
{ what: 'resetToggledNodes' }
);
dom.cl.add(qs$(inspector, '.permatoolbar .revert'), 'disabled'); dom.cl.add(qs$(inspector, '.permatoolbar .revert'), 'disabled');
dom.cl.add(qs$(inspector, '.permatoolbar .commit'), 'disabled'); dom.cl.add(qs$(inspector, '.permatoolbar .commit'), 'disabled');
}; };

View File

@ -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: // Channel:
// documentBlocked // documentBlocked
// privileged // privileged

View File

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
uBlock Origin - a browser extension to block requests. 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 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 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; } if ( typeof vAPI !== 'object' ) { return; }
if ( vAPI.domFilterer instanceof Object === false ) { return; }
/******************************************************************************/ if ( document.querySelector(`iframe[${vAPI.sessionId}]`) !== null ) { return; }
var sessionId = vAPI.sessionId;
if ( document.querySelector('iframe.dom-inspector.' + sessionId) !== null ) {
return;
}
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
let loggerConnectionId;
// Highlighter-related // Highlighter-related
let svgRoot = null; let inspectorRoot = null;
let pickerRoot = null;
let nodeToIdMap = new WeakMap(); // No need to iterate const nodeToIdMap = new WeakMap(); // No need to iterate
let blueNodes = []; let blueNodes = [];
const roRedNodes = new Map(); // node => current cosmetic filter 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([ const skipTagNames = new Set([
'br', 'head', 'link', 'meta', 'script', 'style', 'title' 'br', 'head', 'link', 'meta', 'script', 'style', 'title'
]); ]);
@ -70,87 +65,81 @@ const domLayout = (function() {
[ 'object', 'data' ] [ 'object', 'data' ]
]); ]);
var idGenerator = 0; let idGenerator = 1;
// This will be used to uniquely identify nodes across process. // This will be used to uniquely identify nodes across process.
const newNodeId = function(node) { const newNodeId = node => {
var nid = 'n' + (idGenerator++).toString(36); const nid = `n${(idGenerator++).toString(36)}`;
nodeToIdMap.set(node, nid); nodeToIdMap.set(node, nid);
return nid; return nid;
}; };
const selectorFromNode = function(node) { const selectorFromNode = node => {
var str, attr, pos, sw, i; const tag = node.localName;
var tag = node.localName; let selector = CSS.escape(tag);
var selector = CSS.escape(tag);
// Id // Id
if ( typeof node.id === 'string' ) { if ( typeof node.id === 'string' ) {
str = node.id.trim(); let str = node.id.trim();
if ( str !== '' ) { if ( str !== '' ) {
selector += '#' + CSS.escape(str); selector += `#${CSS.escape(str)}`;
} }
} }
// Class // Class
var cl = node.classList; const cl = node.classList;
if ( cl ) { if ( cl ) {
for ( i = 0; i < cl.length; i++ ) { for ( let i = 0; i < cl.length; i++ ) {
selector += '.' + CSS.escape(cl[i]); selector += `.${CSS.escape(cl[i])}`;
} }
} }
// Tag-specific attributes // Tag-specific attributes
attr = resourceAttrNames.get(tag); const attr = resourceAttrNames.get(tag);
if ( attr !== undefined ) { if ( attr !== undefined ) {
str = node.getAttribute(attr) || ''; let str = node.getAttribute(attr) || '';
str = str.trim(); str = str.trim();
if ( str.startsWith('data:') ) { const pos = str.startsWith('data:') ? 5 : str.search(/[#?]/);
pos = 5; let sw = '';
} else {
pos = str.search(/[#?]/);
}
if ( pos !== -1 ) { if ( pos !== -1 ) {
str = str.slice(0, pos); str = str.slice(0, pos);
sw = '^'; sw = '^';
} else {
sw = '';
} }
if ( str !== '' ) { if ( str !== '' ) {
selector += '[' + attr + sw + '="' + CSS.escape(str, true) + '"]'; selector += `[${attr}${sw}="${CSS.escape(str, true)}"]`;
} }
} }
return selector; return selector;
}; };
const DomRoot = function() { function DomRoot() {
this.nid = newNodeId(document.body); this.nid = newNodeId(document.body);
this.lvl = 0; this.lvl = 0;
this.sel = 'body'; this.sel = 'body';
this.cnt = 0; this.cnt = 0;
this.filter = roRedNodes.get(document.body); this.filter = roRedNodes.get(document.body);
}; }
const DomNode = function(node, level) { function DomNode(node, level) {
this.nid = newNodeId(node); this.nid = newNodeId(node);
this.lvl = level; this.lvl = level;
this.sel = selectorFromNode(node); this.sel = selectorFromNode(node);
this.cnt = 0; this.cnt = 0;
this.filter = roRedNodes.get(node); this.filter = roRedNodes.get(node);
}; }
const domNodeFactory = function(level, node) { const domNodeFactory = (level, node) => {
const localName = node.localName; const localName = node.localName;
if ( skipTagNames.has(localName) ) { return null; } if ( skipTagNames.has(localName) ) { return null; }
// skip uBlock's own nodes // skip uBlock's own nodes
if ( node.classList.contains(sessionId) ) { return null; } if ( node === inspectorRoot ) { return null; }
if ( level === 0 && localName === 'body' ) { if ( level === 0 && localName === 'body' ) {
return new DomRoot(); return new DomRoot();
} }
return new DomNode(node, level); return new DomNode(node, level);
}; };
// Collect layout data. // Collect layout data
const getLayoutData = function() { const getLayoutData = ( ) => {
const layout = []; const layout = [];
const stack = []; const stack = [];
let lvl = 0; let lvl = 0;
@ -188,14 +177,14 @@ const domLayout = (function() {
// Descendant count for each node. // Descendant count for each node.
const patchLayoutData = function(layout) { const patchLayoutData = layout => {
var stack = [], ptr; const stack = [];
var lvl = 0; let ptr;
var domNode, cnt; let lvl = 0;
var i = layout.length; let i = layout.length;
while ( i-- ) { while ( i-- ) {
domNode = layout[i]; const domNode = layout[i];
if ( domNode.lvl === lvl ) { if ( domNode.lvl === lvl ) {
stack[ptr] += 1; stack[ptr] += 1;
continue; continue;
@ -210,7 +199,7 @@ const domLayout = (function() {
continue; continue;
} }
// domNode.lvl < lvl // domNode.lvl < lvl
cnt = stack.pop(); const cnt = stack.pop();
domNode.cnt = cnt; domNode.cnt = cnt;
lvl -= 1; lvl -= 1;
ptr = lvl - 1; ptr = lvl - 1;
@ -221,13 +210,13 @@ const domLayout = (function() {
// Track and report mutations of the DOM // Track and report mutations of the DOM
var mutationObserver = null; let mutationObserver = null;
var mutationTimer; let mutationTimer;
var addedNodelists = []; let addedNodelists = [];
var removedNodelist = []; let removedNodelist = [];
const previousElementSiblingId = function(node) { const previousElementSiblingId = node => {
var sibling = node; let sibling = node;
for (;;) { for (;;) {
sibling = sibling.previousElementSibling; sibling = sibling.previousElementSibling;
if ( sibling === null ) { return null; } if ( sibling === null ) { return null; }
@ -236,11 +225,10 @@ const domLayout = (function() {
} }
}; };
const journalFromBranch = function(root, newNodes, newNodeToIdMap) { const journalFromBranch = (root, newNodes, newNodeToIdMap) => {
var domNode; let node = root.firstElementChild;
var node = root.firstElementChild;
while ( node !== null ) { while ( node !== null ) {
domNode = domNodeFactory(undefined, node); const domNode = domNodeFactory(undefined, node);
if ( domNode !== null ) { if ( domNode !== null ) {
newNodeToIdMap.set(domNode.nid, domNode); newNodeToIdMap.set(domNode.nid, domNode);
newNodes.push(node); newNodes.push(node);
@ -267,22 +255,21 @@ const domLayout = (function() {
} }
}; };
const journalFromMutations = function() { const journalFromMutations = ( ) => {
var nodelist, node, domNode, nid;
mutationTimer = undefined; mutationTimer = undefined;
// This is used to temporarily hold all added nodes, before resolving // This is used to temporarily hold all added nodes, before resolving
// their node id and relative position. // their node id and relative position.
var newNodes = []; const newNodes = [];
var journalEntries = []; const journalEntries = [];
var newNodeToIdMap = new Map(); const newNodeToIdMap = new Map();
for ( nodelist of addedNodelists ) { for ( const nodelist of addedNodelists ) {
for ( node of nodelist ) { for ( const node of nodelist ) {
if ( node.nodeType !== 1 ) { continue; } if ( node.nodeType !== 1 ) { continue; }
if ( node.parentElement === null ) { continue; } if ( node.parentElement === null ) { continue; }
cosmeticFilterMapper.incremental(node); cosmeticFilterMapper.incremental(node);
domNode = domNodeFactory(undefined, node); const domNode = domNodeFactory(undefined, node);
if ( domNode !== null ) { if ( domNode !== null ) {
newNodeToIdMap.set(domNode.nid, domNode); newNodeToIdMap.set(domNode.nid, domNode);
newNodes.push(node); newNodes.push(node);
@ -291,19 +278,16 @@ const domLayout = (function() {
} }
} }
addedNodelists = []; addedNodelists = [];
for ( nodelist of removedNodelist ) { for ( const nodelist of removedNodelist ) {
for ( node of nodelist ) { for ( const node of nodelist ) {
if ( node.nodeType !== 1 ) { continue; } if ( node.nodeType !== 1 ) { continue; }
nid = nodeToIdMap.get(node); const nid = nodeToIdMap.get(node);
if ( nid === undefined ) { continue; } if ( nid === undefined ) { continue; }
journalEntries.push({ journalEntries.push({ what: -1, nid });
what: -1,
nid: nid
});
} }
} }
removedNodelist = []; removedNodelist = [];
for ( node of newNodes ) { for ( const node of newNodes ) {
journalEntries.push({ journalEntries.push({
what: 1, what: 1,
nid: nodeToIdMap.get(node), nid: nodeToIdMap.get(node),
@ -314,7 +298,7 @@ const domLayout = (function() {
if ( journalEntries.length === 0 ) { return; } if ( journalEntries.length === 0 ) { return; }
vAPI.MessagingConnection.sendTo(loggerConnectionId, { inspectorFramePort.postMessage({
what: 'domLayoutIncremental', what: 'domLayoutIncremental',
url: window.location.href, url: window.location.href,
hostname: window.location.hostname, hostname: window.location.hostname,
@ -323,8 +307,8 @@ const domLayout = (function() {
}); });
}; };
const onMutationObserved = function(mutationRecords) { const onMutationObserved = mutationRecords => {
for ( var record of mutationRecords ) { for ( const record of mutationRecords ) {
if ( record.addedNodes.length !== 0 ) { if ( record.addedNodes.length !== 0 ) {
addedNodelists.push(record.addedNodes); addedNodelists.push(record.addedNodes);
} }
@ -339,7 +323,7 @@ const domLayout = (function() {
// API // API
const getLayout = function() { const getLayout = ( ) => {
cosmeticFilterMapper.reset(); cosmeticFilterMapper.reset();
mutationObserver = new MutationObserver(onMutationObserved); mutationObserver = new MutationObserver(onMutationObserved);
mutationObserver.observe(document.body, { mutationObserver.observe(document.body, {
@ -355,11 +339,11 @@ const domLayout = (function() {
}; };
}; };
const reset = function() { const reset = ( ) => {
shutdown(); shutdown();
}; };
const shutdown = function() { const shutdown = ( ) => {
if ( mutationTimer !== undefined ) { if ( mutationTimer !== undefined ) {
clearTimeout(mutationTimer); clearTimeout(mutationTimer);
mutationTimer = undefined; mutationTimer = undefined;
@ -370,35 +354,20 @@ const domLayout = (function() {
} }
addedNodelists = []; addedNodelists = [];
removedNodelist = []; removedNodelist = [];
nodeToIdMap = new WeakMap();
}; };
return { return {
get: getLayout, get: getLayout,
reset: reset, reset,
shutdown: shutdown shutdown,
}; };
})(); })();
// https://www.youtube.com/watch?v=qo8zKhd4Cf0
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
// For browsers not supporting `:scope`, it's not the end of the world: the const cosmeticFilterMapper = (( ) => {
// suggested CSS selectors may just end up being more verbose. const nodesFromStyleTag = rootNode => {
let cssScope = ':scope > ';
try {
document.querySelector(':scope *');
} catch (e) {
cssScope = '';
}
/******************************************************************************/
const cosmeticFilterMapper = (function() {
const nodesFromStyleTag = function(rootNode) {
const filterMap = roRedNodes; const filterMap = roRedNodes;
const details = vAPI.domFilterer.getAllSelectors(); const details = vAPI.domFilterer.getAllSelectors();
@ -434,25 +403,25 @@ const cosmeticFilterMapper = (function() {
} }
}; };
const incremental = function(rootNode) { const incremental = rootNode => {
nodesFromStyleTag(rootNode); nodesFromStyleTag(rootNode);
}; };
const reset = function() { const reset = ( ) => {
roRedNodes.clear(); roRedNodes.clear();
if ( document.documentElement !== null ) { if ( document.documentElement !== null ) {
incremental(document.documentElement); incremental(document.documentElement);
} }
}; };
const shutdown = function() { const shutdown = ( ) => {
vAPI.domFilterer.toggle(true); vAPI.domFilterer.toggle(true);
}; };
return { return {
incremental: incremental, incremental,
reset: reset, reset,
shutdown: shutdown shutdown,
}; };
})(); })();
@ -475,21 +444,18 @@ const elementsFromSelector = function(selector, context) {
}; };
const elementsFromSpecialSelector = function(selector) { const elementsFromSpecialSelector = function(selector) {
var out = [], i; const out = [];
var matches = /^(.+?):has\((.+?)\)$/.exec(selector); let matches = /^(.+?):has\((.+?)\)$/.exec(selector);
if ( matches !== null ) { if ( matches !== null ) {
var nodes; let nodes;
try { try {
nodes = document.querySelectorAll(matches[1]); nodes = document.querySelectorAll(matches[1]);
} catch(ex) { } catch(ex) {
nodes = []; nodes = [];
} }
i = nodes.length; for ( const node of nodes ) {
while ( i-- ) { if ( node.querySelector(matches[2]) === null ) { continue; }
var node = nodes[i]; out.push(node);
if ( node.querySelector(matches[2]) !== null ) {
out.push(node);
}
} }
return out; return out;
} }
@ -503,7 +469,7 @@ const elementsFromSpecialSelector = function(selector) {
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null null
); );
i = xpr.snapshotLength; let i = xpr.snapshotLength;
while ( i-- ) { while ( i-- ) {
out.push(xpr.snapshotItem(i)); out.push(xpr.snapshotItem(i));
} }
@ -512,128 +478,114 @@ const elementsFromSpecialSelector = function(selector) {
/******************************************************************************/ /******************************************************************************/
const getSvgRootChildren = function() { const highlightElements = ( ) => {
if ( svgRoot.children ) { const paths = [];
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 = function() { const path = [];
var islands; for ( const elem of rwRedNodes.keys() ) {
var elem, rect, poly; if ( elem === inspectorRoot ) { continue; }
var xl, xr, yt, yb, w, h, ws;
var svgRootChildren = getSvgRootChildren();
islands = [];
for ( elem of rwRedNodes.keys() ) {
if ( elem === pickerRoot ) { continue; }
if ( rwGreenNodes.has(elem) ) { continue; } if ( rwGreenNodes.has(elem) ) { continue; }
if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; } if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
rect = elem.getBoundingClientRect(); const rect = elem.getBoundingClientRect();
xl = rect.left; const xl = rect.left;
xr = rect.right; const w = rect.width;
w = rect.width; const yt = rect.top;
yt = rect.top; const h = rect.height;
yb = rect.bottom; const ws = w.toFixed(1);
h = rect.height; const poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
ws = w.toFixed(1);
poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
'h' + ws + 'h' + ws +
'v' + h.toFixed(1) + 'v' + h.toFixed(1) +
'h-' + ws + 'h-' + ws +
'z'; 'z';
islands.push(poly); path.push(poly);
} }
svgRootChildren[0].setAttribute('d', islands.join('') || 'M0 0'); paths.push(path.join('') || 'M0 0');
islands = []; path.length = 0;
for ( elem of rwGreenNodes ) { for ( const elem of rwGreenNodes ) {
if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; } if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
rect = elem.getBoundingClientRect(); const rect = elem.getBoundingClientRect();
xl = rect.left; const xl = rect.left;
xr = rect.right; const w = rect.width;
w = rect.width; const yt = rect.top;
yt = rect.top; const h = rect.height;
yb = rect.bottom; const ws = w.toFixed(1);
h = rect.height; const poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
ws = w.toFixed(1);
poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
'h' + ws + 'h' + ws +
'v' + h.toFixed(1) + 'v' + h.toFixed(1) +
'h-' + ws + 'h-' + ws +
'z'; 'z';
islands.push(poly); path.push(poly);
} }
svgRootChildren[1].setAttribute('d', islands.join('') || 'M0 0'); paths.push(path.join('') || 'M0 0');
islands = []; path.length = 0;
for ( elem of roRedNodes.keys() ) { for ( const elem of roRedNodes.keys() ) {
if ( elem === pickerRoot ) { continue; } if ( elem === inspectorRoot ) { continue; }
if ( rwGreenNodes.has(elem) ) { continue; } if ( rwGreenNodes.has(elem) ) { continue; }
if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; } if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
rect = elem.getBoundingClientRect(); const rect = elem.getBoundingClientRect();
xl = rect.left; const xl = rect.left;
xr = rect.right; const w = rect.width;
w = rect.width; const yt = rect.top;
yt = rect.top; const h = rect.height;
yb = rect.bottom; const ws = w.toFixed(1);
h = rect.height; const poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
ws = w.toFixed(1);
poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
'h' + ws + 'h' + ws +
'v' + h.toFixed(1) + 'v' + h.toFixed(1) +
'h-' + ws + 'h-' + ws +
'z'; 'z';
islands.push(poly); path.push(poly);
} }
svgRootChildren[2].setAttribute('d', islands.join('') || 'M0 0'); paths.push(path.join('') || 'M0 0');
islands = []; path.length = 0;
for ( elem of blueNodes ) { for ( const elem of blueNodes ) {
if ( elem === pickerRoot ) { continue; } if ( elem === inspectorRoot ) { continue; }
if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; } if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
rect = elem.getBoundingClientRect(); const rect = elem.getBoundingClientRect();
xl = rect.left; const xl = rect.left;
xr = rect.right; const w = rect.width;
w = rect.width; const yt = rect.top;
yt = rect.top; const h = rect.height;
yb = rect.bottom; const ws = w.toFixed(1);
h = rect.height; const poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
ws = w.toFixed(1);
poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
'h' + ws + 'h' + ws +
'v' + h.toFixed(1) + 'v' + h.toFixed(1) +
'h-' + ws + 'h-' + ws +
'z'; '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() { const onScrolled = (( ) => {
let buffered = false; let timer;
const timerHandler = function() { return ( ) => {
buffered = false; if ( timer ) { return; }
highlightElements(); timer = window.requestAnimationFrame(( ) => {
}; timer = undefined;
return function() { highlightElements();
if ( buffered === false ) { });
window.requestAnimationFrame(timerHandler);
buffered = true;
}
}; };
})(); })();
const onMouseOver = ( ) => {
if ( blueNodes.length === 0 ) { return; }
blueNodes = [];
highlightElements();
};
/******************************************************************************/ /******************************************************************************/
const selectNodes = function(selector, nid) { const selectNodes = (selector, nid) => {
const nodes = elementsFromSelector(selector); const nodes = elementsFromSelector(selector);
if ( nid === '' ) { return nodes; } if ( nid === '' ) { return nodes; }
for ( const node of nodes ) { for ( const node of nodes ) {
@ -646,7 +598,7 @@ const selectNodes = function(selector, nid) {
/******************************************************************************/ /******************************************************************************/
const nodesFromFilter = function(selector) { const nodesFromFilter = selector => {
const out = []; const out = [];
for ( const entry of roRedNodes ) { for ( const entry of roRedNodes ) {
if ( entry[1] === selector ) { 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 ) { for ( const node of nodes ) {
if ( targetState ) { if ( targetState ) {
rwGreenNodes.add(node); 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 ) { for ( const node of nodes ) {
if ( targetState ) { if ( targetState ) {
rwRedNodes.delete(node); rwRedNodes.delete(node);
@ -678,23 +630,28 @@ const toggleFilter = function(nodes, targetState) {
} }
}; };
const resetToggledNodes = function() { const resetToggledNodes = ( ) => {
rwGreenNodes.clear(); rwGreenNodes.clear();
rwRedNodes.clear(); rwRedNodes.clear();
}; };
/******************************************************************************/ /******************************************************************************/
const start = function() { const start = ( ) => {
const onReady = function(ev) { const onReady = ( ) => {
if ( ev ) { window.addEventListener('scroll', onScrolled, {
document.removeEventListener(ev.type, onReady); capture: true,
} passive: true,
vAPI.MessagingConnection.sendTo(loggerConnectionId, domLayout.get()); });
window.addEventListener('mouseover', onMouseOver, {
capture: true,
passive: true,
});
inspectorFramePort.postMessage(domLayout.get());
vAPI.domFilterer.toggle(false, highlightElements); vAPI.domFilterer.toggle(false, highlightElements);
}; };
if ( document.readyState === 'loading' ) { if ( document.readyState === 'loading' ) {
document.addEventListener('DOMContentLoaded', onReady); document.addEventListener('DOMContentLoaded', onReady, { once: true });
} else { } else {
onReady(); onReady();
} }
@ -702,34 +659,49 @@ const start = function() {
/******************************************************************************/ /******************************************************************************/
const shutdown = function() { const shutdown = ( ) => {
cosmeticFilterMapper.shutdown(); cosmeticFilterMapper.shutdown();
domLayout.shutdown(); domLayout.shutdown();
vAPI.MessagingConnection.disconnectFrom(loggerConnectionId); window.removeEventListener('scroll', onScrolled, {
window.removeEventListener('scroll', onScrolled, true); capture: true,
pickerRoot.remove(); passive: true,
pickerRoot = svgRoot = null; });
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) { const onMessage = request => {
var response,
nodes;
switch ( request.what ) { switch ( request.what ) {
case 'startInspector':
start();
break;
case 'quitInspector':
shutdown();
break;
case 'commitFilters': case 'commitFilters':
highlightElements(); highlightElements();
break; break;
case 'domLayout': case 'domLayout':
response = domLayout.get(); domLayout.get();
highlightElements(); highlightElements();
break; break;
case 'highlightMode': case 'highlightMode':
//svgRoot.classList.toggle('invert', request.invert);
break; break;
case 'highlightOne': case 'highlightOne':
@ -753,110 +725,47 @@ const onMessage = function(request) {
highlightElements(); highlightElements();
break; break;
case 'toggleFilter': case 'toggleFilter': {
nodes = selectNodes(request.selector, request.nid); const nodes = selectNodes(request.selector, request.nid);
if ( nodes.length !== 0 ) { nodes[0].scrollIntoView(); } if ( nodes.length !== 0 ) {
nodes[0].scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
});
}
toggleExceptions(nodesFromFilter(request.filter), request.target); toggleExceptions(nodesFromFilter(request.filter), request.target);
highlightElements(); highlightElements();
break; break;
}
case 'toggleNodes': case 'toggleNodes': {
nodes = selectNodes(request.selector, request.nid); const nodes = selectNodes(request.selector, request.nid);
if ( nodes.length !== 0 ) { nodes[0].scrollIntoView(); } if ( nodes.length !== 0 ) {
nodes[0].scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
});
}
toggleFilter(nodes, request.target); toggleFilter(nodes, request.target);
highlightElements(); highlightElements();
break; break;
}
default: default:
break; break;
} }
return response;
}; };
/******************************************************************************/ /******************************************************************************/
// Install DOM inspector widget // 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) { const inspectorCSSStyle = [
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 = [
'background: transparent', 'background: transparent',
'border: 0', 'border: 0',
'border-radius: 0', 'border-radius: 0',
@ -878,8 +787,43 @@ pickerRoot.style.cssText = [
'' ''
].join(' !important;\n'); ].join(' !important;\n');
pickerRoot.addEventListener('load', ev => { bootstrap(ev); }); const inspectorCSS = `
(document.documentElement || document).appendChild(pickerRoot); :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;
/******************************************************************************/ /******************************************************************************/

View File

@ -37,7 +37,6 @@ if ( typeof vAPI !== 'object' || vAPI === null ) {
/******************************************************************************/ /******************************************************************************/
const epickerId = vAPI.randomToken(); const epickerId = vAPI.randomToken();
let epickerConnectionId;
let pickerRoot = document.querySelector(`[${vAPI.sessionId}]`); let pickerRoot = document.querySelector(`[${vAPI.sessionId}]`);
if ( pickerRoot !== null ) { return; } if ( pickerRoot !== null ) { return; }
@ -144,7 +143,7 @@ const highlightElements = function(elems, force) {
); );
} }
vAPI.MessagingConnection.sendTo(epickerConnectionId, { pickerFramePort.postMessage({
what: 'svgPaths', what: 'svgPaths',
ocean: `M0 0h${ow}v${oh}h-${ow}z`, ocean: `M0 0h${ow}v${oh}h-${ow}z`,
islands: islands.join(''), islands: islands.join(''),
@ -900,7 +899,7 @@ const onOptimizeCandidates = function(details) {
if ( r !== 0 ) { return r; } if ( r !== 0 ) { return r; }
return a.selector.length - b.selector.length; return a.selector.length - b.selector.length;
}); });
vAPI.MessagingConnection.sendTo(epickerConnectionId, { pickerFramePort.postMessage({
what: 'candidatesOptimized', what: 'candidatesOptimized',
candidates: results.map(a => a.selector), candidates: results.map(a => a.selector),
slot: details.slot, slot: details.slot,
@ -910,7 +909,7 @@ const onOptimizeCandidates = function(details) {
/******************************************************************************/ /******************************************************************************/
const showDialog = function(options) { const showDialog = function(options) {
vAPI.MessagingConnection.sendTo(epickerConnectionId, { pickerFramePort.postMessage({
what: 'showDialog', what: 'showDialog',
url: self.location.href, url: self.location.href,
netFilters: netFilterCandidates, netFilters: netFilterCandidates,
@ -1141,16 +1140,13 @@ const quitPicker = function() {
self.removeEventListener('resize', onViewportChanged, { passive: true }); self.removeEventListener('resize', onViewportChanged, { passive: true });
self.removeEventListener('keydown', onKeyPressed, true); self.removeEventListener('keydown', onKeyPressed, true);
vAPI.shutdown.remove(quitPicker); vAPI.shutdown.remove(quitPicker);
vAPI.MessagingConnection.disconnectFrom(epickerConnectionId); pickerFramePort.close();
vAPI.MessagingConnection.removeListener(onConnectionMessage); pickerFramePort = undefined;
vAPI.userStylesheet.remove(pickerCSS); vAPI.userStylesheet.remove(pickerCSS);
vAPI.userStylesheet.apply(); vAPI.userStylesheet.apply();
if ( pickerRoot === null ) { return; } if ( pickerRoot === null ) { return; }
pickerRoot.remove(); pickerRoot.remove();
pickerRoot = null; pickerRoot = null;
self.focus(); self.focus();
}; };
@ -1176,7 +1172,7 @@ const onDialogMessage = function(msg) {
const resultset = filterToDOMInterface.queryAll(msg) || []; const resultset = filterToDOMInterface.queryAll(msg) || [];
highlightElements(resultset.map(a => a.elem), true); highlightElements(resultset.map(a => a.elem), true);
if ( msg.filter === '!' ) { break; } if ( msg.filter === '!' ) { break; }
vAPI.MessagingConnection.sendTo(epickerConnectionId, { pickerFramePort.postMessage({
what: 'resultsetDetails', what: 'resultsetDetails',
count: resultset.length, count: resultset.length,
opt: resultset.length !== 0 ? resultset[0].opt : undefined, 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 // 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 // is a sandboxed so as to prevent the page from interfering with its
// content and behavior. // content and behavior.
@ -1249,17 +1228,13 @@ const onConnectionMessage = function(msg) {
// of the iframe, and cannot interfere with its style properties. However the // of the iframe, and cannot interfere with its style properties. However the
// page can remove the iframe. // page can remove the iframe.
// We need extra messaging capabilities + fetch/process picker arguments. // fetch/process picker arguments.
{ {
const results = await Promise.all([ pickerBootArgs = await vAPI.messaging.send('elementPicker', {
vAPI.messaging.extend(), what: 'elementPickerArguments',
vAPI.messaging.send('elementPicker', { what: 'elementPickerArguments' }), });
]); if ( typeof pickerBootArgs !== 'object' ) { return; }
if ( results[0] !== true ) { return; } if ( pickerBootArgs === null ) { return; }
pickerBootArgs = results[1];
if ( typeof pickerBootArgs !== 'object' || pickerBootArgs === null ) {
return;
}
// Restore net filter union data if origin is the same. // Restore net filter union data if origin is the same.
const eprom = pickerBootArgs.eprom || null; const eprom = pickerBootArgs.eprom || null;
if ( eprom !== null && eprom.lastNetFilterSession === lastNetFilterSession ) { if ( eprom !== null && eprom.lastNetFilterSession === lastNetFilterSession ) {
@ -1302,12 +1277,12 @@ const pickerCSSStyle = [
'width: 100%', 'width: 100%',
'z-index: 2147483647', 'z-index: 2147483647',
'' ''
]; ].join(' !important;\n');
const pickerCSS = ` const pickerCSS = `
:root > [${vAPI.sessionId}] { :root > [${vAPI.sessionId}] {
${pickerCSSStyle.join(' !important;')} ${pickerCSSStyle}
} }
:root > [${vAPI.sessionId}-loaded] { :root > [${vAPI.sessionId}-loaded] {
visibility: visible !important; visibility: visible !important;
@ -1326,7 +1301,7 @@ document.documentElement.append(pickerRoot);
vAPI.shutdown.add(quitPicker); vAPI.shutdown.add(quitPicker);
vAPI.MessagingConnection.addListener(onConnectionMessage); let pickerFramePort;
{ {
const url = new URL(pickerBootArgs.pickerURL); const url = new URL(pickerBootArgs.pickerURL);
@ -1334,10 +1309,24 @@ vAPI.MessagingConnection.addListener(onConnectionMessage);
if ( pickerBootArgs.zap ) { if ( pickerBootArgs.zap ) {
url.searchParams.set('zap', '1'); url.searchParams.set('zap', '1');
} }
pickerRoot.addEventListener("load", function() { pickerRoot.addEventListener('load', ( ) => {
pickerRoot.setAttribute(`${vAPI.sessionId}-loaded`, ""); const channel = new MessageChannel();
}); pickerFramePort = channel.port1;
pickerRoot.src = url.href; 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;
} }
/******************************************************************************/ /******************************************************************************/

View File

@ -222,7 +222,6 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.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/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/logger-ui.js" type="module"></script> <script src="js/logger-ui.js" type="module"></script>

View File

@ -121,7 +121,6 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.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/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js" type="module"></script> <script src="js/dashboard-common.js" type="module"></script>

View File

@ -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>

View File

@ -67,7 +67,6 @@
<script src="../js/vapi.js"></script> <script src="../js/vapi.js"></script>
<script src="../js/vapi-common.js"></script> <script src="../js/vapi-common.js"></script>
<script src="../js/vapi-client.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/theme.js" type="module"></script>
<script src="../js/i18n.js" type="module"></script> <script src="../js/i18n.js" type="module"></script>
<script src="../js/epicker-ui.js" type="module"></script> <script src="../js/epicker-ui.js" type="module"></script>