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.", "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" "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": { "supportS6URL": {
"message": "Address of the web page:", "message": "Address of the web page:",
"description": "Label for the URL of the 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 { body {
margin-bottom: 6rem; margin-bottom: 6rem;
} }
@ -11,6 +16,7 @@ h3 {
} }
.supportEntry { .supportEntry {
display: flex; display: flex;
margin-block: 1em;
} }
:root.mobile .supportEntry { :root.mobile .supportEntry {
flex-direction: column; flex-direction: column;
@ -48,6 +54,30 @@ body.filterIssue #moreButton {
display: none; 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 { button {
align-self: center; 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. // Asset updater area.
const updaterAssetDelayDefault = 120000; const updaterAssetDelayDefault = 120000;
const updaterUpdated = []; const updaterUpdated = [];

View File

@ -589,6 +589,53 @@ const getElementCount = async function(tabId, what) {
return total; 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) { const onMessage = function(request, sender, callback) {
// Async // Async
switch ( request.what ) { switch ( request.what ) {
@ -610,36 +657,6 @@ const onMessage = function(request, sender, callback) {
}); });
return; 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: default:
break; break;
} }
@ -659,6 +676,15 @@ const onMessage = function(request, sender, callback) {
response = lastModified !== request.contentLastModified; response = lastModified !== request.contentLastModified;
break; break;
} }
case 'launchReporter': {
launchReporter(request).then(url => {
if ( typeof url !== 'string' ) { return; }
µb.openNewTab({ url, select: true, index: -1 });
});
break;
}
case 'revertFirewallRules': case 'revertFirewallRules':
// TODO: use Set() to message around sets of hostnames // TODO: use Set() to message around sets of hostnames
sessionFirewall.copyRules( sessionFirewall.copyRules(

View File

@ -30,6 +30,7 @@
if ( typeof vAPI !== 'object' ) { return; } if ( typeof vAPI !== 'object' ) { return; }
if ( typeof vAPI.domFilterer !== '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. // Respect cooldown period before launching an emergency update.
const timeSinceLastEmergencyUpdate = (now - lastEmergencyUpdate) / 3600000; const timeSinceLastEmergencyUpdate = (now - lastEmergencyUpdate) / 3600000;
if ( timeSinceLastEmergencyUpdate > 1 ) { if ( timeSinceLastEmergencyUpdate > 1 ) {
const assetDict = await io.metadata(); const entries = await io.getUpdateAges({
for ( const [ assetKey, asset ] of Object.entries(assetDict) ) { filters: µb.selectedFilterLists,
if ( asset.hasRemoteURL !== true ) { continue; } internal: [ '*' ],
if ( asset.content === 'filters' ) { });
if ( µb.selectedFilterLists.includes(assetKey) === false ) { for ( const entry of entries ) {
continue; if ( entry.ageNormalized < 2 ) { continue; }
}
}
if ( asset.obsolete !== true ) { continue; }
const lastUpdateInDays = (now - asset.writeTime) / 86400000;
const daysSinceVeryObsolete = lastUpdateInDays - 2 * asset.updateAfter;
if ( daysSinceVeryObsolete < 0 ) { continue; }
needEmergencyUpdate = true; needEmergencyUpdate = true;
lastEmergencyUpdate = now; lastEmergencyUpdate = now;
break; break;

View File

@ -27,8 +27,6 @@ import { dom, qs$ } from './dom.js';
/******************************************************************************/ /******************************************************************************/
let supportData;
const uselessKeys = [ const uselessKeys = [
'modifiedHiddenSettings.benchmarkDatasetURL', 'modifiedHiddenSettings.benchmarkDatasetURL',
'modifiedHiddenSettings.blockingProfiles', 'modifiedHiddenSettings.blockingProfiles',
@ -138,7 +136,10 @@ function addDetailsToReportURL(id, collapse = false) {
dom.attr(elem, 'data-url', url); 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)); const shownData = JSON.parse(JSON.stringify(supportData));
uselessKeys.forEach(prop => { removeKey(shownData, prop); }); uselessKeys.forEach(prop => { removeKey(shownData, prop); });
const redacted = true; const redacted = true;
@ -196,6 +197,9 @@ const reportedPage = (( ) => {
dom.text(option, parsedURL.href); dom.text(option, parsedURL.href);
select.append(option); select.append(option);
} }
if ( url.searchParams.get('shouldUpdate') !== null ) {
dom.cl.add(dom.body, 'shouldUpdate');
}
dom.cl.add(dom.body, 'filterIssue'); dom.cl.add(dom.body, 'filterIssue');
return { return {
hostname: parsedURL.hostname.replace(/^(m|mobile|www)\./, ''), hostname: parsedURL.hostname.replace(/^(m|mobile|www)\./, ''),
@ -210,7 +214,7 @@ function reportSpecificFilterType() {
return qs$('select[name="type"]').value; 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 githubURL = new URL('https://github.com/uBlockOrigin/uAssets/issues/new?template=specific_report_from_ubo.yml');
const issueType = reportSpecificFilterType(); const issueType = reportSpecificFilterType();
let title = `${reportedPage.hostname}: ${issueType}`; let title = `${reportedPage.hostname}: ${issueType}`;
@ -228,9 +232,25 @@ function reportSpecificFilterIssue(ev) {
what: 'gotoURL', what: 'gotoURL',
details: { url: githubURL.href, select: true, index: -1 }, 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'), { const cmEditor = new CodeMirror(qs$('#supportData'), {
@ -244,11 +264,7 @@ uBlockDashboard.patchCodeMirrorEditor(cmEditor);
/******************************************************************************/ /******************************************************************************/
(async ( ) => { (async ( ) => {
supportData = await vAPI.messaging.send('dashboard', { await showSupportData();
what: 'getSupportData',
});
showData();
dom.on('[data-url]', 'click', ev => { dom.on('[data-url]', 'click', ev => {
const elem = ev.target.closest('[data-url]'); const elem = ev.target.closest('[data-url]');
@ -262,8 +278,16 @@ uBlockDashboard.patchCodeMirrorEditor(cmEditor);
}); });
if ( reportedPage !== null ) { 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 => { dom.on('[data-i18n="supportReportSpecificButton"]', 'click', ev => {
reportSpecificFilterIssue(ev); reportSpecificFilterIssue();
ev.preventDefault();
}); });
dom.on('[data-i18n="supportFindSpecificButton"]', 'click', ev => { 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/themes/default.css">
<link rel="stylesheet" href="css/common.css"> <link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/dashboard-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/support.css">
<link rel="stylesheet" href="css/codemirror.css"> <link rel="stylesheet" href="css/codemirror.css">
<link rel="shortcut icon" type="image/png" href="img/icon_64.png"> <link rel="shortcut icon" type="image/png" href="img/icon_64.png">
@ -62,34 +63,41 @@
</div> </div>
<div class="e"> <div class="e">
<h3 data-i18n="supportS6H"></h3> <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 class="supportEntry">
<div> <hr>
<p data-i18n="supportS3P1"> <p data-i18n="supportS6P1S1"></p>
<p data-i18n="supportS6P1S1">
</div>
<button type="button" data-i18n="supportFindSpecificButton">_<span class="hover"></span></button> <button type="button" data-i18n="supportFindSpecificButton">_<span class="hover"></span></button>
</div> </div>
<div class="supportEntry"> <div class="supportEntry">
<div> <hr>
<p> <p>
<label data-i18n="supportS6URL"></label><br> <label data-i18n="supportS6URL"></label><br>
<select name="url"> <select name="url">
<option></option> <option></option>
</select> </select>
<p> </p>
<label data-i18n="supportS6Select1"></label><br> <p>
<select name="type"> <label data-i18n="supportS6Select1"></label><br>
<option value="[unknown]" data-i18n="supportS6Select1Option0" selected disabled></option> <select name="type">
<option value="ads" data-i18n="supportS6Select1Option1"></option> <option value="[unknown]" data-i18n="supportS6Select1Option0" selected disabled></option>
<option value="detection" data-i18n="supportS6Select1Option3"></option> <option value="ads" data-i18n="supportS6Select1Option1"></option>
<option value="popups" data-i18n="supportS6Select1Option6"></option> <option value="detection" data-i18n="supportS6Select1Option3"></option>
<option value="nuisance" data-i18n="supportS6Select1Option2"></option> <option value="popups" data-i18n="supportS6Select1Option6"></option>
<option value="breakage" data-i18n="supportS6Select1Option5"></option> <option value="nuisance" data-i18n="supportS6Select1Option2"></option>
<option value="privacy" data-i18n="supportS6Select1Option4"></option> <option value="breakage" data-i18n="supportS6Select1Option5"></option>
</select> <option value="privacy" data-i18n="supportS6Select1Option4"></option>
<p> </select>
<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>
</div> <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> <button type="button" data-i18n="supportReportSpecificButton" class="preferred">_<span class="hover"></span></button>
</div> </div>
<hr> <hr>
@ -109,9 +117,11 @@
<script src="lib/codemirror/addon/selection/active-line.js"></script> <script src="lib/codemirror/addon/selection/active-line.js"></script>
<script src="lib/hsluv/hsluv-0.1.0.min.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.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.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/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js" type="module"></script> <script src="js/dashboard-common.js" type="module"></script>