fix(uve): prevent duplicate document.write re-executing inline scripts on VTL pages - #36583
Conversation
…s on VTL pages (#36141) Switches insertPageContent from document.open()/write()/close() to iframeElement.srcdoc so each unique render gets a fresh browsing context. document.open() reuses the same window object, meaning top-level let/const declarations from any prior render remain in the global lexical scope and throw "Identifier '…' has already been declared" on every subsequent write — even across legitimate re-renders such as switching from preview to edit mode, where the server returns different HTML that still contains the same declarations. srcdoc navigates the iframe to a new browsing context on each unique render, clearing the global scope entirely. A lastWrittenKey guard (src + content) prevents re-entrant writes: doc.close() fires a synthetic load event that routes back into insertPageContent, and the reactive effect and the (load) handler can both fire for the same render. Without the guard, srcdoc would be reset on each re-entrant call, causing an infinite reload loop. handleInlineScripts remains unconditional so the inline-edit toggle responds to enableInlineEdit changes independently of whether a write was skipped. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @gortiz-dotcms's task in 8m 33s —— View job PR Review: prevent duplicate document.write re-executing inline scripts
The root-cause analysis is correct: A couple of non-blocking observations: New Issues
Everything else — the re-entrancy rationale, keeping |
Jali's OOO and feedback already addressed
|
Tick the box to add this pull request to the merge queue (same as
|
Summary
Root cause:
insertPageContentcalleddocument.open()/write()/close()to inject server-rendered HTML into the UVE iframe.document.open()replaces theDocumentobject but keeps the samewindow— solet/constdeclarations from any prior render remain in the window's global lexical scope. Any subsequent write throws"Identifier '…' has already been declared"when the same scripts run again. This affects two scenarios:doc.close()fires a syntheticloadevent that routes back intoinsertPageContent, callingdoc.write()a second time on a scope that already has those declarations.pageRenderwith new server-rendered HTML that still contains the samelet/constdeclarations —doc.open()does not clear the scope, so the write throws.Fix: Replaces
document.open()/write()/close()withiframeElement.srcdoc. Settingsrcdocnavigates the iframe to a fresh browsing context on each unique render, clearing the global lexical scope so inline scripts with top-levellet/constdeclarations always start clean.Re-entrancy guard: A
lastWrittenKeyfield (keyed onsrc + content) prevents redundantsrcdocassignments. Without it, the syntheticloadevent fired after eachsrcdocnavigation would route back intoinsertPageContentand resetsrcdocagain, causing an infinite reload loop.handleInlineScriptsremains unconditional so the inline-edit toggle responds toenableInlineEditchanges independently of whether a write was skipped.What this fixes
On traditional (VTL) pages whose inline scripts declare top-level
letorconst(e.g.let resizeTimer;,const urlParams), opening the page in UVE Edit mode no longer throws:Re-render scenarios that still work correctly
loadfromdoc.close()synthetic eventsrcdocassignment skipped — no reload loopsrcdocassignment skippedpageRenderchanges)srcdocwrite in a clean scope ✅srcchanges)srcdocwrite in a clean scope ✅srcdocwrite in a clean scope ✅Test plan
<script>declares a top-levelletorconst— confirm no"Identifier '…' has already been declared"errors in the browser console.pnpm nx test portlets-edit-ema-portlet --testPathPattern=dot-uve-iframeuve.console.error.mov
Fixes #36141
🤖 Generated with Claude Code
This PR fixes: #36141