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

[firefox] Fix DOM inspector being broken in private window

Related issue:
https://github.com/uBlockOrigin/uBlock-issues/issues/3004

Related commit:
ee83a4304a

Use extensions API message ports to establish direct communication
between content script and logger window.
This commit is contained in:
Raymond Hill 2023-12-06 21:16:48 -05:00
parent fddca0b7cb
commit 15b1250c99
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
4 changed files with 225 additions and 114 deletions

View File

@ -25,22 +25,19 @@
/******************************************************************************/
const svgRoot = document.querySelector('svg');
let inspectorContentPort;
const quit = ( ) => {
inspectorContentPort.postMessage({ what: 'quitInspector' });
const shutdown = ( ) => {
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) => {
const contentInspectorChannel = ev => {
const msg = ev.data || {};
switch ( msg.what ) {
case 'quitInspector': {
quit();
shutdown();
break;
}
case 'svgPaths': {
@ -52,41 +49,20 @@ const onMessage = (msg, fromLogger) => {
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.onmessage = contentInspectorChannel;
inspectorContentPort.onmessageerror = shutdown;
inspectorContentPort.postMessage({ what: 'startInspector' });
}, { once: true });
/******************************************************************************/

View File

@ -42,21 +42,97 @@ let inspectedURL = '';
let inspectedHostname = '';
let uidGenerator = 1;
/******************************************************************************/
/*******************************************************************************
*
* How it works:
*
* 1. The logger/inspector is enabled from the logger window
*
* 2. The inspector content script is injected in the root frame of the tab
* currently selected in the logger
*
* 3. The inspector content script asks the logger/inspector to establish
* a two-way communication channel
*
* 3. The inspector content script embed an inspector frame in the document
* being inspected and waits for the inspector frame to be fully loaded
*
* 4. The inspector content script sends a messaging port object to the
* embedded inspector frame for a two-way communication channel between
* the inspector frame and the inspector content script
*
* 5. The inspector content script sends dom information to the
* logger/inspector
*
* */
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 contentInspectorChannel = (( ) => {
let bcChannel;
let toContentPort;
const start = ( ) => {
bcChannel = new globalThis.BroadcastChannel('contentInspectorChannel');
bcChannel.onmessage = ev => {
const msg = ev.data || {};
connect(msg.tabId, msg.frameId);
};
browser.webNavigation.onDOMContentLoaded.addListener(onContentLoaded);
};
const shutdown = ( ) => {
browser.webNavigation.onDOMContentLoaded.removeListener(onContentLoaded);
disconnect();
bcChannel.close();
bcChannel.onmessage = null;
bcChannel = undefined;
};
const connect = (tabId, frameId) => {
disconnect();
try {
toContentPort = browser.tabs.connect(tabId, { frameId });
toContentPort.onMessage.addListener(onContentMessage);
toContentPort.onDisconnect.addListener(onContentDisconnect);
} catch(_) {
}
};
const disconnect = ( ) => {
if ( toContentPort === undefined ) { return; }
toContentPort.onMessage.removeListener(onContentMessage);
toContentPort.onDisconnect.removeListener(onContentDisconnect);
toContentPort.disconnect();
toContentPort = undefined;
};
const send = msg => {
if ( toContentPort === undefined ) { return; }
toContentPort.postMessage(msg);
};
const onContentMessage = msg => {
if ( msg.what === 'domLayoutFull' ) {
inspectedURL = msg.url;
inspectedHostname = msg.hostname;
renderDOMFull(msg);
} else if ( msg.what === 'domLayoutIncremental' ) {
renderDOMIncremental(msg);
}
};
const onContentDisconnect = ( ) => {
disconnect();
};
const onContentLoaded = details => {
if ( details.tabId !== inspectedTabId ) { return; }
if ( details.frameId !== 0 ) { return; }
disconnect();
injectInspector();
};
return { start, disconnect, send, shutdown };
})();
/******************************************************************************/
@ -345,7 +421,7 @@ const startDialog = (( ) => {
};
const showCommitted = function() {
inspectorFramePort.postMessage({
contentInspectorChannel.send({
what: 'showCommitted',
hide: hideSelectors.join(',\n'),
unhide: unhideSelectors.join(',\n')
@ -353,7 +429,7 @@ const startDialog = (( ) => {
};
const showInteractive = function() {
inspectorFramePort.postMessage({
contentInspectorChannel.send({
what: 'showInteractive',
hide: hideSelectors.join(',\n'),
unhide: unhideSelectors.join(',\n')
@ -432,7 +508,7 @@ const onClicked = ev => {
// Toggle cosmetic filter
if ( dom.cl.has(target, 'filter') ) {
inspectorFramePort.postMessage({
contentInspectorChannel.send({
what: 'toggleFilter',
original: false,
target: dom.cl.toggle(target, 'off'),
@ -448,7 +524,7 @@ const onClicked = ev => {
}
// Toggle node
else {
inspectorFramePort.postMessage({
contentInspectorChannel.send({
what: 'toggleNodes',
original: true,
target: dom.cl.toggle(target, 'off') === false,
@ -468,7 +544,7 @@ const onMouseOver = (( ) => {
let mouseoverTarget = null;
const mouseoverTimer = vAPI.defer.create(( ) => {
inspectorFramePort.postMessage({
contentInspectorChannel.send({
what: 'highlightOne',
selector: selectorFromNode(mouseoverTarget),
nid: nidFromNode(mouseoverTarget),
@ -517,7 +593,7 @@ const injectInspector = (( ) => {
/******************************************************************************/
const shutdownInspector = ( ) => {
inspectorFramePort.postMessage({ what: 'quitInspector' });
contentInspectorChannel.disconnect();
logger.removeAllChildren(domTree);
dom.cl.remove(inspector, 'vExpanded');
inspectedTabId = 0;
@ -537,14 +613,6 @@ const onTabIdChanged = ( ) => {
/******************************************************************************/
const onDOMContentLoaded = details => {
if ( details.tabId !== inspectedTabId ) { return; }
if ( details.frameId !== 0 ) { return; }
injectInspector();
};
/******************************************************************************/
const toggleVCompactView = ( ) => {
const state = dom.cl.toggle(inspector, 'vExpanded');
const branches = qsa$('#domInspector li.branch');
@ -561,7 +629,7 @@ const toggleHCompactView = ( ) => {
const revert = ( ) => {
dom.cl.remove('#domTree .off', 'off');
inspectorFramePort.postMessage({ what: 'resetToggledNodes' });
contentInspectorChannel.send({ what: 'resetToggledNodes' });
dom.cl.add(qs$(inspector, '.permatoolbar .revert'), 'disabled');
dom.cl.add(qs$(inspector, '.permatoolbar .commit'), 'disabled');
};
@ -578,7 +646,7 @@ const toggleOn = ( ) => {
dom.on('#domInspector .hCompactToggler', 'click', toggleHCompactView);
dom.on('#domInspector .permatoolbar .revert', 'click', revert);
dom.on('#domInspector .permatoolbar .commit', 'click', startDialog);
browser.webNavigation.onDOMContentLoaded.addListener(onDOMContentLoaded);
contentInspectorChannel.start();
injectInspector();
};
@ -596,7 +664,7 @@ const toggleOff = ( ) => {
dom.off('#domInspector .hCompactToggler', 'click', toggleHCompactView);
dom.off('#domInspector .permatoolbar .revert', 'click', revert);
dom.off('#domInspector .permatoolbar .commit', 'click', startDialog);
browser.webNavigation.onDOMContentLoaded.removeListener(onDOMContentLoaded);
contentInspectorChannel.shutdown();
inspectedTabId = 0;
};

View File

@ -1860,6 +1860,12 @@ const onMessage = (request, sender, callback) => {
let response;
switch ( request.what ) {
case 'getInspectorArgs':
const bc = new globalThis.BroadcastChannel('contentInspectorChannel');
bc.postMessage({
what: 'contentInspectorChannel',
tabId: sender.tabId || 0,
frameId: sender.frameId || 0,
});
response = {
inspectorURL: vAPI.getURL(
`/web_accessible_resources/dom-inspector.html?secret=${vAPI.warSecret.short()}`

View File

@ -19,6 +19,8 @@
Home: https://github.com/gorhill/uBlock
*/
/* globals browser */
'use strict';
/******************************************************************************/
@ -35,9 +37,6 @@ if ( document.querySelector(`iframe[${vAPI.sessionId}]`) !== null ) { return; }
/******************************************************************************/
/******************************************************************************/
// Highlighter-related
let inspectorRoot = null;
const nodeToIdMap = new WeakMap(); // No need to iterate
let blueNodes = [];
@ -130,7 +129,7 @@ const domLayout = (( ) => {
const localName = node.localName;
if ( skipTagNames.has(localName) ) { return null; }
// skip uBlock's own nodes
if ( node === inspectorRoot ) { return null; }
if ( node === inspectorFrame ) { return null; }
if ( level === 0 && localName === 'body' ) {
return new DomRoot();
}
@ -298,7 +297,7 @@ const domLayout = (( ) => {
if ( journalEntries.length === 0 ) { return; }
inspectorFramePort.postMessage({
contentInspectorChannel.toLogger({
what: 'domLayoutIncremental',
url: window.location.href,
hostname: window.location.hostname,
@ -483,7 +482,7 @@ const highlightElements = ( ) => {
const path = [];
for ( const elem of rwRedNodes.keys() ) {
if ( elem === inspectorRoot ) { continue; }
if ( elem === inspectorFrame ) { continue; }
if ( rwGreenNodes.has(elem) ) { continue; }
if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
const rect = elem.getBoundingClientRect();
@ -521,7 +520,7 @@ const highlightElements = ( ) => {
path.length = 0;
for ( const elem of roRedNodes.keys() ) {
if ( elem === inspectorRoot ) { continue; }
if ( elem === inspectorFrame ) { continue; }
if ( rwGreenNodes.has(elem) ) { continue; }
if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
const rect = elem.getBoundingClientRect();
@ -541,7 +540,7 @@ const highlightElements = ( ) => {
path.length = 0;
for ( const elem of blueNodes ) {
if ( elem === inspectorRoot ) { continue; }
if ( elem === inspectorFrame ) { continue; }
if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
const rect = elem.getBoundingClientRect();
const xl = rect.left;
@ -558,7 +557,7 @@ const highlightElements = ( ) => {
}
paths.push(path.join('') || 'M0 0');
inspectorFramePort.postMessage({
contentInspectorChannel.toFrame({
what: 'svgPaths',
paths,
});
@ -637,7 +636,7 @@ const resetToggledNodes = ( ) => {
/******************************************************************************/
const start = ( ) => {
const startInspector = ( ) => {
const onReady = ( ) => {
window.addEventListener('scroll', onScrolled, {
capture: true,
@ -647,7 +646,7 @@ const start = ( ) => {
capture: true,
passive: true,
});
inspectorFramePort.postMessage(domLayout.get());
contentInspectorChannel.toLogger(domLayout.get());
vAPI.domFilterer.toggle(false, highlightElements);
};
if ( document.readyState === 'loading' ) {
@ -659,7 +658,7 @@ const start = ( ) => {
/******************************************************************************/
const shutdown = ( ) => {
const shutdownInspector = ( ) => {
cosmeticFilterMapper.shutdown();
domLayout.shutdown();
window.removeEventListener('scroll', onScrolled, {
@ -670,13 +669,12 @@ const shutdown = ( ) => {
capture: true,
passive: true,
});
inspectorFramePort.close();
inspectorFramePort = undefined;
contentInspectorChannel.shutdown();
vAPI.userStylesheet.remove(inspectorCSS);
vAPI.userStylesheet.apply();
if ( inspectorRoot === null ) { return; }
inspectorRoot.remove();
inspectorRoot = null;
if ( inspectorFrame === null ) { return; }
inspectorFrame.remove();
inspectorFrame = null;
};
/******************************************************************************/
@ -685,11 +683,11 @@ const shutdown = ( ) => {
const onMessage = request => {
switch ( request.what ) {
case 'startInspector':
start();
startInspector();
break;
case 'quitInspector':
shutdown();
shutdownInspector();
break;
case 'commitFilters':
@ -756,15 +754,97 @@ const onMessage = request => {
}
};
/******************************************************************************/
/*******************************************************************************
*
* Establish two-way communication with logger/inspector window and
* inspector frame
*
* */
const contentInspectorChannel = (( ) => {
let toLoggerPort;
let toFramePort;
const toLogger = msg => {
if ( toLoggerPort === undefined ) { return; }
try {
toLoggerPort.postMessage(msg);
} catch(_) {
shutdownInspector();
}
};
const onLoggerMessage = msg => {
onMessage(msg);
};
const onLoggerDisconnect = ( ) => {
shutdownInspector();
};
const onLoggerConnect = port => {
browser.runtime.onConnect.removeListener(onLoggerConnect);
toLoggerPort = port;
port.onMessage.addListener(onLoggerMessage);
port.onDisconnect.addListener(onLoggerDisconnect);
};
const toFrame = msg => {
if ( toFramePort === undefined ) { return; }
toFramePort.postMessage(msg);
};
const shutdown = ( ) => {
if ( toFramePort !== undefined ) {
toFrame({ what: 'quitInspector' });
toFramePort.onmessage = null;
toFramePort.close();
toFramePort = undefined;
}
if ( toLoggerPort !== undefined ) {
toLoggerPort.onMessage.removeListener(onLoggerMessage);
toLoggerPort.onDisconnect.removeListener(onLoggerDisconnect);
toLoggerPort.disconnect();
toLoggerPort = undefined;
}
browser.runtime.onConnect.removeListener(onLoggerConnect);
};
const start = async ( ) => {
browser.runtime.onConnect.addListener(onLoggerConnect);
const inspectorArgs = await vAPI.messaging.send('domInspectorContent', {
what: 'getInspectorArgs',
});
if ( typeof inspectorArgs !== 'object' ) { return; }
if ( inspectorArgs === null ) { return; }
return new Promise(resolve => {
const iframe = document.createElement('iframe');
iframe.setAttribute(vAPI.sessionId, '');
document.documentElement.append(iframe);
iframe.addEventListener('load', ( ) => {
iframe.setAttribute(`${vAPI.sessionId}-loaded`, '');
const channel = new MessageChannel();
toFramePort = channel.port1;
toFramePort.onmessage = ev => {
const msg = ev.data || {};
if ( msg.what !== 'startInspector' ) { return; }
resolve(iframe);
};
iframe.contentWindow.postMessage(
{ what: 'startInspector' },
inspectorArgs.inspectorURL,
[ channel.port2 ]
);
}, { once: true });
iframe.contentWindow.location = inspectorArgs.inspectorURL;
});
};
return { start, toLogger, toFrame, shutdown };
})();
// Install DOM inspector widget
let inspectorArgs = await vAPI.messaging.send('domInspectorContent', {
what: 'getInspectorArgs',
});
if ( typeof inspectorArgs !== 'object' ) { return; }
if ( inspectorArgs === null ) { return; }
const inspectorCSSStyle = [
'background: transparent',
'border: 0',
@ -799,31 +879,12 @@ const inspectorCSS = `
vAPI.userStylesheet.add(inspectorCSS);
vAPI.userStylesheet.apply();
inspectorRoot = document.createElement('iframe');
inspectorRoot.setAttribute(vAPI.sessionId, '');
document.documentElement.append(inspectorRoot);
let inspectorFrame = await contentInspectorChannel.start();
if ( inspectorFrame instanceof HTMLIFrameElement === false ) {
return shutdownInspector();
}
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;
startInspector();
/******************************************************************************/