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 @@
+
+
+
+
+
+
+ +
+ +