Title: [Chrome Ext] DOM updates via Promise/Message Passing fail to trigger UI Repaint (Reflow) until manual interaction
Note for the beginning:
Issue Description
In Orion (Safari core + Chrome extension compatibility layer), I am developing an AI extension. When the AI response data arrives and the DOM is updated programmatically, the UI does not automatically refresh to show the new content.
I have confirmed that the data has arrived and the DOM attributes (textContent or innerHTML) have been modified successfully. However, the browser renders the old state until I manually interact with the page (e.g., clicking a button).
Strange behavior: Once I click the page to force a refresh(tts button, or number button), the subsequent 3-4 queries will render automatically as expected, but then the issue returns, and the UI stops updating again.
Technical Implementation Details
I have two distinct features encountering this issue:
1. Word Popup (Non-streaming)
- Context: Content Script inside a Closed Shadow DOM.
- Mechanism:
fetch -> Background -> Promise.resolve.
- DOM Update Method: Direct assignment to
textContent.
Code snippet (src/service/a4_tooltip_new.js):
fetchAIWordTranslation(word, sentence).then(translation => {
console.log("AI translation loaded", translation);
// Update text content
aiTextElement.textContent = translation; // <--- DOM updates, but UI does not repaint
aiTranslationStatus.ai1.completed = true;
});
2. Sentence Analysis Window (Streaming)
- Context: Content Script listening to
chrome.runtime.onMessage.
- Mechanism: Server-Sent Events (SSE) in Background ->
chrome.tabs.sendMessage -> Content Script.
- DOM Update Method: Appending to
innerHTML.
Background Script sending stream:
chrome.tabs.sendMessage(tabId, {
action: "streamChunk",
data: { content: content, isFirstChunk: isFirstChunk }
}).catch(err => console.log("Failed to send stream:", err));
Content Script receiving stream:
// Inside chrome.runtime.onMessage listener
else if (message.action === "streamChunk") {
const { content, isFirstChunk } = message.data;
if (window.currentStreamContext) {
const context = window.currentStreamContext;
if (context.element) {
// Stream append
context.element.innerHTML += formatContent(content); // <--- DOM updates, but UI does not repaint
context.element.scrollTop = context.element.scrollHeight;
}
}
}
Attempted Fixes (All Failed)
I have tried to force the browser to repaint using standard hacks, but none of them triggered a UI update in Orion:
1. Forcing Reflow (Layout Thrashing)
aiTextElement.textContent = translation;
void aiTextElement.offsetHeight; // Reading offsetHeight to force reflow - Failed
2. requestAnimationFrame + Display Toggling
requestAnimationFrame(() => {
const originalDisplay = element.style.display;
element.style.display = 'none';
void element.offsetHeight;
element.style.display = originalDisplay; // Failed
});
Key Observations
- Data Integrity: The data arrives correctly.
- DOM State: Inspecting the elements in Web Inspector shows the new text/HTML is present in the DOM tree.
- Rendering: The visual layer remains stale.
- Interaction Trigger: Clicking a button triggers event listeners/style calcs, which successfully forces the pending repaint.
- Temporary "Fix": A manual interaction seems to "wake up" the rendering pipeline for the next few API calls, but it eventually falls back to the dormant state.
Suspected Cause
It appears there is a disconnect in the Orion/WebKit rendering pipeline when DOM modifications originate from Chrome Extension asynchronous callbacks (Promises or Message Listeners), specifically within Shadow DOM or complex content scripts. The browser is not flagging the layer as "dirty" for a repaint after these specific JavaScript executions.
Automatically update UI
1.4.1
26