mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-10 17:17:57 +01:00
Add support for click-to-load of embedded frames
Additionally, as a requirement to support click-to-load feature, redirected resources will from now on no longer be collapsed. Related issues: - https://github.com/gorhill/uBlock/issues/2688 - https://github.com/gorhill/uBlock/issues/3619 - https://github.com/gorhill/uBlock/issues/1899 This new feature should considered in its draft stage and it needs to be fine-tuned as per feedback. Important: Only embedded frames can be converted into click-to-load widgets, as only these can be properly shieded from access by page content. Examples of usage: ||youtube.com/embed/$3p,frame,redirect=clicktoload ||scribd.com/embeds/$3p,frame,redirect=clicktoload ||player.vimeo.com/video/$3p,frame,redirect=clicktoload
This commit is contained in:
parent
ba0b62ec97
commit
5916920985
9 changed files with 238 additions and 22 deletions
48
src/css/click-to-load.css
Normal file
48
src/css/click-to-load.css
Normal file
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
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 {
|
||||
align-items: center;
|
||||
background-color: var(--default-surface);
|
||||
border: 1px solid var(--ubo-red);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.logo {
|
||||
left: 0;
|
||||
padding: 2px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
#frameURL {
|
||||
font-family: monospace;
|
||||
font-size: 90%;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#clickToLoad {
|
||||
cursor: default;
|
||||
}
|
|
@ -74,6 +74,8 @@
|
|||
:root {
|
||||
--font-size: 14px;
|
||||
|
||||
--ubo-red: #800000;
|
||||
|
||||
--default-ink: var(--ink-80);
|
||||
--default-ink-a4: var(--ink-80-a4);
|
||||
--default-ink-a50: var(--ink-80-a50);
|
||||
|
|
63
src/js/click-to-load.js
Normal file
63
src/js/click-to-load.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*******************************************************************************
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
(( ) => {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if ( typeof vAPI !== 'object' ) { return; }
|
||||
|
||||
const url = new URL(self.location.href);
|
||||
const frameURL = url.searchParams.get('url');
|
||||
const frameURLElem = document.getElementById('frameURL');
|
||||
|
||||
frameURLElem.textContent = frameURL;
|
||||
|
||||
const onWindowResize = function() {
|
||||
document.body.style.width = `${self.innerWidth}px`;
|
||||
document.body.style.height = `${self.innerHeight}px`;
|
||||
};
|
||||
|
||||
onWindowResize();
|
||||
|
||||
self.addEventListener('resize', onWindowResize);
|
||||
|
||||
document.body.addEventListener('click', ev => {
|
||||
if ( ev.isTrusted === false ) { return; }
|
||||
//if ( ev.target === frameURLElem ) { return; }
|
||||
vAPI.messaging.send('default', {
|
||||
what: 'clickToLoad',
|
||||
frameURL,
|
||||
}).then(ok => {
|
||||
if ( ok ) {
|
||||
self.location.replace(frameURL);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
|
@ -35,7 +35,8 @@
|
|||
this.aliasURL = undefined;
|
||||
this.hostname = undefined;
|
||||
this.domain = undefined;
|
||||
this.docId = undefined;
|
||||
this.docId = -1;
|
||||
this.frameId = -1;
|
||||
this.docOrigin = undefined;
|
||||
this.docHostname = undefined;
|
||||
this.docDomain = undefined;
|
||||
|
@ -69,9 +70,13 @@
|
|||
this.type = details.type;
|
||||
this.setURL(details.url);
|
||||
this.aliasURL = details.aliasURL || undefined;
|
||||
this.docId = details.type !== 'sub_frame'
|
||||
? details.frameId
|
||||
: details.parentFrameId;
|
||||
if ( details.type !== 'sub_frame' ) {
|
||||
this.docId = details.frameId;
|
||||
this.frameId = -1;
|
||||
} else {
|
||||
this.docId = details.parentFrameId;
|
||||
this.frameId = details.frameId;
|
||||
}
|
||||
if ( this.tabId > 0 ) {
|
||||
if ( this.docId === 0 ) {
|
||||
this.docOrigin = this.tabOrigin;
|
||||
|
@ -81,7 +86,7 @@
|
|||
this.setDocOriginFromURL(details.documentUrl);
|
||||
} else {
|
||||
const pageStore = µBlock.pageStoreFromTabId(this.tabId);
|
||||
const docStore = pageStore && pageStore.getFrame(this.docId);
|
||||
const docStore = pageStore && pageStore.getFrameStore(this.docId);
|
||||
if ( docStore ) {
|
||||
this.setDocOriginFromURL(docStore.rawURL);
|
||||
} else {
|
||||
|
@ -109,6 +114,7 @@
|
|||
this.hostname = other.hostname;
|
||||
this.domain = other.domain;
|
||||
this.docId = other.docId;
|
||||
this.frameId = other.frameId;
|
||||
this.docOrigin = other.docOrigin;
|
||||
this.docHostname = other.docHostname;
|
||||
this.docDomain = other.docDomain;
|
||||
|
|
|
@ -41,6 +41,15 @@
|
|||
|
||||
const µb = µBlock;
|
||||
|
||||
const clickToLoad = function(request, sender) {
|
||||
const { tabId, frameId } = µb.getMessageSenderDetails(sender);
|
||||
if ( tabId === undefined || frameId === undefined ) { return false; }
|
||||
const pageStore = µb.pageStoreFromTabId(tabId);
|
||||
if ( pageStore === null ) { return false; }
|
||||
pageStore.clickToLoad(frameId, request.frameURL);
|
||||
return true;
|
||||
};
|
||||
|
||||
const getDomainNames = function(targets) {
|
||||
const µburi = µb.URI;
|
||||
return targets.map(target => {
|
||||
|
@ -93,13 +102,17 @@ const onMessage = function(request, sender, callback) {
|
|||
}
|
||||
|
||||
// Sync
|
||||
var response;
|
||||
let response;
|
||||
|
||||
switch ( request.what ) {
|
||||
case 'applyFilterListSelection':
|
||||
response = µb.applyFilterListSelection(request);
|
||||
break;
|
||||
|
||||
case 'clickToLoad':
|
||||
response = clickToLoad(request, sender);
|
||||
break;
|
||||
|
||||
case 'createUserFilter':
|
||||
µb.createUserFilters(request);
|
||||
break;
|
||||
|
|
|
@ -84,6 +84,12 @@ const NetFilteringResultCache = class {
|
|||
this.hash = now;
|
||||
}
|
||||
|
||||
forgetResult(fctxt) {
|
||||
const key = `${fctxt.getDocHostname()} ${fctxt.type} ${fctxt.url}`;
|
||||
this.results.delete(key);
|
||||
this.blocked.delete(key);
|
||||
}
|
||||
|
||||
empty() {
|
||||
this.blocked.clear();
|
||||
this.results.clear();
|
||||
|
@ -165,6 +171,7 @@ const FrameStore = class {
|
|||
init(frameURL) {
|
||||
this.t0 = Date.now();
|
||||
this.exceptCname = undefined;
|
||||
this.clickToLoad = 0;
|
||||
this.rawURL = frameURL;
|
||||
if ( frameURL !== undefined ) {
|
||||
this.hostname = vAPI.hostnameFromURI(frameURL);
|
||||
|
@ -253,7 +260,7 @@ const PageStore = class {
|
|||
|
||||
this.frameAddCount = 0;
|
||||
this.frames = new Map();
|
||||
this.setFrame(0, tabContext.rawURL);
|
||||
this.setFrameURL(0, tabContext.rawURL);
|
||||
|
||||
// The current filtering context is cloned because:
|
||||
// - We may be called with or without the current context having been
|
||||
|
@ -308,7 +315,7 @@ const PageStore = class {
|
|||
// As part of https://github.com/chrisaljoudi/uBlock/issues/405
|
||||
// URL changed, force a re-evaluation of filtering switch
|
||||
this.rawURL = tabContext.rawURL;
|
||||
this.setFrame(0, this.rawURL);
|
||||
this.setFrameURL(0, this.rawURL);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -353,20 +360,23 @@ const PageStore = class {
|
|||
this.frames.clear();
|
||||
}
|
||||
|
||||
getFrame(frameId) {
|
||||
getFrameStore(frameId) {
|
||||
return this.frames.get(frameId) || null;
|
||||
}
|
||||
|
||||
setFrame(frameId, frameURL) {
|
||||
const frameStore = this.frames.get(frameId);
|
||||
setFrameURL(frameId, frameURL) {
|
||||
let frameStore = this.frames.get(frameId);
|
||||
if ( frameStore !== undefined ) {
|
||||
frameStore.init(frameURL);
|
||||
return;
|
||||
} else {
|
||||
frameStore = FrameStore.factory(frameURL);
|
||||
this.frames.set(frameId, frameStore);
|
||||
this.frameAddCount += 1;
|
||||
if ( (this.frameAddCount & 0b111111) === 0 ) {
|
||||
this.pruneFrames();
|
||||
}
|
||||
}
|
||||
this.frames.set(frameId, FrameStore.factory(frameURL));
|
||||
this.frameAddCount += 1;
|
||||
if ( (this.frameAddCount & 0b111111) !== 0 ) { return; }
|
||||
this.pruneFrames();
|
||||
return frameStore;
|
||||
}
|
||||
|
||||
// There is no event to tell us a specific subframe has been removed from
|
||||
|
@ -597,6 +607,22 @@ const PageStore = class {
|
|||
}
|
||||
}
|
||||
|
||||
// Click-to-load:
|
||||
// When frameId is not -1, the resource is always sub_frame.
|
||||
if ( result === 1 && fctxt.frameId !== -1 ) {
|
||||
const docStore = this.getFrameStore(fctxt.frameId);
|
||||
if ( docStore !== null && docStore.clickToLoad !== 0 ) {
|
||||
result = 2;
|
||||
if ( µb.logger.enabled ) {
|
||||
fctxt.setFilter({
|
||||
result,
|
||||
source: 'network',
|
||||
raw: 'click-to-load',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( cacheableResult ) {
|
||||
this.netFilteringCache.rememberResult(fctxt, result);
|
||||
} else if (
|
||||
|
@ -696,11 +722,19 @@ const PageStore = class {
|
|||
return 1;
|
||||
}
|
||||
|
||||
clickToLoad(frameId, frameURL) {
|
||||
let frameStore = this.getFrameStore(frameId);
|
||||
if ( frameStore === null ) {
|
||||
frameStore = this.setFrameURL(frameId, frameURL);
|
||||
}
|
||||
frameStore.clickToLoad = Date.now();
|
||||
}
|
||||
|
||||
shouldExceptCname(fctxt) {
|
||||
let exceptCname;
|
||||
let frameStore;
|
||||
if ( fctxt.docId !== undefined ) {
|
||||
frameStore = this.getFrame(fctxt.docId);
|
||||
frameStore = this.getFrameStore(fctxt.docId);
|
||||
if ( frameStore instanceof Object ) {
|
||||
exceptCname = frameStore.exceptCname;
|
||||
}
|
||||
|
@ -742,10 +776,12 @@ const PageStore = class {
|
|||
// content script-side (i.e. `iframes` -- unlike `img`).
|
||||
if ( Array.isArray(resources) && resources.length !== 0 ) {
|
||||
for ( const resource of resources ) {
|
||||
this.filterRequest(
|
||||
fctxt.setType(resource.type)
|
||||
.setURL(resource.url)
|
||||
const result = this.filterRequest(
|
||||
fctxt.setType(resource.type).setURL(resource.url)
|
||||
);
|
||||
if ( result === 1 && µb.redirectEngine.toURL(fctxt) ) {
|
||||
this.forgetBlockedResource(fctxt);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( this.netFilteringCache.hash === response.hash ) { return; }
|
||||
|
@ -753,6 +789,11 @@ const PageStore = class {
|
|||
response.blockedResources =
|
||||
this.netFilteringCache.lookupAllBlocked(fctxt.getDocHostname());
|
||||
}
|
||||
|
||||
forgetBlockedResource(fctxt) {
|
||||
if ( this.collapsibleResources.has(fctxt.type) === false ) { return; }
|
||||
this.netFilteringCache.forgetResult(fctxt);
|
||||
}
|
||||
};
|
||||
|
||||
PageStore.prototype.cacheableResults = new Set([
|
||||
|
|
|
@ -67,6 +67,10 @@ const redirectableResources = new Map([
|
|||
[ 'chartbeat.js', {
|
||||
alias: 'static.chartbeat.com/chartbeat.js',
|
||||
} ],
|
||||
[ 'click-to-load.html', {
|
||||
alias: 'clicktoload',
|
||||
params: [ 'url' ],
|
||||
} ],
|
||||
[ 'doubleclick_instream_ad_status.js', {
|
||||
alias: 'doubleclick.net/instream/ad_status.js',
|
||||
} ],
|
||||
|
@ -191,6 +195,7 @@ const RedirectEntry = class {
|
|||
this.mime = '';
|
||||
this.data = '';
|
||||
this.warURL = undefined;
|
||||
this.params = undefined;
|
||||
}
|
||||
|
||||
// Prevent redirection to web accessible resources when the request is
|
||||
|
@ -208,7 +213,15 @@ const RedirectEntry = class {
|
|||
fctxt instanceof Object &&
|
||||
fctxt.type !== 'xmlhttprequest'
|
||||
) {
|
||||
return `${this.warURL}${vAPI.warSecret()}`;
|
||||
let url = `${this.warURL}${vAPI.warSecret()}`;
|
||||
if ( this.params !== undefined ) {
|
||||
for ( const name of this.params ) {
|
||||
const value = fctxt[name];
|
||||
if ( value === undefined ) { continue; }
|
||||
url += `&${name}=${encodeURIComponent(value)}`;
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
if ( this.data === undefined ) { return; }
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/701
|
||||
|
@ -251,6 +264,7 @@ const RedirectEntry = class {
|
|||
r.mime = selfie.mime;
|
||||
r.data = selfie.data;
|
||||
r.warURL = selfie.warURL;
|
||||
r.params = selfie.params;
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
@ -721,6 +735,7 @@ RedirectEngine.prototype.loadBuiltinResources = function() {
|
|||
mime: mimeFromName(name),
|
||||
data,
|
||||
warURL: vAPI.getURL(`/web_accessible_resources/${name}`),
|
||||
params: details.params,
|
||||
});
|
||||
this.resources.set(name, entry);
|
||||
if ( details.alias !== undefined ) {
|
||||
|
|
|
@ -101,7 +101,7 @@ const onBeforeRequest = function(details) {
|
|||
details.type === 'sub_frame' &&
|
||||
details.aliasURL === undefined
|
||||
) {
|
||||
pageStore.setFrame(details.frameId, details.url);
|
||||
pageStore.setFrameURL(details.frameId, details.url);
|
||||
}
|
||||
if ( result === 2 ) {
|
||||
return { cancel: false };
|
||||
|
@ -113,10 +113,13 @@ const onBeforeRequest = function(details) {
|
|||
|
||||
// https://github.com/gorhill/uBlock/issues/949
|
||||
// Redirect blocked request?
|
||||
// https://github.com/gorhill/uBlock/issues/3619
|
||||
// Don't collapse redirected resources
|
||||
if ( µb.hiddenSettings.ignoreRedirectFilters !== true ) {
|
||||
const url = µb.redirectEngine.toURL(fctxt);
|
||||
if ( url !== undefined ) {
|
||||
pageStore.internalRedirectionCount += 1;
|
||||
pageStore.forgetBlockedResource(fctxt);
|
||||
if ( µb.logger.enabled ) {
|
||||
fctxt.setRealm('redirect')
|
||||
.setFilter({ source: 'redirect', raw: µb.redirectEngine.resourceNameRegister })
|
||||
|
|
25
src/web_accessible_resources/click-to-load.html
Normal file
25
src/web_accessible_resources/click-to-load.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html id="ublock0-clicktoload">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>uBlock Origin Click-to-Load</title>
|
||||
<link rel="stylesheet" href="../css/themes/default.css">
|
||||
<link rel="stylesheet" href="../css/common.css">
|
||||
<link rel="stylesheet" href="../css/click-to-load.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<span class="logo"><img src="../img/ublock.svg"></span>
|
||||
<span id="clickToLoad">Click to load</span>
|
||||
<div id="frameURL"></div>
|
||||
|
||||
<script src="../js/vapi.js"></script>
|
||||
<script src="../js/vapi-common.js"></script>
|
||||
<script src="../js/vapi-client.js"></script>
|
||||
<script src="../js/i18n.js"></script>
|
||||
<script src="../js/click-to-load.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue