// ==UserScript==
// @name Automatic Material Dark-Mode for YouTube
// @namespace SteveJobzniak
// @version 1.3
// @description A low-tech solution to a high-tech problem! Automatically clicks YouTube's "Dark Mode" button if dark mode isn't already active.
// @author SteveJobzniak
// @match *://www.youtube.com/*
// @exclude *://www.youtube.com/tv*
// @exclude *://www.youtube.com/embed/*
// @run-at document-end
// @grant none
// @noframes
// ==/UserScript==
(function() {
'use strict';
function findElement( parentElem, elemQuery, expectedLength, selectItem, fnCallback ) {
var elems = parentElem.querySelectorAll( elemQuery );
if( elems.length === expectedLength ) {
var item = elems[selectItem];
fnCallback( item );
return true;
}
//console.log('Debug: Cannot find "'+elemQuery+'".');
return false;
}
function retryFindElement( parentElem, elemQuery, expectedLength, selectItem, fnCallback ) {
// If we can't find the element immediately, we'll perform multiple retries.
var success = findElement( parentElem, elemQuery, expectedLength, selectItem, fnCallback );
if( ! success ) {
var attempt = 0, maxAttempts = 40, waitDelay = 50; // 40 * 50ms = Max ~2 seconds of retries.
var searchTimer = setInterval( function() {
var success = findElement( parentElem, elemQuery, expectedLength, selectItem, fnCallback );
// If we've reached max attempts or found success, we must now stop the interval timer.
if( ++attempt >= maxAttempts || success ) {
clearInterval( searchTimer );
}
}, waitDelay );
}
}
function enableDark() {
// Wait until the settings menu is available, to ensure that YouTube's "dark mode state" and code has been loaded...
retryFindElement( document, 'yt-icon.style-scope.ytd-topbar-menu-button-renderer', 2, 1, function( settingsMenuButton ) {
// Check the dark mode state "flag" and abort processing if dark mode is already active.
if( document.body.getAttribute( 'dark' ) === 'true' ) { return; }
// We MUST open the "settings" menu, otherwise nothing will react to the "toggle dark mode" event!
settingsMenuButton.click();
// Wait a moment for the settings-menu to open up after clicking...
retryFindElement( document, 'div#label.style-scope.ytd-toggle-theme-compact-link-renderer', 1, 0, function( darkModeSubMenuButton ) {
// Next, go to the "toggle dark mode" settings sub-page.
darkModeSubMenuButton.click();
// Wait a moment for the settings sub-page to switch...
retryFindElement( document, 'ytd-toggle-item-renderer.style-scope.ytd-multi-page-menu-renderer', 1, 0, function( darkModeSubPageContainer ) {
// Get a reference to the "activate dark mode" button...
retryFindElement( darkModeSubPageContainer, 'paper-toggle-button.style-scope.ytd-toggle-item-renderer', 1, 0, function( darkModeButton ) {
// We MUST now use this very ugly, hardcoded sleep-timer to ensure that YouTube's "activate dark mode" code is fully
// loaded; otherwise, YouTube will be completely BUGGED OUT and WON'T save the fact that we've enabled dark mode!
// Since JavaScript is single-threaded, this timeout simply ensures that we'll leave our current code so that we allow
// YouTube's event handlers to deal with loading the settings-page, and then the timeout gives control back to us.
setTimeout( function() {
// Now simply click YouTube's button to enable their dark mode.
darkModeButton.click();
// And lastly, give keyboard focus back to the input search field... (We don't need any setTimeout here...)
retryFindElement( document, 'input#search', 1, 0, function( searchField ) {
searchField.click(); // First, click the search-field to force the settings-panel to close...
searchField.focus(); // ...and finally give the search-field focus! Voila!
} );
}, 20 ); // We can use 0ms here for "as soon as possible" instead, but our "at least 20ms" might be safer just in case.
} );
} );
} );
} );
// Alternative method, which switches using an internal YouTube event instead of clicking
// the menus... I decided to disable this method, since it relies on intricate internal
// details, and it still requires their menu to be open to work anyway (because their
// code for changing theme isn't active until the Dark Mode settings menu is open),
// so we may as well just click the actual menu items. ;-)
/*
var ytDebugMenu = document.querySelectorAll('ytd-debug-menu');
ytDebugMenu = (ytDebugMenu.length === 1 ? ytDebugMenu[0] : undefined);
if( ytDebugMenu ) {
ytDebugMenu.fire(
'yt-action',
{
actionName:'yt-signal-action-toggle-dark-theme-on',
optionalAction:false,
args:[
{signalAction:{signal:'TOGGLE_DARK_THEME_ON'}},
toggleMenuElem,
undefined
],
returnValue: []
},
{}
);
}
*/
// Also note that it may be possible to simply modify the YouTube cookies, by changing
// "PREF=f1=50000000;" to "PREF=f1=50000000&f6=400;" (dark mode on) and then reloading the page.
// However, a reload is always slower than toggling the settings menu, so I didn't do that.
}
if( document.readyState === 'complete' ) {
enableDark();
} else {
document.addEventListener( 'readystatechange', function( evt ) {
if( document.readyState === 'complete' ) {
enableDark();
}
} );
}
})();