diff --git a/src/css/document-blocked.css b/src/css/document-blocked.css
index 75fa392f0..169acf9d6 100644
--- a/src/css/document-blocked.css
+++ b/src/css/document-blocked.css
@@ -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 {
diff --git a/src/document-blocked.html b/src/document-blocked.html
index 436ede4ef..1fb387672 100644
--- a/src/document-blocked.html
+++ b/src/document-blocked.html
@@ -11,7 +11,7 @@
-
+
-
+
@@ -47,14 +40,28 @@
-
+
+
diff --git a/src/js/document-blocked.js b/src/js/document-blocked.js
index aabcf2eb3..561c22437 100644
--- a/src/js/document-blocked.js
+++ b/src/js/document-blocked.js
@@ -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);
+});
+
/******************************************************************************/
diff --git a/src/js/reverselookup-worker.js b/src/js/reverselookup-worker.js
index 1f14dd4ef..c8d3dc363 100644
--- a/src/js/reverselookup-worker.js
+++ b/src/js/reverselookup-worker.js
@@ -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;
}
diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js
index 7ff107786..2087d360d 100644
--- a/src/js/static-filtering-parser.js
+++ b/src/js/static-filtering-parser.js
@@ -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 } ],
diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js
index 3c0d3f73f..4596f7886 100644
--- a/src/js/static-net-filtering.js
+++ b/src/js/static-net-filtering.js
@@ -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:
diff --git a/src/js/traffic.js b/src/js/traffic.js
index 827c94d51..478487e77 100644
--- a/src/js/traffic.js
+++ b/src/js/traffic.js
@@ -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 };