uBlock/src/js/support.js

336 lines
11 KiB
JavaScript

/*******************************************************************************
uBlock Origin - a comprehensive, efficient 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
*/
/* global CodeMirror, uBlockDashboard */
'use strict';
import { onBroadcast } from './broadcast.js';
import { dom, qs$ } from './dom.js';
/******************************************************************************/
const uselessKeys = [
'hiddenSettings.benchmarkDatasetURL',
'hiddenSettings.blockingProfiles',
'hiddenSettings.consoleLogLevel',
'hiddenSettings.uiPopupConfig',
'userSettings.alwaysDetachLogger',
'userSettings.firewallPaneMinimized',
'userSettings.externalLists',
'userSettings.importedLists',
'userSettings.popupPanelSections',
'userSettings.uiAccentCustom',
'userSettings.uiAccentCustom0',
'userSettings.uiTheme',
];
const sensitiveValues = [
'filterset (user)',
'userSettings.popupPanelSections',
'hiddenSettings.userResourcesLocation',
'trustedset.added',
'hostRuleset.added',
'switchRuleset.added',
'urlRuleset.added',
];
const sensitiveKeys = [
'listset.added',
];
/******************************************************************************/
function removeKey(data, prop) {
if ( data instanceof Object === false ) { return; }
const pos = prop.indexOf('.');
if ( pos !== -1 ) {
const key = prop.slice(0, pos);
return removeKey(data[key], prop.slice(pos + 1));
}
delete data[prop];
}
function redactValue(data, prop) {
if ( data instanceof Object === false ) { return; }
const pos = prop.indexOf('.');
if ( pos !== -1 ) {
return redactValue(data[prop.slice(0, pos)], prop.slice(pos + 1));
}
let value = data[prop];
if ( value === undefined ) { return; }
if ( Array.isArray(value) ) {
if ( value.length !== 0 ) {
value = `[array of ${value.length} redacted]`;
} else {
value = '[empty]';
}
} else {
value = '[redacted]';
}
data[prop] = value;
}
function redactKeys(data, prop) {
if ( data instanceof Object === false ) { return; }
const pos = prop.indexOf('.');
if ( pos !== -1 ) {
return redactKeys(data[prop.slice(0, pos)], prop.slice(pos + 1));
}
const obj = data[prop];
if ( obj instanceof Object === false ) { return; }
let count = 1;
for ( const key in obj ) {
if ( key.startsWith('file://') === false ) { continue; }
const newkey = `[list name ${count} redacted]`;
obj[newkey] = obj[key];
obj[key] = undefined;
count += 1;
}
}
function patchEmptiness(data, prop) {
const entry = data[prop];
if ( Array.isArray(entry) && entry.length === 0 ) {
data[prop] = '[empty]';
return;
}
if ( entry instanceof Object === false ) { return; }
if ( Object.keys(entry).length === 0 ) {
data[prop] = '[none]';
return;
}
for ( const key in entry ) {
patchEmptiness(entry, key);
}
}
function configToMarkdown(collapse = false) {
const text = cmEditor.getValue().trim();
return collapse
? '<details>\n\n```yaml\n' + text + '\n```\n</details>'
: '```yaml\n' + text + '\n```\n';
}
function addDetailsToReportURL(id, collapse = false) {
const elem = qs$(`#${id}`);
const url = new URL(dom.attr(elem, 'data-url'));
url.searchParams.set('configuration', configToMarkdown(collapse));
dom.attr(elem, 'data-url', url);
}
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 showSupportData() {
const supportData = await vAPI.messaging.send('dashboard', {
what: 'getSupportData',
});
const shownData = JSON.parse(JSON.stringify(supportData));
uselessKeys.forEach(prop => { removeKey(shownData, prop); });
const redacted = true;
if ( redacted ) {
sensitiveValues.forEach(prop => { redactValue(shownData, prop); });
sensitiveKeys.forEach(prop => { redactKeys(shownData, prop); });
}
for ( const prop in shownData ) {
patchEmptiness(shownData, prop);
}
if ( reportedPage !== null ) {
shownData.popupPanel = reportedPage.popupPanel;
}
const text = renderData(shownData);
cmEditor.setValue(text);
cmEditor.clearHistory();
addDetailsToReportURL('filterReport', true);
addDetailsToReportURL('bugReport', true);
}
/******************************************************************************/
const reportedPage = (( ) => {
const url = new URL(window.location.href);
try {
const pageURL = url.searchParams.get('pageURL');
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);
}
const shouldUpdateLists = url.searchParams.get('shouldUpdateLists');
if ( shouldUpdateLists !== null ) {
dom.body.dataset.shouldUpdateLists = shouldUpdateLists;
}
dom.cl.add(dom.body, 'filterIssue');
return {
hostname: parsedURL.hostname.replace(/^(m|mobile|www)\./, ''),
popupPanel: JSON.parse(url.searchParams.get('popupPanel')),
};
} catch(ex) {
}
return null;
})();
function reportSpecificFilterType() {
return qs$('select[name="type"]').value;
}
function reportSpecificFilterIssue() {
const githubURL = new URL(
'https://github.com/uBlockOrigin/uAssets/issues/new?template=specific_report_from_ubo.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('configuration', configToMarkdown(true));
vAPI.messaging.send('default', {
what: 'gotoURL',
details: { url: githubURL.href, select: true, index: -1 },
});
}
async function updateFilterLists() {
if ( dom.body.dataset.shouldUpdateLists === undefined ) { return false; }
dom.cl.add(dom.body, 'updating');
const assetKeys = JSON.parse(dom.body.dataset.shouldUpdateLists);
vAPI.messaging.send('dashboard', { what: 'supportUpdateNow', assetKeys });
return true;
}
/******************************************************************************/
const cmEditor = new CodeMirror(qs$('#supportData'), {
autofocus: true,
readOnly: true,
styleActiveLine: true,
});
uBlockDashboard.patchCodeMirrorEditor(cmEditor);
/******************************************************************************/
(async ( ) => {
await showSupportData();
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; }
vAPI.messaging.send('default', {
what: 'gotoURL',
details: { url, select: true, index: -1, shiftKey: ev.shiftKey },
});
ev.preventDefault();
});
if ( reportedPage !== null ) {
if ( dom.body.dataset.shouldUpdateLists ) {
dom.on('.supportEntry.shouldUpdate button', 'click', ev => {
if ( updateFilterLists() === false ) { return; }
ev.preventDefault();
});
}
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`);
vAPI.messaging.send('default', {
what: 'gotoURL',
details: { url: url.href, select: true, index: -1 },
});
ev.preventDefault();
});
dom.on('#showSupportInfo', 'click', ev => {
const button = ev.target.closest('#showSupportInfo');
dom.cl.add(button, 'hidden');
dom.cl.add('.a.b.c.d', 'e');
cmEditor.refresh();
});
}
onBroadcast(msg => {
if ( msg.what === 'assetsUpdated' ) {
dom.cl.remove(dom.body, 'updating');
dom.cl.add(dom.body, 'updated');
return;
}
if ( msg.what === 'staticFilteringDataChanged' ) {
showSupportData();
return;
}
});
dom.on('#selectAllButton', 'click', ( ) => {
cmEditor.focus();
cmEditor.execCommand('selectAll');
});
})();