YouTube Unhold IndexedDB

释放 YouTube 用过的 IndexDBs 让后台页面能进入休眠

目前为 2022-12-27 提交的版本。查看 最新版本

// ==UserScript==
// @name                YouTube Unhold IndexedDB
// @name:en             YouTube Unhold IndexedDB
// @name:ja             YouTube Unhold IndexedDB
// @name:zh-TW          YouTube Unhold IndexedDB
// @name:zh-CN          YouTube Unhold IndexedDB
// @namespace           http://tampermonkey.net/
// @version             2022.12.27
// @license             MIT License
// @description         Release YouTube's used IndexDBs to make background tabs able to sleep
// @description:en      Release YouTube's used IndexDBs to make background tabs able to sleep
// @description:ja      YouTube の 使用済みIndexDB を解放して、バックグラウンドページを休止状態になるように
// @description:zh-TW   釋放 YouTube 用過的 IndexDBs 讓後台頁面能進入休眠
// @description:zh-CN   释放 YouTube 用过的 IndexDBs 让后台页面能进入休眠
// @author              CY Fung
// @match               https://www.youtube.com/*
// @match               https://www.youtube.com/embed/*
// @match               https://www.youtube-nocookie.com/embed/*
// @match               https://www.youtube.com/live_chat*
// @match               https://www.youtube.com/live_chat_replay*
// @match               https://music.youtube.com/*
// @exclude             /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
// @icon                https://raw.githubusercontent.com/cyfung1031/userscript-supports/main/icons/youtube-unlock-indexedDB.png
// @supportURL          https://github.com/cyfung1031/userscript-supports
// @run-at              document-start
// @grant               none
// @unwrap
// @allFrames
// @inject-into page
// ==/UserScript==

/* jshint esversion:8 */

const DEBUG_LOG = false;
const DB_NAME_FOR_TESTING = 'testdb-Q4IOpq0p'
let runCount = 0;
const isSupported = (function (console, consoleX) {
  'use strict';
  let [window] = new Function('return [window];')(); // real window object

  const isSupported = (((window || 0).indexedDB || 0).constructor || 0).name === 'IDBFactory' && typeof requestIdleCallback === 'function'
  if (isSupported) {
    const addEventListenerKey = Symbol();
    const removeEventListenerKey = Symbol();
    const openKey = Symbol();
    const funcHooks = new WeakMap();
    let openCount = 0;

    const msgStore = [];
    const message = (message) => {
      msgStore.push(message);
      if (openCount === 0 && msgStore.length > 0) {
        setTimeout(() => {
          if (openCount === 0 && msgStore.length > 0) {
            let messages = [...msgStore]
            msgStore.length = 0
            messages.sort((a, b) => a.databaseId.localeCompare(b.databaseId) || a.time - b.time)
            consoleX.dir(messages)
          }
        }, 300)
      }
    };
    const mTime = Date.now()
    function releaseOnIdle(db, databaseId, eventType, event_type) {
      setTimeout(() => {
        requestIdleCallback(() => {
          Promise.resolve(db).then((db) => {
            DEBUG_LOG && console.log(db, databaseId, eventType, event_type)
            db.close();
            db = null;
            openCount--
            message({ databaseId: databaseId, action: 'close', time: Date.now() })
            runCount++
            if (runCount > 1e9) runCount = 0
          }).catch(consoleX.warn)
          db = null
        }, { timeout: 300 })
      }, 300)
    }

    function makeHandler(handler, databaseId, eventType) {
      return function (event) {
        DEBUG_LOG && console.log(32, 'addEventListener', databaseId, eventType, event.type)
        handler.call(this, arguments)
        releaseOnIdle(event.target.result, databaseId, eventType, event.type)
        DEBUG_LOG && console.log(441, 'addEventListener', databaseId, eventType, event.type)
      }
    }

    function makeAddEventListener(databaseId) {
      return function (eventType, handler) {
        const addEventListener = this[addEventListenerKey]
        if (arguments.length !== 2) return addEventListener.call(this, ...arguments)
        if (eventType === 'error' || eventType === 'success') {
          DEBUG_LOG && console.log(31, databaseId, eventType)
          let gx = funcHooks.get(handler)
          if (!gx) {
            gx = makeHandler(handler, databaseId, eventType) // databaseId and eventType are just for logging; not reliable
            funcHooks.set(handler, gx)
          }
          return addEventListener.call(this, eventType, gx)
        }
        return addEventListener.call(this, ...arguments)
      }
    }

    function makeRemoveEventListener(databaseId) {
      return function (eventType, handler) {
        const removeEventListener = this[removeEventListenerKey]
        if (arguments.length !== 2) return removeEventListener.call(this, ...arguments)
        if (eventType === 'error' || eventType === 'success') {
          let gx = funcHooks.get(handler)
          DEBUG_LOG && console.log(30, 'removeEventListener', databaseId, eventType)
          let ret = removeEventListener.call(this, eventType, gx || handler)
          DEBUG_LOG && console.log(442, 'removeEventListener', databaseId, eventType)
          return ret
        }
        return removeEventListener.call(this, ...arguments);
      }
    }

    function makeOpen() {
      return function (databaseId) {
        let request = this[openKey](databaseId); // IDBRequest
        request[addEventListenerKey] = request.addEventListener;
        request.addEventListener = makeAddEventListener(databaseId);
        request[removeEventListenerKey] = request.removeEventListener;
        request.removeEventListener = makeRemoveEventListener(databaseId);
        openCount++
        message({ databaseId: databaseId, action: 'open', time: Date.now() })
        return request;
      }
    }
    window.indexedDB.constructor.prototype[openKey] = window.indexedDB.constructor.prototype.open;
    window.indexedDB.constructor.prototype.open = makeOpen();

  }
  console.log(22)

  return isSupported

})(DEBUG_LOG ? console : Object.assign({}, console, { log: function () { } }), console);

isSupported && (function () {
  let request = indexedDB.open(DB_NAME_FOR_TESTING);
  let mi = 0;
  let px = function () {
    mi += 1000;
  };
  request.addEventListener('success', px);
  request.addEventListener('success', function () {
    mi += 101;
  });
  request.addEventListener('error', px);
  request.addEventListener('error', function () {
    mi += 201;
  });
  request.removeEventListener('success', px);
  request.removeEventListener('error', px);

  indexedDB.deleteDatabase(DB_NAME_FOR_TESTING);

  setTimeout(() => {
    requestIdleCallback(() => {
      setTimeout(() => {
        Promise.resolve(0).then(() => {
          if ((mi === 101 || mi === 201) && runCount >= 1) {
            console.log(`%cYouTube Unhold IndexDB - %cInjection Success ${mi} ${runCount}`, 'background: #222; color: #fff', 'background: #222; color: #bada55');
          } else {
            console.log(`%cYouTube Unhold IndexDB - %cInjection Failure ${mi} ${runCount}`, 'background: #222; color: #fff', 'background: #222; color: #da5a2f');
          }
        }).catch(console.warn)
      }, 100)
    }, { timeout: 300 })
  }, 300)

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址