diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionStore.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionStore.java index 2006d0935..8a0c2cae8 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionStore.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionStore.java @@ -59,7 +59,7 @@ import static org.mozilla.vrbrowser.utils.ServoUtils.isServoAvailable; public class SessionStore implements ContentBlocking.Delegate, GeckoSession.NavigationDelegate, - GeckoSession.ProgressDelegate, GeckoSession.ContentDelegate, GeckoSession.TextInputDelegate, + GeckoSession.ProgressDelegate, GeckoSession.ContentDelegate, GeckoSession.TextInputDelegate, GeckoSession.PromptDelegate, GeckoSession.MediaDelegate, SharedPreferences.OnSharedPreferenceChangeListener { private static SessionStore mInstance; @@ -1057,7 +1057,9 @@ public void onCanGoForward(@NonNull GeckoSession aSession, boolean aCanGoForward private String checkYoutubeOverride(String aUri) { try { Uri uri = Uri.parse(aUri); - if (!uri.getHost().toLowerCase().contains("www.youtube.")) { + String hostLower = uri.getHost().toLowerCase(); + if (!hostLower.endsWith(".youtube.com") && + !hostLower.endsWith(".youtube-nocookie.com")) { return null; } String query = uri.getQueryParameter("disable_polymer"); diff --git a/app/src/main/assets/web_extensions/youtube_webcompat/main.css b/app/src/main/assets/web_extensions/youtube_webcompat/main.css index f1b59e6df..2e13a7b52 100644 --- a/app/src/main/assets/web_extensions/youtube_webcompat/main.css +++ b/app/src/main/assets/web_extensions/youtube_webcompat/main.css @@ -1,3 +1,6 @@ -div.alert-with-button-renderer { - display:none; +/* To hide harmless warnings. */ +.alert-with-button-renderer, .ytm-alert-with-button-renderer, ytm-alert-with-button-renderer, +.ytp-ad-module, ytp-ad-module +.ytp-generic-popup, ytp-generic-popup { + display: none; } diff --git a/app/src/main/assets/web_extensions/youtube_webcompat/main.js b/app/src/main/assets/web_extensions/youtube_webcompat/main.js index 74667eba4..bd33ab6e0 100644 --- a/app/src/main/assets/web_extensions/youtube_webcompat/main.js +++ b/app/src/main/assets/web_extensions/youtube_webcompat/main.js @@ -1,8 +1,197 @@ -// Add meta-viewport -let viewport = document.head.querySelector("meta[name='viewport']"); -if (!viewport) { - viewport = document.createElement("meta"); - viewport.name = "viewport"; - viewport.content = "width=user-width, initial-scale=1"; - document.head.appendChild(viewport); -} +(function () { + // If missing, inject a `` tag to trigger YouTube's mobile layout. + window.addEventListener('load', () => { + let viewport = document.head.querySelector('meta[name="viewport"]'); + if (!viewport) { + viewport = document.createElement('meta'); + viewport.name = 'viewport'; + viewport.content = 'width=device-width, initial-scale=1'; + document.head.appendChild(viewport); + } + }); + + const LOGTAG = '[firefoxreality:webcompat]' + const qs = new URLSearchParams(window.location.search); + let retryTimeout = null; + + function getTruthyQS (key) { + if (!qs || !qs.has(key)) { + return false; + } + const valueLower = (qs.get('key') || '').trim().toLowerCase(); + return valueLower === '' || valueLower === '1' || valueLower === 'true' || valueLower === 'yes' || valueLower === 'on'; + } + + const prefs = { + hd: false, + quality: 1440, + log: qs.get('mozDebug') ? getTruthyQS('mozDebug') : true, + retryAttempts: parseInt(qs.get('retryAttempts') || qs.get('retryattempts') || '10', 10), + retryTimeout: parseInt(qs.get('retryTimeout') || qs.get('retrytimeout') || '500', 10) + }; + + const printLog = String(prefs.log) === 'true'; + + const log = (...args) => printLog && console.log(LOGTAG, ...args); + const logError = (...args) => printLog && console.error(LOGTAG, ...args); + const logWarn = (...args) => printLog && console.warn(LOGTAG, ...args); + + const ytImprover = window.ytImprover = (state, attempts) => { + if (ytImprover.completed) { + return; + } + + if (typeof attempts === 'undefined') { + attempts = 1; + } + if (attempts >= prefs.retryAttempts) { + logError(`Giving up trying to increase resolution after ${prefs.retryAttempts} attempts.`); + return; + } + + let player = document.getElementById('movie_player'); + let reason = 'unknown'; + if (state !== 1) { + reason = 'invalid state'; + } else if (!player) { + reason = 'player not found'; + } else if (!player.wrappedJSObject) { + reason = 'player.wrappedJSObject not found'; + player = null; + } else if (!player.wrappedJSObject.getAvailableQualityLevels) { + reason = 'player.wrappedJSObject.getAvailableQualityLevels not found'; + player = null; + } + + if (!player) { + logWarn(`Cannot find player because ${reason}. attempts: ${attempts}`); + attempts++; + retryTimeout = setTimeout(() => { + ytImprover(state, attempts); + }, prefs.retryTimeout); + return; + } + + player = player.wrappedJSObject; + + const levels = player.getAvailableQualityLevels(); + if (!levels || !levels.length) { + logWarn(`Cannot read 'player.getAvailableQualityLevels()' attempts: ${attempts}`); + attempts++; + retryTimeout = setTimeout(() => { + ytImprover(state, attempts); + }, prefs.retryTimeout); + return; + } + + clearTimeout(retryTimeout); + ytImprover.completed = true; + + prefs.qualities = [ + 'highres', 'h2880', 'hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'auto' + ]; + prefs.qualityLabels = { + '4320': 'highres', // 8K / 4320p / QUHD + '2880': 'hd2880', // 5K / 2880p / UHD+ + '2160': 'hd2160', // 4K / 2160p / UHD + '1440': 'hd1440', // 1440p / QHD + '1080': 'hd1080', // 1080p / FHD + '720': 'hd720', // 720p / HD + '480': 'large', // 480p + '360': 'medium', // 360p + '240': 'small', // 240p + '144': 'tiny', // 144p + '0': 'auto' + }; + + const getDesiredQuality = () => { + const qsQuality = (qs.get('vq') || qs.get('quality') || '').trim().toLowerCase(); + if (qsQuality) { + if (qsQuality in prefs.qualityLabels) { + prefs.quality = prefs.qualityLabels[qsQuality]; + } else { + const qsQualityNumber = parseInt(qsQuality, 10); + if (Number.isInteger(qsQualityNumber)) { + prefs.quality = qsQualityNumber; + } else { + prefs.quality = qsQuality; + } + } + } + prefs.quality = String(prefs.quality).toLowerCase(); + if (qsQuality === 'auto' || qsQuality === 'default') { + prefs.quality = 'auto'; + } + if (prefs.quality in prefs.qualityLabels) { + prefs.quality = prefs.qualityLabels[prefs.quality]; + } + return prefs.quality; + }; + + prefs.quality = getDesiredQuality(); + if (prefs.quality === 'auto') { + return log(`Desired quality is fine (${prefs.quality})`); + } + + const currentQuality = player.getPlaybackQuality(); + if (prefs.quality === currentQuality) { + return log(`Current quality is desired quality (${currentQuality})`); + } + + const findBestQuality = increase => { + if (prefs.quality === 'highest' || prefs.quality === 'best' || prefs.quality === 'max' || prefs.quality === 'maximum') { + return levels[0]; + } + if (prefs.quality === 'lowest' || prefs.quality === 'worst' || prefs.quality === 'min' || prefs.quality === 'minimum') { + return levels[levels.length - 1]; + } + if (increase) { + prefs.quality = prefs.qualities[prefs.qualities.indexOf(prefs.quality) - 1] || levels[0]; + } + const index = levels.indexOf(prefs.quality); + if (index !== -1) { + return prefs.quality; + } + return findBestQuality(true); + }; + const newBestQuality = findBestQuality(); + if (currentQuality === newBestQuality) { + return log(`Current quality "${currentQuality}" is the best available quality`); + } + + if (!player.setPlaybackQuality) { + return logError('`player.setPlaybackQuality` not available'); + } + player.setPlaybackQuality(newBestQuality); + + if (!player.setPlaybackQualityRange) { + return logError('`player.setPlaybackQualityRange` not available'); + } + try { + player.setPlaybackQualityRange(newBestQuality, newBestQuality); + } catch (e) { + logError(`Failed to call 'player.setPlaybackQualityRange(${newBestQuality}, ${newBestQuality})' with exception: `, e); + return; + } + + log(`Changed quality from "${currentQuality}" to "${newBestQuality}"`); + }; + + if (window.location.pathname.startsWith('/watch')) { + const onYouTubePlayerReady = window.onYouTubePlayerReady = evt => { + log('`onYouTubePlayerReady` called'); + window.ytImprover(1); + evt.addEventListener('onStateChange', 'ytImprover'); + }; + + window.addEventListener('spfready', () => { + log('`spfready` event fired'); + if (typeof window.ytplayer === 'object' && window.ytplayer.config) { + log('`window.ytplayer.config.args.jsapicallback` set'); + window.ytplayer.config.args.jsapicallback = 'onYouTubePlayerReady'; + } + }); + + ytImprover(1); + } +})(); diff --git a/app/src/main/assets/web_extensions/youtube_webcompat/manifest.json b/app/src/main/assets/web_extensions/youtube_webcompat/manifest.json index 4550b031c..0949b2854 100644 --- a/app/src/main/assets/web_extensions/youtube_webcompat/manifest.json +++ b/app/src/main/assets/web_extensions/youtube_webcompat/manifest.json @@ -1,14 +1,22 @@ { "manifest_version": 2, - "name": "YoutubeWebCompat", + "name": "FirefoxRealityYouTubeWebCompat", "version": "1.0", - "description": "Youtube web compat fixes for Firefox Reality", + "description": "YouTube web-site compatability fixes for Firefox Reality.", "content_scripts": [ { - "matches": ["*://*.youtube.com/*"], - "css": ["main.css"], - "js": ["main.js"], - "run_at": "document_end" + "matches": [ + "*://*.youtube.com/*", + "*://*.youtube-nocookie.com/*" + ], + "css": [ + "main.css" + ], + "js": [ + "main.js" + ], + "run_at": "document_start", + "all_frames": true } ] }