Complete support for reporing strict-block messages

Related issue:
https://github.com/uBlockOrigin/uBlock-issues/issues/1195
This commit is contained in:
Raymond Hill 2025-06-22 11:28:54 -04:00
parent 1b00490a4d
commit 253ef7ade3
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
7 changed files with 135 additions and 90 deletions

View file

@ -23,6 +23,9 @@ body {
padding: var(--default-gap-xxlarge) var(--default-gap-small);
justify-content: center;
}
body.loading {
opacity: 0;
}
:root.mobile body {
padding: var(--default-gap-small);
}
@ -128,15 +131,18 @@ body[dir="rtl"] #toggleParse {
font-weight: bold;
}
#whyex a {
.why-extra a {
white-space: nowrap;
}
#whyex ul {
.why-extra ul {
display: flex;
flex-direction: column;
margin: 0;
padding-inline-start: var(--default-gap-xsmall);
}
details > *:not(summary) {
margin-inline-start: 1em;
}
#urlskip a {
display: block;
@ -161,11 +167,14 @@ body[dir="rtl"] #toggleParse {
}
.filterList {
display: flex;
}
.filterList .filterListSupport[href=""] {
white-space: nowrap;
}
.filterList .filterListSupport[href="#"] {
display: none;
}
.filterList .filterListSupport:not([href="#"]) {
margin-inline-start: 0.25em;
}
/* Small-screen devices */
:root.mobile button {

View file

@ -11,7 +11,7 @@
<link rel="stylesheet" href="css/document-blocked.css">
<link rel="icon" type="image/png" href="img/icon_64.png">
</head>
<body>
<body class="loading">
<div id="rootContainer">
<div id="warningSign">
<a class="fa-icon" href="https://github.com/gorhill/uBlock/wiki/Strict-blocking" target="_blank" rel="noopener noreferrer">exclamation-triangle</a>
@ -25,14 +25,7 @@
</div>
</div>
<div>
<p data-i18n="docblockedPrompt2">_</p>
<p id="why" class="code">&nbsp;</p>
<div id="whyex" style="visibility: hidden;">
<span data-i18n="docblockedFoundIn">_</span>
<ul><li class="filterList">&nbsp;</ul>
</div>
</div>
<div id="why"></div>
<div id="urlskip" hidden>
</div>
@ -47,14 +40,28 @@
<button id="proceed" class="preferred" data-i18n="docblockedDisable" type="button"><span class="hover"></span></button>
</div>
</div>
<div id="templates" style="display: none;">
<ul>
<li class="filterList">
<a class="filterListSource" href="asset-viewer.html?url=" target="_blank"></a>&nbsp;<!--
<template class="why">
<p data-i18n="docblockedPrompt2">_</p>
<p class="why code">&nbsp;</p>
</template>
<template class="why-reason">
<details><summary><span data-i18n="docBlockedReasonLabel"></span> <span></span></summary>
<p data-i18n="docblockedPrompt2">_</p>
<p class="why code">&nbsp;</p>
</details>
</template>
<template class="why-extra">
<div class="why-extra">
<span data-i18n="docblockedFoundIn">_</span>&nbsp;
</div>
</template>
<template class="filterList">
<span class="filterList">
<a class="filterListSource" href="asset-viewer.html?url=" target="_blank"></a><!--
--><a class="fa-icon filterListSupport hidden" href="#" target="_blank" rel="noopener noreferrer">home</a>
</li>
</ul>
</div>
</span>
</template>
<script src="lib/hsluv/hsluv-0.1.0.min.js"></script>

View file

@ -21,60 +21,22 @@
import { dom, qs$ } from './dom.js';
import { i18n, i18n$ } from './i18n.js';
import { faIconsInit } from './fa-icons.js';
/******************************************************************************/
const messaging = vAPI.messaging;
let details = {};
const details = {};
{
const matches = /details=([^&]+)/.exec(window.location.search);
if ( matches !== null ) {
details = JSON.parse(decodeURIComponent(matches[1]));
Object.assign(details, JSON.parse(decodeURIComponent(matches[1])));
}
}
/******************************************************************************/
(async ( ) => {
const response = await messaging.send('documentBlocked', {
what: 'listsFromNetFilter',
rawFilter: details.fs,
});
if ( response instanceof Object === false ) { return; }
let lists;
for ( const rawFilter in response ) {
if ( Object.hasOwn(response, rawFilter) ) {
lists = response[rawFilter];
break;
}
}
if ( Array.isArray(lists) === false || lists.length === 0 ) {
qs$('#whyex').style.setProperty('visibility', 'collapse');
return;
}
const parent = qs$('#whyex > ul');
parent.firstElementChild.remove(); // remove placeholder element
for ( const list of lists ) {
const listElem = dom.clone('#templates .filterList');
const sourceElem = qs$(listElem, '.filterListSource');
sourceElem.href += encodeURIComponent(list.assetKey);
sourceElem.append(i18n.patchUnicodeFlags(list.title));
if ( typeof list.supportURL === 'string' && list.supportURL !== '' ) {
const supportElem = qs$(listElem, '.filterListSupport');
dom.attr(supportElem, 'href', list.supportURL);
dom.cl.remove(supportElem, 'hidden');
}
parent.appendChild(listElem);
}
qs$('#whyex').style.removeProperty('visibility');
})();
/******************************************************************************/
const urlToFragment = raw => {
try {
const fragment = new DocumentFragment();
@ -94,7 +56,26 @@ const urlToFragment = raw => {
dom.clear('#theURL > p > span:first-of-type');
qs$('#theURL > p > span:first-of-type').append(urlToFragment(details.url));
dom.text('#why', details.fs);
/******************************************************************************/
const lookupFilterLists = async ( ) => {
const response = await messaging.send('documentBlocked', {
what: 'listsFromNetFilter',
rawFilter: details.fs,
});
if ( response instanceof Object === false ) { return; }
let lists;
for ( const rawFilter in response ) {
if ( Object.hasOwn(response, rawFilter) ) {
lists = response[rawFilter];
break;
}
}
return lists;
};
/******************************************************************************/
if ( typeof details.to === 'string' && details.to.length !== 0 ) {
const fragment = new DocumentFragment();
@ -221,10 +202,6 @@ if ( window.history.length > 1 ) {
/******************************************************************************/
const getTargetHostname = function() {
return details.hn;
};
const proceedToURL = function() {
window.location.replace(details.url);
};
@ -232,7 +209,7 @@ const proceedToURL = function() {
const proceedTemporary = async function() {
await messaging.send('documentBlocked', {
what: 'temporarilyWhitelistDocument',
hostname: getTargetHostname(),
hostname: details.hn,
});
proceedToURL();
};
@ -241,7 +218,7 @@ const proceedPermanent = async function() {
await messaging.send('documentBlocked', {
what: 'toggleHostnameSwitch',
name: 'no-strict-blocking',
hostname: getTargetHostname(),
hostname: details.hn,
deep: true,
state: true,
persist: true,
@ -263,4 +240,49 @@ dom.on('#proceed', 'click', ( ) => {
}
});
lookupFilterLists().then((lists = []) => {
let reason = details.reason;
if ( Boolean(reason) === false ) {
reason = lists.reduce((a, b) => a || b.reason, undefined);
}
if ( reason ) {
const msg = i18n$(`docblockedReason${reason.charAt(0).toUpperCase()}${reason.slice(1)}`);
if ( msg ) { reason = msg };
}
const why = qs$(reason ? 'template.why-reason' : 'template.why')
.content
.cloneNode(true);
i18n.render(why);
dom.text(qs$(why, '.why'), details.fs);
if ( reason ) {
dom.text(qs$(why, 'summary'), `Reason: ${reason}`);
}
qs$('#why').append(why);
dom.cl.remove(dom.body, 'loading');
if ( lists.length === 0 ) { return; }
const whyExtra = qs$('template.why-extra').content.cloneNode(true);
i18n.render(whyExtra);
const listTemplate = qs$('template.filterList');
const parent = qs$(whyExtra, '.why-extra');
let separator = '';
for ( const list of lists ) {
const listElem = listTemplate.content.cloneNode(true);
const sourceElem = qs$(listElem, '.filterListSource');
sourceElem.href += encodeURIComponent(list.assetKey);
sourceElem.append(i18n.patchUnicodeFlags(list.title));
if ( typeof list.supportURL === 'string' && list.supportURL !== '' ) {
const supportElem = qs$(listElem, '.filterListSupport');
dom.attr(supportElem, 'href', list.supportURL);
dom.cl.remove(supportElem, 'hidden');
}
parent.append(separator, listElem);
separator = '\u00A0\u2022\u00A0';
}
faIconsInit(whyExtra);
qs$('#why .why').after(whyExtra);
});
/******************************************************************************/

View file

@ -73,9 +73,10 @@ const fromNetFilter = function(details) {
continue;
}
lists.push({
assetKey: assetKey,
assetKey,
title: entry.title,
supportURL: entry.supportURL
supportURL: entry.supportURL,
reason: entry.reason,
});
break;
}

View file

@ -174,7 +174,6 @@ export const NODE_TYPE_NET_OPTION_NAME_INLINESCRIPT = iota++;
export const NODE_TYPE_NET_OPTION_NAME_IPADDRESS = iota++;
export const NODE_TYPE_NET_OPTION_NAME_MATCHCASE = iota++;
export const NODE_TYPE_NET_OPTION_NAME_MEDIA = iota++;
export const NODE_TYPE_NET_OPTION_NAME_MESSAGE = iota++;
export const NODE_TYPE_NET_OPTION_NAME_METHOD = iota++;
export const NODE_TYPE_NET_OPTION_NAME_MP4 = iota++;
export const NODE_TYPE_NET_OPTION_NAME_NOOP = iota++;
@ -184,6 +183,7 @@ export const NODE_TYPE_NET_OPTION_NAME_PERMISSIONS = iota++;
export const NODE_TYPE_NET_OPTION_NAME_PING = iota++;
export const NODE_TYPE_NET_OPTION_NAME_POPUNDER = iota++;
export const NODE_TYPE_NET_OPTION_NAME_POPUP = iota++;
export const NODE_TYPE_NET_OPTION_NAME_REASON = iota++;
export const NODE_TYPE_NET_OPTION_NAME_REDIRECT = iota++;
export const NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE = iota++;
export const NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM = iota++;
@ -256,7 +256,6 @@ export const nodeTypeFromOptionName = new Map([
[ 'ipaddress', NODE_TYPE_NET_OPTION_NAME_IPADDRESS ],
[ 'match-case', NODE_TYPE_NET_OPTION_NAME_MATCHCASE ],
[ 'media', NODE_TYPE_NET_OPTION_NAME_MEDIA ],
[ 'message', NODE_TYPE_NET_OPTION_NAME_MESSAGE ],
[ 'method', NODE_TYPE_NET_OPTION_NAME_METHOD ],
[ 'mp4', NODE_TYPE_NET_OPTION_NAME_MP4 ],
[ '_', NODE_TYPE_NET_OPTION_NAME_NOOP ],
@ -268,6 +267,7 @@ export const nodeTypeFromOptionName = new Map([
/* synonym */ [ 'beacon', NODE_TYPE_NET_OPTION_NAME_PING ],
[ 'popunder', NODE_TYPE_NET_OPTION_NAME_POPUNDER ],
[ 'popup', NODE_TYPE_NET_OPTION_NAME_POPUP ],
[ 'reason', NODE_TYPE_NET_OPTION_NAME_REASON ],
[ 'redirect', NODE_TYPE_NET_OPTION_NAME_REDIRECT ],
/* synonym */ [ 'rewrite', NODE_TYPE_NET_OPTION_NAME_REDIRECT ],
[ 'redirect-rule', NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE ],
@ -1352,9 +1352,6 @@ export class AstFilterParser {
case NODE_TYPE_NET_OPTION_NAME_MATCHCASE:
realBad = this.isRegexPattern() === false;
break;
case NODE_TYPE_NET_OPTION_NAME_MESSAGE:
realBad = hasValue === false;
break;
case NODE_TYPE_NET_OPTION_NAME_PERMISSIONS:
realBad = modifierType !== 0 ||
(hasValue || isException) === false ||
@ -1378,6 +1375,9 @@ export class AstFilterParser {
abstractTypeCount += 1;
unredirectableTypeCount += 1;
break;
case NODE_TYPE_NET_OPTION_NAME_REASON:
realBad = hasValue === false;
break;
case NODE_TYPE_NET_OPTION_NAME_REDIRECT:
case NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE:
case NODE_TYPE_NET_OPTION_NAME_REPLACE:
@ -3154,7 +3154,6 @@ export const netOptionTokenDescriptors = new Map([
[ 'ipaddress', { mustAssign: true } ],
[ 'match-case', { } ],
[ 'media', { canNegate: true } ],
[ 'message', { mustAssign: true } ],
[ 'method', { mustAssign: true } ],
[ 'mp4', { blockOnly: true } ],
[ '_', { } ],
@ -3166,6 +3165,7 @@ export const netOptionTokenDescriptors = new Map([
/* synonym */ [ 'beacon', { canNegate: true } ],
[ 'popunder', { } ],
[ 'popup', { canNegate: true } ],
[ 'reason', { mustAssign: true } ],
[ 'redirect', { mustAssign: true } ],
/* synonym */ [ 'rewrite', { mustAssign: true } ],
[ 'redirect-rule', { mustAssign: true } ],

View file

@ -411,6 +411,9 @@ class LogData {
}
this.raw = raw;
this.regex = logData.regex.join('');
if ( logData.reason ) {
this.reason = logData.reason;
}
}
isUntokenized() {
return this.tokenHash === NO_TOKEN_HASH;
@ -3113,7 +3116,7 @@ class FilterMessage {
static compile(details) {
return [
FilterMessage.fid,
encodeURIComponent(details.optionValues.get('message')),
encodeURIComponent(details.optionValues.get('reason')),
];
}
@ -3127,11 +3130,11 @@ class FilterMessage {
}
static logData(idata, details) {
const msg = bidiTrie.extractString(
filterData[idata+1],
filterData[idata+2]
const reason = decodeURIComponent(
bidiTrie.extractString(filterData[idata+1], filterData[idata+2])
);
details.options.push(`message=${decodeURIComponent(msg)}`);
details.reason = reason;
details.options.push(`reason=${reason}`);
}
}
@ -3612,10 +3615,6 @@ class FilterCompiler {
this.optionValues.set('ipaddress', parser.getNetOptionValue(id) || '');
this.optionUnitBits |= IPADDRESS_BIT;
break;
case sfp.NODE_TYPE_NET_OPTION_NAME_MESSAGE:
this.optionValues.set('message', parser.getNetOptionValue(id));
this.optionUnitBits |= MESSAGE_BIT;
break;
case sfp.NODE_TYPE_NET_OPTION_NAME_METHOD:
this.processMethodOption(parser.getNetOptionValue(id));
this.optionUnitBits |= METHOD_BIT;
@ -3631,6 +3630,10 @@ class FilterCompiler {
}
this.optionUnitBits |= MODIFY_BIT;
break;
case sfp.NODE_TYPE_NET_OPTION_NAME_REASON:
this.optionValues.set('reason', parser.getNetOptionValue(id));
this.optionUnitBits |= MESSAGE_BIT;
break;
case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECT: {
const actualId = this.action === ALLOW_REALM
? sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE
@ -3737,9 +3740,9 @@ class FilterCompiler {
case sfp.NODE_TYPE_NET_OPTION_NAME_FROM:
case sfp.NODE_TYPE_NET_OPTION_NAME_HEADER:
case sfp.NODE_TYPE_NET_OPTION_NAME_IPADDRESS:
case sfp.NODE_TYPE_NET_OPTION_NAME_MESSAGE:
case sfp.NODE_TYPE_NET_OPTION_NAME_METHOD:
case sfp.NODE_TYPE_NET_OPTION_NAME_PERMISSIONS:
case sfp.NODE_TYPE_NET_OPTION_NAME_REASON:
case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECT:
case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE:
case sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM:

View file

@ -224,17 +224,20 @@ const onBeforeRootFrameRequest = function(fctxt) {
// Find out the URL navigated to should the document not be strict-blocked
pageStore.skipMainDocument(fctxt, false);
const query = encodeURIComponent(JSON.stringify({
const query = {
url: requestURL,
dn: fctxt.getDomain() || requestHostname,
fs: logData.raw,
hn: requestHostname,
to: fctxt.redirectURL || '',
}));
};
if ( logData.reason ) {
query.reason = logData.reason;
}
vAPI.tabs.replace(
fctxt.tabId,
vAPI.getURL('document-blocked.html?details=') + query
`${vAPI.getURL('document-blocked.html?details=')}${encodeURIComponent(JSON.stringify(query))}`
);
return { cancel: true };