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;
try {
script = doc.createElement('script');
script.appendChild(doc.createTextNode(scriptlets));
script.appendChild(doc.createTextNode(details.scriptlets));
(doc.head || doc.documentElement).appendChild(script);
self.uBO_scriptletsInjected = true;
self.uBO_scriptletsInjected = details.filters;
} catch (ex) {
}
if ( script ) {

View File

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

View File

@ -304,16 +304,19 @@ vAPI.Net = class extends vAPI.Net {
/******************************************************************************/
vAPI.scriptletsInjector = ((doc, scriptlets) => {
vAPI.scriptletsInjector = ((doc, details) => {
let script, url;
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);
script = doc.createElement('script');
script.async = false;
script.src = url;
(doc.head || doc.documentElement).appendChild(script);
self.uBO_scriptletsInjected = true;
self.uBO_scriptletsInjected = details.filters;
} catch (ex) {
}
if ( url ) {

View File

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

View File

@ -623,8 +623,11 @@ const launchReporter = async function(request) {
if ( Array.isArray(v) ) { a.push(...v); }
return a;
}, []);
// Remove duplicate, truncate too long filters.
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'));
@ -833,8 +836,20 @@ const retrieveContentScriptParameters = async function(sender, request) {
// https://github.com/uBlockOrigin/uBlock-issues/issues/688#issuecomment-748179731
// For non-network URIs, scriptlet injection is deferred to here. The
// effective URL is available here in `request.url`.
if ( request.needScriptlets ) {
response.scriptlets = scriptletFilteringEngine.injectNow(request);
if ( logger.enabled || request.needScriptlets ) {
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

View File

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