Report injected scriptlets in troubleshooting information

This requires to rewrite portions of scriptlet filtering
code.
This commit is contained in:
Raymond Hill 2023-05-09 12:44:14 -04:00
parent 39a8aaa39b
commit 578fc21bd9
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
7 changed files with 94 additions and 85 deletions

View File

@ -236,13 +236,13 @@ vAPI.prefetching = (( ) => {
/******************************************************************************/ /******************************************************************************/
vAPI.scriptletsInjector = ((doc, scriptlets) => { vAPI.scriptletsInjector = ((doc, details) => {
let script; let script;
try { try {
script = doc.createElement('script'); script = doc.createElement('script');
script.appendChild(doc.createTextNode(scriptlets)); script.appendChild(doc.createTextNode(details.scriptlets));
(doc.head || doc.documentElement).appendChild(script); (doc.head || doc.documentElement).appendChild(script);
self.uBO_scriptletsInjected = true; self.uBO_scriptletsInjected = details.filters;
} catch (ex) { } catch (ex) {
} }
if ( script ) { if ( script ) {

View File

@ -1391,7 +1391,7 @@ vAPI.Net = class {
// To be defined by platform-specific code. // To be defined by platform-specific code.
vAPI.scriptletsInjector = (( ) => { vAPI.scriptletsInjector = (( ) => {
self.uBO_scriptletsInjected = true; self.uBO_scriptletsInjected = '';
}).toString(); }).toString();
/******************************************************************************/ /******************************************************************************/

View File

@ -304,16 +304,19 @@ vAPI.Net = class extends vAPI.Net {
/******************************************************************************/ /******************************************************************************/
vAPI.scriptletsInjector = ((doc, scriptlets) => { vAPI.scriptletsInjector = ((doc, details) => {
let script, url; let script, url;
try { try {
const blob = new self.Blob([ scriptlets ], { type: 'text/javascript; charset=utf-8' }); const blob = new self.Blob(
[ details.scriptlets ],
{ type: 'text/javascript; charset=utf-8' }
);
url = self.URL.createObjectURL(blob); url = self.URL.createObjectURL(blob);
script = doc.createElement('script'); script = doc.createElement('script');
script.async = false; script.async = false;
script.src = url; script.src = url;
(doc.head || doc.documentElement).appendChild(script); (doc.head || doc.documentElement).appendChild(script);
self.uBO_scriptletsInjected = true; self.uBO_scriptletsInjected = details.filters;
} catch (ex) { } catch (ex) {
} }
if ( url ) { if ( url ) {

View File

@ -1298,7 +1298,7 @@ vAPI.DOMFilterer = class {
const { const {
noSpecificCosmeticFiltering, noSpecificCosmeticFiltering,
noGenericCosmeticFiltering, noGenericCosmeticFiltering,
scriptlets, scriptletDetails,
} = response; } = response;
vAPI.noSpecificCosmeticFiltering = noSpecificCosmeticFiltering; vAPI.noSpecificCosmeticFiltering = noSpecificCosmeticFiltering;
@ -1322,10 +1322,12 @@ vAPI.DOMFilterer = class {
// Library of resources is located at: // Library of resources is located at:
// https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt // https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt
if ( scriptlets && typeof self.uBO_scriptletsInjected !== 'boolean' ) { if ( scriptletDetails && typeof self.uBO_scriptletsInjected !== 'string' ) {
self.uBO_scriptletsInjected = true; self.uBO_scriptletsInjected = scriptletDetails.filters;
vAPI.injectScriptlet(document, scriptlets); if ( scriptletDetails.scriptlets ) {
vAPI.injectedScripts = scriptlets; vAPI.injectScriptlet(document, scriptletDetails.scriptlets);
vAPI.injectedScripts = scriptletDetails.scriptlets;
}
} }
if ( vAPI.domSurveyor ) { if ( vAPI.domSurveyor ) {
@ -1346,7 +1348,7 @@ vAPI.DOMFilterer = class {
vAPI.messaging.send('contentscript', { vAPI.messaging.send('contentscript', {
what: 'retrieveContentScriptParameters', what: 'retrieveContentScriptParameters',
url: vAPI.effectiveSelf.location.href, url: vAPI.effectiveSelf.location.href,
needScriptlets: typeof self.uBO_scriptletsInjected !== 'boolean', needScriptlets: typeof self.uBO_scriptletsInjected !== 'string',
}).then(response => { }).then(response => {
onResponseReady(response); onResponseReady(response);
}); });

View File

@ -623,8 +623,11 @@ const launchReporter = async function(request) {
if ( Array.isArray(v) ) { a.push(...v); } if ( Array.isArray(v) ) { a.push(...v); }
return a; return a;
}, []); }, []);
// Remove duplicate, truncate too long filters.
if ( filters.length !== 0 ) { if ( filters.length !== 0 ) {
request.popupPanel.cosmetic = filters; request.popupPanel.extended = Array.from(
new Set(filters.map(s => s.length <= 64 ? s : `${s.slice(0, 64)}`))
);
} }
const supportURL = new URL(vAPI.getURL('support.html')); const supportURL = new URL(vAPI.getURL('support.html'));
@ -833,8 +836,20 @@ const retrieveContentScriptParameters = async function(sender, request) {
// https://github.com/uBlockOrigin/uBlock-issues/issues/688#issuecomment-748179731 // https://github.com/uBlockOrigin/uBlock-issues/issues/688#issuecomment-748179731
// For non-network URIs, scriptlet injection is deferred to here. The // For non-network URIs, scriptlet injection is deferred to here. The
// effective URL is available here in `request.url`. // effective URL is available here in `request.url`.
if ( request.needScriptlets ) { if ( logger.enabled || request.needScriptlets ) {
response.scriptlets = scriptletFilteringEngine.injectNow(request); const scriptletDetails = scriptletFilteringEngine.injectNow(request);
if ( scriptletDetails !== undefined ) {
if ( logger.enabled ) {
scriptletFilteringEngine.logFilters(
tabId,
request.url,
scriptletDetails.filters
);
}
if ( request.needScriptlets ) {
response.scriptletDetails = scriptletDetails;
}
}
} }
// https://github.com/NanoMeow/QuickReports/issues/6#issuecomment-414516623 // https://github.com/NanoMeow/QuickReports/issues/6#issuecomment-414516623

View File

@ -23,7 +23,6 @@
/******************************************************************************/ /******************************************************************************/
import logger from './logger.js';
import µb from './background.js'; import µb from './background.js';
import { redirectEngine } from './redirect-engine.js'; import { redirectEngine } from './redirect-engine.js';
import { sessionFirewall } from './filtering-engines.js'; import { sessionFirewall } from './filtering-engines.js';
@ -86,31 +85,32 @@ const scriptletFilteringEngine = {
const contentscriptCode = (( ) => { const contentscriptCode = (( ) => {
const parts = [ const parts = [
'(', '(',
function(injector, hostname, scriptlets) { function(injector, details) {
const doc = document; const doc = document;
if ( if (
doc.location === null || doc.location === null ||
hostname !== doc.location.hostname || details.hostname !== doc.location.hostname ||
typeof self.uBO_scriptletsInjected === 'boolean' typeof self.uBO_scriptletsInjected === 'string'
) { ) {
return; return;
} }
injector(doc, decodeURIComponent(scriptlets)); injector(doc, details);
if ( typeof self.uBO_scriptletsInjected === 'boolean' ) { return 0; } if ( typeof self.uBO_scriptletsInjected === 'string' ) { return 0; }
}.toString(), }.toString(),
')(', ')(',
vAPI.scriptletsInjector, ', ', vAPI.scriptletsInjector, ', ',
'"', 'hostname-slot', '", ', 'json-slot',
'"', 'scriptlets-slot', '"',
');', ');',
]; ];
return { return {
parts: parts, parts,
hostnameSlot: parts.indexOf('hostname-slot'), jsonSlot: parts.indexOf('json-slot'),
scriptletsSlot: parts.indexOf('scriptlets-slot'), assemble: function(hostname, scriptlets, filters) {
assemble: function(hostname, scriptlets) { this.parts[this.jsonSlot] = JSON.stringify({
this.parts[this.hostnameSlot] = hostname; hostname,
this.parts[this.scriptletsSlot] = encodeURIComponent(scriptlets); scriptlets,
filters,
});
return this.parts.join(''); return this.parts.join('');
} }
}; };
@ -221,16 +221,18 @@ const patchScriptlet = function(content, args) {
); );
}; };
const logOne = function(tabId, url, filter) { scriptletFilteringEngine.logFilters = function(tabId, url, filters) {
µb.filteringContext if ( typeof filters !== 'string' ) { return; }
.duplicate() const fctxt = µb.filteringContext
.fromTabId(tabId) .duplicate()
.setRealm('extended') .fromTabId(tabId)
.setType('scriptlet') .setRealm('extended')
.setURL(url) .setType('scriptlet')
.setDocOriginFromURL(url) .setURL(url)
.setFilter({ source: 'extended', raw: filter }) .setDocOriginFromURL(url);
.toLogger(); for ( const filter of filters.split('\n') ) {
fctxt.setFilter({ source: 'extended', raw: filter }).toLogger();
}
}; };
scriptletFilteringEngine.reset = function() { scriptletFilteringEngine.reset = function() {
@ -308,7 +310,7 @@ const $exceptions = new Set();
const $scriptletMap = new Map(); const $scriptletMap = new Map();
const $scriptletDependencyMap = new Map(); const $scriptletDependencyMap = new Map();
scriptletFilteringEngine.retrieve = function(request, options = {}) { scriptletFilteringEngine.retrieve = function(request) {
if ( scriptletDB.size === 0 ) { return; } if ( scriptletDB.size === 0 ) { return; }
const hostname = request.hostname; const hostname = request.hostname;
@ -332,14 +334,13 @@ scriptletFilteringEngine.retrieve = function(request, options = {}) {
return; return;
} }
const mustLog = Array.isArray(options.logEntries);
// Wholly disable scriptlet injection? // Wholly disable scriptlet injection?
if ( $exceptions.has('') ) { if ( $exceptions.has('') ) {
if ( mustLog ) { return {
logOne(request.tabId, request.url, '#@#+js()'); filters: [
} { tabId: request.tabId, url: request.url, filter: '#@#+js()' }
return; ]
};
} }
if ( scriptletCache.resetTime < redirectEngine.modifyTime ) { if ( scriptletCache.resetTime < redirectEngine.modifyTime ) {
@ -349,45 +350,38 @@ scriptletFilteringEngine.retrieve = function(request, options = {}) {
let cacheDetails = scriptletCache.lookup(hostname); let cacheDetails = scriptletCache.lookup(hostname);
if ( cacheDetails === undefined ) { if ( cacheDetails === undefined ) {
const fullCode = []; const fullCode = [];
for ( const token of $exceptions ) {
if ( $scriptlets.has(token) ) {
$scriptlets.delete(token);
} else {
$exceptions.delete(token);
}
}
for ( const token of $scriptlets ) { for ( const token of $scriptlets ) {
if ( $exceptions.has(token) ) { continue; }
lookupScriptlet(token, $scriptletMap, $scriptletDependencyMap); lookupScriptlet(token, $scriptletMap, $scriptletDependencyMap);
} }
for ( const token of $scriptlets ) { for ( const token of $scriptlets ) {
const isException = $exceptions.has(token); fullCode.push($scriptletMap.get(token));
if ( isException === false ) {
fullCode.push($scriptletMap.get(token));
}
} }
for ( const code of $scriptletDependencyMap.values() ) { for ( const code of $scriptletDependencyMap.values() ) {
fullCode.push(code); fullCode.push(code);
} }
cacheDetails = { cacheDetails = {
code: fullCode.join('\n\n'), code: fullCode.join('\n\n'),
tokens: Array.from($scriptlets), filters: [
exceptions: Array.from($exceptions), ...Array.from($scriptlets).map(s => `##+js(${s})`),
...Array.from($exceptions).map(s => `#@#+js(${s})`),
].join('\n'),
}; };
scriptletCache.add(hostname, cacheDetails); scriptletCache.add(hostname, cacheDetails);
$scriptletMap.clear(); $scriptletMap.clear();
$scriptletDependencyMap.clear(); $scriptletDependencyMap.clear();
} }
if ( mustLog ) { if ( cacheDetails.code === '' ) {
for ( const token of cacheDetails.tokens ) { return { filters: cacheDetails.filters };
if ( cacheDetails.exceptions.includes(token) ) {
logOne(request.tabId, request.url, `#@#+js(${token})`);
} else {
options.logEntries.push({
token: `##+js(${token})`,
tabId: request.tabId,
url: request.url,
});
}
}
} }
if ( cacheDetails.code === '' ) { return; }
const scriptletGlobals = []; const scriptletGlobals = [];
if ( isDevBuild === undefined ) { if ( isDevBuild === undefined ) {
@ -412,7 +406,7 @@ scriptletFilteringEngine.retrieve = function(request, options = {}) {
'})();', '})();',
]; ];
return out.join('\n'); return { scriptlets: out.join('\n'), filters: cacheDetails.filters };
}; };
scriptletFilteringEngine.injectNow = function(details) { scriptletFilteringEngine.injectNow = function(details) {
@ -427,30 +421,21 @@ scriptletFilteringEngine.injectNow = function(details) {
}; };
request.domain = domainFromHostname(request.hostname); request.domain = domainFromHostname(request.hostname);
request.entity = entityFromDomain(request.domain); request.entity = entityFromDomain(request.domain);
const logEntries = logger.enabled ? [] : undefined; const scriptletDetails = this.retrieve(request);
const scriptlets = this.retrieve(request, { logEntries }); if ( scriptletDetails === undefined ) { return; }
if ( scriptlets === undefined ) { return; } const { scriptlets = '', filters } = scriptletDetails;
let code = contentscriptCode.assemble(request.hostname, scriptlets); if ( scriptlets === '' ) { return scriptletDetails; }
let code = contentscriptCode.assemble(request.hostname, scriptlets, filters);
if ( µb.hiddenSettings.debugScriptletInjector ) { if ( µb.hiddenSettings.debugScriptletInjector ) {
code = 'debugger;\n' + code; code = 'debugger;\n' + code;
} }
const promise = vAPI.tabs.executeScript(details.tabId, { vAPI.tabs.executeScript(details.tabId, {
code, code,
frameId: details.frameId, frameId: details.frameId,
matchAboutBlank: true, matchAboutBlank: true,
runAt: 'document_start', runAt: 'document_start',
}); });
if ( logEntries !== undefined ) { return scriptletDetails;
promise.then(results => {
if ( Array.isArray(results) === false || results[0] !== 0 ) {
return;
}
for ( const entry of logEntries ) {
logOne(entry.tabId, entry.url, entry.token);
}
});
}
return scriptlets;
}; };
scriptletFilteringEngine.toSelfie = function() { scriptletFilteringEngine.toSelfie = function() {

View File

@ -127,6 +127,10 @@ if ( Array.isArray(allSelectors.exceptions) ) {
} }
} }
if ( typeof self.uBO_scriptletsInjected === 'string' ) {
matchedSelectors.push(...self.uBO_scriptletsInjected.split('\n'));
}
if ( matchedSelectors.length === 0 ) { return; } if ( matchedSelectors.length === 0 ) { return; }
return matchedSelectors; return matchedSelectors;