Frontend iframe renderer framework: 3D models, OpenAPI (#37233)

Introduces a frontend external-render framework that runs renderer
plugins inside an `iframe` (loaded via `srcdoc` to keep the CSP
`sandbox` directive working without origin-related console noise), and
migrates the 3D viewer and OpenAPI/Swagger renderers onto it. PDF and
asciicast paths are refactored to share the same `data-render-name`
mechanism.

Adds e2e coverage for 3D, PDF, asciicast and OpenAPI render paths, plus
a regression for the `RefTypeNameSubURL` double-escape on non-ASCII
branch names.

Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.6) <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
silverwind
2026-04-18 00:30:17 +02:00
committed by GitHub
parent 0161f3019b
commit d5831b9385
32 changed files with 540 additions and 293 deletions
+17 -1
View File
@@ -1,6 +1,6 @@
import {env} from 'node:process';
import {expect} from '@playwright/test';
import type {APIRequestContext, Page} from '@playwright/test';
import type {APIRequestContext, Locator, Page} from '@playwright/test';
/** Generate a random alphanumeric string. */
export function randomString(length: number): string {
@@ -67,6 +67,13 @@ export async function apiCreateFile(requestContext: APIRequestContext, owner: st
}), 'apiCreateFile');
}
export async function apiCreateBranch(requestContext: APIRequestContext, owner: string, repo: string, newBranch: string) {
await apiRetry(() => requestContext.post(`${baseUrl()}/api/v1/repos/${owner}/${repo}/branches`, {
headers: apiHeaders(),
data: {new_branch_name: newBranch},
}), 'apiCreateBranch');
}
export async function apiDeleteRepo(requestContext: APIRequestContext, owner: string, name: string) {
await apiRetry(() => requestContext.delete(`${baseUrl()}/api/v1/repos/${owner}/${name}`, {
headers: apiHeaders(),
@@ -115,6 +122,15 @@ export async function assertNoJsError(page: Page) {
await expect(page.locator('.js-global-error')).toHaveCount(0);
}
/* asserts the child has no horizontal inset from its parent — catches padding/border anywhere
* in between regardless of which element declares it */
export async function assertFlushWithParent(child: Locator, parent: Locator) {
const [childBox, parentBox] = await Promise.all([child.boundingBox(), parent.boundingBox()]);
if (!childBox || !parentBox) throw new Error('boundingBox returned null');
expect(childBox.x).toBe(parentBox.x);
expect(childBox.width).toBe(parentBox.width);
}
export async function logout(page: Page) {
await page.context().clearCookies(); // workaround issues related to fomantic dropdown
await page.goto('/');