Skip to content

Commit 53dd339

Browse files
committed
Improve interactivity for blocked large media elements
Related issues: - #1390 - #2334 The deadline to interactively load a specific media element has been extended from 2sec to 5sec. Clicking over a blocked large media element will cause uBO to lookup and handle all potentially blocked large elements at the cursor position. This should take care of being able to unblock media elements hidden under other DOM object. The CSS style applied to blocked large media elements has been fine tuned to improve interactivity. uBO will now remember the specific media elements which were unblocked and keep them exempted from being further blocked. This would be an issue when unblocking a video and then a bit later seeking to another point in the video, in which case uBO would again block network requests for that video.
1 parent 9947fcf commit 53dd339

File tree

5 files changed

+114
-77
lines changed

5 files changed

+114
-77
lines changed

src/js/messaging.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1641,7 +1641,7 @@ const onMessage = function(request, sender, callback) {
16411641

16421642
case 'temporarilyAllowLargeMediaElement':
16431643
if ( pageStore !== null ) {
1644-
pageStore.allowLargeMediaElementsUntil = Date.now() + 2000;
1644+
pageStore.allowLargeMediaElementsUntil = Date.now() + 5000;
16451645
}
16461646
break;
16471647

src/js/pagestore.js

+27-3
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ const PageStore = class {
244244
typeof this.allowLargeMediaElementsUntil !== 'number' ||
245245
tabContext.rootHostname !== this.tabHostname
246246
) {
247-
this.allowLargeMediaElementsUntil = 0;
247+
this.allowLargeMediaElementsUntil = Date.now();
248248
}
249249

250250
this.tabHostname = tabContext.rootHostname;
@@ -260,6 +260,7 @@ const PageStore = class {
260260
this.largeMediaCount = 0;
261261
this.largeMediaTimer = null;
262262
this.internalRedirectionCount = 0;
263+
this.allowLargeMediaElementsRegex = undefined;
263264
this.extraData.clear();
264265

265266
this.frameAddCount = 0;
@@ -339,7 +340,8 @@ const PageStore = class {
339340
this.rawURL = '';
340341
this.hostnameToCountMap = null;
341342
this.netFilteringCache.empty();
342-
this.allowLargeMediaElementsUntil = 0;
343+
this.allowLargeMediaElementsUntil = Date.now();
344+
this.allowLargeMediaElementsRegex = undefined;
343345
if ( this.largeMediaTimer !== null ) {
344346
clearTimeout(this.largeMediaTimer);
345347
this.largeMediaTimer = null;
@@ -438,7 +440,12 @@ const PageStore = class {
438440
temporarilyAllowLargeMediaElements(state) {
439441
this.largeMediaCount = 0;
440442
µb.contextMenu.update(this.tabId);
441-
this.allowLargeMediaElementsUntil = state ? Date.now() + 86400000 : 0;
443+
if ( state ) {
444+
this.allowLargeMediaElementsUntil = 0;
445+
this.allowLargeMediaElementsRegex = undefined;
446+
} else {
447+
this.allowLargeMediaElementsUntil = Date.now();
448+
}
442449
µb.scriptlets.injectDeep(this.tabId, 'load-large-media-all');
443450
}
444451

@@ -704,7 +711,23 @@ const PageStore = class {
704711
filterLargeMediaElement(fctxt, size) {
705712
fctxt.filter = undefined;
706713

714+
if ( this.allowLargeMediaElementsUntil === 0 ) {
715+
return 0;
716+
}
717+
// Disregard large media elements previously allowed: for example, to
718+
// seek inside a previously allowed audio/video.
719+
if (
720+
this.allowLargeMediaElementsRegex instanceof RegExp &&
721+
this.allowLargeMediaElementsRegex.test(fctxt.url)
722+
) {
723+
return 0;
724+
}
707725
if ( Date.now() < this.allowLargeMediaElementsUntil ) {
726+
const sources = this.allowLargeMediaElementsRegex instanceof RegExp
727+
? [ this.allowLargeMediaElementsRegex.source ]
728+
: [];
729+
sources.push('^' + µb.escapeRegex(fctxt.url));
730+
this.allowLargeMediaElementsRegex = new RegExp(sources.join('|'));
708731
return 0;
709732
}
710733
if (
@@ -713,6 +736,7 @@ const PageStore = class {
713736
fctxt.getTabHostname()
714737
) !== true
715738
) {
739+
this.allowLargeMediaElementsUntil = 0;
716740
return 0;
717741
}
718742
if ( (size >>> 10) < µb.userSettings.largeMediaSize ) {

src/js/scriptlets/load-large-media-all.js

+12-24
Original file line numberDiff line numberDiff line change
@@ -19,48 +19,36 @@
1919
Home: https://github.com/gorhill/uBlock
2020
*/
2121

22+
'use strict';
23+
2224
/******************************************************************************/
2325

24-
(function() {
25-
26-
'use strict';
26+
(( ) => {
2727

2828
/******************************************************************************/
2929

3030
// For all media resources which have failed to load, trigger a reload.
3131

32-
var elems, i, elem, src;
33-
3432
// <audio> and <video> elements.
3533
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
3634

37-
elems = document.querySelectorAll('audio,video');
38-
i = elems.length;
39-
while ( i-- ) {
40-
elem = elems[i];
41-
if ( elem.error !== null ) {
42-
elem.load();
43-
}
35+
for ( const elem of document.querySelectorAll('audio,video') ) {
36+
if ( elem.error === null ) { continue; }
37+
elem.load();
4438
}
4539

4640
// <img> elements.
4741
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
4842

49-
elems = document.querySelectorAll('img');
50-
i = elems.length;
51-
while ( i-- ) {
52-
elem = elems[i];
53-
if ( elem.naturalWidth !== 0 && elem.naturalHeight !== 0 ) {
54-
continue;
55-
}
43+
for ( const elem of document.querySelectorAll('img') ) {
44+
if ( elem.naturalWidth !== 0 && elem.naturalHeight !== 0 ) { continue; }
5645
if ( window.getComputedStyle(elem).getPropertyValue('display') === 'none' ) {
5746
continue;
5847
}
59-
src = elem.getAttribute('src');
60-
if ( src ) {
61-
elem.removeAttribute('src');
62-
elem.setAttribute('src', src);
63-
}
48+
const src = elem.getAttribute('src') || '';
49+
if ( src === '' ) { continue; }
50+
elem.removeAttribute('src');
51+
elem.setAttribute('src', src);
6452
}
6553

6654
/******************************************************************************/

src/js/scriptlets/load-large-media-interactive.js

+73-48
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ if ( typeof vAPI !== 'object' || vAPI.loadLargeMediaInteractive === true ) {
3636

3737
const largeMediaElementAttribute = 'data-' + vAPI.sessionId;
3838
const largeMediaElementSelector =
39-
':root audio[' + largeMediaElementAttribute + '],\n' +
40-
':root img[' + largeMediaElementAttribute + '],\n' +
41-
':root video[' + largeMediaElementAttribute + ']';
39+
':root audio[' + largeMediaElementAttribute + '],\n' +
40+
':root img[' + largeMediaElementAttribute + '],\n' +
41+
':root picture[' + largeMediaElementAttribute + '],\n' +
42+
':root video[' + largeMediaElementAttribute + ']';
4243

4344
/******************************************************************************/
4445

@@ -51,9 +52,7 @@ const mediaNotLoaded = function(elem) {
5152
case 'video':
5253
return elem.error !== null;
5354
case 'img':
54-
if ( elem.naturalWidth !== 0 || elem.naturalHeight !== 0 ) {
55-
break;
56-
}
55+
if ( elem.naturalWidth !== 0 || elem.naturalHeight !== 0 ) { break; }
5756
const style = window.getComputedStyle(elem);
5857
// For some reason, style can be null with Pale Moon.
5958
return style !== null ?
@@ -74,50 +73,57 @@ const mediaNotLoaded = function(elem) {
7473
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
7574

7675
const surveyMissingMediaElements = function() {
77-
var largeMediaElementCount = 0;
78-
var elems = document.querySelectorAll('audio,img,video');
79-
var i = elems.length, elem;
80-
while ( i-- ) {
81-
elem = elems[i];
82-
if ( mediaNotLoaded(elem) ) {
83-
elem.setAttribute(largeMediaElementAttribute, '');
84-
largeMediaElementCount += 1;
76+
let largeMediaElementCount = 0;
77+
for ( const elem of document.querySelectorAll('audio,img,video') ) {
78+
if ( mediaNotLoaded(elem) === false ) { continue; }
79+
elem.setAttribute(largeMediaElementAttribute, '');
80+
largeMediaElementCount += 1;
81+
switch ( elem.localName ) {
82+
case 'img': {
83+
const picture = elem.closest('picture');
84+
if ( picture !== null ) {
85+
picture.setAttribute(largeMediaElementAttribute, '');
86+
}
87+
} break;
88+
default:
89+
break;
8590
}
8691
}
8792
return largeMediaElementCount;
8893
};
8994

90-
if ( surveyMissingMediaElements() === 0 ) {
91-
return;
92-
}
95+
if ( surveyMissingMediaElements() === 0 ) { return; }
9396

9497
vAPI.loadLargeMediaInteractive = true;
9598

96-
// Insert custom style tag.
97-
let styleTag = document.createElement('style');
98-
styleTag.setAttribute('type', 'text/css');
99-
styleTag.textContent = [
100-
largeMediaElementSelector + ' {',
101-
'border: 1px dotted red !important;',
102-
'box-sizing: border-box !important;',
103-
'cursor: zoom-in !important;',
104-
'display: inline-block;',
105-
'font-size: 1em !important;',
106-
'min-height: 1em !important;',
107-
'min-width: 1em !important;',
108-
'opacity: 1 !important;',
109-
'outline: none !important;',
110-
'}'
111-
].join('\n');
112-
document.head.appendChild(styleTag);
99+
// Insert CSS to highlight blocked media elements.
100+
if ( vAPI.largeMediaElementStyleSheet === undefined ) {
101+
vAPI.largeMediaElementStyleSheet = [
102+
largeMediaElementSelector + ' {',
103+
'border: 2px dotted red !important;',
104+
'box-sizing: border-box !important;',
105+
'cursor: zoom-in !important;',
106+
'display: inline-block;',
107+
'font-size: 1rem !important;',
108+
'min-height: 1em !important;',
109+
'min-width: 1em !important;',
110+
'opacity: 1 !important;',
111+
'outline: none !important;',
112+
'visibility: visible !important;',
113+
'z-index: 2147483647',
114+
'}',
115+
].join('\n');
116+
vAPI.userStylesheet.add(vAPI.largeMediaElementStyleSheet);
117+
vAPI.userStylesheet.apply();
118+
}
113119

114120
/******************************************************************************/
115121

116122
const stayOrLeave = (( ) => {
117-
let timer = null;
123+
let timer;
118124

119125
const timeoutHandler = function(leaveNow) {
120-
timer = null;
126+
timer = undefined;
121127
if ( leaveNow !== true ) {
122128
if (
123129
document.querySelector(largeMediaElementSelector) !== null ||
@@ -127,17 +133,16 @@ const stayOrLeave = (( ) => {
127133
}
128134
}
129135
// Leave
130-
if ( styleTag !== null ) {
131-
styleTag.parentNode.removeChild(styleTag);
132-
styleTag = null;
136+
for ( const elem of document.querySelectorAll(largeMediaElementSelector) ) {
137+
elem.removeAttribute(largeMediaElementAttribute);
133138
}
134139
vAPI.loadLargeMediaInteractive = false;
135140
document.removeEventListener('error', onLoadError, true);
136141
document.removeEventListener('click', onMouseClick, true);
137142
};
138143

139144
return function(leaveNow) {
140-
if ( timer !== null ) {
145+
if ( timer !== undefined ) {
141146
clearTimeout(timer);
142147
}
143148
if ( leaveNow ) {
@@ -160,24 +165,44 @@ const loadImage = async function(elem) {
160165

161166
elem.setAttribute('src', src);
162167
elem.removeAttribute(largeMediaElementAttribute);
168+
169+
switch ( elem.localName ) {
170+
case 'img': {
171+
const picture = elem.closest('picture');
172+
if ( picture !== null ) {
173+
picture.removeAttribute(largeMediaElementAttribute);
174+
}
175+
} break;
176+
default:
177+
break;
178+
}
179+
163180
stayOrLeave();
164181
};
165182

166183
/******************************************************************************/
167184

168185
const onMouseClick = function(ev) {
169-
if ( ev.button !== 0 ) { return; }
170-
171-
const elem = ev.target;
172-
if ( elem.matches(largeMediaElementSelector) === false ) { return; }
186+
if ( ev.button !== 0 || ev.isTrusted === false ) { return; }
187+
188+
const toLoad = [];
189+
const elems = document.elementsFromPoint instanceof Function
190+
? document.elementsFromPoint(ev.clientX, ev.clientY)
191+
: [ ev.target ];
192+
for ( const elem of elems ) {
193+
if ( elem.matches(largeMediaElementSelector) && mediaNotLoaded(elem) ) {
194+
toLoad.push(elem);
195+
}
196+
}
173197

174-
if ( mediaNotLoaded(elem) === false ) {
175-
elem.removeAttribute(largeMediaElementAttribute);
198+
if ( toLoad.length === 0 ) {
176199
stayOrLeave();
177200
return;
178201
}
179202

180-
loadImage(elem);
203+
for ( const elem of toLoad ) {
204+
loadImage(elem);
205+
}
181206

182207
ev.preventDefault();
183208
ev.stopPropagation();
@@ -210,7 +235,7 @@ document.addEventListener('error', onLoadError, true);
210235

211236
/******************************************************************************/
212237

213-
vAPI.shutdown.add(function() {
238+
vAPI.shutdown.add(( ) => {
214239
stayOrLeave(true);
215240
});
216241

src/js/traffic.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ const onHeadersReceived = function(details) {
469469
if ( isRootDoc ) {
470470
const contentType = headerValueFromName('content-type', responseHeaders);
471471
if ( reMediaContentTypes.test(contentType) ) {
472-
pageStore.allowLargeMediaElementsUntil = Date.now() + 86400000;
472+
pageStore.allowLargeMediaElementsUntil = 0;
473473
return;
474474
}
475475
}

0 commit comments

Comments
 (0)