create fresh JS execution context on each render

Hunter
Thursday, July 24th, 2025 at 3:29 PM

index.html+54-90

index.html
182  		}
183  
184  
    -		function createFullscreenToggle() {
    -			try {
    -				if (!preview.contentDocument) return;
    -				
    -				const doc = preview.contentDocument;
    -				
    -				// Remove existing button if it exists
    -				const existingButton = doc.getElementById('fullscreenToggle');
    -				if (existingButton) {
    -					existingButton.remove();
    -				}
    -				
    -				// Create button element
    -				const button = doc.createElement('button');
    -				button.id = 'fullscreenToggle';
    -				button.className = 'iframe-fullscreen-toggle';
    -				button.title = 'Toggle fullscreen';
    -				button.innerHTML = `<img src="resources/fullscreen.svg" width="20" height="20" alt="Fullscreen">`;
    -				
    -				// Add click handler
    -				button.addEventListener('click', toggleFullscreen);
    -				
    -				// Append to body
    -				if (doc.body) {
    -					doc.body.appendChild(button);
    -				}
    -			} catch (e) {
    -				// Ignore cross-origin errors
185 +		// Listen for messages from iframe
186 +		window.addEventListener('message', function(event) {
187 +			if (event.data === 'toggleFullscreen') {
188 +				toggleFullscreen();
189  			}
    -		}
190 +		});
191  
192  		function updatePreview() {
193  			const code = editorView.state.doc.toString();

203  				// Ignore cross-origin errors
204  			}
205  			
    -			// Write HTML directly to iframe document
    -			const doc = preview.contentDocument || preview.contentWindow.document;
    -			doc.open();
    -			
    -			// Ensure we have a proper HTML document structure for the button
    -			const htmlContent = code.trim() || '<!DOCTYPE html><html><head></head><body></body></html>';
    -			doc.write(htmlContent);
206 +			// Use srcdoc to create a completely fresh document context
207 +			preview.srcdoc = code.trim() || '<!DOCTYPE html><html><head></head><body></body></html>';
208  			
    -			// Inject CSS to disable overscroll and add fullscreen toggle styles
    -			const style = doc.createElement('style');
    -			style.textContent = `
    -				* { overscroll-behavior: none !important; }
    -				
    -				.iframe-fullscreen-toggle {
    -					position: fixed;
    -					top: 5px;
    -					right: 5px;
    -					z-index: 10000;
    -					background: rgba(0, 0, 0, 0.2);
    -					color: white;
    -					border: none;
    -					border-radius: 4px;
    -					width: 32px;
    -					height: 32px;
    -					display: flex;
    -					align-items: center;
    -					justify-content: center;
    -					cursor: pointer;
    -					transition: background-color 0.2s;
    -					box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
    -				}
    -
    -				.iframe-fullscreen-toggle img {
    -					filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.3));
    -					opacity: 0.8;
    -					transition: opacity 0.2s;
    -				}
    -
    -				@media (hover: hover) and (pointer: fine) {
    -					.iframe-fullscreen-toggle:hover {
    -						background: rgba(0, 0, 0, 0.35);
    -					}
    -
    -					.iframe-fullscreen-toggle:hover img {
    -						opacity: 1;
    -					}
    -				}
    -			`;
    -			if (doc.head) {
    -				doc.head.appendChild(style);
    -			}
    -			
    -			// Create fullscreen toggle button immediately
    -			createFullscreenToggle();
    -			
    -			doc.close();
    -			
    -			// Restore scroll position after a short delay to ensure content is rendered
    -			setTimeout(() => {
209 +			// Add our functionality after the iframe loads
210 +			const onLoad = () => {
211  				try {
    -					if (preview.contentWindow) {
    -						preview.contentWindow.scrollTo(scrollX, scrollY);
212 +					const doc = preview.contentDocument;
213 +					if (!doc) return;
214 +					
215 +					// Add CSS for overscroll and button
216 +					const style = doc.createElement('style');
217 +					style.textContent = '* { overscroll-behavior: none !important; } .iframe-fullscreen-toggle { position: fixed; top: 5px; right: 5px; z-index: 10000; background: rgba(0, 0, 0, 0.2); color: white; border: none; border-radius: 4px; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: background-color 0.2s; box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); } .iframe-fullscreen-toggle img { filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.3)); opacity: 0.8; transition: opacity 0.2s; } @media (hover: hover) and (pointer: fine) { .iframe-fullscreen-toggle:hover { background: rgba(0, 0, 0, 0.35); } .iframe-fullscreen-toggle:hover img { opacity: 1; } }';
218 +					doc.head.appendChild(style);
219 +					
220 +					// Create fullscreen button
221 +					const existingButton = doc.getElementById('fullscreenToggle');
222 +					if (existingButton) existingButton.remove();
223 +					
224 +					const button = doc.createElement('button');
225 +					button.id = 'fullscreenToggle';
226 +					button.className = 'iframe-fullscreen-toggle';
227 +					button.title = 'Toggle fullscreen';
228 +					
229 +					const img = doc.createElement('img');
230 +					img.src = 'resources/fullscreen.svg';
231 +					img.width = 20;
232 +					img.height = 20;
233 +					img.alt = 'Fullscreen';
234 +					button.appendChild(img);
235 +					
236 +					button.addEventListener('click', function() {
237 +						parent.postMessage('toggleFullscreen', '*');
238 +					});
239 +					
240 +					if (doc.body) {
241 +						doc.body.appendChild(button);
242  					}
243 +					
244 +					// Restore scroll position
245 +					setTimeout(() => {
246 +						try {
247 +							if (preview.contentWindow) {
248 +								preview.contentWindow.scrollTo(scrollX, scrollY);
249 +							}
250 +						} catch (e) {
251 +							// Ignore cross-origin errors
252 +						}
253 +					}, 10);
254  				} catch (e) {
255  					// Ignore cross-origin errors
256  				}
    -			}, 10);
257 +				
258 +				preview.removeEventListener('load', onLoad);
259 +			};
260 +			preview.addEventListener('load', onLoad);
261  		}
262  
263  		function saveToStorage() {