Revisit the behavior of the click-to-subscribe content script

Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/763

Changes:

From now on, uBO will allow click-to-subscribe on only
a few select domains, currently:
- https://filterlists.com/
- https://github.com/
- https://github.io/

More domains can be added if and only the demonstration
is made that more than a marginal number of filter lists
can be subscribed from those domains.

The browser alert box is no longer used to confirm
subscription to a filter list. Instead, the asset
viewer has been expanded to serve that purpose. This
way, users can peruse at the content of a filter list
before subscribing to it.
This commit is contained in:
Raymond Hill 2020-09-13 08:01:53 -04:00
parent da7ff2b382
commit e60042595c
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
15 changed files with 198 additions and 117 deletions

View file

@ -45,8 +45,9 @@
},
{
"matches": [
"http://*/*",
"https://*/*"
"https://filterlists.com/*",
"https://github.com/*",
"https://*.github.io/*"
],
"js": [
"/js/scriptlets/subscriber.js"

View file

@ -55,8 +55,9 @@
},
{
"matches": [
"http://*/*",
"https://*/*"
"https://filterlists.com/*",
"https://github.com/*",
"https://*.github.io/*"
],
"js": [
"/js/scriptlets/subscriber.js"

View file

@ -44,15 +44,16 @@
"run_at": "document_start"
},
{
"all_frames": false,
"js": [
"js/scriptlets/subscriber.js"
],
"matches": [
"http://*/*",
"https://*/*"
"https://filterlists.com/*",
"https://github.com/*",
"https://*.github.io/*"
],
"run_at": "document_idle"
"js": [
"/js/scriptlets/subscriber.js"
],
"run_at": "document_idle",
"all_frames": false
}
],
"default_locale": "en",

View file

@ -36,8 +36,9 @@
},
{
"matches": [
"http://*/*",
"https://*/*"
"https://filterlists.com/*",
"https://github.com/*",
"https://*.github.io/*"
],
"js": [
"/js/scriptlets/subscriber.js"

View file

@ -49,8 +49,9 @@
},
{
"matches": [
"http://*/*",
"https://*/*"
"https://filterlists.com/*",
"https://github.com/*",
"https://*.github.io/*"
],
"js": [
"/js/scriptlets/subscriber.js"

View file

@ -904,8 +904,12 @@
"description": "English: Network error: {{msg}}"
},
"subscriberConfirm": {
"message": "uBlock₀: Add the following URL to your custom filter lists?\n\nTitle: \"{{title}}\"\nURL: {{url}}",
"description": "English: The message seen by the user to confirm subscription to a ABP filter list"
"message": "Add the following URL to your custom filter lists?\n\nTitle: \"{{title}}\"\nURL: {{url}}",
"description": "No longer used"
},
"subscribeButton": {
"message": "Subscribe",
"description": "For the button used to subscribe to a filter list"
},
"elapsedOneMinuteAgo": {
"message": "a minute ago",

View file

@ -11,21 +11,17 @@
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/fa-icons.css">
<link rel="stylesheet" href="css/codemirror.css">
<link rel="stylesheet" href="css/asset-viewer.css">
<link rel="shortcut icon" type="image/png" href="img/icon_32.png"/>
<style>
body {
border: 0;
margin: 0;
overflow: hidden;
padding: 0;
}
#content {
height: 100vh;
width: 100vw;
}
</style>
</head>
<body>
<body class="loading">
<div id="subscribe" class="hide">
<span class="logo"><img data-i18n-title="extName" src="img/ublock.svg"></span>
<span id="subscribePrompt"><span></span><a></a></span>
<span class="fa-icon">spinner</span>
<button id="subscribeButton" type="button" data-i18n="subscribeButton"></button>
</div>
<div id="content" class="codeMirrorContainer codeMirrorBreakAll"></div>

77
src/css/asset-viewer.css Normal file
View file

@ -0,0 +1,77 @@
/**
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
*/
body {
border: 0;
display: flex;
flex-direction: column;
height: 100vh;
margin: 0;
overflow: hidden;
padding: 0;
width: 100vw;
}
#subscribe {
background-color: var(--bg-transient-notice);
display: flex;
flex-shrink: 0;
padding: 4px;
justify-content: space-between;
max-height: 6em;
}
#subscribe.hide {
display: none;
}
.logo {
flex-shrink: 0;
width: 2em;
}
#subscribePrompt {
display: inline-flex;
flex-direction: column;
padding: 0 0.5em;
}
#subscribePrompt > span {
font-weight: bold;
}
#subscribePrompt > a {
font-size: 14px;
word-break: break-all;
}
#subscribe > button {
align-self: center;
}
#subscribe > .fa-icon {
font-size: 20px;
}
body.loading #subscribe > button,
body:not(.loading) #subscribe > .fa-icon {
display: none;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
body.loading #subscribe > .fa-icon > svg {
animation: spin 1s steps(8) infinite;
}
#content {
flex-grow: 1;
}

View file

@ -243,6 +243,16 @@ select {
}
}
.logo {
align-items: center;
display: inline-flex;
padding: 0 0.5em;
width: 1.25em;
}
.logo > img {
width: 100%;
}
.ubo-icon {
align-items: center;
background-color: transparent;

View file

@ -21,15 +21,6 @@ html, body {
width: 100%;
z-index: 10;
}
#dashboard-nav .logo {
align-items: center;
display: inline-flex;
padding: 0 0.5em;
width: 1.25em;
}
#dashboard-nav .logo > img {
width: 100%;
}
.tabButton {
border: 0;
border-bottom: 3px solid var(--bg-1);

View file

@ -12,7 +12,7 @@
<body>
<div id="dashboard-nav">
<span class="logo" ><img data-i18n-title="extName" src="img/ublock.svg"></span><!--
<span class="logo"><img data-i18n-title="extName" src="img/ublock.svg"></span><!--
--><span class="tabButton" data-pane="settings.html" data-i18n="settingsPageName"></span><!--
--><span class="tabButton" data-pane="3p-filters.html" data-i18n="3pPageName"></span><!--
--><span class="tabButton" data-pane="1p-filters.html" data-i18n="1pPageName"></span><!--

View file

@ -26,10 +26,24 @@
/******************************************************************************/
(async ( ) => {
const params = new URL(document.location).searchParams;
const assetKey = params.get('url');
const subscribeURL = new URL(document.location);
const subscribeParams = subscribeURL.searchParams;
const assetKey = subscribeParams.get('url');
if ( assetKey === null ) { return; }
const subscribeElem = subscribeParams.get('subscribe') !== null
? document.getElementById('subscribe')
: null;
if ( subscribeElem !== null && subscribeURL.hash !== '#subscribed' ) {
const title = subscribeParams.get('title');
const promptElem = document.getElementById('subscribePrompt');
promptElem.children[0].textContent = title;
const a = promptElem.children[1];
a.textContent = assetKey;
a.setAttribute('href', assetKey);
subscribeElem.classList.remove('hide');
}
const cmEditor = new CodeMirror(document.getElementById('content'), {
autofocus: true,
foldGutter: true,
@ -59,9 +73,30 @@
url: assetKey,
});
cmEditor.setValue(details && details.content || '');
if ( subscribeElem !== null ) {
document.getElementById('subscribeButton').addEventListener(
'click',
( ) => {
subscribeElem.classList.add('hide');
vAPI.messaging.send('scriptlets', {
what: 'applyFilterListSelection',
toImport: assetKey,
}).then(( ) => {
vAPI.messaging.send('scriptlets', {
what: 'reloadAllFilters'
});
});
},
{ once: true }
);
}
if ( details.sourceURL ) {
const a = document.querySelector('.cm-search-widget .sourceURL');
a.setAttribute('href', details.sourceURL);
a.setAttribute('title', details.sourceURL);
}
document.body.classList.remove('loading');
})();

View file

@ -716,6 +716,9 @@ api.get = async function(assetKey, options = {}) {
contentURLs = [ assetDetails.contentURL ];
} else if ( Array.isArray(assetDetails.contentURL) ) {
contentURLs = assetDetails.contentURL.slice(0);
} else if ( reIsExternalPath.test(assetKey) ) {
assetDetails.content = 'filters';
contentURLs = [ assetKey ];
}
for ( const contentURL of contentURLs ) {

View file

@ -56,10 +56,10 @@ const onMessage = function(request, sender, callback) {
switch ( request.what ) {
case 'getAssetContent':
// https://github.com/chrisaljoudi/uBlock/issues/417
µb.assets.get(
request.url,
{ dontCache: true, needSourceURL: true }
).then(result => {
µb.assets.get(request.url, {
dontCache: true,
needSourceURL: true,
}).then(result => {
callback(result);
});
return;
@ -1611,10 +1611,6 @@ const onMessage = function(request, sender, callback) {
logCosmeticFilters(tabId, request);
break;
case 'reloadAllFilters':
µb.loadFilterLists();
return;
case 'securityPolicyViolation':
response = logCSPViolations(pageStore, request);
break;
@ -1625,10 +1621,16 @@ const onMessage = function(request, sender, callback) {
}
break;
case 'subscriberData':
response = {
confirmStr: vAPI.i18n('subscriberConfirm')
};
case 'subscribeTo':
const url = encodeURIComponent(request.location);
const title = encodeURIComponent(request.title);
const hash = µb.availableFilterLists[request.location] !== undefined
? '#subscribed'
: '';
vAPI.tabs.open({
url: `/asset-viewer.html?url=${url}&title=${title}&subscribe=1${hash}`,
select: true,
});
break;
default:

View file

@ -31,91 +31,49 @@
/******************************************************************************/
(( ) => {
// >>>>> start of local scope
/******************************************************************************/
// https://github.com/chrisaljoudi/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
//console.debug('subscriber.js > not a HTLMDocument');
return;
}
if ( document instanceof HTMLDocument === false ) { return; }
// Because in case
if ( typeof vAPI !== 'object' ) {
//console.debug('subscriber.js > vAPI not found');
return;
}
// Maybe uBO has gone away meanwhile.
if ( typeof vAPI !== 'object' || vAPI === null ) { return; }
/******************************************************************************/
// https://github.com/easylist/EasyListHebrew/issues/89
// Ensure trusted events only.
const processSubscription = async function(location, title) {
const details = await vAPI.messaging.send('scriptlets', {
what: 'subscriberData',
});
const onMaybeSubscriptionLinkClicked = function(ev) {
if ( ev.button !== 0 || ev.isTrusted === false ) { return; }
const confirmStr = details.confirmStr
.replace('{{url}}', location)
.replace('{{title}}', title);
if ( window.confirm(confirmStr) === false ) { return; }
const target = ev.target.closest('a');
if ( target instanceof HTMLAnchorElement === false ) { return; }
await vAPI.messaging.send('scriptlets', {
what: 'applyFilterListSelection',
toImport: location,
});
vAPI.messaging.send('scriptlets', {
what: 'reloadAllFilters',
});
};
/******************************************************************************/
const onMaybeAbpLinkClicked = function(ev) {
if ( ev.button !== 0 ) { return; }
// This addresses https://github.com/easylist/EasyListHebrew/issues/89
// Also, as per feedback to original fix:
// https://github.com/gorhill/uBlock/commit/99a3d9631047d33dc7a454296ab3dd0a1e91d6f1
const target = ev.target;
if (
ev.isTrusted === false ||
target instanceof HTMLAnchorElement === false
) {
if ( vAPI instanceof Object === false ) {
document.removeEventListener('click', onMaybeSubscriptionLinkClicked);
return;
}
const href = target.href || '';
if ( href === '' ) { return; }
const matches = /^(?:abp|ubo):\/*subscribe\/*\?location=([^&]+).*title=([^&]+)/.exec(href);
if ( matches === null ) { return; }
let matches = /^(?:abp|ubo):\/*subscribe\/*\?location=([^&]+).*title=([^&]+)/.exec(href);
if ( matches === null ) {
matches = /^https?:\/\/.*?[&?]location=([^&]+).*?&title=([^&]+)/.exec(href);
if ( matches === null ) { return; }
}
const location = decodeURIComponent(matches[1]);
const title = decodeURIComponent(matches[2]);
processSubscription(location, title);
vAPI.messaging.send('scriptlets', {
what: 'subscribeTo',
location: decodeURIComponent(matches[1]),
title: decodeURIComponent(matches[2]),
});
ev.stopPropagation();
ev.preventDefault();
};
/******************************************************************************/
// Only if at least one subscribe link exists on the page.
setTimeout(function() {
if (
document.querySelector('link[rel="canonical"][href="https://filterlists.com/"]') !== null ||
document.querySelector('a[href^="abp:"],a[href^="ubo:"],a[href^="https://subscribe.adblockplus.org/?"]') !== null
) {
document.addEventListener('click', onMaybeAbpLinkClicked);
}
}, 997);
document.addEventListener('click', onMaybeSubscriptionLinkClicked);
/******************************************************************************/
// <<<<< end of local scope
})();