diff --git a/platform/mv3/extension/css/matched-rules.css b/platform/mv3/extension/css/matched-rules.css new file mode 100644 index 000000000..9db4d9c73 --- /dev/null +++ b/platform/mv3/extension/css/matched-rules.css @@ -0,0 +1,28 @@ + +#matchedEntries { + display: flex; + flex-direction: column; + font-family: monospace; + font-size: small; + white-space: pre-wrap; + word-break: break-all; +} + +.matchInfo { + display: flex; + flex-wrap: nowrap; +} + +.matchInfo:nth-of-type(2n) { + background-color: lightgray; +} + +.requestInfo { + border-inline-end: 1px dotted black; + padding-inline-end: 0.5em; + width: 25vw; +} + +.ruleInfo { + padding-inline-start: 0.5em; +} diff --git a/platform/mv3/extension/js/background.js b/platform/mv3/extension/js/background.js index e65775e9d..17601b114 100644 --- a/platform/mv3/extension/js/background.js +++ b/platform/mv3/extension/js/background.js @@ -19,8 +19,6 @@ Home: https://github.com/gorhill/uBlock */ -/******************************************************************************/ - import { adminRead, browser, @@ -28,13 +26,9 @@ import { localRead, localWrite, runtime, sessionRead, sessionWrite, + windows, } from './ext.js'; -import { - broadcastMessage, - ubolLog, -} from './utils.js'; - import { defaultRulesetsFromLanguage, enableRulesets, @@ -54,8 +48,13 @@ import { } from './mode-manager.js'; import { - registerInjectables, -} from './scripting-manager.js'; + getMatchedRules, + isSideloaded, + ubolLog, +} from './debug.js'; + +import { broadcastMessage } from './utils.js'; +import { registerInjectables } from './scripting-manager.js'; /******************************************************************************/ @@ -253,6 +252,7 @@ function onMessage(request, sender, callback) { hasOmnipotence: results[1], hasGreatPowers: results[2], rulesetDetails: results[3], + isSideloaded, }); }); return true; @@ -308,6 +308,19 @@ function onMessage(request, sender, callback) { }); return true; + case 'getMatchedRules': + getMatchedRules(request.tabId).then(entries => { + callback(entries); + }); + return true; + + case 'showMatchedRules': + windows.create({ + type: 'popup', + url: `/matched-rules.html?tab=${request.tabId}`, + }); + break; + default: break; } diff --git a/platform/mv3/extension/js/dashboard.js b/platform/mv3/extension/js/dashboard.js index 363f30173..b8c240d5c 100644 --- a/platform/mv3/extension/js/dashboard.js +++ b/platform/mv3/extension/js/dashboard.js @@ -19,10 +19,8 @@ Home: https://github.com/gorhill/uBlock */ -'use strict'; - -import { runtime } from './ext.js'; import { dom } from './dom.js'; +import { runtime } from './ext.js'; /******************************************************************************/ diff --git a/platform/mv3/extension/js/debug.js b/platform/mv3/extension/js/debug.js new file mode 100644 index 000000000..a5f1a852f --- /dev/null +++ b/platform/mv3/extension/js/debug.js @@ -0,0 +1,124 @@ +/******************************************************************************* + + uBlock Origin Lite - a comprehensive, MV3-compliant content blocker + Copyright (C) 2024-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +import { browser, dnr } from './ext.js'; + +/******************************************************************************/ + +export const isSideloaded = (( ) => { + if ( dnr.onRuleMatchedDebug instanceof Object === false ) { return false; } + const { id } = browser.runtime; + // https://addons.mozilla.org/en-US/firefox/addon/ublock-origin-lite/ + if ( id === 'uBOLite@raymondhill.net' ) { return false; } + // https://chromewebstore.google.com/detail/ddkjiahejlhfcafbddmgiahcphecmpfh + if ( id === 'ddkjiahejlhfcafbddmgiahcphecmpfh' ) { return false; } + // https://microsoftedge.microsoft.com/addons/detail/cimighlppcgcoapaliogpjjdehbnofhn + if ( id === 'cimighlppcgcoapaliogpjjdehbnofhn' ) { return false; } + return true; +})(); + +/******************************************************************************/ + +export const ubolLog = (...args) => { + // Do not pollute dev console in stable releases. + if ( isSideloaded !== true ) { return; } + console.info('[uBOL]', ...args); +}; + +/******************************************************************************/ + +export const getMatchedRules = (( ) => { + const noopFn = ( ) => Promise.resolve([]); + if ( isSideloaded !== true ) { return noopFn; } + if ( dnr.onRuleMatchedDebug instanceof Object === false ) { return noopFn; } + + const rulesets = new Map(); + const bufferSize = 256; + const matchedRules = new Array(bufferSize); + matchedRules.fill(null); + let writePtr = 0; + + const pruneLongLists = list => { + if ( list.length <= 21 ) { return list; } + return [ ...list.slice(0, 10), '...', ...list.slice(-10) ]; + + }; + + const getRuleset = async rulesetId => { + if ( rulesets.has(rulesetId) ) { + return rulesets.get(rulesetId); + } + let rules; + if ( rulesetId === dnr.DYNAMIC_RULESET_ID ) { + rules = await dnr.getDynamicRules().catch(( ) => undefined); + } else { + const response = await fetch(`/rulesets/main/${rulesetId}.json`).catch(( ) => undefined); + if ( response === undefined ) { return; } + rules = await response.json().catch(( ) => undefined); + } + if ( Array.isArray(rules) === false ) { return; } + const ruleset = new Map(); + for ( const rule of rules ) { + const condition = rule.condition; + if ( condition ) { + if ( condition.requestDomains ) { + condition.requestDomains = pruneLongLists(condition.requestDomains); + } + if ( condition.initiatorDomains ) { + condition.initiatorDomains = pruneLongLists(condition.initiatorDomains); + } + } + const ruleId = rule.id; + rule.id = `${rulesetId}-${ruleId}`; + ruleset.set(ruleId, rule); + } + rulesets.set(rulesetId, ruleset); + return ruleset; + }; + + const getRuleDetails = async ruleInfo => { + const { rulesetId, ruleId } = ruleInfo.rule; + const ruleset = await getRuleset(rulesetId); + if ( ruleset === undefined ) { return; } + return { request: ruleInfo.request, rule: ruleset.get(ruleId) }; + }; + + dnr.onRuleMatchedDebug.addListener(ruleInfo => { + matchedRules[writePtr] = ruleInfo; + writePtr = (writePtr + 1) % bufferSize; + }); + + return async tabId => { + const promises = []; + for ( let i = 0; i < bufferSize; i++ ) { + const j = (writePtr + i) % bufferSize; + const ruleInfo = matchedRules[j]; + if ( ruleInfo === null ) { continue; } + if ( ruleInfo.request.tabId !== tabId ) { continue; } + const promise = getRuleDetails(ruleInfo); + if ( promise === undefined ) { continue; } + promises.unshift(promise); + } + return Promise.all(promises); + }; +})(); + +/******************************************************************************/ diff --git a/platform/mv3/extension/js/ext.js b/platform/mv3/extension/js/ext.js index 0126c1894..94d5118a2 100644 --- a/platform/mv3/extension/js/ext.js +++ b/platform/mv3/extension/js/ext.js @@ -19,12 +19,6 @@ Home: https://github.com/gorhill/uBlock */ -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - export const browser = self.browser instanceof Object && self.browser instanceof Element === false @@ -34,6 +28,7 @@ export const browser = export const dnr = browser.declarativeNetRequest; export const i18n = browser.i18n; export const runtime = browser.runtime; +export const windows = browser.windows; /******************************************************************************/ diff --git a/platform/mv3/extension/js/matched-rules.js b/platform/mv3/extension/js/matched-rules.js new file mode 100644 index 000000000..75a17caf4 --- /dev/null +++ b/platform/mv3/extension/js/matched-rules.js @@ -0,0 +1,48 @@ +/******************************************************************************* + + uBlock Origin Lite - a comprehensive, MV3-compliant content blocker + Copyright (C) 2014-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +import { dom, qs$ } from './dom.js'; +import { sendMessage } from './ext.js'; + +/******************************************************************************/ + +const url = new URL(document.location.href); +const tabId = parseInt(url.searchParams.get('tab'), 10) || 0; + +const entries = await sendMessage({ + what: 'getMatchedRules', + tabId, +}); + +const fragment = new DocumentFragment(); +const template = qs$('#matchInfo'); +for ( const entry of (entries || []) ) { + if ( entry instanceof Object === false ) { continue; } + const row = template.content.cloneNode(true); + qs$(row, '.requestInfo').textContent = JSON.stringify(entry.request, null, 2); + qs$(row, '.ruleInfo').textContent = JSON.stringify(entry.rule, null, 2); + fragment.append(row); +} + +dom.empty('#matchedEntries'); +qs$('#matchedEntries').append(fragment); + +/******************************************************************************/ diff --git a/platform/mv3/extension/js/popup.js b/platform/mv3/extension/js/popup.js index 29b993b24..712dbbdf2 100644 --- a/platform/mv3/extension/js/popup.js +++ b/platform/mv3/extension/js/popup.js @@ -19,17 +19,11 @@ Home: https://github.com/gorhill/uBlock */ -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - import { browser, + localRead, localWrite, runtime, sendMessage, - localRead, localWrite, } from './ext.js'; import { dom, qs$ } from './dom.js'; @@ -271,6 +265,15 @@ dom.on('[data-i18n-title="popupTipDashboard"]', 'click', ev => { runtime.openOptionsPage(); }); +dom.on('#showMatchedRules', 'click', ev => { + if ( ev.isTrusted !== true ) { return; } + if ( ev.button !== 0 ) { return; } + sendMessage({ + what: 'showMatchedRules', + tabId: currentTab.id, + }); +}); + /******************************************************************************/ async function init() { @@ -303,6 +306,12 @@ async function init() { dom.text('#hostname', punycode.toUnicode(tabHostname)); + dom.cl.toggle('#showMatchedRules', 'enabled', + popupPanelData.isSideloaded === true && + typeof currentTab.id === 'number' && + isNaN(currentTab.id) === false + ); + const parent = qs$('#rulesetStats'); for ( const details of popupPanelData.rulesetDetails || [] ) { const div = dom.clone('#templates .rulesetDetails'); diff --git a/platform/mv3/extension/js/ruleset-manager.js b/platform/mv3/extension/js/ruleset-manager.js index a484e1d21..749a83c70 100644 --- a/platform/mv3/extension/js/ruleset-manager.js +++ b/platform/mv3/extension/js/ruleset-manager.js @@ -19,15 +19,9 @@ Home: https://github.com/gorhill/uBlock */ -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - import { browser, dnr, i18n } from './ext.js'; import { fetchJSON } from './fetch.js'; -import { ubolLog } from './utils.js'; +import { ubolLog } from './debug.js'; /******************************************************************************/ diff --git a/platform/mv3/extension/js/scripting-manager.js b/platform/mv3/extension/js/scripting-manager.js index 10175fc92..6502cbc0a 100644 --- a/platform/mv3/extension/js/scripting-manager.js +++ b/platform/mv3/extension/js/scripting-manager.js @@ -19,18 +19,13 @@ Home: https://github.com/gorhill/uBlock */ -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ +import * as ut from './utils.js'; import { browser } from './ext.js'; import { fetchJSON } from './fetch.js'; -import { getFilteringModeDetails } from './mode-manager.js'; import { getEnabledRulesetsDetails } from './ruleset-manager.js'; - -import * as ut from './utils.js'; +import { getFilteringModeDetails } from './mode-manager.js'; +import { ubolLog } from './debug.js'; /******************************************************************************/ @@ -542,13 +537,13 @@ async function registerInjectables(origins) { toRemove.push(...Array.from(before.keys())); if ( toRemove.length !== 0 ) { - ut.ubolLog(`Unregistered ${toRemove} content (css/js)`); + ubolLog(`Unregistered ${toRemove} content (css/js)`); await browser.scripting.unregisterContentScripts({ ids: toRemove }) .catch(reason => { console.info(reason); }); } if ( toAdd.length !== 0 ) { - ut.ubolLog(`Registered ${toAdd.map(v => v.id)} content (css/js)`); + ubolLog(`Registered ${toAdd.map(v => v.id)} content (css/js)`); await browser.scripting.registerContentScripts(toAdd) .catch(reason => { console.info(reason); }); } diff --git a/platform/mv3/extension/js/utils.js b/platform/mv3/extension/js/utils.js index cadeaea0b..cdfc98b25 100644 --- a/platform/mv3/extension/js/utils.js +++ b/platform/mv3/extension/js/utils.js @@ -19,14 +19,6 @@ Home: https://github.com/gorhill/uBlock */ -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -import { browser } from './ext.js'; - /******************************************************************************/ function parsedURLromOrigin(origin) { @@ -114,7 +106,7 @@ const hostnamesFromMatches = origins => { out.push('all-urls'); continue; } - const match = /^\*:\/\/(?:\*\.)?([^\/]+)\/\*/.exec(origin); + const match = /^\*:\/\/(?:\*\.)?([^/]+)\/\*/.exec(origin); if ( match === null ) { continue; } out.push(match[1]); } @@ -130,25 +122,6 @@ export const broadcastMessage = message => { /******************************************************************************/ -const ubolLog = (...args) => { - // Do not pollute dev console in stable releases. - if ( shouldLog !== true ) { return; } - console.info('[uBOL]', ...args); -}; - -const shouldLog = (( ) => { - const { id } = browser.runtime; - // https://addons.mozilla.org/en-US/firefox/addon/ublock-origin-lite/ - if ( id === 'uBOLite@raymondhill.net' ) { return false; } - // https://chromewebstore.google.com/detail/ddkjiahejlhfcafbddmgiahcphecmpfh - if ( id === 'ddkjiahejlhfcafbddmgiahcphecmpfh' ) { return false; } - // https://microsoftedge.microsoft.com/addons/detail/cimighlppcgcoapaliogpjjdehbnofhn - if ( id === 'cimighlppcgcoapaliogpjjdehbnofhn' ) { return false; } - return true; -})(); - -/******************************************************************************/ - export { parsedURLromOrigin, toBroaderHostname, @@ -158,5 +131,4 @@ export { subtractHostnameIters, matchesFromHostnames, hostnamesFromMatches, - ubolLog, }; diff --git a/platform/mv3/extension/matched-rules.html b/platform/mv3/extension/matched-rules.html new file mode 100644 index 000000000..ec392ca23 --- /dev/null +++ b/platform/mv3/extension/matched-rules.html @@ -0,0 +1,33 @@ + + + + + +Matched rules + + + + + + + + + + + +
+
+ + + + + + + + + diff --git a/platform/mv3/extension/popup.html b/platform/mv3/extension/popup.html index 7492469bd..990f6590a 100644 --- a/platform/mv3/extension/popup.html +++ b/platform/mv3/extension/popup.html @@ -29,7 +29,7 @@ - + list-altShow matched rules cogs diff --git a/src/js/dom.js b/src/js/dom.js index 5c4d19479..241af9d52 100644 --- a/src/js/dom.js +++ b/src/js/dom.js @@ -19,10 +19,6 @@ Home: https://github.com/gorhill/uBlock */ -/* jshint esversion:11 */ - -'use strict'; - /******************************************************************************/ const normalizeTarget = target => { @@ -113,6 +109,14 @@ class dom { } } + static empty(target) { + for ( const elem of normalizeTarget(target) ) { + while ( elem.firstElementChild !== null ) { + elem.firstElementChild.remove(); + } + } + } + // target, type, callback, [options] // target, type, subtarget, callback, [options] diff --git a/tools/make-mv3.sh b/tools/make-mv3.sh index 13f82f4db..a4c20c48e 100755 --- a/tools/make-mv3.sh +++ b/tools/make-mv3.sh @@ -128,11 +128,19 @@ fi echo "*** uBOLite.mv3: extension ready" echo "Extension location: $DES/" -# Local build: use a different extension id than the official one -if [ -z "$TAGNAME" ] && [ "$PLATFORM" = "firefox" ]; then +# Local build +if [ -z "$TAGNAME" ]; then + # Enable DNR rule debugging tmp=$(mktemp) - jq '.browser_specific_settings.gecko.id = "uBOLite.dev@raymondhill.net"' "$DES/manifest.json" > "$tmp" \ + jq '.permissions += ["declarativeNetRequestFeedback"]' \ + "$DES/manifest.json" > "$tmp" \ && mv "$tmp" "$DES/manifest.json" + # Use a different extension id than the official one + if [ "$PLATFORM" = "firefox" ]; then + tmp=$(mktemp) + jq '.browser_specific_settings.gecko.id = "uBOLite.dev@raymondhill.net"' "$DES/manifest.json" > "$tmp" \ + && mv "$tmp" "$DES/manifest.json" + fi fi if [ "$FULL" = "yes" ]; then