- 楼主 编辑于
不知道什么时候加的自动全屏,有点难受
油猴脚本
// ==UserScript== // @name 烧饼游戏 · 阻止自动网页全屏 // @namespace sb-matching-tool // @version 1.0.0 // @description 阻止所有烧饼游戏自动网页全屏,兼容以后沿用共用实现的新游戏,保留手动全屏。 // @match https://sb.sb/games/* // @run-at document-start // @grant none // @sandbox raw // @noframes // @license MIT // ==/UserScript== (function preventAutoWebFullscreen() { 'use strict'; if (location.origin !== 'https://sb.sb' || !location.pathname.startsWith('/games/')) return; const installed = Symbol.for('sb.no-auto-webfullscreen.v1'); if (window[installed]) return; Object.defineProperty(window, installed, { value: true }); const panelSelector = '[data-wheel-panel], [data-t3d-panel], #kt-app'; const controls = '[data-game-webfs], [data-game-fs], #kt-webfs, #kt-fullscreen, [data-act="webfs"], [data-act="fs"]'; const panels = new Map(); const games = new WeakSet(); const renderers = new WeakSet(); const mounts = new WeakMap(); const block = function () {}; // Observe assignments synchronously: game modules often cache bbsGame immediately // after games.js initializes it. Polling or DOMContentLoaded would be too late. function watchValue(owner, key, transform) { const descriptor = Object.getOwnPropertyDescriptor(owner, key); if (descriptor && !descriptor.configurable) { const next = transform(owner[key]); if (descriptor.writable) owner[key] = next; return; } let value = transform(owner[key]); Object.defineProperty(owner, key, { configurable: true, enumerable: descriptor ? descriptor.enumerable : true, get() { return descriptor && descriptor.get ? transform(descriptor.get.call(this)) : value; }, set(next) { if (descriptor && descriptor.set) { descriptor.set.call(this, next); value = transform(descriptor.get ? descriptor.get.call(this) : next); } else value = transform(next); } }); } function isObject(value) { return value !== null && (typeof value === 'object' || typeof value === 'function'); } watchValue(window, 'bbsGame', game => { if (isObject(game) && !games.has(game)) { games.add(game); watchValue(game, 'playFS', () => block); } return game; }); function nativeFullscreen() { return document.fullscreenElement || document.webkitFullscreenElement || null; } function expanded(record) { const list = record.element.classList; return record.kind === 'temple' ? list.contains('kt-webfs') : list.contains('is-fs') || list.contains('is-webfs'); } function remember(element) { if (!element || !element.matches(panelSelector)) return null; if (!panels.has(element)) { panels.set(element, { element, kind: element.id === 'kt-app' ? 'temple' : element.hasAttribute('data-t3d-panel') ? 'thunder3d' : 'common', manual: false, pending: false, active: false, timer: null }); } return panels.get(element); } function forgetIntent(record) { window.clearTimeout(record.timer); record.timer = null; record.manual = record.pending = false; } function manualIntent(record, mode) { if (!record) return; const exiting = mode === 'web' ? expanded(record) : nativeFullscreen() === record.element; forgetIntent(record); if (exiting) return; record.manual = record.pending = true; // Keep permission through asynchronous native <-> web fullscreen transitions. // A failed/unused button must not authorize a later automatic expansion. record.timer = window.setTimeout(() => { record.timer = null; record.pending = false; if (!expanded(record)) record.manual = false; }, 5000); } // The 3D game's fullscreen controls also work via its keyboard/gamepad menu. // Its start/practice/resume callbacks deliberately receive no permission. watchValue(window, 'bbsThunder3DGame', renderer => { if (isObject(renderer) && !renderers.has(renderer)) { renderers.add(renderer); watchValue(renderer, 'mount', original => { if (typeof original !== 'function') return original; if (mounts.has(original)) return mounts.get(original); const wrapped = function (options, ...rest) { if (!options || typeof options.onMenu !== 'function') return Reflect.apply(original, this, [options, ...rest]); const onMenu = options.onMenu; const next = { ...options, onMenu: function (action, ...args) { if (action === 'fs' || action === 'webfs') { const panel = options.root && options.root.closest('[data-t3d-panel]'); manualIntent(remember(panel || document.querySelector('[data-t3d-panel]')), action === 'fs' ? 'native' : 'web'); } return Reflect.apply(onMenu, this, [action, ...args]); } }; return Reflect.apply(original, this, [next, ...rest]); }; mounts.set(original, wrapped); mounts.set(wrapped, wrapped); return wrapped; }); } return renderer; }); function unlockPage() { if (nativeFullscreen()) return; for (const record of panels.values()) { if (record.kind !== 'temple' && record.element.isConnected && expanded(record)) { document.documentElement?.classList.add('game-fs-lock'); return; } } document.documentElement?.classList.remove('game-fs-lock'); } function restore(record) { const panel = record.element; if (record.kind === 'temple') { // The site's handler restores its private qa flag, saved overflow and scroll // position. Merely removing kt-webfs leaves those out of sync. const button = panel.querySelector('#kt-webfs'); if (button && typeof button.onclick === 'function') button.click(); return; } if (record.kind === 'common') { const button = panel.querySelector(panel.classList.contains('is-webfs') ? '[data-game-webfs]' : '[data-game-fs]'); if (button) button.click(); } if (record.kind === 'thunder3d' || expanded(record)) { panel.classList.remove('is-fs', 'is-webfs'); for (const [selector, label, title] of [ ['[data-game-webfs]', '网页全屏', '网页全屏(Esc 退出)'], ['[data-game-fs]', '全屏', '全屏'] ]) { const button = panel.querySelector(selector); if (button) { button.classList.remove('is-on'); button.setAttribute('aria-label', label); button.title = title; } } const renderer = window.bbsThunder3DGame; if (record.kind === 'thunder3d' && renderer && typeof renderer.fsLabels === 'function') renderer.fsLabels(false, false); window.dispatchEvent(new Event('resize')); } unlockPage(); } function check(record) { if (nativeFullscreen() === record.element) { record.active = false; forgetIntent(record); } else if (expanded(record)) { if (record.manual) { record.active = true; record.pending = false; window.clearTimeout(record.timer); record.timer = null; } else restore(record); } else if (record.active) { record.active = false; if (!record.pending) forgetIntent(record); } } function discover(root, changed) { if (root.nodeType === 1 && root.matches(panelSelector)) changed.add(remember(root)); if (root.querySelectorAll) root.querySelectorAll(panelSelector).forEach(element => changed.add(remember(element))); } const observer = new MutationObserver(mutations => { const changed = new Set(); for (const mutation of mutations) { if (mutation.type === 'childList') mutation.addedNodes.forEach(node => discover(node, changed)); else if (panels.has(mutation.target)) changed.add(panels.get(mutation.target)); } for (const [element, record] of panels) { if (!element.isConnected) { if (expanded(record)) restore(record); forgetIntent(record); panels.delete(element); } } changed.forEach(check); }); observer.observe(document, { subtree: true, childList: true, attributes: true, attributeFilter: ['class'] }); document.addEventListener('click', event => { if (!event.isTrusted) return; const target = event.target && event.target.closest ? event.target : event.target?.parentElement; const button = target && target.closest(controls); const panel = button && button.closest(panelSelector); if (panel) { const web = button.matches('[data-game-webfs], #kt-webfs, [data-act="webfs"]'); manualIntent(remember(panel), web ? 'web' : 'native'); } else { for (const record of panels.values()) { if (record.pending && !expanded(record)) forgetIntent(record); } } }, true); for (const eventName of ['fullscreenchange', 'webkitfullscreenchange']) { document.addEventListener(eventName, () => panels.forEach(check)); } const initial = new Set(); discover(document, initial); initial.forEach(check); })();收藏支持反对打赏个性签名即使是坏掉的钟,一天也有两次是准的。