Offer opportunity to update filter lists before reporting issue

Related discussion:
- https://github.com/uBlockOrigin/uBlock-issues/discussions/2582

If there exist any built-in filter list which last update time
is older than 2 hours, the "Report a filter issue" page will ask
the user to update their filter lists then verify that the issue
still exists.

Once filter lists are updated, the troubleshooting information
will reflect the change in update time.
This commit is contained in:
Raymond Hill 2023-04-23 13:45:11 -04:00
parent ec4480e122
commit 4a92f96206
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
8 changed files with 193 additions and 77 deletions

View File

@ -952,6 +952,14 @@
"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"
},
"supportS6P2S1": {
"message": "Filter lists are updated daily. Be sure your issue has not already been addressed in the most recent filter lists.",
"description": "A paragraph in the filter issue reporter section"
},
"supportS6P2S2": {
"message": "Verify that the issue still exists after reloading the problematic webpage.",
"description": "A paragraph in the filter issue reporter section"
},
"supportS6URL": {
"message": "Address of the web page:",
"description": "Label for the URL of the page"

View File

@ -1,3 +1,8 @@
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
body {
margin-bottom: 6rem;
}
@ -11,6 +16,7 @@ h3 {
}
.supportEntry {
display: flex;
margin-block: 1em;
}
:root.mobile .supportEntry {
flex-direction: column;
@ -48,6 +54,30 @@ body.filterIssue #moreButton {
display: none;
}
body:not(.shouldUpdate) .shouldUpdate {
display: none;
}
body.updating {
pointer-events: none;
}
body.updating button {
filter: grayscale(1);
opacity: 0.5;
}
body.updated .shouldUpdate button {
display: none;
}
body.updating .shouldUpdate button .fa-icon svg {
animation: spin 1s linear infinite;
transform-origin: 50%;
}
body .shouldUpdate .updated {
align-self: center;
}
body:not(.updated) .shouldUpdate .updated {
display: none;
}
button {
align-self: center;
}

View File

@ -946,6 +946,29 @@ assets.rmrf = function() {
/******************************************************************************/
assets.getUpdateAges = async function(conditions = {}) {
const assetDict = await assets.metadata();
const now = Date.now();
const out = [];
for ( const [ assetKey, asset ] of Object.entries(assetDict) ) {
if ( asset.hasRemoteURL !== true ) { continue; }
const tokens = conditions[asset.content];
if ( Array.isArray(tokens) === false ) { continue; }
if ( tokens.includes('*') === false ) {
if ( tokens.includes(assetKey) === false ) { continue; }
}
const age = now - (asset.writeTime || 0);
out.push({
assetKey,
age,
ageNormalized: age / (asset.updateAfter * 86400000),
});
}
return out;
};
/******************************************************************************/
// Asset updater area.
const updaterAssetDelayDefault = 120000;
const updaterUpdated = [];

View File

@ -589,6 +589,53 @@ const getElementCount = async function(tabId, what) {
return total;
};
const launchReporter = async function(request) {
const pageStore = µb.pageStoreFromTabId(request.tabId);
if ( pageStore === null ) { return; }
if ( pageStore.hasUnprocessedRequest ) {
request.popupPanel.hasUnprocessedRequest = true;
}
const entries = await io.getUpdateAges({
filters: µb.selectedFilterLists.filter(
a => (/^https?:/.test(a) === false)
)
});
let shoudUpdateLists = false;
for ( const entry of entries ) {
if ( entry.age < (2 * 60 * 60 * 1000) ) { continue; }
io.purge(entry.assetKey);
shoudUpdateLists = true;
}
// https://github.com/gorhill/uBlock/commit/6efd8eb#commitcomment-107523558
// Important: for whatever reason, not using `document_start` causes the
// Promise returned by `tabs.executeScript()` to resolve only when the
// associated tab is closed.
const cosmeticSurveyResults = await vAPI.tabs.executeScript(request.tabId, {
allFrames: true,
file: '/js/scriptlets/cosmetic-report.js',
matchAboutBlank: true,
runAt: 'document_start',
});
const filters = cosmeticSurveyResults.reduce((a, v) => {
if ( Array.isArray(v) ) { a.push(...v); }
return a;
}, []);
if ( filters.length !== 0 ) {
request.popupPanel.cosmetic = filters;
}
const supportURL = new URL(vAPI.getURL('support.html'));
supportURL.searchParams.set('pageURL', request.pageURL);
supportURL.searchParams.set('popupPanel', JSON.stringify(request.popupPanel));
if ( shoudUpdateLists ) {
supportURL.searchParams.set('shouldUpdate', 1);
}
return supportURL.href;
};
const onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
@ -610,36 +657,6 @@ const onMessage = function(request, sender, callback) {
});
return;
// https://github.com/gorhill/uBlock/commit/6efd8eb#commitcomment-107523558
// Important: for whatever reason, not using `document_start` causes the
// Promise returned by `tabs.executeScript()` to resolve only when the
// associated tab is closed.
case 'launchReporter': {
const pageStore = µb.pageStoreFromTabId(request.tabId);
if ( pageStore === null ) { break; }
if ( pageStore.hasUnprocessedRequest ) {
request.popupPanel.hasUnprocessedRequest = true;
}
vAPI.tabs.executeScript(request.tabId, {
allFrames: true,
file: '/js/scriptlets/cosmetic-report.js',
matchAboutBlank: true,
runAt: 'document_start',
}).then(results => {
const filters = results.reduce((a, v) => {
if ( Array.isArray(v) ) { a.push(...v); }
return a;
}, []);
if ( filters.length !== 0 ) {
request.popupPanel.cosmetic = filters;
}
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 });
});
return;
}
default:
break;
}
@ -659,6 +676,15 @@ const onMessage = function(request, sender, callback) {
response = lastModified !== request.contentLastModified;
break;
}
case 'launchReporter': {
launchReporter(request).then(url => {
if ( typeof url !== 'string' ) { return; }
µb.openNewTab({ url, select: true, index: -1 });
});
break;
}
case 'revertFirewallRules':
// TODO: use Set() to message around sets of hostnames
sessionFirewall.copyRules(

View File

@ -30,6 +30,7 @@
if ( typeof vAPI !== 'object' ) { return; }
if ( typeof vAPI.domFilterer !== 'object' ) { return; }
if ( vAPI.domFilterer === null ) { return; }
/******************************************************************************/

View File

@ -1470,18 +1470,12 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
// Respect cooldown period before launching an emergency update.
const timeSinceLastEmergencyUpdate = (now - lastEmergencyUpdate) / 3600000;
if ( timeSinceLastEmergencyUpdate > 1 ) {
const assetDict = await io.metadata();
for ( const [ assetKey, asset ] of Object.entries(assetDict) ) {
if ( asset.hasRemoteURL !== true ) { continue; }
if ( asset.content === 'filters' ) {
if ( µb.selectedFilterLists.includes(assetKey) === false ) {
continue;
}
}
if ( asset.obsolete !== true ) { continue; }
const lastUpdateInDays = (now - asset.writeTime) / 86400000;
const daysSinceVeryObsolete = lastUpdateInDays - 2 * asset.updateAfter;
if ( daysSinceVeryObsolete < 0 ) { continue; }
const entries = await io.getUpdateAges({
filters: µb.selectedFilterLists,
internal: [ '*' ],
});
for ( const entry of entries ) {
if ( entry.ageNormalized < 2 ) { continue; }
needEmergencyUpdate = true;
lastEmergencyUpdate = now;
break;

View File

@ -27,8 +27,6 @@ import { dom, qs$ } from './dom.js';
/******************************************************************************/
let supportData;
const uselessKeys = [
'modifiedHiddenSettings.benchmarkDatasetURL',
'modifiedHiddenSettings.blockingProfiles',
@ -138,7 +136,10 @@ function addDetailsToReportURL(id, collapse = false) {
dom.attr(elem, 'data-url', url);
}
function showData() {
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;
@ -196,6 +197,9 @@ const reportedPage = (( ) => {
dom.text(option, parsedURL.href);
select.append(option);
}
if ( url.searchParams.get('shouldUpdate') !== null ) {
dom.cl.add(dom.body, 'shouldUpdate');
}
dom.cl.add(dom.body, 'filterIssue');
return {
hostname: parsedURL.hostname.replace(/^(m|mobile|www)\./, ''),
@ -210,7 +214,7 @@ function reportSpecificFilterType() {
return qs$('select[name="type"]').value;
}
function reportSpecificFilterIssue(ev) {
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}`;
@ -228,9 +232,25 @@ function reportSpecificFilterIssue(ev) {
what: 'gotoURL',
details: { url: githubURL.href, select: true, index: -1 },
});
ev.preventDefault();
}
async function updateFilterLists() {
dom.cl.add(dom.body, 'updating');
vAPI.messaging.send('dashboard', { what: 'forceUpdateAssets' });
}
vAPI.broadcastListener.add(msg => {
switch ( msg.what ) {
case 'assetsUpdated':
showSupportData();
dom.cl.remove(dom.body, 'updating');
dom.cl.add(dom.body, 'updated');
break;
default:
break;
}
});
/******************************************************************************/
const cmEditor = new CodeMirror(qs$('#supportData'), {
@ -244,11 +264,7 @@ uBlockDashboard.patchCodeMirrorEditor(cmEditor);
/******************************************************************************/
(async ( ) => {
supportData = await vAPI.messaging.send('dashboard', {
what: 'getSupportData',
});
showData();
await showSupportData();
dom.on('[data-url]', 'click', ev => {
const elem = ev.target.closest('[data-url]');
@ -262,8 +278,16 @@ uBlockDashboard.patchCodeMirrorEditor(cmEditor);
});
if ( reportedPage !== null ) {
if ( dom.cl.has(dom.body, 'shouldUpdate') ) {
dom.on('.shouldUpdate button', 'click', ev => {
updateFilterLists();
ev.preventDefault();
});
}
dom.on('[data-i18n="supportReportSpecificButton"]', 'click', ev => {
reportSpecificFilterIssue(ev);
reportSpecificFilterIssue();
ev.preventDefault();
});
dom.on('[data-i18n="supportFindSpecificButton"]', 'click', ev => {

View File

@ -10,6 +10,7 @@
<link rel="stylesheet" href="css/themes/default.css">
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/dashboard-common.css">
<link rel="stylesheet" href="css/fa-icons.css">
<link rel="stylesheet" href="css/support.css">
<link rel="stylesheet" href="css/codemirror.css">
<link rel="shortcut icon" type="image/png" href="img/icon_64.png">
@ -62,34 +63,41 @@
</div>
<div class="e">
<h3 data-i18n="supportS6H"></h3>
<p data-i18n="supportS3P1"></p>
<div class="supportEntry shouldUpdate">
<hr>
<p data-i18n="supportS6P2S1">_</p>
<button type="button" class="iconified"><span class="fa-icon">refresh</span><span data-i18n="3pUpdateNow">_</span><span class="hover"></span></button>
<u class="updated" data-i18n="supportS6P2S2">_</u>
</div>
<div class="supportEntry">
<div>
<p data-i18n="supportS3P1">
<p data-i18n="supportS6P1S1">
</div>
<hr>
<p data-i18n="supportS6P1S1"></p>
<button type="button" data-i18n="supportFindSpecificButton">_<span class="hover"></span></button>
</div>
<div class="supportEntry">
<div>
<p>
<label data-i18n="supportS6URL"></label><br>
<select name="url">
<option></option>
</select>
<p>
<label data-i18n="supportS6Select1"></label><br>
<select name="type">
<option value="[unknown]" data-i18n="supportS6Select1Option0" selected disabled></option>
<option value="ads" data-i18n="supportS6Select1Option1"></option>
<option value="detection" data-i18n="supportS6Select1Option3"></option>
<option value="popups" data-i18n="supportS6Select1Option6"></option>
<option value="nuisance" data-i18n="supportS6Select1Option2"></option>
<option value="breakage" data-i18n="supportS6Select1Option5"></option>
<option value="privacy" data-i18n="supportS6Select1Option4"></option>
</select>
<p>
<label><span class="input checkbox"><input id="isNSFW" type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span data-i18n="supportS6Checkbox1"></span></label>
</div>
<hr>
<p>
<label data-i18n="supportS6URL"></label><br>
<select name="url">
<option></option>
</select>
</p>
<p>
<label data-i18n="supportS6Select1"></label><br>
<select name="type">
<option value="[unknown]" data-i18n="supportS6Select1Option0" selected disabled></option>
<option value="ads" data-i18n="supportS6Select1Option1"></option>
<option value="detection" data-i18n="supportS6Select1Option3"></option>
<option value="popups" data-i18n="supportS6Select1Option6"></option>
<option value="nuisance" data-i18n="supportS6Select1Option2"></option>
<option value="breakage" data-i18n="supportS6Select1Option5"></option>
<option value="privacy" data-i18n="supportS6Select1Option4"></option>
</select>
</p>
<p>
<label><span class="input checkbox"><input id="isNSFW" type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span data-i18n="supportS6Checkbox1"></span></label>
</p>
<button type="button" data-i18n="supportReportSpecificButton" class="preferred">_<span class="hover"></span></button>
</div>
<hr>
@ -109,9 +117,11 @@
<script src="lib/codemirror/addon/selection/active-line.js"></script>
<script src="lib/hsluv/hsluv-0.1.0.min.js"></script>
<script src="js/fa-icons.js" type="module"></script>
<script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/vapi-client-extra.js"></script>
<script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js" type="module"></script>