summaryrefslogtreecommitdiffstats
path: root/src/js/scriptlets/load-large-media-interactive.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/scriptlets/load-large-media-interactive.js')
-rw-r--r--src/js/scriptlets/load-large-media-interactive.js299
1 files changed, 299 insertions, 0 deletions
diff --git a/src/js/scriptlets/load-large-media-interactive.js b/src/js/scriptlets/load-large-media-interactive.js
new file mode 100644
index 0000000..57198e4
--- /dev/null
+++ b/src/js/scriptlets/load-large-media-interactive.js
@@ -0,0 +1,299 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2015-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';
+
+/******************************************************************************/
+
+(( ) => {
+
+/******************************************************************************/
+
+// This can happen
+if ( typeof vAPI !== 'object' || vAPI.loadAllLargeMedia instanceof Function ) {
+ return;
+}
+
+/******************************************************************************/
+
+const largeMediaElementAttribute = 'data-' + vAPI.sessionId;
+const largeMediaElementSelector =
+ ':root audio[' + largeMediaElementAttribute + '],\n' +
+ ':root img[' + largeMediaElementAttribute + '],\n' +
+ ':root picture[' + largeMediaElementAttribute + '],\n' +
+ ':root video[' + largeMediaElementAttribute + ']';
+
+/******************************************************************************/
+
+const isMediaElement = function(elem) {
+ return /^(?:audio|img|picture|video)$/.test(elem.localName);
+};
+
+/******************************************************************************/
+
+const mediaNotLoaded = function(elem) {
+ switch ( elem.localName ) {
+ case 'audio':
+ case 'video': {
+ const src = elem.src || '';
+ if ( src.startsWith('blob:') ) {
+ elem.autoplay = false;
+ elem.pause();
+ }
+ return elem.readyState === 0 || elem.error !== null;
+ }
+ case 'img': {
+ if ( elem.naturalWidth !== 0 || elem.naturalHeight !== 0 ) {
+ break;
+ }
+ const style = window.getComputedStyle(elem);
+ // For some reason, style can be null with Pale Moon.
+ return style !== null ?
+ style.getPropertyValue('display') !== 'none' :
+ elem.offsetHeight !== 0 && elem.offsetWidth !== 0;
+ }
+ default:
+ break;
+ }
+ return false;
+};
+
+/******************************************************************************/
+
+// For all media resources which have failed to load, trigger a reload.
+
+// <audio> and <video> elements.
+// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
+
+const surveyMissingMediaElements = function() {
+ let largeMediaElementCount = 0;
+ for ( const elem of document.querySelectorAll('audio,img,video') ) {
+ if ( mediaNotLoaded(elem) === false ) { continue; }
+ elem.setAttribute(largeMediaElementAttribute, '');
+ largeMediaElementCount += 1;
+ switch ( elem.localName ) {
+ case 'img': {
+ const picture = elem.closest('picture');
+ if ( picture !== null ) {
+ picture.setAttribute(largeMediaElementAttribute, '');
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+ return largeMediaElementCount;
+};
+
+if ( surveyMissingMediaElements() === 0 ) { return; }
+
+// Insert CSS to highlight blocked media elements.
+if ( vAPI.largeMediaElementStyleSheet === undefined ) {
+ vAPI.largeMediaElementStyleSheet = [
+ largeMediaElementSelector + ' {',
+ 'border: 2px dotted red !important;',
+ 'box-sizing: border-box !important;',
+ 'cursor: zoom-in !important;',
+ 'display: inline-block;',
+ 'filter: none !important;',
+ 'font-size: 1rem !important;',
+ 'min-height: 1em !important;',
+ 'min-width: 1em !important;',
+ 'opacity: 1 !important;',
+ 'outline: none !important;',
+ 'transform: none !important;',
+ 'visibility: visible !important;',
+ 'z-index: 2147483647',
+ '}',
+ ].join('\n');
+ vAPI.userStylesheet.add(vAPI.largeMediaElementStyleSheet);
+ vAPI.userStylesheet.apply();
+}
+
+/******************************************************************************/
+
+const loadMedia = async function(elem) {
+ const src = elem.getAttribute('src') || '';
+ elem.removeAttribute('src');
+
+ await vAPI.messaging.send('scriptlets', {
+ what: 'temporarilyAllowLargeMediaElement',
+ });
+
+ if ( src !== '' ) {
+ elem.setAttribute('src', src);
+ }
+ elem.load();
+};
+
+/******************************************************************************/
+
+const loadImage = async function(elem) {
+ const src = elem.getAttribute('src') || '';
+ elem.removeAttribute('src');
+
+ await vAPI.messaging.send('scriptlets', {
+ what: 'temporarilyAllowLargeMediaElement',
+ });
+
+ if ( src !== '' ) {
+ elem.setAttribute('src', src);
+ }
+};
+
+/******************************************************************************/
+
+const loadMany = function(elems) {
+ for ( const elem of elems ) {
+ switch ( elem.localName ) {
+ case 'audio':
+ case 'video':
+ loadMedia(elem);
+ break;
+ case 'img':
+ loadImage(elem);
+ break;
+ default:
+ break;
+ }
+ }
+};
+
+/******************************************************************************/
+
+const onMouseClick = function(ev) {
+ if ( ev.button !== 0 || ev.isTrusted === false ) { return; }
+
+ const toLoad = [];
+ const elems = document.elementsFromPoint instanceof Function
+ ? document.elementsFromPoint(ev.clientX, ev.clientY)
+ : [ ev.target ];
+ for ( const elem of elems ) {
+ if ( elem.matches(largeMediaElementSelector) === false ) { continue; }
+ elem.removeAttribute(largeMediaElementAttribute);
+ if ( mediaNotLoaded(elem) ) {
+ toLoad.push(elem);
+ }
+ }
+
+ if ( toLoad.length === 0 ) { return; }
+
+ loadMany(toLoad);
+
+ ev.preventDefault();
+ ev.stopPropagation();
+};
+
+document.addEventListener('click', onMouseClick, true);
+
+/******************************************************************************/
+
+const onLoadedData = function(ev) {
+ const media = ev.target;
+ if ( media.localName !== 'audio' && media.localName !== 'video' ) {
+ return;
+ }
+ const src = media.src;
+ if ( typeof src === 'string' && src.startsWith('blob:') === false ) {
+ return;
+ }
+ media.autoplay = false;
+ media.pause();
+};
+
+// https://www.reddit.com/r/uBlockOrigin/comments/mxgpmc/
+// Support cases where the media source is not yet set.
+for ( const media of document.querySelectorAll('audio,video') ) {
+ const src = media.src;
+ if (
+ (typeof src === 'string') &&
+ (src === '' || src.startsWith('blob:'))
+ ) {
+ media.autoplay = false;
+ media.pause();
+ }
+}
+
+document.addEventListener('loadeddata', onLoadedData);
+
+/******************************************************************************/
+
+const onLoad = function(ev) {
+ const elem = ev.target;
+ if ( isMediaElement(elem) === false ) { return; }
+ elem.removeAttribute(largeMediaElementAttribute);
+};
+
+document.addEventListener('load', onLoad, true);
+
+/******************************************************************************/
+
+const onLoadError = function(ev) {
+ const elem = ev.target;
+ if ( isMediaElement(elem) === false ) { return; }
+ if ( mediaNotLoaded(elem) ) {
+ elem.setAttribute(largeMediaElementAttribute, '');
+ }
+};
+
+document.addEventListener('error', onLoadError, true);
+
+/******************************************************************************/
+
+vAPI.loadAllLargeMedia = function() {
+ document.removeEventListener('click', onMouseClick, true);
+ document.removeEventListener('loadeddata', onLoadedData, true);
+ document.removeEventListener('load', onLoad, true);
+ document.removeEventListener('error', onLoadError, true);
+
+ const toLoad = [];
+ for ( const elem of document.querySelectorAll(largeMediaElementSelector) ) {
+ elem.removeAttribute(largeMediaElementAttribute);
+ if ( mediaNotLoaded(elem) ) {
+ toLoad.push(elem);
+ }
+ }
+ loadMany(toLoad);
+};
+
+/******************************************************************************/
+
+})();
+
+
+
+
+
+
+
+
+/*******************************************************************************
+
+ DO NOT:
+ - Remove the following code
+ - Add code beyond the following code
+ Reason:
+ - https://github.com/gorhill/uBlock/pull/3721
+ - uBO never uses the return value from injected content scripts
+
+**/
+
+void 0;