fix(inspect): reset a released preview video in the next task

On WebKit (GStreamer) a video replaced while its pipeline was still
starting blocked the page: pause, src removal and load() in one task
never returned, so the next chooser selection hung (CI page workflows
webkit shard 2/2, "URLs and media end with their owning session").
The src is still dropped before the URL is revoked; only the load()
reset moves to the next task. A fresh-page regression replaces the
first video as soon as it shows, fifteen times.
This commit is contained in:
Vito Sansevero
2026-10-22 12:40:18 -07:00
parent 27ea042018
commit b7db4c35f6
3 changed files with 32 additions and 4 deletions
+4 -2
View File
@@ -1296,8 +1296,10 @@ mapped differences"):
`image/png,image/jpeg,image/webp` plus `video/*,.mp4,.mov,.m4v,.webm,.mkv,.avi,.wmv`. A
video previews in the same `#local-preview` slot as `video.local-file-preview` with
native `controls`, `playsInline`, `preload="metadata"` and `muted`, never autoplays
inline, and is paused, unloaded (`removeAttribute('src')`, `load()`) and removed on
replacement or dispose.
inline, and is paused, has its `src` removed and is removed from the page on replacement
or dispose, before its URL is revoked; its `load()` reset runs in the next task, because
WebKit (GStreamer) blocked the page when a still-starting video was reset in the same
task (`replacing a video that just started loading never blocks the page`).
- **D63** (status line): one `p.small.spaced-16` in `#local-preview`, for states the
concept never shows: "Drop one file at a time.", "Folders cannot be inspected. Choose
one image or video file.", "This file type cannot be previewed or inspected here.
+22
View File
@@ -391,6 +391,28 @@ test('URLs and media end with their owning session', async ({ page }) => {
expect(state.revoked.filter(url => url === state.created[7])).toHaveLength(1);
});
test('replacing a video that just started loading never blocks the page', async ({ context, browserName }) => {
test.setTimeout(120000);
// WebKit (GStreamer) blocked the page when a video's pipeline was reset with load() in the same task that
// removed its src, while it was still starting: the next chooser selection never completed. Each fresh page
// replaces its first video as soon as it shows.
for (let attempt = 0; attempt < 15; attempt++) {
const page = await context.newPage();
try {
if (attempt === 0) await startCoverage(page, browserName);
await openInspect(page);
await choose(page, localFixture('core-tags.webm', 'video/webm'));
await expect(preview(page).locator('video.local-file-preview')).toBeVisible();
await chooser(page).setInputFiles(parityPng('f02.png'), { timeout: 8000 });
await expect(preview(page).locator('img.local-file-preview')).toBeVisible();
await expect(preview(page).locator('video')).toHaveCount(0);
if (attempt === 0) await saveCoverage(page, browserName);
} finally {
await page.close();
}
}
});
test('replacing a file mid-read never shows or sends the old record', async ({ page, context }) => {
const host = await bridgeHost(context);
try {
+6 -2
View File
@@ -37,10 +37,14 @@ function element(tag, className, text) {
return node;
}
/** Stops a preview from holding its blob: URL: media is paused and unloaded before the session revokes the URL. */
/**
* Stops a preview from holding its blob: URL: media is paused and its src dropped before the session revokes the URL.
* The media resets with load() in the next task: WebKit (GStreamer) blocked the page when a starting video was reset in
* the same task that removed its src.
*/
function releaseMedia(node) {
if (!node) return;
if (node instanceof HTMLMediaElement) { node.pause(); node.removeAttribute('src'); node.load(); }
if (node instanceof HTMLMediaElement) { node.pause(); node.removeAttribute('src'); setTimeout(() => node.load(), 0); }
else node.removeAttribute('src');
node.remove();
}