Skip to content

Commit

Permalink
Report injected scriptlets in troubleshooting information
Browse files Browse the repository at this point in the history
This requires to rewrite portions of scriptlet filtering
code.
  • Loading branch information
gorhill committed May 9, 2023
1 parent 39a8aaa commit 578fc21
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 85 deletions.
6 changes: 3 additions & 3 deletions platform/chromium/vapi-background-ext.js
Expand Up @@ -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 ) {
Expand Down
2 changes: 1 addition & 1 deletion platform/common/vapi-background.js
Expand Up @@ -1391,7 +1391,7 @@ vAPI.Net = class {
// To be defined by platform-specific code.

vAPI.scriptletsInjector = (( ) => {
self.uBO_scriptletsInjected = true;
self.uBO_scriptletsInjected = '';
}).toString();

/******************************************************************************/
Expand Down
9 changes: 6 additions & 3 deletions platform/firefox/vapi-background-ext.js
Expand Up @@ -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 ) {
Expand Down
14 changes: 8 additions & 6 deletions src/js/contentscript.js
Expand Up @@ -1298,7 +1298,7 @@ vAPI.DOMFilterer = class {
const {
noSpecificCosmeticFiltering,
noGenericCosmeticFiltering,
scriptlets,
scriptletDetails,
} = response;

vAPI.noSpecificCosmeticFiltering = noSpecificCosmeticFiltering;
Expand All @@ -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 ) {
Expand All @@ -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);
});
Expand Down
21 changes: 18 additions & 3 deletions src/js/messaging.js
Expand Up @@ -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'));
Expand Down Expand Up @@ -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
Expand Down
123 changes: 54 additions & 69 deletions src/js/scriptlet-filtering.js
Expand Up @@ -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';
Expand Down Expand Up @@ -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('');
}
};
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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;
Expand All @@ -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 ) {
Expand All @@ -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 ) {
Expand All @@ -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) {
Expand All @@ -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() {
Expand Down
4 changes: 4 additions & 0 deletions src/js/scriptlets/cosmetic-report.js
Expand Up @@ -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;
Expand Down

0 comments on commit 578fc21

Please sign in to comment.