fix(inspect): never reset a preview video that is still starting

Deferring load() to the next task was not enough: CI on b7db4c3 hung
WebKit one step later in the fresh-page regression. Reset with load()
only a settled video (metadata read, or failed); a starting one is
paused, loses its src before the URL is revoked and is removed, then
left to be collected. The ownership workflow waits for the video to
settle before asserting the reset.
This commit is contained in:
Vito Sansevero
2026-10-22 13:08:23 -07:00
parent b7db4c35f6
commit 2a0ecd9b1b
3 changed files with 16 additions and 10 deletions
+4 -3
View File
@@ -1297,9 +1297,10 @@ mapped differences"):
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, 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`).
or dispose, before its URL is revoked. A settled video (metadata read, or failed) is then
reset with `load()`; one still starting is not, because WebKit (GStreamer) blocked the
page when such a pipeline was reset (`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.
+5 -3
View File
@@ -355,6 +355,8 @@ test('URLs and media end with their owning session', async ({ page }) => {
});
await choose(page, localFixture('core-tags.webm', 'video/webm'));
await expect(preview(page).locator('video')).toBeVisible();
// A settled video (metadata read, or failed) is reset with load() when it is released.
await expect.poll(() => preview(page).locator('video').evaluate(node => node.readyState >= 1 || !!node.error)).toBe(true);
// Replacement.
await choose(page, parityPng('f02.png'));
await expect(preview(page).locator('img.local-file-preview')).toBeVisible();
@@ -393,9 +395,9 @@ test('URLs and media end with their owning session', async ({ page }) => {
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.
// WebKit (GStreamer) blocked the page when a video whose pipeline was still starting was reset with load(): the
// next chooser selection, or the steps after it, 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 {
+7 -4
View File
@@ -39,13 +39,16 @@ function element(tag, className, text) {
/**
* 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.
* A settled video (metadata read, or failed) is then reset with load() to free its decoder at once. A video still
* starting is not: WebKit (GStreamer) blocked the page when such a pipeline was reset, so it is left to be collected.
*/
function releaseMedia(node) {
if (!node) return;
if (node instanceof HTMLMediaElement) { node.pause(); node.removeAttribute('src'); setTimeout(() => node.load(), 0); }
else node.removeAttribute('src');
if (node instanceof HTMLMediaElement) {
const settled = node.readyState >= HTMLMediaElement.HAVE_METADATA || node.error !== null;
node.pause(); node.removeAttribute('src');
if (settled) node.load();
} else node.removeAttribute('src');
node.remove();
}