mirror of
https://github.com/vector-im/element-web.git
synced 2026-05-06 04:36:21 +02:00
fix(docs): fix MutationObserver feedback loop and own-event echo
Two root causes of instability: 1. Receiver re-sends deltas it just received: MutationObserver callbacks are async microtasks, so setting suppressMutations=false synchronously after innerHTML= was too early — the observer callback hadn't fired yet. Fix: defer the reset via requestAnimationFrame() so the flag is still true when the observer's microtask runs. 2. Sender's own events echo back and rewrite DOM: in encrypted rooms device_id is not reliably in event.getUnsigned(), so the per-device skip check failed and the sender's own deltas were treated as remote. This caused innerHTML to be set from get_content_as_html() which may differ from the WASM useListeners DOM output (e.g. <p> vs <br> for newlines), collapsing structure. Fix: skip by userId alone — the local model already has our changes from save_incremental(), so applying our own events is both unnecessary and harmful. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
5c5b3d7215
commit
3f42a0bcd3
@ -189,7 +189,7 @@ function useDocumentSync(
|
||||
if (editorRef.current) {
|
||||
suppressMutations.current = true;
|
||||
editorRef.current.innerHTML = composerModel.get_content_as_html();
|
||||
suppressMutations.current = false;
|
||||
requestAnimationFrame(() => { suppressMutations.current = false; });
|
||||
onContentChanged();
|
||||
}
|
||||
logger.info("[DocumentView] Loaded document from room state");
|
||||
@ -216,8 +216,10 @@ function useDocumentSync(
|
||||
if (event.getRoomId() !== room.roomId) return;
|
||||
if (event.getType() !== DOC_DELTA_EVENT_TYPE) return;
|
||||
|
||||
const eventDeviceId = event.getUnsigned()?.["device_id"] as string | undefined;
|
||||
if (event.getSender() === client.getUserId() && eventDeviceId === client.getDeviceId()) return;
|
||||
// Skip our own events. In encrypted rooms device_id may not be in
|
||||
// unsigned, so also skip by sender alone — the local model already
|
||||
// has our own changes via save_incremental().
|
||||
if (event.getSender() === client.getUserId()) return;
|
||||
|
||||
const model = composerModelRef.current;
|
||||
if (!isCollaborative(model)) { logger.warn("[DocumentView] Model not collaborative yet, dropping delta"); return; }
|
||||
@ -227,11 +229,14 @@ function useDocumentSync(
|
||||
try {
|
||||
model.receive_changes(base64Decode(data));
|
||||
if (editorRef.current) {
|
||||
// Suppress MutationObserver during DOM update. Use
|
||||
// requestAnimationFrame to reset the flag AFTER the observer's
|
||||
// microtask has fired so it doesn't schedule a spurious delta send.
|
||||
suppressMutations.current = true;
|
||||
const caretOffset = saveCaretOffset(editorRef.current);
|
||||
editorRef.current.innerHTML = model.get_content_as_html();
|
||||
restoreCaretOffset(editorRef.current, caretOffset);
|
||||
suppressMutations.current = false;
|
||||
requestAnimationFrame(() => { suppressMutations.current = false; });
|
||||
onContentChanged();
|
||||
}
|
||||
logger.info("[DocumentView] Applied remote delta successfully");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user