From 560def639fdfd9bf99096fcf81a664bc2e01f4d8 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 28 Sep 2024 14:08:42 -0400 Subject: [PATCH] [mv3] Add a _chat_ icon in popup panel to report filter issues Just the same as with uBO, but for uBOL. --- .../mv3/extension/_locales/en/messages.json | 68 ++++++++ .../mv3/extension/css/dashboard-common.css | 13 ++ platform/mv3/extension/css/dashboard.css | 9 - platform/mv3/extension/css/settings.css | 4 - platform/mv3/extension/js/background.js | 41 +++++ platform/mv3/extension/js/popup.js | 37 ++++- platform/mv3/extension/js/report.js | 155 ++++++++++++++++++ platform/mv3/extension/matched-rules.html | 1 - platform/mv3/extension/popup.html | 2 + platform/mv3/extension/report.html | 61 +++++++ 10 files changed, 371 insertions(+), 20 deletions(-) create mode 100644 platform/mv3/extension/js/report.js create mode 100644 platform/mv3/extension/report.html diff --git a/platform/mv3/extension/_locales/en/messages.json b/platform/mv3/extension/_locales/en/messages.json index 9f281de19..f8c4b376c 100644 --- a/platform/mv3/extension/_locales/en/messages.json +++ b/platform/mv3/extension/_locales/en/messages.json @@ -31,6 +31,10 @@ "message": "filtering mode", "description": "Label in the popup panel for the current filtering mode" }, + "popupTipReport": { + "message": "Report an issue on this website", + "description": "Tooltip used for the 'chat' icon in the panel" + }, "popupTipDashboard": { "message": "Open the dashboard", "description": "English: Click to open the dashboard" @@ -99,6 +103,70 @@ "message": "External dependencies (GPLv3-compatible):", "description": "Shown in the About pane" }, + "supportS6H": { + "message": "Report a filter issue", + "description": "Header of 'Report a filter issue' section in Support pane" + }, + "supportS3P1": { + "message": "Report filter issues with specific websites to the uBlockOrigin/uAssets issue tracker. Requires a GitHub account.", + "description": "First paragraph of 'Filter issues' section in Support pane" + }, + "supportS6P1S1": { + "message": "To avoid burdening volunteers with duplicate reports, please verify that the issue has not already been reported.", + "description": "A paragraph in the filter issue reporter section" + }, + "supportFindSpecificButton": { + "message": "Find similar reports", + "description": "A clickable link in the filter issue reporter section" + }, + "supportS6URL": { + "message": "Address of the webpage:", + "description": "Label for the URL of the page" + }, + "supportS6Select1": { + "message": "The webpage…", + "description": "Label for widget to select type of issue" + }, + "supportS6Select1Option0": { + "message": "-- Pick an entry --", + "description": "An entry in the widget used to select the type of issue" + }, + "supportS6Select1Option1": { + "message": "Shows ads or ad leftovers", + "description": "An entry in the widget used to select the type of issue" + }, + "supportS6Select1Option2": { + "message": "Has overlays or other nuisances", + "description": "An entry in the widget used to select the type of issue" + }, + "supportS6Select1Option3": { + "message": "Detects uBO Lite", + "description": "An entry in the widget used to select the type of issue" + }, + "supportS6Select1Option4": { + "message": "Has privacy-related issues", + "description": "An entry in the widget used to select the type of issue" + }, + "supportS6Select1Option5": { + "message": "Malfunctions when uBO Lite is enabled", + "description": "An entry in the widget used to select the type of issue" + }, + "supportS6Select1Option6": { + "message": "Opens unwanted tabs or windows", + "description": "An entry in the widget used to select the type of issue" + }, + "supportS6Select1Option7": { + "message": "Leads to badware, phishing", + "description": "An entry in the widget used to select the type of issue" + }, + "supportS6Checkbox1": { + "message": "Label the webpage as “NSFW” (“Not Safe For Work”)", + "description": "A checkbox to use for NSFW sites" + }, + "supportReportSpecificButton": { + "message": "Create new report", + "description": "Text for button which open an external webpage in Support pane" + }, "firstRunSectionLabel": { "message": "Welcome", "description": "The header text for the welcome message section" diff --git a/platform/mv3/extension/css/dashboard-common.css b/platform/mv3/extension/css/dashboard-common.css index 41d641621..8621d0870 100644 --- a/platform/mv3/extension/css/dashboard-common.css +++ b/platform/mv3/extension/css/dashboard-common.css @@ -1,3 +1,14 @@ +body { + align-items: center; + box-sizing: border-box; + display: flex; + flex-direction: column; + padding: 0 var(--default-gap-xxsmall); + } +body > * { + width: min(640px, 100%); + } + h2, h3 { margin: 1em 0; } @@ -7,9 +18,11 @@ h2 { h3 { font-size: 16px; } + a { text-decoration: none; } + .fa-icon.info { color: var(--info0-ink); fill: var(--info0-ink); diff --git a/platform/mv3/extension/css/dashboard.css b/platform/mv3/extension/css/dashboard.css index e98bcc9a4..6506c2272 100644 --- a/platform/mv3/extension/css/dashboard.css +++ b/platform/mv3/extension/css/dashboard.css @@ -1,12 +1,3 @@ -body { - align-items: center; - box-sizing: border-box; - display: flex; - flex-direction: column; - } -body > * { - width: min(640px, 100%); - } #dashboard-nav { background-color: var(--surface-1); border: 0; diff --git a/platform/mv3/extension/css/settings.css b/platform/mv3/extension/css/settings.css index 2e727fc0e..b81e79a52 100644 --- a/platform/mv3/extension/css/settings.css +++ b/platform/mv3/extension/css/settings.css @@ -22,10 +22,6 @@ p { white-space: pre-line; } -section > div { - padding: 0 var(--default-gap-xxsmall); - } - #showBlockedCount:has(input[type="checkbox"][disabled]) { opacity: 0.5; } diff --git a/platform/mv3/extension/js/background.js b/platform/mv3/extension/js/background.js index 17cfff4bc..c8183e925 100644 --- a/platform/mv3/extension/js/background.js +++ b/platform/mv3/extension/js/background.js @@ -145,6 +145,36 @@ async function onPermissionsRemoved() { /******************************************************************************/ +async function gotoURL(url, type) { + const pageURL = new URL(url, runtime.getURL('/')); + const tabs = await browser.tabs.query({ + url: pageURL.href, + windowType: type !== 'popup' ? 'normal' : 'popup' + }); + + if ( Array.isArray(tabs) && tabs.length !== 0 ) { + const { windowId, id } = tabs[0]; + return Promise.all([ + browser.windows.update(windowId, { focused: true }), + browser.tabs.update(id, { active: true }), + ]); + } + + if ( type === 'popup' ) { + return windows.create({ + type: 'popup', + url: pageURL.href, + }); + } + + return browser.tabs.create({ + active: true, + url: pageURL.href, + }); +} + +/******************************************************************************/ + function onMessage(request, sender, callback) { // Does not require trusted origin. @@ -265,6 +295,10 @@ function onMessage(request, sender, callback) { return true; } + case 'gotoURL': + gotoURL(request.url, request.type); + break; + case 'setFilteringMode': { getFilteringMode(request.hostname).then(actualLevel => { if ( request.level === actualLevel ) { return actualLevel; } @@ -276,6 +310,13 @@ function onMessage(request, sender, callback) { return true; } + case 'getDefaultFilteringMode': { + getDefaultFilteringMode().then(level => { + callback(level); + }); + return true; + } + case 'setDefaultFilteringMode': { getDefaultFilteringMode().then(beforeLevel => setDefaultFilteringMode(request.level).then(afterLevel => diff --git a/platform/mv3/extension/js/popup.js b/platform/mv3/extension/js/popup.js index 123c20bc7..4242e5bac 100644 --- a/platform/mv3/extension/js/popup.js +++ b/platform/mv3/extension/js/popup.js @@ -261,12 +261,6 @@ dom.on('#lessButton', 'click', ( ) => { /******************************************************************************/ -dom.on('[data-i18n-title="popupTipDashboard"]', 'click', ev => { - if ( ev.isTrusted !== true ) { return; } - if ( ev.button !== 0 ) { return; } - runtime.openOptionsPage(); -}); - dom.on('#showMatchedRules', 'click', ev => { if ( ev.isTrusted !== true ) { return; } if ( ev.button !== 0 ) { return; } @@ -278,6 +272,33 @@ dom.on('#showMatchedRules', 'click', ev => { /******************************************************************************/ +dom.on('[data-i18n-title="popupTipReport"]', 'click', ev => { + if ( ev.isTrusted !== true ) { return; } + let url; + try { + url = new URL(currentTab.url); + } catch(_) { + } + if ( url === undefined ) { return; } + const reportURL = new URL(runtime.getURL('/report.html')); + reportURL.searchParams.set('url', url.href); + reportURL.searchParams.set('mode', popupPanelData.level); + sendMessage({ + what: 'gotoURL', + url: `${reportURL.pathname}${reportURL.search}`, + }); +}); + +/******************************************************************************/ + +dom.on('[data-i18n-title="popupTipDashboard"]', 'click', ev => { + if ( ev.isTrusted !== true ) { return; } + if ( ev.button !== 0 ) { return; } + runtime.openOptionsPage(); +}); + +/******************************************************************************/ + async function init() { const [ tab ] = await browser.tabs.query({ active: true, @@ -314,6 +335,10 @@ async function init() { isNaN(currentTab.id) === false ); + dom.cl.toggle('#reportFilterIssue', 'enabled', + /^https?:\/\//.test(url?.href) + ); + const parent = qs$('#rulesetStats'); for ( const details of popupPanelData.rulesetDetails || [] ) { const div = dom.clone('#templates .rulesetDetails'); diff --git a/platform/mv3/extension/js/report.js b/platform/mv3/extension/js/report.js new file mode 100644 index 000000000..676342b9e --- /dev/null +++ b/platform/mv3/extension/js/report.js @@ -0,0 +1,155 @@ +/******************************************************************************* + + uBlock Origin - a comprehensive, efficient 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 { dnr, runtime } from './ext.js'; +import { dom, qs$ } from './dom.js'; +import { sendMessage } from './ext.js'; + +/******************************************************************************/ + +const reportedPage = (( ) => { + const url = new URL(window.location.href); + try { + const pageURL = url.searchParams.get('url'); + if ( pageURL === null ) { return null; } + const parsedURL = new URL(pageURL); + parsedURL.username = ''; + parsedURL.password = ''; + parsedURL.hash = ''; + const select = qs$('select[name="url"]'); + dom.text(select.options[0], parsedURL.href); + if ( parsedURL.search !== '' ) { + const option = dom.create('option'); + parsedURL.search = ''; + dom.text(option, parsedURL.href); + select.append(option); + } + if ( parsedURL.pathname !== '/' ) { + const option = dom.create('option'); + parsedURL.pathname = ''; + dom.text(option, parsedURL.href); + select.append(option); + } + return { + hostname: parsedURL.hostname.replace(/^(m|mobile|www)\./, ''), + mode: url.searchParams.get('mode'), + }; + } catch(ex) { + } + return null; +})(); + +/******************************************************************************/ + +function reportSpecificFilterType() { + return qs$('select[name="type"]').value; +} + +/******************************************************************************/ + +function renderData(data, depth = 0) { + const indent = ' '.repeat(depth); + if ( Array.isArray(data) ) { + const out = []; + for ( const value of data ) { + out.push(renderData(value, depth)); + } + return out.join('\n'); + } + if ( typeof data !== 'object' || data === null ) { + return `${indent}${data}`; + } + const out = []; + for ( const [ name, value ] of Object.entries(data) ) { + if ( typeof value === 'object' && value !== null ) { + out.push(`${indent}${name}:`); + out.push(renderData(value, depth + 1)); + continue; + } + out.push(`${indent}${name}: ${value}`); + } + return out.join('\n'); +} + +/******************************************************************************/ + +async function reportSpecificFilterIssue() { + const githubURL = new URL( + 'https://github.com/uBlockOrigin/uAssets/issues/new?template=specific_report_from_ubol.yml' + ); + const issueType = reportSpecificFilterType(); + let title = `${reportedPage.hostname}: ${issueType}`; + if ( qs$('#isNSFW').checked ) { + title = `[nsfw] ${title}`; + } + githubURL.searchParams.set('title', title); + githubURL.searchParams.set( + 'url_address_of_the_web_page', + '`' + qs$('select[name="url"]').value + '`' + ); + githubURL.searchParams.set('category', issueType); + githubURL.searchParams.set('labels', 'uBOL'); + + const manifest = runtime.getManifest(); + const rulesets = await dnr.getEnabledRulesets(); + const defaultMode = await sendMessage({ what: 'getDefaultFilteringMode' }); + const modes = [ 'no filtering', 'basic', 'optimal', 'complete' ]; + const config = { + version: `uBOL ${manifest.version}`, + mode: `${modes[reportedPage.mode]} / ${modes[defaultMode]}`, + rulesets, + }; + const configBody = [ + '```yaml', + renderData(config), + '```', + '', + ].join('\n'); + githubURL.searchParams.set('configuration', configBody); + sendMessage({ what: 'gotoURL', url: githubURL.href }); +} + +/******************************************************************************/ + +(async ( ) => { + dom.on('[data-url]', 'click', ev => { + const elem = ev.target.closest('[data-url]'); + const url = dom.attr(elem, 'data-url'); + if ( typeof url !== 'string' || url === '' ) { return; } + sendMessage({ what: 'gotoURL', url }); + ev.preventDefault(); + }); + + if ( reportedPage !== null ) { + dom.on('[data-i18n="supportReportSpecificButton"]', 'click', ev => { + reportSpecificFilterIssue(); + ev.preventDefault(); + }); + + dom.on('[data-i18n="supportFindSpecificButton"]', 'click', ev => { + const url = new URL('https://github.com/uBlockOrigin/uAssets/issues'); + url.searchParams.set('q', `is:issue sort:updated-desc "${reportedPage.hostname}" in:title`); + sendMessage({ what: 'gotoURL', url: url.href }); + ev.preventDefault(); + }); + } + +})(); diff --git a/platform/mv3/extension/matched-rules.html b/platform/mv3/extension/matched-rules.html index ec392ca23..488e150a3 100644 --- a/platform/mv3/extension/matched-rules.html +++ b/platform/mv3/extension/matched-rules.html @@ -7,7 +7,6 @@ - diff --git a/platform/mv3/extension/popup.html b/platform/mv3/extension/popup.html index 990f6590a..e231acc9b 100644 --- a/platform/mv3/extension/popup.html +++ b/platform/mv3/extension/popup.html @@ -13,6 +13,7 @@ +
­
@@ -30,6 +31,7 @@ list-altShow matched rules + comment-alt cogs
diff --git a/platform/mv3/extension/report.html b/platform/mv3/extension/report.html new file mode 100644 index 000000000..21997e936 --- /dev/null +++ b/platform/mv3/extension/report.html @@ -0,0 +1,61 @@ + + + + + +uBO Lite — Report + + + + + + + + + + +
+ +

+

+
+
+

+ +
+
+
+

+
+ +

+

+
+ +

+

+ +

+ +
+ +
+ + + + + + + +