10000 Blocking large media elements also prevents autoplay, regardless of size · gorhill/uBlock@73ce4e6 · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Commit 73ce4e6

Browse files
committed
Blocking large media elements also prevents autoplay, regardless of size
Related issue: uBlockOrigin/uBlock-issues#3394 When the "No large media elements" per-site switch is toggled on, it will also act to prevent autoplay of video/audio media, regardless of their size. This also works for xhr-based media streaming. If blocking by size is not desirable while blocking autoplay is desired, one can toggle on "No large media elements" switch while setting "Block media elements larger than ..." to a very high value.
1 parent 0b02c7c commit 73ce4e6

File tree

6 files changed

+139
-132
lines changed

6 files changed

+139
-132
lines changed

platform/chromium/vapi-background-ext.js

+21-49
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@
1919
Home: https://github.com/gorhill/uBlock
2020
*/
2121

22-
/* globals browser */
23-
24-
'use strict';
25-
2622
/******************************************************************************/
2723

2824
// https://github.com/uBlockOrigin/uBlock-issues/issues/1659
@@ -90,71 +86,47 @@ vAPI.Tabs = class extends vAPI.Tabs {
9086
['gif','image'],['ico','image'],['jpeg','image'],['jpg','image'],['png','image'],['webp','image']
9187
]);
9288

93-
const headerValue = (headers, name) => {
94-
let i = headers.length;
95-
while ( i-- ) {
96-
if ( headers[i].name.toLowerCase() === name ) {
97-
return headers[i].value.trim();
98-
}
99-
}
100-
return '';
101-
};
102-
10389
const parsedURL = new URL('https://www.example.org/');
10490

105-
// Extend base class to normalize as per platform.
91+
// Extend base class to normalize as per platform
10692

10793
vAPI.Net = class extends vAPI.Net {
10894
normalizeDetails(details) {
10995
// Chromium 63+ supports the `initiator` property, which contains
110-
// the URL of the origin from which the network request was made.
111-
if (
112-
typeof details.initiator === 'string' &&
113-
details.initiator !== 'null'
114-
) {
96+
// the URL of the origin from which the network request was made
97+
if ( details.initiator && details.initiator !== 'null' ) {
11598
details.documentUrl = details.initiator;
11699
}
117-
118-
let type = details.type;
119-
100+
const type = details.type;
120101
if ( type === 'imageset' ) {
121102
details.type = 'image';
122103
return;
123104
}
124-
125-
// The rest of the function code is to normalize type
126105
if ( type !== 'other' ) { return; }
127-
128-
// Try to map known "extension" part of URL to request type.
129-
parsedURL.href = details.url;
130-
const path = parsedURL.pathname,
131-
pos = path.indexOf('.', path.length - 6);
132-
if ( pos !== -1 && (type = extToTypeMap.get(path.slice(pos + 1))) ) {
133-
details.type = type;
106+
// Try to map known "extension" part of URL to request type
107+
if ( details.responseHeaders === undefined ) {
108+
parsedURL.href = details.url;
109+
const path = parsedURL.pathname;
110+
const pos = path.indexOf('.', path.length - 6);
111+
if ( pos !== -1 ) {
112+
details.type = extToTypeMap.get(path.slice(pos + 1)) || type;
113+
}
134114
return;
135115
}
136-
137-
// Try to extract type from response headers if present.
138-
if ( details.responseHeaders ) {
139-
type = headerValue(details.responseHeaders, 'content-type');
140-
if ( type.startsWith('font/') ) {
141-
details.type = 'font';
142-
return;
143-
}
144-
if ( type.startsWith('image/') ) {
145-
details.type = 'image';
146-
return;
147-
}
148-
if ( type.startsWith('audio/') || type.startsWith('video/') ) {
149-
details.type = 'media';
150-
return;
151-
}
116+
// Try to extract type from response headers
117+
const ctype = this.headerValue(details.responseHeaders, 'content-type');
118+
if ( ctype.startsWith('font/') ) {
119+
details.type = 'font';
120+
} else if ( ctype.startsWith('image/') ) {
121+
details.type = 'image';
122+
} else if ( ctype.startsWith('audio/') || ctype.startsWith('video/') ) {
123+
details.type = 'media';
152124
}
153125
}
154126

155127
// https://www.reddit.com/r/uBlockOrigin/comments/9vcrk3/
156128
// Some types can be mapped from 'other', thus include 'other' if and
157-
// only if the caller is interested in at least one of those types.
129+
// only if the caller is interested in at least one of those types
158130
denormalizeTypes(types) {
159131
if ( types.length === 0 ) {
160132
return Array.from(this.validTypes);

platform/common/vapi-background.js

+8
Original file line numberDiff line numberDiff line change
@@ -1382,6 +1382,14 @@ vAPI.Net = class {
13821382
if ( this.suspendDepth !== 0 ) { return; }
13831383
this.unsuspendAllRequests(discard);
13841384
}
1385+
headerValue(headers, name) {
1386+
for ( const header of headers ) {
1387+
if ( header.name.toLowerCase() === name ) {
1388+
return header.value.trim();
1389+
}
1390+
}
1391+
return '';
1392+
}
13851393
static canSuspend() {
13861394
return false;
13871395
}

platform/firefox/vapi-background-ext.js

+6-9
Original file line numberDiff line numberDiff line change
@@ -110,18 +110,15 @@ vAPI.Net = class extends vAPI.Net {
110110
details.type = 'image';
111111
return;
112112
}
113+
if ( type !== 'object' ) { return; }
114+
// Try to extract type from response headers if present.
115+
if ( details.responseHeaders === undefined ) { return; }
116+
const ctype = this.headerValue(details.responseHeaders, 'content-type');
113117
// https://github.com/uBlockOrigin/uBlock-issues/issues/345
114118
// Re-categorize an embedded object as a `sub_frame` if its
115119
// content type is that of a HTML document.
116-
if ( type === 'object' && Array.isArray(details.responseHeaders) ) {
117-
for ( const header of details.responseHeaders ) {
118-
if ( header.name.toLowerCase() === 'content-type' ) {
119-
if ( header.value.startsWith('text/html') ) {
120-
details.type = 'sub_frame';
121-
}
122-
break;
123-
}
124-
}
120+
if ( ctype === 'text/html' ) {
121+
details.type = 'sub_frame';
125122
}
126123
}
127124

src/js/pagestore.js

+27-24
Original file line numberDiff line numberDiff line change
@@ -1019,48 +1019,51 @@ const PageStore = class {
10191019
}
10201020

10211021
// The caller is responsible to check whether filtering is enabled or not.
1022-
filterLargeMediaElement(fctxt, size) {
1022+
filterLargeMediaElement(fctxt, headers) {
10231023
fctxt.filter = undefined;
1024-
1025-
if ( this.allowLargeMediaElementsUntil === 0 ) {
1024+
if ( this.allowLargeMediaElementsUntil === 0 ) { return 0; }
1025+
if ( sessionSwitches.evaluateZ('no-large-media', fctxt.getTabHostname() ) !== true ) {
1026+
this.allowLargeMediaElementsUntil = 0;
10261027
return 0;
10271028
}
1028-
// Disregard large media elements previously allowed: for example, to
1029-
// seek inside a previously allowed audio/video.
1030-
if (
1031-
this.allowLargeMediaElementsRegex instanceof RegExp &&
1032-
this.allowLargeMediaElementsRegex.test(fctxt.url)
1033-
) {
1029+
// XHR-based streaming is never blocked but we want to prevent autoplay
1030+
if ( fctxt.itype === fctxt.XMLHTTPREQUEST ) {
1031+
const ctype = headers.contentType;
1032+
if ( ctype.startsWith('audio/') || ctype.startsWith('video/') ) {
1033+
this.largeMediaTimer.on(500);
1034+
}
10341035
return 0;
10351036
}
10361037
if ( Date.now() < this.allowLargeMediaElementsUntil ) {
1037-
const sources = this.allowLargeMediaElementsRegex instanceof RegExp
1038-
? [ this.allowLargeMediaElementsRegex.source ]
1039-
: [];
1040-
sources.push('^' + µb.escapeRegex(fctxt.url));
1041-
this.allowLargeMediaElementsRegex = new RegExp(sources.join('|'));
1038+
if ( fctxt.itype === fctxt.MEDIA ) {
1039+
const sources = this.allowLargeMediaElementsRegex instanceof RegExp
1040+
? [ this.allowLargeMediaElementsRegex.source ]
1041+
: [];
1042+
sources.push('^' + µb.escapeRegex(fctxt.url));
1043+
this.allowLargeMediaElementsRegex = new RegExp(sources.join('|'));
1044+
}
10421045
return 0;
10431046
}
1047+
// Disregard large media elements previously allowed: for example, to
1048+
// seek inside a previously allowed audio/video.
10441049
if (
1045-
sessionSwitches.evaluateZ(
1046-
'no-large-media',
1047-
fctxt.getTabHostname()
1048-
) !== true
1050+
this.allowLargeMediaElementsRegex instanceof RegExp &&
1051+
this.allowLargeMediaElementsRegex.test(fctxt.url)
10491052
) {
1050-
this.allowLargeMediaElementsUntil = 0;
10511053
return 0;
10521054
}
1053-
if ( (size >>> 10) < µb.userSettings.largeMediaSize ) {
1054-
return 0;
1055+
// Regardless of whether a media is blocked, we want to prevent autoplay
1056+
if ( fctxt.itype === fctxt.MEDIA ) {
1057+
this.largeMediaTimer.on(500);
10551058
}
1056-
1059+
const size = headers.contentLength;
1060+
if ( isNaN(size) ) { return 0; }
1061+
if ( (size >>> 10) < µb.userSettings.largeMediaSize ) { return 0; }
10571062
this.largeMediaCount += 1;
10581063
this.largeMediaTimer.on(500);
1059-
10601064
if ( logger.enabled ) {
10611065
fctxt.filter = sessionSwitches.toLogData();
10621066
}
1063-
10641067
return 1;
10651068
}
10661069

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

+49-36
Original file line numberDiff line numberDiff line change
@@ -28,34 +28,26 @@ if ( typeof vAPI !== 'object' || vAPI.loadAllLargeMedia instanceof Function ) {
2828
return;
2929
}
3030

31-
/******************************************************************************/
32-
3331
const largeMediaElementAttribute = 'data-' + vAPI.sessionId;
3432
const largeMediaElementSelector =
3533
':root audio[' + largeMediaElementAttribute + '],\n' +
3634
':root img[' + largeMediaElementAttribute + '],\n' +
3735
':root picture[' + largeMediaElementAttribute + '],\n' +
3836
':root video[' + largeMediaElementAttribute + ']';
3937

40-
/******************************************************************************/
38+
const isMediaElement = elem =>
39+
(/^(?:audio|img|picture|video)$/.test(elem.localName));
4140

42-
const isMediaElement = function(elem) {
43-
return /^(?:audio|img|picture|video)$/.test(elem.localName);
44-
};
41+
const isPlayableMediaElement = elem =>
42+
(/^(?:audio|video)$/.test(elem.localName));
4543

4644
/******************************************************************************/
4745

4846
const mediaNotLoaded = function(elem) {
4947
switch ( elem.localName ) {
5048
case 'audio': 8000
51-
case 'video': {
52-
const src = elem.src || '';
53-
if ( src.startsWith('blob:') ) {
54-
elem.autoplay = false;
55-
elem.pause();
56-
}
49+
case 'video':
5750
return elem.readyState === 0 || elem.error !== null;
58-
}
5951
case 'img': {
6052
if ( elem.naturalWidth !== 0 || elem.naturalHeight !== 0 ) {
6153
break;
@@ -99,29 +91,29 @@ const surveyMissingMediaElements = function() {
9991
return largeMediaElementCount;
10092
};
10193

102-
if ( surveyMissingMediaElements() === 0 ) { return; }
103-
104-
// Insert CSS to highlight blocked media elements.
105-
if ( vAPI.largeMediaElementStyleSheet === undefined ) {
106-
vAPI.largeMediaElementStyleSheet = [
107-
largeMediaElementSelector + ' {',
108-
'border: 2px dotted red !important;',
109-
'box-sizing: border-box !important;',
110-
'cursor: zoom-in !important;',
111-
'display: inline-block;',
112-
'filter: none !important;',
113-
'font-size: 1rem !important;',
114-
'min-height: 1em !important;',
115-
'min-width: 1em !important;',
116-
'opacity: 1 !important;',
117-
'outline: none !important;',
118-
'transform: none !important;',
119-
'visibility: visible !important;',
120-
'z-index: 2147483647',
121-
'}',
122-
].join('\n');
123-
vAPI.userStylesheet.add(vAPI.largeMediaElementStyleSheet);
124-
vAPI.userStylesheet.apply();
94+
if ( surveyMissingMediaElements() ) {
95+
// Insert CSS to highlight blocked media elements.
96+
if ( vAPI.largeMediaElementStyleSheet === undefined ) {
97+
vAPI.largeMediaElementStyleSheet = [
98+
largeMediaElementSelector + ' {',
99+
'border: 2px dotted red !important;',
100+
'box-sizing: border-box !important;',
101+
'cursor: zoom-in !important;',
102+
'display: inline-block;',
103+
'filter: none !important;',
104+
'font-size: 1rem !important;',
105+
'min-height: 1em !important;',
106+
'min-width: 1em !important;',
107+
'opacity: 1 !important;',
108+
'outline: none !important;',
109+
'transform: none !important;',
110+
'visibility: visible !important;',
111+
'z-index: 2147483647',
112+
'}',
113+
].join('\n');
114+
vAPI.userStylesheet.add(vAPI.largeMediaElementStyleSheet);
115+
vAPI.userStylesheet.apply();
116+
}
125117
}
126118

127119
/******************************************************************************/
@@ -258,6 +250,27 @@ document.addEventListener('error', onLoadError, true);
258250

259251
/******************************************************************************/
260252

253+
const autoPausedMedia = new WeakMap();
254+
255+
for ( const elem of document.querySelectorAll('audio,video') ) {
256+
elem.setAttribute('autoplay', 'false');
257+
}
258+
259+
const preventAutoplay = function(ev) {
260+
const elem = ev.target;
261+
if ( isPlayableMediaElement(elem) === false ) { return; }
262+
const currentSrc = elem.getAttribute('src') || '';
263+
const pausedSrc = autoPausedMedia.get(elem);
264+
if ( pausedSrc === currentSrc ) { return; }
265+
autoPausedMedia.set(elem, currentSrc);
266+
elem.setAttribute('autoplay', 'false');
267+
elem.pause();
268+
};
269+
270+
document.addEventListener('timeupdate', preventAutoplay, true);
271+
272+
/******************************************************************************/
273+
261274
vAPI.loadAllLargeMedia = function() {
262275
document.removeEventListener('click', onMouseClick, true);
263276
document.removeEventListener('loadeddata', onLoadedData, true);

0 commit comments

Comments
 (0)
0