From 6ef952c71f1c3383359f99415dcbae1c993a12c2 Mon Sep 17 00:00:00 2001 From: Ben Joldersma Date: Tue, 12 May 2026 21:40:34 -0700 Subject: [PATCH] FIX: use correct iframe selector and defer init until Docs is ready MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The extension was attaching the keydown listener to the wrong iframe — getElementsByTagName('iframe')[0] returns the first iframe in DOM order, which is not the text event target. page_script.js already used the correct .docs-texteventtarget-iframe selector; content.js now matches it. Additionally, Google Docs injects its iframes dynamically after document_idle, so a MutationObserver is used to defer initialization until the target iframe exists. cursorTop is also fetched lazily for the same reason. Co-Authored-By: Claude Sonnet 4.6 --- content.js | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/content.js b/content.js index 452befc..fd400a9 100644 --- a/content.js +++ b/content.js @@ -9,10 +9,7 @@ // before sending to layout engine and interpret them into respective vim motion/command. // Then implement those motions by sending relevant keystrokes. Essentially doing a keystroke to keystroke remapping. -const iframe = document.getElementsByTagName('iframe')[0] // https://stackoverflow.com/a/4388829 -iframe.contentDocument.addEventListener('keydown', eventHandler, true) - -const cursorTop = document.getElementsByClassName("kix-cursor-top")[0] // element to edit to show normal vs insert mode +let cursorTop = null let mode = 'normal' let tempnormal = false // State variable for indicating temperory normal mode let multipleMotion = { @@ -124,16 +121,19 @@ function switchModeToNormal() { mode = 'normal' updateModeIndicator(mode) - //caret indicating visual mode - cursorTop.style.opacity = 1 - cursorTop.style.display = "block" - cursorTop.style.backgroundColor = "black" + cursorTop = cursorTop || document.getElementsByClassName("kix-cursor-top")[0] + if (cursorTop) { + cursorTop.style.opacity = 1 + cursorTop.style.display = "block" + cursorTop.style.backgroundColor = "black" + } } function switchModeToInsert() { mode = 'insert' updateModeIndicator(mode) - cursorTop.style.opacity = 0 + cursorTop = cursorTop || document.getElementsByClassName("kix-cursor-top")[0] + if (cursorTop) cursorTop.style.opacity = 0 } function switchModeToWait() { @@ -685,5 +685,17 @@ function activateTopLevelMenu(menuCaption) { simulateClick(button); } -// Initiate to Normal Mode -switchModeToNormal() +function initialize() { + const iframe = document.querySelector('.docs-texteventtarget-iframe') + if (!iframe || !iframe.contentDocument) return false + iframe.contentDocument.addEventListener('keydown', eventHandler, true) + switchModeToNormal() + return true +} + +if (!initialize()) { + const observer = new MutationObserver(() => { + if (initialize()) observer.disconnect() + }) + observer.observe(document.documentElement, { childList: true, subtree: true }) +}