From 6efd8eb84aaae43b05c3e9f2bffd13cf85f16018 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 4 Apr 2023 11:15:44 -0400 Subject: [PATCH] Add matched cosmetic filters in troubleshooting information --- src/js/messaging.js | 37 +++++--- src/js/scriptlets/cosmetic-logger.js | 2 +- src/js/scriptlets/cosmetic-report.js | 137 +++++++++++++++++++++++++++ src/js/support.js | 8 ++ 4 files changed, 171 insertions(+), 13 deletions(-) create mode 100644 src/js/scriptlets/cosmetic-report.js diff --git a/src/js/messaging.js b/src/js/messaging.js index c05eefdf7..ab0f05096 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -609,6 +609,31 @@ const onMessage = function(request, sender, callback) { }); return; + case 'launchReporter': { + const pageStore = µb.pageStoreFromTabId(request.tabId); + if ( pageStore === null ) { break; } + if ( vAPI.net.hasUnprocessedRequest(request.tabId) ) { + request.popupPanel.hasUnprocessedRequest = true; + } + const supportURL = new URL(vAPI.getURL('support.html')); + supportURL.searchParams.set('pageURL', request.pageURL); + supportURL.searchParams.set('popupPanel', JSON.stringify(request.popupPanel)); + vAPI.tabs.executeScript(request.tabId, { + allFrames: true, + file: '/js/scriptlets/cosmetic-report.js', + matchAboutBlank: true, + }).then(results => { + const filters = results.reduce((a, v) => { + if ( Array.isArray(v) ) { a.push(...v); } + return a; + }, []); + if ( filters.length !== 0 ) { + supportURL.searchParams.set('cosmetic', JSON.stringify(filters)); + } + µb.openNewTab({ url: supportURL.href, select: true, index: -1 }); + }); + return; + } default: break; } @@ -623,18 +648,6 @@ const onMessage = function(request, sender, callback) { response = lastModified !== request.contentLastModified; break; } - case 'launchReporter': { - const pageStore = µb.pageStoreFromTabId(request.tabId); - if ( pageStore === null ) { break; } - if ( vAPI.net.hasUnprocessedRequest(request.tabId) ) { - request.popupPanel.hasUnprocessedRequest = true; - } - const supportURL = new URL(vAPI.getURL('support.html')); - supportURL.searchParams.set('pageURL', request.pageURL); - supportURL.searchParams.set('popupPanel', JSON.stringify(request.popupPanel)); - µb.openNewTab({ url: supportURL.href, select: true, index: -1 }); - break; - } case 'revertFirewallRules': // TODO: use Set() to message around sets of hostnames sessionFirewall.copyRules( diff --git a/src/js/scriptlets/cosmetic-logger.js b/src/js/scriptlets/cosmetic-logger.js index 44b83cca7..f023d231c 100644 --- a/src/js/scriptlets/cosmetic-logger.js +++ b/src/js/scriptlets/cosmetic-logger.js @@ -269,7 +269,7 @@ const handlers = { if ( details.action !== undefined && details.tasks === undefined && - details.action[0] === ':style' + details.action[0] === 'style' ) { exceptionDict.set(details.selector, details.raw); continue; diff --git a/src/js/scriptlets/cosmetic-report.js b/src/js/scriptlets/cosmetic-report.js new file mode 100644 index 000000000..94988a770 --- /dev/null +++ b/src/js/scriptlets/cosmetic-report.js @@ -0,0 +1,137 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-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 +*/ + +'use strict'; + +/******************************************************************************/ + +(( ) => { +// >>>>>>>> start of private namespace + +/******************************************************************************/ + +if ( typeof vAPI !== 'object' ) { return; } +if ( typeof vAPI.domFilterer !== 'object' ) { return; } + +/******************************************************************************/ + +const rePseudoElements = /:(?::?after|:?before|:[a-z-]+)$/; + +const hasSelector = selector => { + try { + return document.querySelector(selector) !== null; + } + catch(ex) { + } + return false; +}; + +const safeQuerySelector = selector => { + const safeSelector = rePseudoElements.test(selector) + ? selector.replace(rePseudoElements, '') + : selector; + try { + return document.querySelector(safeSelector); + } + catch(ex) { + } + return null; +}; + +const safeGroupSelectors = selectors => { + const arr = Array.isArray(selectors) + ? selectors + : Array.from(selectors); + return arr.map(s => { + return rePseudoElements.test(s) + ? s.replace(rePseudoElements, '') + : s; + }).join(',\n'); +}; + +const allSelectors = vAPI.domFilterer.getAllSelectors(); +const matchedSelectors = []; + +if ( Array.isArray(allSelectors.declarative) ) { + const declarativeSet = new Set(); + for ( const block of allSelectors.declarative ) { + for ( const selector of block.split(',\n') ) { + declarativeSet.add(selector); + } + } + if ( hasSelector(safeGroupSelectors(declarativeSet)) ) { + for ( const selector of declarativeSet ) { + if ( safeQuerySelector(selector) === null ) { continue; } + matchedSelectors.push(`##${selector}`); + } + } +} + +if ( + Array.isArray(allSelectors.procedural) && + allSelectors.procedural.length !== 0 +) { + for ( const pselector of allSelectors.procedural ) { + if ( pselector.hit === false && pselector.exec().length === 0 ) { continue; } + matchedSelectors.push(`##${pselector.raw}`); + } +} + +if ( Array.isArray(allSelectors.exceptions) ) { + const exceptionDict = new Map(); + for ( const selector of allSelectors.exceptions ) { + if ( selector.charCodeAt(0) !== 0x7B /* '{' */ ) { + exceptionDict.set(selector, selector); + continue; + } + const details = JSON.parse(selector); + if ( + details.action !== undefined && + details.tasks === undefined && + details.action[0] === 'style' + ) { + exceptionDict.set(details.selector, details.raw); + continue; + } + const pselector = vAPI.domFilterer.createProceduralFilter(details); + if ( pselector.test() === false ) { continue; } + matchedSelectors.push(`#@#${pselector.raw}`); + } + if ( + exceptionDict.size !== 0 && + hasSelector(safeGroupSelectors(exceptionDict.keys())) + ) { + for ( const [ selector, raw ] of exceptionDict ) { + if ( safeQuerySelector(selector) === null ) { continue; } + matchedSelectors.push(`#@#${raw}`); + } + } +} + +if ( matchedSelectors.length === 0 ) { return; } + +return matchedSelectors; + +/******************************************************************************/ + +// <<<<<<<< end of private namespace +})(); + diff --git a/src/js/support.js b/src/js/support.js index 3b878b035..d31579868 100644 --- a/src/js/support.js +++ b/src/js/support.js @@ -151,6 +151,9 @@ function showData() { } if ( reportedPage !== null ) { shownData.popupPanel = reportedPage.popupPanel; + if ( reportedPage.cosmeticFilters ) { + shownData.cosmeticFilters = reportedPage.cosmeticFilters; + } } const text = JSON.stringify(shownData, null, 2) .split('\n') @@ -197,9 +200,14 @@ const reportedPage = (( ) => { select.append(option); } dom.cl.add(dom.body, 'filterIssue'); + let cosmeticFilters; + if ( url.searchParams.has('cosmetic') ) { + cosmeticFilters = JSON.parse(url.searchParams.get('cosmetic')); + } return { hostname: parsedURL.hostname.replace(/^(m|mobile|www)\./, ''), popupPanel: JSON.parse(url.searchParams.get('popupPanel')), + cosmeticFilters, }; } catch(ex) { }