Created
December 18, 2025 19:20
-
-
Save cspotcode/748976b32f4a2e9ce0021729f5383980 to your computer and use it in GitHub Desktop.
Steal focus from Yomitan popup so that asbplayer hotkeys work
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Auto-Restore Focus from Yomitan Iframe | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.1 | |
| // @description Automatically restore focus from Yomitan iframe to prevent hotkey conflicts with asbplayer | |
| // @author You | |
| // @match *://*/* | |
| // @grant none | |
| // @run-at document-start | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| // Configuration | |
| const CHECK_INTERVAL_MS = 50; // Check every 50ms (adjust if needed) | |
| // Debug tags: only log messages with these tags | |
| // Available tags: 'init', 'detect', 'restore', 'track', 'error' | |
| // Use ['*'] to log everything, or [] to disable all logging | |
| const DEBUG_TAGS = [/*'init', 'detect',*/ 'restore']; // Customize which logs you want to see | |
| let lastNonIframeFocus = null; | |
| let isRestoring = false; | |
| function log(tag, ...args) { | |
| // Check if this tag should be logged | |
| if (DEBUG_TAGS.includes('*') || DEBUG_TAGS.includes(tag)) { | |
| console.log(`[Focus Restorer:${tag}]`, ...args); | |
| } | |
| } | |
| function isYomitanIframe(element) { | |
| if (!element || element.tagName !== 'IFRAME') { | |
| return false; | |
| } | |
| // Check for the specific class "yomitan-popup" | |
| if (element.classList && element.classList.contains('yomitan-popup')) { | |
| log('detect', 'Found Yomitan iframe with class "yomitan-popup"'); | |
| return true; | |
| } | |
| // Check other Yomitan identifiers as fallback | |
| const id = element.id || ''; | |
| const className = element.className || ''; | |
| if (id.includes('yomitan') || | |
| id.includes('yomichan') || | |
| className.includes('yomitan') || | |
| className.includes('yomichan')) { | |
| log('detect', 'Found Yomitan iframe with identifier:', id || className); | |
| return true; | |
| } | |
| // Check parent elements for Yomitan container | |
| let parent = element.parentElement; | |
| while (parent) { | |
| const parentId = parent.id || ''; | |
| const parentClass = parent.className || ''; | |
| if (parentId.includes('yomitan') || | |
| parentId.includes('yomichan') || | |
| parentClass.includes('yomitan') || | |
| parentClass.includes('yomichan')) { | |
| log('detect', 'Found Yomitan iframe via parent container:', parentId || parentClass); | |
| return true; | |
| } | |
| parent = parent.parentElement; | |
| } | |
| return false; | |
| } | |
| function isInsideIframe(element) { | |
| if (!element) return false; | |
| // Check if element is inside an iframe by checking if we can access it | |
| try { | |
| // If element is in an iframe, it will have a different window | |
| return element.ownerDocument !== document; | |
| } catch (e) { | |
| // Cross-origin iframe - we can't access it | |
| return true; | |
| } | |
| } | |
| function shouldTrackElement(element) { | |
| if (!element) return false; | |
| // Don't track if it's an iframe or inside an iframe | |
| if (element.tagName === 'IFRAME') return false; | |
| if (isInsideIframe(element)) return false; | |
| return true; | |
| } | |
| function checkAndRestoreFocus() { | |
| if (isRestoring) return; // Prevent recursive restoration | |
| const activeElement = document.activeElement; | |
| // If focus is on an iframe (likely Yomitan) | |
| if (activeElement && activeElement.tagName === 'IFRAME') { | |
| if (isYomitanIframe(activeElement)) { | |
| log('restore', 'Yomitan iframe has focus, attempting to restore...'); | |
| if (lastNonIframeFocus && document.contains(lastNonIframeFocus)) { | |
| isRestoring = true; | |
| try { | |
| lastNonIframeFocus.focus(); | |
| log('restore', 'Restored focus to:', lastNonIframeFocus); | |
| } catch (e) { | |
| log('error', 'Failed to restore focus:', e); | |
| } finally { | |
| isRestoring = false; | |
| } | |
| } else { | |
| // Fallback: try to focus the video element or body | |
| isRestoring = true; | |
| try { | |
| const video = document.querySelector('video'); | |
| if (video) { | |
| video.focus(); | |
| log('restore', 'Restored focus to video element'); | |
| } else { | |
| document.body.focus(); | |
| log('restore', 'Restored focus to body'); | |
| } | |
| } catch (e) { | |
| log('error', 'Failed to restore focus:', e); | |
| } finally { | |
| isRestoring = false; | |
| } | |
| } | |
| } | |
| } else if (shouldTrackElement(activeElement)) { | |
| // Track this as the last valid non-iframe focus | |
| lastNonIframeFocus = activeElement; | |
| log('track', 'Tracking focus:', activeElement.tagName, activeElement.id || activeElement.className); | |
| } | |
| } | |
| // Start monitoring after page loads | |
| function init() { | |
| log('init', 'Focus restorer initialized'); | |
| // Set initial focus tracking | |
| if (shouldTrackElement(document.activeElement)) { | |
| lastNonIframeFocus = document.activeElement; | |
| } else { | |
| // Default to body | |
| lastNonIframeFocus = document.body; | |
| } | |
| // Start the interval check | |
| setInterval(checkAndRestoreFocus, CHECK_INTERVAL_MS); | |
| // Also monitor focus changes via event (more responsive) | |
| document.addEventListener('focusin', function(e) { | |
| if (!isRestoring) { | |
| checkAndRestoreFocus(); | |
| } | |
| }, true); | |
| log('init', 'Started monitoring focus with', CHECK_INTERVAL_MS, 'ms interval'); | |
| } | |
| // Initialize when DOM is ready | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', init); | |
| } else { | |
| init(); | |
| } | |
| log('init', 'Script loaded and ready'); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment