/******************************************************************************* 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 ? '
\n\n```yaml\n' + text + '\n```\n
' : '```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: 'purgeCaches', assetKeys }); vAPI.messaging.send('dashboard', { what: 'forceUpdateAssets' }); 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'); }); })();