mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-10 01:02:08 +01:00
Add ability to update lists through links with specifically crafted URLs
As per discussion with uBO volunteers. Volunteers offering support for uBO will be able to craft links with specially formed URLs, which once clicked will cause uBO to automatically force an update of specified filter lists. The URL must be crafted as shown in the example below: https://ublockorigin.github.io/uAssets/update-lists.html?listkeys=ublock-filters,easylist Where the `listkeys` parameter is a comma-separated list of tokens corresponding to filter lists. If a token does not match an enabled filter list, it will be ignored. The ability to update filter lists through a specially crafted link is available only on uBO's own support sites: - https://github.com/uBlockOrigin/ - https://reddit.com/r/uBlockOrigin/ - https://ublockorigin.github.io/ Additionally, a visual cue has been added in the "Filter lists" pane to easily spot the filter lists which have been recently updated, where "recently" is currently defined as less than an hour ago.
This commit is contained in:
parent
17d30343c5
commit
0325dcdcb4
7 changed files with 152 additions and 5 deletions
|
@ -62,6 +62,18 @@
|
||||||
],
|
],
|
||||||
"run_at": "document_idle",
|
"run_at": "document_idle",
|
||||||
"all_frames": false
|
"all_frames": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matches": [
|
||||||
|
"https://github.com/uBlockOrigin/*",
|
||||||
|
"https://ublockorigin.github.io/*",
|
||||||
|
"https://*.reddit.com/r/uBlockOrigin/*"
|
||||||
|
],
|
||||||
|
"js": [
|
||||||
|
"/js/scriptlets/updater.js"
|
||||||
|
],
|
||||||
|
"run_at": "document_idle",
|
||||||
|
"all_frames": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"content_security_policy": "script-src 'self'; object-src 'self'",
|
"content_security_policy": "script-src 'self'; object-src 'self'",
|
||||||
|
|
|
@ -204,6 +204,10 @@ body.working #actions button {
|
||||||
#lists .listEntry.checked.cached:not(.obsolete) > .detailbar .iconbar .cache {
|
#lists .listEntry.checked.cached:not(.obsolete) > .detailbar .iconbar .cache {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
#lists .listEntry.cached.recent:not(.obsolete) > .detailbar .iconbar .cache {
|
||||||
|
color: var(--dashboard-happy-green);
|
||||||
|
fill: var(--dashboard-happy-green);
|
||||||
|
}
|
||||||
#lists .iconbar .obsolete {
|
#lists .iconbar .obsolete {
|
||||||
color: var(--info2-ink);
|
color: var(--info2-ink);
|
||||||
fill: var(--info2-ink);
|
fill: var(--info2-ink);
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
--green-40: 84 255 189;
|
--green-40: 84 255 189;
|
||||||
--green-50: 63 225 176;
|
--green-50: 63 225 176;
|
||||||
--green-60: 42 195 162;
|
--green-60: 42 195 162;
|
||||||
|
--green-65: 21 165 149;
|
||||||
--green-70: 0 135 135;
|
--green-70: 0 135 135;
|
||||||
--green-80: 0 94 94;
|
--green-80: 0 94 94;
|
||||||
--ink-10: 57 52 115;
|
--ink-10: 57 52 115;
|
||||||
|
@ -239,6 +240,8 @@
|
||||||
--dashboard-tab-focus-surface-rgb: var(--primary-90);
|
--dashboard-tab-focus-surface-rgb: var(--primary-90);
|
||||||
--dashboard-highlight-surface-rgb: var(--primary-90);
|
--dashboard-highlight-surface-rgb: var(--primary-90);
|
||||||
|
|
||||||
|
--dashboard-happy-green: rgb(var(--green-65));
|
||||||
|
|
||||||
/* popup panel */
|
/* popup panel */
|
||||||
--popup-cell-cname-ink: #0054d7; /* h260 S:100 Luv:40 */;
|
--popup-cell-cname-ink: #0054d7; /* h260 S:100 Luv:40 */;
|
||||||
--popup-cell-label-mixed-surface: #c29100; /* TODO: fix */
|
--popup-cell-label-mixed-surface: #c29100; /* TODO: fix */
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { dom, qs$, qsa$ } from './dom.js';
|
||||||
const lastUpdateTemplateString = i18n$('3pLastUpdate');
|
const lastUpdateTemplateString = i18n$('3pLastUpdate');
|
||||||
const obsoleteTemplateString = i18n$('3pExternalListObsolete');
|
const obsoleteTemplateString = i18n$('3pExternalListObsolete');
|
||||||
const reValidExternalList = /^[a-z-]+:\/\/(?:\S+\/\S*|\/\S+)/m;
|
const reValidExternalList = /^[a-z-]+:\/\/(?:\S+\/\S*|\/\S+)/m;
|
||||||
|
const recentlyUpdated = 1 * 60 * 60 * 1000; // 1 hour
|
||||||
|
|
||||||
let listsetDetails = {};
|
let listsetDetails = {};
|
||||||
|
|
||||||
|
@ -154,6 +155,8 @@ const renderFilterLists = ( ) => {
|
||||||
if ( asset.cached === true ) {
|
if ( asset.cached === true ) {
|
||||||
dom.cl.add(listEntry, 'cached');
|
dom.cl.add(listEntry, 'cached');
|
||||||
dom.attr(qs$(listEntry, ':scope > .detailbar .status.cache'), 'title', lastUpdateString);
|
dom.attr(qs$(listEntry, ':scope > .detailbar .status.cache'), 'title', lastUpdateString);
|
||||||
|
const timeSinceLastUpdate = Date.now() - asset.writeTime;
|
||||||
|
dom.cl.toggle(listEntry, 'recent', timeSinceLastUpdate < recentlyUpdated);
|
||||||
} else {
|
} else {
|
||||||
dom.cl.remove(listEntry, 'cached');
|
dom.cl.remove(listEntry, 'cached');
|
||||||
}
|
}
|
||||||
|
@ -308,7 +311,7 @@ const updateAssetStatus = details => {
|
||||||
dom.attr(qs$(listEntry, '.status.cache'), 'title',
|
dom.attr(qs$(listEntry, '.status.cache'), 'title',
|
||||||
lastUpdateTemplateString.replace('{{ago}}', i18n.renderElapsedTimeToString(Date.now()))
|
lastUpdateTemplateString.replace('{{ago}}', i18n.renderElapsedTimeToString(Date.now()))
|
||||||
);
|
);
|
||||||
|
dom.cl.add(listEntry, 'recent');
|
||||||
}
|
}
|
||||||
updateAncestorListNodes(listEntry, ancestor => {
|
updateAncestorListNodes(listEntry, ancestor => {
|
||||||
updateListNode(ancestor);
|
updateListNode(ancestor);
|
||||||
|
@ -413,7 +416,8 @@ const updateListNode = listNode => {
|
||||||
let totalFilterCount = 0;
|
let totalFilterCount = 0;
|
||||||
let isCached = false;
|
let isCached = false;
|
||||||
let isObsolete = false;
|
let isObsolete = false;
|
||||||
let writeTime = 0;
|
let latestWriteTime = 0;
|
||||||
|
let oldestWriteTime = Number.MAX_SAFE_INTEGER;
|
||||||
for ( const listLeaf of checkedListLeaves ) {
|
for ( const listLeaf of checkedListLeaves ) {
|
||||||
const listkey = listLeaf.dataset.key;
|
const listkey = listLeaf.dataset.key;
|
||||||
const listDetails = listsetDetails.available[listkey];
|
const listDetails = listsetDetails.available[listkey];
|
||||||
|
@ -422,7 +426,8 @@ const updateListNode = listNode => {
|
||||||
const assetCache = listsetDetails.cache[listkey] || {};
|
const assetCache = listsetDetails.cache[listkey] || {};
|
||||||
isCached = isCached || dom.cl.has(listLeaf, 'cached');
|
isCached = isCached || dom.cl.has(listLeaf, 'cached');
|
||||||
isObsolete = isObsolete || dom.cl.has(listLeaf, 'obsolete');
|
isObsolete = isObsolete || dom.cl.has(listLeaf, 'obsolete');
|
||||||
writeTime = Math.max(writeTime, assetCache.writeTime || 0);
|
latestWriteTime = Math.max(latestWriteTime, assetCache.writeTime || 0);
|
||||||
|
oldestWriteTime = Math.min(oldestWriteTime, assetCache.writeTime || Number.MAX_SAFE_INTEGER);
|
||||||
}
|
}
|
||||||
dom.cl.toggle(listNode, 'checked', checkedListLeaves.length !== 0);
|
dom.cl.toggle(listNode, 'checked', checkedListLeaves.length !== 0);
|
||||||
dom.cl.toggle(qs$(listNode, ':scope > .detailbar .checkbox'),
|
dom.cl.toggle(qs$(listNode, ':scope > .detailbar .checkbox'),
|
||||||
|
@ -449,8 +454,9 @@ const updateListNode = listNode => {
|
||||||
dom.cl.toggle(listNode, 'obsolete', isObsolete);
|
dom.cl.toggle(listNode, 'obsolete', isObsolete);
|
||||||
if ( isCached ) {
|
if ( isCached ) {
|
||||||
dom.attr(qs$(listNode, ':scope > .detailbar .cache'), 'title',
|
dom.attr(qs$(listNode, ':scope > .detailbar .cache'), 'title',
|
||||||
lastUpdateTemplateString.replace('{{ago}}', i18n.renderElapsedTimeToString(writeTime))
|
lastUpdateTemplateString.replace('{{ago}}', i18n.renderElapsedTimeToString(latestWriteTime))
|
||||||
);
|
);
|
||||||
|
dom.cl.toggle(listNode, 'recent', (Date.now() - oldestWriteTime) < recentlyUpdated);
|
||||||
}
|
}
|
||||||
if ( qs$(listNode, '.listEntry.isDefault') !== null ) {
|
if ( qs$(listNode, '.listEntry.isDefault') !== null ) {
|
||||||
dom.cl.add(listNode, 'isDefault');
|
dom.cl.add(listNode, 'isDefault');
|
||||||
|
|
|
@ -149,10 +149,18 @@ if ( self.location.hash.slice(1) === 'no-dashboard.html' ) {
|
||||||
dom.on('.tabButton', 'click', onTabClickHandler);
|
dom.on('.tabButton', 'click', onTabClickHandler);
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
|
// https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
|
||||||
dom.on(window, 'beforeunload', ( ) => {
|
dom.on(self, 'beforeunload', ( ) => {
|
||||||
if ( discardUnsavedData(true) ) { return; }
|
if ( discardUnsavedData(true) ) { return; }
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.returnValue = '';
|
event.returnValue = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
|
||||||
|
dom.on(self, 'hashchange', ( ) => {
|
||||||
|
const pane = self.location.hash.slice(1);
|
||||||
|
if ( pane === '' ) { return; }
|
||||||
|
loadDashboardPanel(pane);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -2057,6 +2057,21 @@ const onMessage = function(request, sender, callback) {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'updateLists':
|
||||||
|
const listkeys = request.listkeys.split(',').filter(s => s !== '');
|
||||||
|
if ( listkeys.length === 0 ) { return; }
|
||||||
|
for ( const listkey of listkeys ) {
|
||||||
|
io.purge(listkey);
|
||||||
|
io.remove(`compiled/${listkey}`);
|
||||||
|
}
|
||||||
|
µb.scheduleAssetUpdater(0);
|
||||||
|
µb.openNewTab({
|
||||||
|
url: 'dashboard.html#3p-filters.html',
|
||||||
|
select: true,
|
||||||
|
});
|
||||||
|
io.updateStart({ delay: 100 });
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return vAPI.messaging.UNHANDLED;
|
return vAPI.messaging.UNHANDLED;
|
||||||
}
|
}
|
||||||
|
|
99
src/js/scriptlets/updater.js
Normal file
99
src/js/scriptlets/updater.js
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
uBlock Origin - a browser extension to block requests.
|
||||||
|
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 HTMLDocument */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// Injected into specific webpages, those which have been pre-selected
|
||||||
|
// because they are known to contain `https://ublockorigin.github.io/update-lists?` links.
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
(( ) => {
|
||||||
|
// >>>>> start of local scope
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
if ( document instanceof HTMLDocument === false ) { return; }
|
||||||
|
|
||||||
|
// Maybe uBO has gone away meanwhile.
|
||||||
|
if ( typeof vAPI !== 'object' || vAPI === null ) { return; }
|
||||||
|
|
||||||
|
function updateStockLists(target) {
|
||||||
|
if ( vAPI instanceof Object === false ) {
|
||||||
|
document.removeEventListener('click', updateStockLists);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const updateURL = new URL(target.href);
|
||||||
|
if ( updateURL.hostname !== 'ublockorigin.github.io') { return; }
|
||||||
|
if ( updateURL.pathname !== '/uAssets/update-lists.html') { return; }
|
||||||
|
const listkeys = updateURL.searchParams.get('listkeys') || '';
|
||||||
|
if ( listkeys === '' ) { return true; }
|
||||||
|
vAPI.messaging.send('scriptlets', {
|
||||||
|
what: 'updateLists',
|
||||||
|
listkeys,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/easylist/EasyListHebrew/issues/89
|
||||||
|
// Ensure trusted events only.
|
||||||
|
|
||||||
|
document.addEventListener('click', ev => {
|
||||||
|
if ( ev.button !== 0 || ev.isTrusted === false ) { return; }
|
||||||
|
const target = ev.target.closest('a');
|
||||||
|
if ( target instanceof HTMLAnchorElement === false ) { return; }
|
||||||
|
if ( updateStockLists(target) === true ) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// <<<<< end of local scope
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
DO NOT:
|
||||||
|
- Remove the following code
|
||||||
|
- Add code beyond the following code
|
||||||
|
Reason:
|
||||||
|
- https://github.com/gorhill/uBlock/pull/3721
|
||||||
|
- uBO never uses the return value from injected content scripts
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
void 0;
|
Loading…
Reference in a new issue