YouTube Restore Fullscreen Scrolling

Strips the deprecate-fullerscreen-ui attribute everywhere on YouTube, early and repeatedly

// ==UserScript==
// @name         YouTube Restore Fullscreen Scrolling
// @namespace    waldoocs-tools
// @version      1.3
// @description  Strips the deprecate-fullerscreen-ui attribute everywhere on YouTube, early and repeatedly
// @author       Waldoocs
// @match        https://www.youtube.com/*
// @match        https://m.youtube.com/*
// @run-at       document-start
// @inject-into  page
// @noframes
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';
  const ATTR = 'deprecate-fullerscreen-ui';

  // Block attempts to set the attribute
  try {
    const origSetAttribute = Element.prototype.setAttribute;
    const origSetAttributeNS = Element.prototype.setAttributeNS;
    const origToggleAttribute = Element.prototype.toggleAttribute;

    Element.prototype.setAttribute = function (name, value) {
      if (name === ATTR) return;
      return origSetAttribute.call(this, name, value);
    };
    Element.prototype.setAttributeNS = function (ns, name, value) {
      if (name === ATTR) return;
      return origSetAttributeNS.call(this, ns, name, value);
    };
    Element.prototype.toggleAttribute = function (name, force) {
      if (name === ATTR) return false;
      return origToggleAttribute.call(this, name, force);
    };
  } catch (e) {
    // ignore
  }

  function stripIn(node) {
    if (!node || node.nodeType !== 1) return;
    if (node.hasAttribute(ATTR)) node.removeAttribute(ATTR);
    if (node.querySelectorAll) {
      for (const el of node.querySelectorAll('[' + ATTR + ']')) {
        el.removeAttribute(ATTR);
      }
    }
  }

  function sweep() {
    stripIn(document.documentElement);
  }

  // First sweep
  sweep();

  // Observe attribute changes and new nodes
  const mo = new MutationObserver(muts => {
    for (const m of muts) {
      if (m.type === 'attributes' && m.attributeName === ATTR && m.target instanceof Element) {
        m.target.removeAttribute(ATTR);
      } else if (m.type === 'childList') {
        for (const n of m.addedNodes) stripIn(n);
      }
    }
  });

  const startObserver = () => {
    mo.observe(document.documentElement, {
      subtree: true,
      attributes: true,
      attributeFilter: [ATTR],
      childList: true
    });
  };

  if (document.documentElement) startObserver();
  else document.addEventListener('readystatechange', startObserver, { once: true });

  // Handle YouTube SPA navigations
  window.addEventListener('yt-navigate-finish', sweep);
  window.addEventListener('yt-page-data-updated', sweep);

  // Extra sweeps for the first ~10 seconds
  let i = 0;
  const interval = setInterval(() => {
    sweep();
    if (++i > 100) clearInterval(interval);
  }, 100);
})();