If you have ever tried to write a simple Chrome extension to paste text into a modern web app like ChatGPT or Claude, you probably ran into a frustrating wall. Setting textarea.value = "Hello" does absolutely nothing. The text appears on screen for a split second, and then vanishes.
Why? Because modern web applications are built with React and sophisticated rich-text editors like ProseMirror. In this article, we'll explore the engineering behind ContxtAI's ContextBridge and how we successfully inject thousands of tokens of context natively into these complex DOMs.
The React Synthetic Event Problem
React maintains an internal representation of the DOM (the Virtual DOM). When you manually mutate a DOM element's value via JavaScript, React doesn't know about it. The next time React re-renders that component, it looks at its internal state, sees that the state is empty, and overwrites your injected text, wiping it from the screen.
To fix this, you must trick React into thinking the user actually typed the text. This means dispatching Synthetic Events.
For simple React apps, dispatching an input event is enough. But ChatGPT is not a simple React app.
The ProseMirror Boss Fight
ChatGPT's input box is not a standard <textarea>. It is a highly customized contenteditable div managed by ProseMirror. ProseMirror completely hijacks standard browser inputs to manage its own complex document state.
When dealing with a ProseMirror instance, simply dispatching an input event will fail. ProseMirror relies heavily on the browser's Selection API and the execCommand API to track where the user's cursor is and what is being inserted.
Here is how ContxtAI successfully bridges this gap:
By using document.execCommand('insertText'), we bypass React entirely and speak directly to the browser's native text editing engine, which ProseMirror actively listens to. This registers as a legitimate keystroke sequence, forcing ProseMirror to update its internal state model, which then triggers React to update its state.
Handling Dynamic DOM Changes (SPAs)
The final hurdle is finding the input box in the first place. ChatGPT is a Single Page Application (SPA). When you navigate between chats, the page doesn't reload, but the DOM is completely destroyed and rebuilt.
If your extension caches a reference to the input box when the page first loads, that reference will become a "stale element" (detached from the DOM) the moment the user clicks a different chat history item.
In ContxtAI, we solve this by utilizing a Just-In-Time (JIT) DOM query. We never cache DOM elements in our background scripts. Instead, the moment the user clicks "Inject Context", the popup sends a message to the content script, which executes a fresh document.querySelector('#prompt-textarea') query.
By understanding how React and ProseMirror manage state, ContxtAI achieves near-instant, reliable injection across the most complex AI interfaces on the web.