fix(web): stabilize HTML preview URL on tab switch (#1959)

Co-authored-by: GHX5T-SOL <200635707+GHX5T-SOL@users.noreply.github.com>
This commit is contained in:
Ghxst
2026-05-17 23:25:14 +08:00
committed by GitHub
co-authored by GHX5T-SOL
parent 91be2696dd
commit 332e982a07
2 changed files with 72 additions and 3 deletions
+8 -2
View File
@@ -3907,6 +3907,12 @@ function HtmlViewer({
[projectId, file.name, file.mtime, reloadKey],
);
const [previewSrcUrl, setPreviewSrcUrl] = useState(basePreviewSrcUrl);
const activePreviewSrcUrl = (
previewSrcUrl === basePreviewSrcUrl ||
previewSrcUrl.startsWith(`${basePreviewSrcUrl}&`)
)
? previewSrcUrl
: basePreviewSrcUrl;
useEffect(() => {
setPreviewSrcUrl(basePreviewSrcUrl);
}, [basePreviewSrcUrl]);
@@ -3961,7 +3967,7 @@ function HtmlViewer({
}, [useUrlLoadPreview]);
const useLazySrcDocTransport = useUrlLoadPreview || hasLazySrcDocTransport;
const srcDocTransportContent = useLazySrcDocTransport ? lazySrcDocTransport : srcDoc;
const urlTransportSrc = useUrlLoadPreview ? previewSrcUrl : 'about:blank';
const urlTransportSrc = useUrlLoadPreview ? activePreviewSrcUrl : 'about:blank';
const activateSrcDocTransport = useCallback((target: HTMLIFrameElement | null = srcDocPreviewIframeRef.current) => {
const win = target?.contentWindow;
if (!win || !srcDoc || useUrlLoadPreview || !useLazySrcDocTransport) return false;
@@ -6185,7 +6191,7 @@ function HtmlViewer({
title="present"
sandbox="allow-scripts allow-downloads"
data-od-render-mode="url-load"
src={previewSrcUrl}
src={activePreviewSrcUrl}
/>
) : (
<iframe
+64 -1
View File
@@ -2,7 +2,7 @@
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { useState } from 'react';
import { useLayoutEffect, useRef, useState } from 'react';
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { renderToStaticMarkup } from 'react-dom/server';
import { afterEach, describe, expect, it, vi } from 'vitest';
@@ -431,6 +431,69 @@ describe('FileViewer SVG artifacts', () => {
});
});
it('uses the next file URL immediately when switching URL-loaded HTML previews', () => {
const first = baseFile({
name: 'first.html',
path: 'first.html',
mime: 'text/html',
kind: 'html',
artifactManifest: {
version: 1,
kind: 'html',
title: 'First',
entry: 'first.html',
renderer: 'html',
exports: ['html'],
},
});
const second = {
...first,
name: 'second.html',
path: 'second.html',
artifactManifest: {
...first.artifactManifest!,
title: 'Second',
entry: 'second.html',
},
};
const observedCommittedSrcs: Array<string | null> = [];
function Switcher() {
const [file, setFile] = useState<ProjectFile>(first);
const hostRef = useRef<HTMLDivElement | null>(null);
useLayoutEffect(() => {
observedCommittedSrcs.push(
hostRef.current
?.querySelector<HTMLIFrameElement>('[data-testid="artifact-preview-frame"]')
?.getAttribute('src') ?? null,
);
});
return (
<div ref={hostRef}>
<button type="button" onClick={() => setFile(second)}>Switch file</button>
<FileViewer projectId="project-1" projectKind="prototype" file={file}
liveHtml="<html><body>preview</body></html>"
/>
</div>
);
}
const { container } = render(<Switcher />);
const getFrame = () => container.querySelector<HTMLIFrameElement>('[data-testid="artifact-preview-frame"]');
const initialFrame = getFrame();
expect(initialFrame?.getAttribute('src')).toBe('/api/projects/project-1/raw/first.html?v=1710000000&r=0');
const observationsBeforeSwitch = observedCommittedSrcs.length;
fireEvent.click(screen.getByRole('button', { name: 'Switch file' }));
const nextFrame = getFrame();
expect(nextFrame).toBe(initialFrame);
expect(observedCommittedSrcs[observationsBeforeSwitch]).toBe(
'/api/projects/project-1/raw/second.html?v=1710000000&r=0',
);
expect(nextFrame?.getAttribute('src')).toBe('/api/projects/project-1/raw/second.html?v=1710000000&r=0');
});
it('allows downloads in the in-tab HTML presentation iframe', async () => {
const file = baseFile({
name: 'page.html',