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 2e13a7b52..03b410669 100644
--- a/app/src/main/assets/web_extensions/youtube_webcompat/main.css
+++ b/app/src/main/assets/web_extensions/youtube_webcompat/main.css
@@ -1,6 +1,6 @@
/* 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-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 bd33ab6e0..c9c0f8748 100644
--- a/app/src/main/assets/web_extensions/youtube_webcompat/main.js
+++ b/app/src/main/assets/web_extensions/youtube_webcompat/main.js
@@ -1,31 +1,35 @@
-(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 CUSTOM_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12) AppleWebKit/602.1.21 (KHTML, like Gecko) Version/9.2 Safari/602.1.21';
+const LOGTAG = '[firefoxreality:webcompat:youtube]';
+const YT_SELECTORS = {
+ disclaimer: '.yt-alert-message, yt-alert-message',
+ moviePlayer: '#movie_player'
+};
+const YT_PATHS = {
+ watch: '/watch'
+};
- const LOGTAG = '[firefoxreality:webcompat]'
- const qs = new URLSearchParams(window.location.search);
- let retryTimeout = null;
+try {
+ // Note: À la Oculus Browser, we intentionally use this particular `User-Agent` string
+ // for YouTube to force the most optimal, high-resolution layout available for playback in a mobile VR browser.
+ Object.defineProperty(navigator, 'userAgent', {
+ get: () => CUSTOM_USER_AGENT
+ });
- 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';
+ // If missing, inject a `` tag to trigger YouTube's mobile layout.
+ let viewportEl = document.querySelector('meta[name="viewport"]');
+ if (!viewportEl) {
+ document.documentElement.insertAdjacentHTML('afterbegin',
+ ``);
}
+ let is360 = null;
+ let qs = new URLSearchParams(window.location.search);
+ let retryTimeout = null;
+
const prefs = {
hd: false,
quality: 1440,
- log: qs.get('mozDebug') ? getTruthyQS('mozDebug') : true,
+ log: qs.get('mozDebug') !== '0' && qs.get('mozdebug') !== '0' && qs.get('debug') !== '0',
retryAttempts: parseInt(qs.get('retryAttempts') || qs.get('retryattempts') || '10', 10),
retryTimeout: parseInt(qs.get('retryTimeout') || qs.get('retrytimeout') || '500', 10)
};
@@ -36,7 +40,86 @@
const logError = (...args) => printLog && console.error(LOGTAG, ...args);
const logWarn = (...args) => printLog && console.warn(LOGTAG, ...args);
+ const onNavigate = (delayTime = 500) => setTimeout(() => {
+ ytImprover360();
+
+ ytImprover.completed = false;
+ ytImprover(1);
+ }, delayTime);
+
+ window.addEventListener('load', () => {
+ viewportEl = document.querySelector('meta[name="viewport"]:not([data-fxr-injected])');
+ if (viewportEl) {
+ viewportEl.parentNode.removeChild(viewportEl);
+ }
+
+ onNavigate(0);
+ });
+
+ window.addEventListener('pushstate', onNavigate);
+
+ window.addEventListener('popstate', onNavigate);
+
+ window.addEventListener('click', evt => {
+ if (!window.location.pathname.startsWith(YT_PATHS.watch)) {
+ return;
+ }
+ if (is360 && evt.target.closest(YT_SELECTORS.moviePlayer) && !evt.target.closest('.ytp-chrome-bottom')) {
+ const playerEl = document.querySelector(YT_SELECTORS.moviePlayer);
+ if (!playerEl) {
+ return;
+ }
+ playerEl.requestFullscreen();
+ }
+ });
+
+ function ytImprover360 () {
+ if (!window.location.pathname.startsWith(YT_PATHS.watch)) {
+ is360 = false;
+ return;
+ }
+
+ const disclaimerEl = document.querySelector(YT_SELECTORS.disclaimer);
+ is360 = disclaimerEl ? disclaimerEl.textContent.includes('360') : false;
+
+ if (!is360) {
+ return;
+ }
+
+ qs = new URLSearchParams(window.location.search);
+
+ const currentProjection = (qs.get('mozVideoProjection') || '').toLowerCase();
+ qs.delete('mozVideoProjection');
+ switch (currentProjection) {
+ case '360':
+ qs.set('mozVideoProjection', '360');
+ break;
+ case '360_auto':
+ default:
+ qs.set('mozVideoProjection', '360_auto');
+ break;
+ }
+
+ const newUrl = getNewUrl(qs);
+ if (newUrl && window.location.pathname + window.location.search !== newUrl) {
+ window.history.replaceState({}, document.title, newUrl);
+ return newUrl;
+ }
+ }
+
+ function getNewUrl (qs) {
+ let newUrl = `${window.location.pathname}`;
+ if (qs) {
+ newUrl = `${newUrl}?${qs}`;
+ }
+ return newUrl;
+ }
+
const ytImprover = window.ytImprover = (state, attempts) => {
+ if (!window.location.pathname.startsWith(YT_PATHS.watch)) {
+ ytImprover.completed = true;
+ return;
+ }
if (ytImprover.completed) {
return;
}
@@ -49,7 +132,7 @@
return;
}
- let player = document.getElementById('movie_player');
+ let player = document.querySelector(YT_SELECTORS.moviePlayer);
let reason = 'unknown';
if (state !== 1) {
reason = 'invalid state';
@@ -177,21 +260,20 @@
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';
- }
- });
+ window.onYouTubePlayerReady = evt => {
+ log('`onYouTubePlayerReady` called');
+ window.ytImprover(1);
+ evt.addEventListener('onStateChange', 'ytImprover');
+ ytImprover360();
+ };
- ytImprover(1);
- }
-})();
+ 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';
+ }
+ });
+} catch (err) {
+ console.error(LOGTAG, 'Encountered error:', err);
+}