Skip to content

Commit

Permalink
Add support for click-to-load of embedded frames
Browse files Browse the repository at this point in the history
Additionally, as a requirement to support click-to-load
feature, redirected resources will from now on no
longer be collapsed.

Related issues:
- #2688
- #3619
- #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
  • Loading branch information
gorhill committed Oct 9, 2020
1 parent ba0b62e commit 5916920
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 22 deletions.
48 changes: 48 additions & 0 deletions src/css/click-to-load.css
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 2 additions & 0 deletions src/css/themes/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
63 changes: 63 additions & 0 deletions src/js/click-to-load.js
Original file line number Diff line number Diff line change
@@ -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);
}
});
});

/******************************************************************************/

})();
16 changes: 11 additions & 5 deletions src/js/filtering-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
15 changes: 14 additions & 1 deletion src/js/messaging.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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;
Expand Down
69 changes: 55 additions & 14 deletions src/js/pagestore.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -742,17 +776,24 @@ 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; }
response.hash = this.netFilteringCache.hash;
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([
Expand Down
17 changes: 16 additions & 1 deletion src/js/redirect-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
} ],
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
};
Expand Down Expand Up @@ -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 ) {
Expand Down
Loading

7 comments on commit 5916920

@troysjanda
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this work for Disqus Click to Load again?

@gorhill
Copy link
Owner Author

@gorhill gorhill commented on 5916920 Oct 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will have to be investigated. I found a site which uses a Disqus widget: https://www.iphoneincanada.ca/news/apple-telegram-belarus/

So it seems we might have to use a cosmetic filter to force Disqus iframe to have non-zero dimension:

*##iframe[src^="https://disqus.com/embed/comments/"]:style(height:auto!important;width:auto!important;)

More examples of sites using Disqus will have to be tested.

@gorhill
Copy link
Owner Author

@gorhill gorhill commented on 5916920 Oct 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works better:

*##iframe[src^="https://disqus.com/embed/comments/"]:style(min-height:3rem!important;min-width:100%!important;)

Examples (I will add as I find them):

  • https://www.iphoneincanada.ca/news/apple-telegram-belarus/
  • https://sparktoro.com/blog/product-market-fit-is-a-broken-concept-theres-a-better-way/
  • https://petapixel.com/2020/10/06/nvidia-uses-ai-to-slash-bandwidth-on-video-calls/
  • https://publicdomainreview.org/essay/fungi-folklore-and-fairyland

@gwarser
Copy link
Contributor

@gwarser gwarser commented on 5916920 Oct 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not block Disqus completely, because script from disqus.com is executed even before frame is loaded. It seems it's actually the script which creates the frame. Example: https://florydziak.com/blogger/2020/10/nie-uwierzycie.html

BTW, you can still use custom scriptlet: https://gist.github.com/gwarser/2d4b5dcd32b0d219e0ae65b43eb1f53d

@troysjanda
Copy link

@troysjanda troysjanda commented on 5916920 Oct 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not block Disqus completely, because script from disqus.com is executed even before frame is loaded. It seems it's actually the script which creates the frame. Example: https://florydziak.com/blogger/2020/10/nie-uwierzycie.html

BTW, you can still use custom scriptlet: https://gist.github.com/gwarser/2d4b5dcd32b0d219e0ae65b43eb1f53d

How to use the scriptlet, Do I host it on github and add to the advance settings as resource?

Never mind I figured it out, thanks

@gorhill
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not block Disqus completely, because script from disqus.com is executed even before frame is loaded

I know, but for now that's best I can do, next step is possibly to understand what that script does and have a local neutered version.

@gwarser
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*$3p,frame,redirect-rule=click2load.html is aggressively cached in Firefox. Causes placeholder to be displayed even when page is whitelisted.

  • add above filter
  • dynamic block 3p frames
  • load https://www.wykop.pl/link/5769513/za-narod-podzielony-pieklo-was-pochlonie-kibice-lech-poznan-za-kobietami/
  • disable uBO from popup power button
  • click the reload button in popup

Click2load placeholder still displayed. Forcing cache-free reload works fine.

Please sign in to comment.