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 @@ - +
exclamation-triangle @@ -25,14 +25,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 };