diff --git a/package.json b/package.json index 2e9ea2a1c..ca5616571 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports", "test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js", "typecheck": "node scripts/typecheck.mjs", - "test:unit": "node --test test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js", + "test:unit": "node --test test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js", "test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs", "test": "yarn test:node && yarn test:baseline", "test:browser": "playwright test", @@ -82,7 +82,8 @@ "test:mediabunny-soak": "playwright test --config playwright.soak.config.js", "generate:media:hls": "node scripts/generate-hls-fixture.mjs", "test:iframe": "node --test test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe.test.js test/iframe-lifecycle.test.js refactor/scripts/iframe-contract.test.mjs refactor/scripts/iframe-types.test.mjs", - "test:iframe-types-package": "node refactor/scripts/iframe-package-types.mjs" + "test:iframe-types-package": "node refactor/scripts/iframe-package-types.mjs", + "test:thumbnail": "node --test test/thumbnail.test.js refactor/scripts/thumbnail-contract.test.mjs" }, "browserslist": "last 1 Chrome version", "devDependencies": { diff --git a/refactor/baselines/thumbnail-behavior-validation.json b/refactor/baselines/thumbnail-behavior-validation.json new file mode 100644 index 000000000..2626269d3 --- /dev/null +++ b/refactor/baselines/thumbnail-behavior-validation.json @@ -0,0 +1,3240 @@ +{ + "schemaVersion": 1, + "task": "PKG-TOOL-THUMB-02", + "verifiedAt": "2026-09-12T19:29:08.765Z", + "startedFrom": "0338e0b8b1d9814ebe1628c7b55d08003ffa6347", + "node": { + "version": "v24.21.0", + "contract": 26, + "behavior": 36, + "total": 62, + "failed": 0, + "skipped": 0 + }, + "browser": { + "total": 39, + "actualToolScenarios": 24, + "blobUnavailableControls": 12, + "nativeMediaControls": 3, + "retries": 0, + "skips": 0, + "cases": [ + { + "title": "thumbnail-native.spec.js / Thumbnail native HTTP and Blob media controls", + "project": "chromium", + "status": "passed", + "retry": 0, + "durationMs": 238, + "kind": "native-media-control", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-native-media-controls", + "data": [ + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.046009, + "width": 640 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.046009, + "width": 640 + } + ] + } + ] + }, + { + "title": "thumbnail-native.spec.js / Thumbnail native HTTP and Blob media controls", + "project": "firefox", + "status": "passed", + "retry": 0, + "durationMs": 1796, + "kind": "native-media-control", + "browser": "155.0", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-native-media-controls", + "data": [ + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.045986, + "width": 640 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.045986, + "width": 640 + } + ] + } + ] + }, + { + "title": "thumbnail-native.spec.js / Thumbnail native HTTP and Blob media controls", + "project": "webkit", + "status": "passed", + "retry": 0, + "durationMs": 2020, + "kind": "native-media-control", + "browser": "26.6", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-native-media-controls", + "data": [ + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 3, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "error", + "readyState": 0, + "error": 4, + "duration": null, + "width": 0 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "error", + "readyState": 0, + "error": 4, + "duration": null, + "width": 0 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 2, + "error": 0, + "duration": 90.094, + "width": 640 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "error", + "readyState": 0, + "error": 4, + "duration": null, + "width": 0 + } + ] + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "status": "passed", + "retry": 0, + "durationMs": 8134, + "kind": "actual-tool-scenario", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 150, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 30, + "mime": "image/png", + "bytes": 26427, + "nonBlack": 2400, + "initialEvents": [ + { + "event": "file", + "progress": null, + "processing": false + }, + { + "event": "video", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + } + ], + "events": [ + { + "event": "file", + "progress": null, + "processing": false + }, + { + "event": "video", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + }, + { + "event": "destroy", + "progress": null, + "processing": false + } + ], + "first": "blob:http://127.0.0.1:8084/b2227f84-f711-4487-9f9b-23f616f414e2", + "second": "blob:http://127.0.0.1:8084/2420188c-dffc-4162-832f-8bcea9e55ace", + "created": [ + "blob:http://127.0.0.1:8084/6dce1c0e-3645-4f6b-992d-655352a2d65f", + "blob:http://127.0.0.1:8084/8e34dc09-2c83-471f-8a22-1337fd6958ed", + "blob:http://127.0.0.1:8084/84026ebe-842f-4853-bf78-f302fe754cac", + "blob:http://127.0.0.1:8084/4dc807ea-8b39-4fb5-93ed-d1a080562429", + "blob:http://127.0.0.1:8084/6fb3f973-d450-463c-a1dc-3cd967271b22", + "blob:http://127.0.0.1:8084/285ed7b2-0e24-478e-b671-220c2d1ba53f", + "blob:http://127.0.0.1:8084/5ac65382-a9b2-43dc-9c2d-c4a05c51dff6", + "blob:http://127.0.0.1:8084/b7540f20-33c8-4133-a4e8-2d7dd7ed9e4c", + "blob:http://127.0.0.1:8084/cc33c15a-4a2c-4f0e-88fc-57ad23621526", + "blob:http://127.0.0.1:8084/3547aa42-e731-4628-b11c-d21abb5fb460", + "blob:http://127.0.0.1:8084/b2227f84-f711-4487-9f9b-23f616f414e2", + "blob:http://127.0.0.1:8084/3f2869e5-2430-49df-87e0-a350d762be18", + "blob:http://127.0.0.1:8084/7a806c72-b0df-4145-9f46-e378d5760ead", + "blob:http://127.0.0.1:8084/8a16b215-4b0f-4593-b2db-0351031a14d5", + "blob:http://127.0.0.1:8084/6f8ecfdb-789a-4c73-8270-035b7afdf673", + "blob:http://127.0.0.1:8084/0ce17b14-78c6-43ce-85ae-929a95c91e79", + "blob:http://127.0.0.1:8084/5a454408-673e-43cb-bd1a-750e649c96c0", + "blob:http://127.0.0.1:8084/cb6fbc2f-7410-4856-a6e5-551d9991df65", + "blob:http://127.0.0.1:8084/f4c31648-4300-4ac4-863d-46ddf8afc2cd", + "blob:http://127.0.0.1:8084/1a564c5d-0166-4ed7-bf7e-d440245eed76", + "blob:http://127.0.0.1:8084/2420188c-dffc-4162-832f-8bcea9e55ace" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/8e34dc09-2c83-471f-8a22-1337fd6958ed", + "blob:http://127.0.0.1:8084/84026ebe-842f-4853-bf78-f302fe754cac", + "blob:http://127.0.0.1:8084/4dc807ea-8b39-4fb5-93ed-d1a080562429", + "blob:http://127.0.0.1:8084/6fb3f973-d450-463c-a1dc-3cd967271b22", + "blob:http://127.0.0.1:8084/285ed7b2-0e24-478e-b671-220c2d1ba53f", + "blob:http://127.0.0.1:8084/5ac65382-a9b2-43dc-9c2d-c4a05c51dff6", + "blob:http://127.0.0.1:8084/b7540f20-33c8-4133-a4e8-2d7dd7ed9e4c", + "blob:http://127.0.0.1:8084/cc33c15a-4a2c-4f0e-88fc-57ad23621526", + "blob:http://127.0.0.1:8084/3547aa42-e731-4628-b11c-d21abb5fb460", + "blob:http://127.0.0.1:8084/b2227f84-f711-4487-9f9b-23f616f414e2", + "blob:http://127.0.0.1:8084/3f2869e5-2430-49df-87e0-a350d762be18", + "blob:http://127.0.0.1:8084/7a806c72-b0df-4145-9f46-e378d5760ead", + "blob:http://127.0.0.1:8084/8a16b215-4b0f-4593-b2db-0351031a14d5", + "blob:http://127.0.0.1:8084/6f8ecfdb-789a-4c73-8270-035b7afdf673", + "blob:http://127.0.0.1:8084/0ce17b14-78c6-43ce-85ae-929a95c91e79", + "blob:http://127.0.0.1:8084/5a454408-673e-43cb-bd1a-750e649c96c0", + "blob:http://127.0.0.1:8084/cb6fbc2f-7410-4856-a6e5-551d9991df65", + "blob:http://127.0.0.1:8084/f4c31648-4300-4ac4-863d-46ddf8afc2cd", + "blob:http://127.0.0.1:8084/1a564c5d-0166-4ed7-bf7e-d440245eed76", + "blob:http://127.0.0.1:8084/6dce1c0e-3645-4f6b-992d-655352a2d65f", + "blob:http://127.0.0.1:8084/2420188c-dffc-4162-832f-8bcea9e55ace" + ], + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "chromium", + "status": "passed", + "retry": 0, + "durationMs": 300, + "kind": "actual-tool-scenario", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "status": "passed", + "retry": 0, + "durationMs": 274, + "kind": "actual-tool-scenario", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/ef0b028f-a71f-4d93-a1c4-b59d04ef3cd8" + }, + "result": { + "value": "C:\\fakepath\\thumbnail-pattern.mp4", + "created": [ + "blob:http://127.0.0.1:8084/ef0b028f-a71f-4d93-a1c4-b59d04ef3cd8", + "blob:http://127.0.0.1:8084/541314bd-d6a8-4125-bc5d-a1f3ac95b03a" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/541314bd-d6a8-4125-bc5d-a1f3ac95b03a" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "chromium", + "status": "passed", + "retry": 0, + "durationMs": 621, + "kind": "actual-tool-scenario", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/54f01da3-122b-4890-a630-a052719f5c07", + "blob:http://127.0.0.1:8084/7b48eb43-f87e-4f49-9f71-6b8e415bc174" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/54f01da3-122b-4890-a630-a052719f5c07" + ], + "pendingState": "pending", + "events": [ + "file", + "canvas", + "video", + "destroy", + "update" + ], + "connected": false, + "bytes": 7540 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "status": "passed", + "retry": 0, + "durationMs": 9361, + "kind": "actual-tool-scenario", + "browser": "155.0", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 150, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 30, + "mime": "image/png", + "bytes": 20020, + "nonBlack": 2400, + "initialEvents": [ + { + "event": "file", + "progress": null, + "processing": false + }, + { + "event": "video", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + } + ], + "events": [ + { + "event": "file", + "progress": null, + "processing": false + }, + { + "event": "video", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + }, + { + "event": "destroy", + "progress": null, + "processing": false + } + ], + "first": "blob:http://127.0.0.1:8084/574464ab-e4f5-42e4-ab4e-7de0f4ff39e0", + "second": "blob:http://127.0.0.1:8084/805082a4-dd92-4d1c-aadc-ea4558b091b0", + "created": [ + "blob:http://127.0.0.1:8084/7368ea83-ce42-409a-b060-cabf4d775f11", + "blob:http://127.0.0.1:8084/dba49a4a-dad5-413c-bb38-aec47cd49f37", + "blob:http://127.0.0.1:8084/81d2c431-6175-41d1-a2bd-7ce7cea893ef", + "blob:http://127.0.0.1:8084/68efa570-379b-4f5b-b6e3-5ba142373e19", + "blob:http://127.0.0.1:8084/4e9771dc-b366-4500-a056-122c2bc14104", + "blob:http://127.0.0.1:8084/59d18d4a-6ee4-442d-b8fa-87cef9409b10", + "blob:http://127.0.0.1:8084/d6ef92ba-2d02-432e-bee4-fb0e3f1b057c", + "blob:http://127.0.0.1:8084/24349f4b-a197-4b12-9244-7ead753ce735", + "blob:http://127.0.0.1:8084/60266ef1-f847-48f5-a778-b2c39d80f716", + "blob:http://127.0.0.1:8084/7f1d6bf6-e454-4657-b5f0-f1fb4c5c69ac", + "blob:http://127.0.0.1:8084/574464ab-e4f5-42e4-ab4e-7de0f4ff39e0", + "blob:http://127.0.0.1:8084/e0c4d831-985a-499d-b90d-e53ee29eb198", + "blob:http://127.0.0.1:8084/d1445c7c-fe79-4948-aae0-d4911f340ff1", + "blob:http://127.0.0.1:8084/c8b12ed1-20cd-404b-86f3-746bcc47a7db", + "blob:http://127.0.0.1:8084/6d22c856-c1b9-449a-86bf-f6913e620554", + "blob:http://127.0.0.1:8084/adda5cce-c6a8-4995-a600-2f24e05b1146", + "blob:http://127.0.0.1:8084/c510d367-0d97-4c87-a4be-711d58475bee", + "blob:http://127.0.0.1:8084/33e1115d-0025-44f7-9128-d68b98b305f6", + "blob:http://127.0.0.1:8084/c287c0f0-269a-449c-a6b9-e3ff091c172d", + "blob:http://127.0.0.1:8084/9d303573-c390-498a-a6a0-01b613cf7a02", + "blob:http://127.0.0.1:8084/805082a4-dd92-4d1c-aadc-ea4558b091b0" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/dba49a4a-dad5-413c-bb38-aec47cd49f37", + "blob:http://127.0.0.1:8084/81d2c431-6175-41d1-a2bd-7ce7cea893ef", + "blob:http://127.0.0.1:8084/68efa570-379b-4f5b-b6e3-5ba142373e19", + "blob:http://127.0.0.1:8084/4e9771dc-b366-4500-a056-122c2bc14104", + "blob:http://127.0.0.1:8084/59d18d4a-6ee4-442d-b8fa-87cef9409b10", + "blob:http://127.0.0.1:8084/d6ef92ba-2d02-432e-bee4-fb0e3f1b057c", + "blob:http://127.0.0.1:8084/24349f4b-a197-4b12-9244-7ead753ce735", + "blob:http://127.0.0.1:8084/60266ef1-f847-48f5-a778-b2c39d80f716", + "blob:http://127.0.0.1:8084/7f1d6bf6-e454-4657-b5f0-f1fb4c5c69ac", + "blob:http://127.0.0.1:8084/574464ab-e4f5-42e4-ab4e-7de0f4ff39e0", + "blob:http://127.0.0.1:8084/e0c4d831-985a-499d-b90d-e53ee29eb198", + "blob:http://127.0.0.1:8084/d1445c7c-fe79-4948-aae0-d4911f340ff1", + "blob:http://127.0.0.1:8084/c8b12ed1-20cd-404b-86f3-746bcc47a7db", + "blob:http://127.0.0.1:8084/6d22c856-c1b9-449a-86bf-f6913e620554", + "blob:http://127.0.0.1:8084/adda5cce-c6a8-4995-a600-2f24e05b1146", + "blob:http://127.0.0.1:8084/c510d367-0d97-4c87-a4be-711d58475bee", + "blob:http://127.0.0.1:8084/33e1115d-0025-44f7-9128-d68b98b305f6", + "blob:http://127.0.0.1:8084/c287c0f0-269a-449c-a6b9-e3ff091c172d", + "blob:http://127.0.0.1:8084/9d303573-c390-498a-a6a0-01b613cf7a02", + "blob:http://127.0.0.1:8084/7368ea83-ce42-409a-b060-cabf4d775f11", + "blob:http://127.0.0.1:8084/805082a4-dd92-4d1c-aadc-ea4558b091b0" + ], + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "firefox", + "status": "passed", + "retry": 0, + "durationMs": 405, + "kind": "actual-tool-scenario", + "browser": "155.0", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "status": "passed", + "retry": 0, + "durationMs": 546, + "kind": "actual-tool-scenario", + "browser": "155.0", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/c3ddd37c-ab5a-41a6-93e0-ebee99230050" + }, + "result": { + "value": "C:\\fakepath\\thumbnail-pattern.mp4", + "created": [ + "blob:http://127.0.0.1:8084/c3ddd37c-ab5a-41a6-93e0-ebee99230050", + "blob:http://127.0.0.1:8084/7206d5b4-42b6-4d9e-a223-1e4db95f54f3" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/7206d5b4-42b6-4d9e-a223-1e4db95f54f3" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "firefox", + "status": "passed", + "retry": 0, + "durationMs": 970, + "kind": "actual-tool-scenario", + "browser": "155.0", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/af1a6dde-b583-49be-b07d-e17e09232fd8", + "blob:http://127.0.0.1:8084/ea4b476b-b733-40a4-b7a1-1cb45218974f" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/af1a6dde-b583-49be-b07d-e17e09232fd8" + ], + "pendingState": "pending", + "events": [ + "file", + "canvas", + "video", + "destroy", + "update" + ], + "connected": false, + "bytes": 4980 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "status": "passed", + "retry": 0, + "durationMs": 1360, + "kind": "blob-unavailable-control", + "browser": "26.6", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "webkit", + "status": "passed", + "retry": 0, + "durationMs": 1363, + "kind": "blob-unavailable-control", + "browser": "26.6", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "status": "passed", + "retry": 0, + "durationMs": 1362, + "kind": "blob-unavailable-control", + "browser": "26.6", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "webkit", + "status": "passed", + "retry": 0, + "durationMs": 1498, + "kind": "blob-unavailable-control", + "browser": "26.6", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "status": "passed", + "retry": 0, + "durationMs": 454, + "kind": "actual-tool-scenario", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 44055, + "nonBlack": 3600, + "initialEvents": [ + { + "event": "file", + "progress": null, + "processing": false + }, + { + "event": "video", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + } + ], + "events": [ + { + "event": "file", + "progress": null, + "processing": false + }, + { + "event": "video", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + }, + { + "event": "destroy", + "progress": null, + "processing": false + } + ], + "first": "blob:http://127.0.0.1:8084/472fe49a-ca12-4408-b260-311273dde475", + "second": "blob:http://127.0.0.1:8084/6b030fe7-2e0b-4219-b5e7-4a0782448d64", + "created": [ + "blob:http://127.0.0.1:8084/884dbedd-b255-49c1-a1ff-e8400386c783", + "blob:http://127.0.0.1:8084/16551af9-c444-47cf-9bc8-7a49537da241", + "blob:http://127.0.0.1:8084/e1525c61-36fa-41ec-ae98-4657581721e0", + "blob:http://127.0.0.1:8084/c27eec2c-ee95-407e-8a81-8cc60ffdf7d3", + "blob:http://127.0.0.1:8084/e5ebde5d-4a7e-4fd9-a6bb-c592c4192c52", + "blob:http://127.0.0.1:8084/0b4bcba2-4e9c-405c-a666-3ebc0d8d8b57", + "blob:http://127.0.0.1:8084/665b6dda-07b8-42fa-ab98-3a8d63a9f395", + "blob:http://127.0.0.1:8084/39a6de8f-f028-4ee5-94df-77007632429f", + "blob:http://127.0.0.1:8084/16abed7d-454c-4391-a8c3-d42cc1db9452", + "blob:http://127.0.0.1:8084/97719189-add3-4b86-a37e-932bcd21bb7c", + "blob:http://127.0.0.1:8084/472fe49a-ca12-4408-b260-311273dde475", + "blob:http://127.0.0.1:8084/ca0e888e-8ed3-4cba-8520-3598e4a96c74", + "blob:http://127.0.0.1:8084/1a63c3bb-135f-405b-aeda-3eb90547954b", + "blob:http://127.0.0.1:8084/a29f8760-e79f-4e7d-b937-92ff7dff2c8b", + "blob:http://127.0.0.1:8084/312a731d-79a1-45c6-a7d4-af1470c863e3", + "blob:http://127.0.0.1:8084/b12fe140-dc82-44b8-a9d1-8ee258a5db98", + "blob:http://127.0.0.1:8084/e6d660fa-fb69-44dd-9518-e30278443176", + "blob:http://127.0.0.1:8084/cd404be0-6c37-4983-b8ff-b6e1c1d2589b", + "blob:http://127.0.0.1:8084/b8aceb8e-fe92-4bd8-9afa-6ad08a66c28b", + "blob:http://127.0.0.1:8084/12d50133-b418-49d1-93cc-a8230e796df8", + "blob:http://127.0.0.1:8084/6b030fe7-2e0b-4219-b5e7-4a0782448d64" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/16551af9-c444-47cf-9bc8-7a49537da241", + "blob:http://127.0.0.1:8084/e1525c61-36fa-41ec-ae98-4657581721e0", + "blob:http://127.0.0.1:8084/c27eec2c-ee95-407e-8a81-8cc60ffdf7d3", + "blob:http://127.0.0.1:8084/e5ebde5d-4a7e-4fd9-a6bb-c592c4192c52", + "blob:http://127.0.0.1:8084/0b4bcba2-4e9c-405c-a666-3ebc0d8d8b57", + "blob:http://127.0.0.1:8084/665b6dda-07b8-42fa-ab98-3a8d63a9f395", + "blob:http://127.0.0.1:8084/39a6de8f-f028-4ee5-94df-77007632429f", + "blob:http://127.0.0.1:8084/16abed7d-454c-4391-a8c3-d42cc1db9452", + "blob:http://127.0.0.1:8084/97719189-add3-4b86-a37e-932bcd21bb7c", + "blob:http://127.0.0.1:8084/472fe49a-ca12-4408-b260-311273dde475", + "blob:http://127.0.0.1:8084/ca0e888e-8ed3-4cba-8520-3598e4a96c74", + "blob:http://127.0.0.1:8084/1a63c3bb-135f-405b-aeda-3eb90547954b", + "blob:http://127.0.0.1:8084/a29f8760-e79f-4e7d-b937-92ff7dff2c8b", + "blob:http://127.0.0.1:8084/312a731d-79a1-45c6-a7d4-af1470c863e3", + "blob:http://127.0.0.1:8084/b12fe140-dc82-44b8-a9d1-8ee258a5db98", + "blob:http://127.0.0.1:8084/e6d660fa-fb69-44dd-9518-e30278443176", + "blob:http://127.0.0.1:8084/cd404be0-6c37-4983-b8ff-b6e1c1d2589b", + "blob:http://127.0.0.1:8084/b8aceb8e-fe92-4bd8-9afa-6ad08a66c28b", + "blob:http://127.0.0.1:8084/12d50133-b418-49d1-93cc-a8230e796df8", + "blob:http://127.0.0.1:8084/884dbedd-b255-49c1-a1ff-e8400386c783", + "blob:http://127.0.0.1:8084/6b030fe7-2e0b-4219-b5e7-4a0782448d64" + ], + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "chromium", + "status": "passed", + "retry": 0, + "durationMs": 204, + "kind": "actual-tool-scenario", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "status": "passed", + "retry": 0, + "durationMs": 231, + "kind": "actual-tool-scenario", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/ee4ce199-882b-48c2-876c-977ecaca8f2d" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/ee4ce199-882b-48c2-876c-977ecaca8f2d", + "blob:http://127.0.0.1:8084/40f2175e-e95f-4cdd-8e78-961b61f248a1" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/40f2175e-e95f-4cdd-8e78-961b61f248a1" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "chromium", + "status": "passed", + "retry": 0, + "durationMs": 358, + "kind": "actual-tool-scenario", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/d0c25798-c6b6-49fd-9d4d-b3d15260a676", + "blob:http://127.0.0.1:8084/36f7bdb2-be69-49d6-9af9-5df9bdad9196" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/d0c25798-c6b6-49fd-9d4d-b3d15260a676" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 9120 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "status": "passed", + "retry": 0, + "durationMs": 1435, + "kind": "actual-tool-scenario", + "browser": "155.0", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 38895, + "nonBlack": 3600, + "initialEvents": [ + { + "event": "file", + "progress": null, + "processing": false + }, + { + "event": "video", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + } + ], + "events": [ + { + "event": "file", + "progress": null, + "processing": false + }, + { + "event": "video", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + }, + { + "event": "destroy", + "progress": null, + "processing": false + } + ], + "first": "blob:http://127.0.0.1:8084/937734cc-5671-41d6-8d8d-ace72ab77a10", + "second": "blob:http://127.0.0.1:8084/b35fc20d-fde8-44e3-8f8b-ab229d9ce767", + "created": [ + "blob:http://127.0.0.1:8084/c3718396-afea-456c-995e-ceab12733d79", + "blob:http://127.0.0.1:8084/98b5e0ec-2d18-4558-b0aa-2f521c7690ea", + "blob:http://127.0.0.1:8084/f4837a95-fe6e-49e6-adc0-1259bf6c7d23", + "blob:http://127.0.0.1:8084/2c83bf25-1364-47e8-91db-9dd73e6f5654", + "blob:http://127.0.0.1:8084/597487c7-bc41-4165-b377-abf993c48bc7", + "blob:http://127.0.0.1:8084/586c64a9-2241-4f1d-9b34-6cfe63fe1395", + "blob:http://127.0.0.1:8084/0d5eb020-9141-48fa-ad4f-4bc99a615d69", + "blob:http://127.0.0.1:8084/d8d87788-ad22-49c9-bc29-087f3fe4ffbf", + "blob:http://127.0.0.1:8084/acd39be0-7044-4c60-9ebb-4eab6e0bb2dc", + "blob:http://127.0.0.1:8084/5d051c19-7f8c-4aee-9c6c-06e879ac786a", + "blob:http://127.0.0.1:8084/937734cc-5671-41d6-8d8d-ace72ab77a10", + "blob:http://127.0.0.1:8084/101cb0bc-fbe7-429d-b2fa-9ac9865e0247", + "blob:http://127.0.0.1:8084/4e7e21fb-fbd6-4b9a-b9d2-7a658651d97e", + "blob:http://127.0.0.1:8084/ffcba3d4-f0be-42ec-9093-3e87362ee0af", + "blob:http://127.0.0.1:8084/109e9124-0b19-42b5-8440-56813f82b073", + "blob:http://127.0.0.1:8084/ffb0470d-17c2-45d6-83ce-0629f594a0d5", + "blob:http://127.0.0.1:8084/ad9e5caf-7b48-495a-96bf-ff4e54a24723", + "blob:http://127.0.0.1:8084/a6f53dd5-e931-4f67-a629-efb688568cbf", + "blob:http://127.0.0.1:8084/c20a84fc-12e7-4ccb-a2a4-a532bb6631df", + "blob:http://127.0.0.1:8084/54f228e7-c8fc-4530-93c1-8d9fbd9108c4", + "blob:http://127.0.0.1:8084/b35fc20d-fde8-44e3-8f8b-ab229d9ce767" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/98b5e0ec-2d18-4558-b0aa-2f521c7690ea", + "blob:http://127.0.0.1:8084/f4837a95-fe6e-49e6-adc0-1259bf6c7d23", + "blob:http://127.0.0.1:8084/2c83bf25-1364-47e8-91db-9dd73e6f5654", + "blob:http://127.0.0.1:8084/597487c7-bc41-4165-b377-abf993c48bc7", + "blob:http://127.0.0.1:8084/586c64a9-2241-4f1d-9b34-6cfe63fe1395", + "blob:http://127.0.0.1:8084/0d5eb020-9141-48fa-ad4f-4bc99a615d69", + "blob:http://127.0.0.1:8084/d8d87788-ad22-49c9-bc29-087f3fe4ffbf", + "blob:http://127.0.0.1:8084/acd39be0-7044-4c60-9ebb-4eab6e0bb2dc", + "blob:http://127.0.0.1:8084/5d051c19-7f8c-4aee-9c6c-06e879ac786a", + "blob:http://127.0.0.1:8084/937734cc-5671-41d6-8d8d-ace72ab77a10", + "blob:http://127.0.0.1:8084/101cb0bc-fbe7-429d-b2fa-9ac9865e0247", + "blob:http://127.0.0.1:8084/4e7e21fb-fbd6-4b9a-b9d2-7a658651d97e", + "blob:http://127.0.0.1:8084/ffcba3d4-f0be-42ec-9093-3e87362ee0af", + "blob:http://127.0.0.1:8084/109e9124-0b19-42b5-8440-56813f82b073", + "blob:http://127.0.0.1:8084/ffb0470d-17c2-45d6-83ce-0629f594a0d5", + "blob:http://127.0.0.1:8084/ad9e5caf-7b48-495a-96bf-ff4e54a24723", + "blob:http://127.0.0.1:8084/a6f53dd5-e931-4f67-a629-efb688568cbf", + "blob:http://127.0.0.1:8084/c20a84fc-12e7-4ccb-a2a4-a532bb6631df", + "blob:http://127.0.0.1:8084/54f228e7-c8fc-4530-93c1-8d9fbd9108c4", + "blob:http://127.0.0.1:8084/c3718396-afea-456c-995e-ceab12733d79", + "blob:http://127.0.0.1:8084/b35fc20d-fde8-44e3-8f8b-ab229d9ce767" + ], + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "firefox", + "status": "passed", + "retry": 0, + "durationMs": 1700, + "kind": "actual-tool-scenario", + "browser": "155.0", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "status": "passed", + "retry": 0, + "durationMs": 447, + "kind": "actual-tool-scenario", + "browser": "155.0", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/ae4dc9a0-6104-4d07-9a63-3dd86bab207f" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/ae4dc9a0-6104-4d07-9a63-3dd86bab207f", + "blob:http://127.0.0.1:8084/6693fd23-10a5-48ac-b17b-b6dd670088fd" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/6693fd23-10a5-48ac-b17b-b6dd670088fd" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "firefox", + "status": "passed", + "retry": 0, + "durationMs": 540, + "kind": "actual-tool-scenario", + "browser": "155.0", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/685bc7a7-b15c-4523-871f-9505e1d350f7", + "blob:http://127.0.0.1:8084/42185e62-a95f-4dab-bf01-19e8a94c09b0" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/685bc7a7-b15c-4523-871f-9505e1d350f7" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 6496 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "status": "passed", + "retry": 0, + "durationMs": 1451, + "kind": "blob-unavailable-control", + "browser": "26.6", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "webkit", + "status": "passed", + "retry": 0, + "durationMs": 1453, + "kind": "blob-unavailable-control", + "browser": "26.6", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "status": "passed", + "retry": 0, + "durationMs": 1472, + "kind": "blob-unavailable-control", + "browser": "26.6", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "webkit", + "status": "passed", + "retry": 0, + "durationMs": 1326, + "kind": "blob-unavailable-control", + "browser": "26.6", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "status": "passed", + "retry": 0, + "durationMs": 515, + "kind": "actual-tool-scenario", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 44055, + "nonBlack": 3600, + "initialEvents": [ + { + "event": "file", + "progress": null, + "processing": false + }, + { + "event": "video", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + } + ], + "events": [ + { + "event": "file", + "progress": null, + "processing": false + }, + { + "event": "video", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + }, + { + "event": "destroy", + "progress": null, + "processing": false + } + ], + "first": "blob:http://127.0.0.1:8084/8881bb84-7566-4c48-a8ee-36b7e6860de3", + "second": "blob:http://127.0.0.1:8084/50b18ac6-aacd-4ac7-b9ce-656e01555936", + "created": [ + "blob:http://127.0.0.1:8084/3026db06-715d-4a40-a59b-7ce85196dfde", + "blob:http://127.0.0.1:8084/5d300d52-58fb-4bba-8d6f-0d78a41d3d77", + "blob:http://127.0.0.1:8084/07fbb260-c51e-4191-93c4-b78d993a0457", + "blob:http://127.0.0.1:8084/285e3005-4edc-4b8c-a9a1-d050fc7ed913", + "blob:http://127.0.0.1:8084/076a89cc-406f-49c0-baae-9155eaf8d89e", + "blob:http://127.0.0.1:8084/8b303fab-c98e-4b43-acd6-d0d68969ed1b", + "blob:http://127.0.0.1:8084/1d214799-0001-4c69-bf92-bedaeb743c92", + "blob:http://127.0.0.1:8084/bb187cdc-1731-4b0c-a950-e8f7852de2ba", + "blob:http://127.0.0.1:8084/36fb498c-3edd-48ce-9fef-333c6ba49341", + "blob:http://127.0.0.1:8084/a57e43f1-1218-4669-a0f5-56f592f2ee71", + "blob:http://127.0.0.1:8084/8881bb84-7566-4c48-a8ee-36b7e6860de3", + "blob:http://127.0.0.1:8084/8422a5f9-b181-4c2a-b2ed-85077a48b4c4", + "blob:http://127.0.0.1:8084/01e82a80-5ffe-4804-b617-cda28632cace", + "blob:http://127.0.0.1:8084/72908550-4188-44f6-9a42-c00ddd16f30f", + "blob:http://127.0.0.1:8084/d02aab10-7594-4563-a818-cddeb21c9471", + "blob:http://127.0.0.1:8084/99f23171-20e5-4e7d-8b25-605ee9acdf17", + "blob:http://127.0.0.1:8084/00f8e4d9-f3d6-4ff0-998a-c273c2db240e", + "blob:http://127.0.0.1:8084/a8c4846c-efc7-4878-9c01-a9ee941ebb52", + "blob:http://127.0.0.1:8084/54c6591c-800e-4680-9860-2f1d224526f2", + "blob:http://127.0.0.1:8084/5533f4bc-b91f-41f4-bde3-ab53910a5595", + "blob:http://127.0.0.1:8084/50b18ac6-aacd-4ac7-b9ce-656e01555936" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/5d300d52-58fb-4bba-8d6f-0d78a41d3d77", + "blob:http://127.0.0.1:8084/07fbb260-c51e-4191-93c4-b78d993a0457", + "blob:http://127.0.0.1:8084/285e3005-4edc-4b8c-a9a1-d050fc7ed913", + "blob:http://127.0.0.1:8084/076a89cc-406f-49c0-baae-9155eaf8d89e", + "blob:http://127.0.0.1:8084/8b303fab-c98e-4b43-acd6-d0d68969ed1b", + "blob:http://127.0.0.1:8084/1d214799-0001-4c69-bf92-bedaeb743c92", + "blob:http://127.0.0.1:8084/bb187cdc-1731-4b0c-a950-e8f7852de2ba", + "blob:http://127.0.0.1:8084/36fb498c-3edd-48ce-9fef-333c6ba49341", + "blob:http://127.0.0.1:8084/a57e43f1-1218-4669-a0f5-56f592f2ee71", + "blob:http://127.0.0.1:8084/8881bb84-7566-4c48-a8ee-36b7e6860de3", + "blob:http://127.0.0.1:8084/8422a5f9-b181-4c2a-b2ed-85077a48b4c4", + "blob:http://127.0.0.1:8084/01e82a80-5ffe-4804-b617-cda28632cace", + "blob:http://127.0.0.1:8084/72908550-4188-44f6-9a42-c00ddd16f30f", + "blob:http://127.0.0.1:8084/d02aab10-7594-4563-a818-cddeb21c9471", + "blob:http://127.0.0.1:8084/99f23171-20e5-4e7d-8b25-605ee9acdf17", + "blob:http://127.0.0.1:8084/00f8e4d9-f3d6-4ff0-998a-c273c2db240e", + "blob:http://127.0.0.1:8084/a8c4846c-efc7-4878-9c01-a9ee941ebb52", + "blob:http://127.0.0.1:8084/54c6591c-800e-4680-9860-2f1d224526f2", + "blob:http://127.0.0.1:8084/5533f4bc-b91f-41f4-bde3-ab53910a5595", + "blob:http://127.0.0.1:8084/3026db06-715d-4a40-a59b-7ce85196dfde", + "blob:http://127.0.0.1:8084/50b18ac6-aacd-4ac7-b9ce-656e01555936" + ], + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "chromium", + "status": "passed", + "retry": 0, + "durationMs": 204, + "kind": "actual-tool-scenario", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "status": "passed", + "retry": 0, + "durationMs": 251, + "kind": "actual-tool-scenario", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/ae674dfa-6146-4655-98b0-4e36161a913e" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/ae674dfa-6146-4655-98b0-4e36161a913e", + "blob:http://127.0.0.1:8084/7eab0002-8887-4854-adfe-3c93d8c40e5e" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/7eab0002-8887-4854-adfe-3c93d8c40e5e" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "chromium", + "status": "passed", + "retry": 0, + "durationMs": 353, + "kind": "actual-tool-scenario", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/6a37f6f8-760c-440c-a331-7043d202d968", + "blob:http://127.0.0.1:8084/62dcbc6e-5ad6-4a29-8de3-8974943d0f8c" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/6a37f6f8-760c-440c-a331-7043d202d968" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 9120 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "status": "passed", + "retry": 0, + "durationMs": 1518, + "kind": "actual-tool-scenario", + "browser": "155.0", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 38895, + "nonBlack": 3600, + "initialEvents": [ + { + "event": "file", + "progress": null, + "processing": false + }, + { + "event": "video", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + } + ], + "events": [ + { + "event": "file", + "progress": null, + "processing": false + }, + { + "event": "video", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + }, + { + "event": "canvas", + "progress": null, + "processing": false + }, + { + "event": "update", + "progress": 0.1, + "processing": true + }, + { + "event": "update", + "progress": 0.2, + "processing": true + }, + { + "event": "update", + "progress": 0.3, + "processing": true + }, + { + "event": "update", + "progress": 0.4, + "processing": true + }, + { + "event": "update", + "progress": 0.5, + "processing": true + }, + { + "event": "update", + "progress": 0.6, + "processing": true + }, + { + "event": "update", + "progress": 0.7, + "processing": true + }, + { + "event": "update", + "progress": 0.8, + "processing": true + }, + { + "event": "update", + "progress": 0.9, + "processing": true + }, + { + "event": "update", + "progress": 1, + "processing": true + }, + { + "event": "done", + "progress": null, + "processing": false + }, + { + "event": "destroy", + "progress": null, + "processing": false + } + ], + "first": "blob:http://127.0.0.1:8084/408690a5-94ae-4818-85fa-59330dded1a5", + "second": "blob:http://127.0.0.1:8084/46b0dc47-5275-4deb-9b6e-5b8d33a1678d", + "created": [ + "blob:http://127.0.0.1:8084/e12c84d4-2234-4419-8907-80be255127f8", + "blob:http://127.0.0.1:8084/341934b1-a3a0-4c0b-84f0-be9ec3f0fc4a", + "blob:http://127.0.0.1:8084/8ee58f72-146a-48a8-9f29-46deaf42299b", + "blob:http://127.0.0.1:8084/88791c79-ecc9-462c-948a-8e7c5c979459", + "blob:http://127.0.0.1:8084/8d61481f-0e22-4faa-85f9-8e58d35a6ccb", + "blob:http://127.0.0.1:8084/72854226-0dc3-49e0-acca-916fca1aadf8", + "blob:http://127.0.0.1:8084/dc4eb49b-8f62-4927-9d85-26fd1c9faf64", + "blob:http://127.0.0.1:8084/610e38db-af43-4bf2-bacf-5988365625e1", + "blob:http://127.0.0.1:8084/a20987d2-f04d-4f29-83c0-9beb88db4e33", + "blob:http://127.0.0.1:8084/519481bb-203f-4001-9b04-6faa22b9989f", + "blob:http://127.0.0.1:8084/408690a5-94ae-4818-85fa-59330dded1a5", + "blob:http://127.0.0.1:8084/e0521ed2-8b3c-46c9-b9d9-d67a403dd6bb", + "blob:http://127.0.0.1:8084/59b80836-5a12-489a-ab74-6fa4462f494d", + "blob:http://127.0.0.1:8084/eeb76df6-4286-4cf4-8877-def8a43ab20e", + "blob:http://127.0.0.1:8084/1ebf77b9-8d71-46c2-b776-d5dd7ad4749a", + "blob:http://127.0.0.1:8084/4433bccd-049d-473c-b8bb-c7c74c6cd636", + "blob:http://127.0.0.1:8084/da7c83ed-e22e-4ff7-80c4-caba6c020898", + "blob:http://127.0.0.1:8084/d0ee04cc-6130-4057-9ff1-75622e46f388", + "blob:http://127.0.0.1:8084/8575a8c8-1140-4843-8595-4db989ee7691", + "blob:http://127.0.0.1:8084/f76d4bf0-bae7-4cbd-b4f9-5cee7dd1cc6b", + "blob:http://127.0.0.1:8084/46b0dc47-5275-4deb-9b6e-5b8d33a1678d" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/341934b1-a3a0-4c0b-84f0-be9ec3f0fc4a", + "blob:http://127.0.0.1:8084/8ee58f72-146a-48a8-9f29-46deaf42299b", + "blob:http://127.0.0.1:8084/88791c79-ecc9-462c-948a-8e7c5c979459", + "blob:http://127.0.0.1:8084/8d61481f-0e22-4faa-85f9-8e58d35a6ccb", + "blob:http://127.0.0.1:8084/72854226-0dc3-49e0-acca-916fca1aadf8", + "blob:http://127.0.0.1:8084/dc4eb49b-8f62-4927-9d85-26fd1c9faf64", + "blob:http://127.0.0.1:8084/610e38db-af43-4bf2-bacf-5988365625e1", + "blob:http://127.0.0.1:8084/a20987d2-f04d-4f29-83c0-9beb88db4e33", + "blob:http://127.0.0.1:8084/519481bb-203f-4001-9b04-6faa22b9989f", + "blob:http://127.0.0.1:8084/408690a5-94ae-4818-85fa-59330dded1a5", + "blob:http://127.0.0.1:8084/e0521ed2-8b3c-46c9-b9d9-d67a403dd6bb", + "blob:http://127.0.0.1:8084/59b80836-5a12-489a-ab74-6fa4462f494d", + "blob:http://127.0.0.1:8084/eeb76df6-4286-4cf4-8877-def8a43ab20e", + "blob:http://127.0.0.1:8084/1ebf77b9-8d71-46c2-b776-d5dd7ad4749a", + "blob:http://127.0.0.1:8084/4433bccd-049d-473c-b8bb-c7c74c6cd636", + "blob:http://127.0.0.1:8084/da7c83ed-e22e-4ff7-80c4-caba6c020898", + "blob:http://127.0.0.1:8084/d0ee04cc-6130-4057-9ff1-75622e46f388", + "blob:http://127.0.0.1:8084/8575a8c8-1140-4843-8595-4db989ee7691", + "blob:http://127.0.0.1:8084/f76d4bf0-bae7-4cbd-b4f9-5cee7dd1cc6b", + "blob:http://127.0.0.1:8084/e12c84d4-2234-4419-8907-80be255127f8", + "blob:http://127.0.0.1:8084/46b0dc47-5275-4deb-9b6e-5b8d33a1678d" + ], + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "firefox", + "status": "passed", + "retry": 0, + "durationMs": 452, + "kind": "actual-tool-scenario", + "browser": "155.0", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "status": "passed", + "retry": 0, + "durationMs": 623, + "kind": "actual-tool-scenario", + "browser": "155.0", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/0e3206bc-185a-4f10-94a6-d5d1fe922e90" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/0e3206bc-185a-4f10-94a6-d5d1fe922e90", + "blob:http://127.0.0.1:8084/3fc61d14-e357-460b-a58d-81f17c0d39ae" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/3fc61d14-e357-460b-a58d-81f17c0d39ae" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "firefox", + "status": "passed", + "retry": 0, + "durationMs": 1385, + "kind": "actual-tool-scenario", + "browser": "155.0", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/15301fa5-719e-42ec-8cbc-925b159ae1e4", + "blob:http://127.0.0.1:8084/90128236-e178-404c-9e14-ba17ff758bbb" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/15301fa5-719e-42ec-8cbc-925b159ae1e4" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 6496 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "status": "passed", + "retry": 0, + "durationMs": 1437, + "kind": "blob-unavailable-control", + "browser": "26.6", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "webkit", + "status": "passed", + "retry": 0, + "durationMs": 1453, + "kind": "blob-unavailable-control", + "browser": "26.6", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "status": "passed", + "retry": 0, + "durationMs": 1314, + "kind": "blob-unavailable-control", + "browser": "26.6", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "webkit", + "status": "passed", + "retry": 0, + "durationMs": 1170, + "kind": "blob-unavailable-control", + "browser": "26.6", + "platform": "win32", + "errors": [], + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + } + ] + }, + "inputs": [ + { + "file": "test/helpers/thumbnail.js", + "sha256": "d198f7c17f7acfbf9f064a2154ed7350ea00c63b69e6f827f63e6a5c72b27bd2" + }, + { + "file": "test/thumbnail.test.js", + "sha256": "530c0fc158ca5699d445cc90553d26852cb48b474db3deb7b2b873314d391ad9" + }, + { + "file": "test/browser/thumbnail-tool.spec.js", + "sha256": "eea72bd3858bfb1c59ef9ed5d1bf78adc49ed05ecd25cc2c5b56e56a940bd3ac" + }, + { + "file": "test/browser/thumbnail-native.spec.js", + "sha256": "50d2724c4903c2281cf7a5fa8577e4a8174742ac30b48cd3c679eaecc2d27f3e" + }, + { + "file": "test/browser/server.mjs", + "sha256": "b7813692efd0e98ca41dab5e625ac2949b609c7796f3e0930919bcee03d0e9d7" + }, + { + "file": "test/browser/media/thumbnail-pattern.mp4", + "sha256": "4c977be04d97f6ac1342ac4959b8e61678f390d88a088430e4d0200137a03d1c" + }, + { + "file": "refactor/baselines/thumbnail-release.json", + "sha256": "ad02af2abdb3185598b76a0c617eb5a5ffc89f89a7ef6f01ba8f3db0a87012be" + } + ], + "logs": [ + { + "file": "refactor/.cache/thumbnail02-node.log", + "sha256": "ec272411952d927eb0e553d2003321fcf0430f28b8c4d68eb0a5e6d5e6d03d12" + }, + { + "file": "refactor/.cache/thumbnail02-lint.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "file": "refactor/.cache/thumbnail02-browser.log", + "sha256": "1cc065d63c5a17250db490ae74942cc69e42f647ad9be46db94d8cecc258fdc4" + }, + { + "file": "refactor/.cache/thumbnail02-browser.json", + "sha256": "bbe41949c562439cbf7ecf5d34cc5703ed1094777ee059db051e0d7e38e0a805" + }, + { + "file": "refactor/.cache/thumbnail02-browser-probe.json", + "sha256": "7544b076a5fb818e5f1b66f95fd43513ef03bfa3bcd6503a824ed537e9567448" + }, + { + "file": "refactor/.cache/thumbnail02-browser-probe2.json", + "sha256": "969f553a1d9fc06e4fc20cd63de7fa01d2c2d2a6e54c50d7ff4f2184d395647e" + }, + { + "file": "refactor/.cache/thumbnail02-native-probe.json", + "sha256": "5bd329b2083262fa188aa083e13cfd349ef55c50d2bcded95831fdd338234afe" + }, + { + "file": "refactor/.cache/thumbnail02-ci.log", + "sha256": "196228d966b5841d52ea92a9fe01a5d92a254b9ea70e9ce842ac627b5ae35349" + } + ], + "archivedResults": [ + "refactor/.cache/thumbnail02-browser-results", + "refactor/.cache/thumbnail02-browser-probe-results", + "refactor/.cache/thumbnail02-browser-probe2-results", + "refactor/.cache/thumbnail02-native-probe-results" + ], + "limitations": [ + "Historical behavior only; resource fixes remain in 03.", + "Windows WebKit native Blob media error 4 is reproduced independently; 12 controls are not successful extraction.", + "Synthetic DOM drop dispatch with real File is not physical OS drag/drop.", + "No new coverage measurement, production build, hosted CI or npm publication." + ], + "ci": { + "exitCode": 0, + "unit": 1401, + "engineering": 14, + "baseline": 191, + "total": 1606, + "productionTs": 330 + } +} diff --git a/refactor/changes/2026-09-13-PKG-TOOL-THUMB-02-behavior.md b/refactor/changes/2026-09-13-PKG-TOOL-THUMB-02-behavior.md new file mode 100644 index 000000000..6d6f6d72a --- /dev/null +++ b/refactor/changes/2026-09-13-PKG-TOOL-THUMB-02-behavior.md @@ -0,0 +1,79 @@ +# PKG-TOOL-THUMB-02 extraction and lifecycle baseline + +Started from 0338e0b8b1d9814ebe1628c7b55d08003ffa6347. No production runtime or +distribution changes. This task reproduces behavior before the resource/module +work in 03 and TS migration in 04. The old default-semantics conflict remains open. + +## Repeatable controlled tests + +`yarn test:thumbnail` runs the 26 frozen contract assertions and 36 extraction +assertions against recovered CDN 3.5.31 and frozen workspace main/legacy. +The new behavior file also runs in the normal unit/CI command. + +Controlled timers and deferred Blob callbacks allow assertions without arbitrary +waiting: serial frames, interval clamps/density guards, progress/done ordering, +repeat generation, duplicate start/download, pending callback gating, null Blob, +late callback after destroy, replacement URLs, missing metadata, changed input, +seek setter failure, throwing done/update listeners. These are baseline tests, +not assertions that the defects are fixed. Future candidate regression tests +must assert corrected resource ownership separately while preserving old evidence. + +The old and current implementations leak the first source URL on replacement, +retain changed-input listeners and generated wrapper inputs, permit late update +after destroy, and leave several callback-failure promises unsettled. Missing +metadata rejects with an unbound receiver in 3.5.31; workspace keeps polling even +after destruction. Seek-assignment failure rejects correctly in both; a throwing +done listener emits error twice in 3.5.31 and once in workspace. Normal progress +and PNG URL replacement remain ordered. These distinctions guide 03 fixes. + +## Actual browser evidence and limitations + +Run `yarn test:browser test/browser/thumbnail-tool.spec.js test/browser/thumbnail-native.spec.js`. +The final 39 rows have zero retries/skips: **24 actual tool scenarios** in +Chromium/Firefox, **12 Windows WebKit Blob-unavailable controls**, and **3 native +media comparison tests**. The 12 controls do not establish successful extraction. +Every control proves that the same file loads in native HTTP video but fails in +native Blob video with media error 4, then reproduces the tool's corresponding +failure and cleanup. If native Blob starts working, the full scenario runs. + +Actual scenarios use native file selection, metadata/seek, real canvas.toBlob, +PNG decode and non-black opaque pixels. They verify ten updates, two complete +generations and normal final URL cleanup. DOM drop dispatch with a real File +reproduces the missing listener; calling the existing ondrop method does load. +Corrupt media raises a native error without a tool error event; replacement leaks +the first URL and a second destroy throws NotFoundError. A captured **native PNG +callback**, delivered after destroy, still emits update and creates an unreleased +URL. Only callback delivery is controlled; encoding and media are native. + +Initial docs sample testing passed 24 Chromium/Firefox cases and failed all 12 +WebKit cases. A second 24-second video-only MP4 probe still failed WebKit. The +independent native control then demonstrated HTTP success and Blob failure for +the original pattern, looped pattern and docs sample. An exploratory WebM HTTP +control did not settle within its 1.5-second observation and is not used to claim +codec support. All failed reports and trace directories remain archived. No +production media workaround or claimed physical Safari result was introduced. + +`test/browser/media/thumbnail-pattern.mp4` is a 24-second stream-copy loop of the +existing generated 8-second pattern, needed for the tool's minimum ten samples +and maximum one-sample-per-second density. Reproduce with FFmpeg: + +```sh +ffmpeg -stream_loop 2 -i test/browser/media/pattern.mp4 -t 24 -an -c:v copy -movflags +faststart test/browser/media/thumbnail-pattern.mp4 +``` + +The local test server exposes this fixture; no dist or compiled docs files were +edited. Hashes, individual outcomes, measurements, browser versions and CI counts +are in `refactor/baselines/thumbnail-behavior-validation.json`. The ordinary +browser job discovers the new spec files automatically. No hosted CI run, +coverage improvement, successful WebKit Blob extraction or npm publication is claimed. + +## Handoff and rollback + +03 should own source URLs, generated inputs/listeners, metadata waiting and +extraction callbacks; settle pending work on cancellation/error and stop late +effects. Keep historical public methods/events and use a documented compatibility +policy for delay/height defaults. 04 covers emitter provenance and strict TS; +05 keeps actual Safari/WebKit Blob extraction and old/new core sheet integration +open; 06 owns complete installed-package/demo checks. Reverting this task's +dedicated commit removes only tests, fixture/server registration, scripts and +refactor evidence; it does not roll back production code. diff --git a/refactor/plan.md b/refactor/plan.md index 1bbfbee11..4d7b6b6d8 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -4,7 +4,7 @@ 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 222 项,范围 22 个包及工作区/示例。 -状态:todo 102 / doing 10 / blocked 0 / done 110 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 101 / doing 10 / blocked 0 / done 111 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -356,7 +356,7 @@ | ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 | | --- | --- | --- | --- | --- | --- | --- | | PKG-TOOL-THUMB-01 | artplayer-tool-thumbnail
核对包契约与历史用法 | BASE-05 | 构造/defaults/事件/方法/历史拼写、.esm.js 入口和缺失 types | 源码/声明/README/demo/可恢复发布内容差异已登记;公开形状和版本范围冻结;区分旧 npm 的 main/CSS 与当前工作区无 main、.esm.js/类型缺失,不把工作区状态冒称旧 npm 发布事实 | H | done | -| PKG-TOOL-THUMB-02 | artplayer-tool-thumbnail
建立特有行为与错误测试 | PKG-TOOL-THUMB-01, ENG-03, ENG-05 | 文件输入/拖放、抽帧网格、begin/end、进度/失败/重复任务 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | todo | +| PKG-TOOL-THUMB-02 | artplayer-tool-thumbnail
建立特有行为与错误测试 | PKG-TOOL-THUMB-01, ENG-03, ENG-05 | 文件输入/拖放、抽帧网格、begin/end、进度/失败/重复任务 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done | | PKG-TOOL-THUMB-03 | artplayer-tool-thumbnail
整理内部职责与资源 | PKG-TOOL-THUMB-02, CORE-01 | 输入、抽帧队列、网格导出、URL/监听清理分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | todo | | PKG-TOOL-THUMB-04 | artplayer-tool-thumbnail
迁移自有源码和公开类型 | PKG-TOOL-THUMB-03, ENG-04, ENG-06, CORE-07 | 补齐真实 API 声明,自有 emitter/utils TS 化 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;为 BASE-DIST-01 提供兼容入口/声明及消费者验证 | H | todo | | PKG-TOOL-THUMB-05 | artplayer-tool-thumbnail
验证新旧核心和组合 | PKG-TOOL-THUMB-04, CORE-22 | 工具独立浏览器使用,生成缩略图在核心中显示 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo | @@ -536,4 +536,5 @@ - PKG-IFRAME-04: [记录](changes/2026-09-13-PKG-IFRAME-04-types.md) [记录](baselines/iframe-types-validation.json) - PKG-IFRAME-05: [记录](changes/2026-09-13-PKG-IFRAME-05-integration.md) [记录](baselines/iframe-integration-validation.json) [记录](changes/2026-09-13-PKG-IFRAME-05-history.md) [记录](baselines/iframe-history-validation.json) - PKG-TOOL-THUMB-01: [记录](baselines/thumbnail-release.json) [记录](baselines/thumbnail-contract.md) [记录](changes/2026-09-13-PKG-TOOL-THUMB-01-contract.md) [记录](baselines/thumbnail-contract-validation.json) +- PKG-TOOL-THUMB-02: [记录](baselines/thumbnail-behavior-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-02-behavior.md) - PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md) diff --git a/refactor/progress.md b/refactor/progress.md index 1a181b59b..4dcc7513d 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,17 @@ # 进度与证据 +## PKG-TOOL-THUMB-02 完成:抽帧、失败与资源基线 + +新增36项历史抽帧/生命周期测试,与26项契约共同通过;普通CI已接入新测试。 +真实浏览器39项记录分为24个Chromium/Firefox工具场景、12个Windows WebKit +Blob不可用对照、3个原生媒体对照。验证实际PNG像素/重复生成/正常URL清理,并复现 +迟到原生Blob回调在destroy后继续update和泄漏、替换文件URL泄漏、drop监听缺失。 +原生HTTP能播放而Blob失败,未将WebKit对照冒称抽帧成功;原始失败报告完整保留。 +完整CI1606、330生产TS检查通过。生产源码未改,03负责资源修复/拆分,04负责TS, +05/06保留WebKit/Safari实际文件抽帧和分发验收。见[变更](changes/2026-09-13-PKG-TOOL-THUMB-02-behavior.md) +与[验证](baselines/thumbnail-behavior-validation.json)。222项:111 done、10 doing、101 todo。 + + ## PKG-TOOL-THUMB-01 完成:历史契约与发布证据 冻结11条HTTP观察、12个工作区及8个历史Git输入;恢复3.5.31 CDN主文件并逐字节 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index 1e19d6af0..2addd364d 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -180,3 +180,4 @@ | THUMB-COMPAT-01 | open / 已复现 | Thumbnail recovered 3.5.31 and workspace 4.4.0 have conflicting height and event timing defaults | PKG-TOOL-THUMB-02, PKG-TOOL-THUMB-03, PKG-TOOL-THUMB-04 | | THUMB-LIFE-01 | open / 已复现 | Thumbnail drop binding and repeated destroy fail; pending work and replacement resource ownership need repair | PKG-TOOL-THUMB-02, PKG-TOOL-THUMB-03, PKG-TOOL-THUMB-05 | | THUMB-PROVENANCE-01 | open / 源码/产物事实 | Thumbnail local emitter attribution and historical distribution completeness remain unverified | PKG-TOOL-THUMB-04, PKG-TOOL-THUMB-06 | +| THUMB-MEDIA-01 | open / 已复现 | Windows WebKit native Blob URLs cannot load tested MP4 media despite successful HTTP controls | PKG-TOOL-THUMB-05, PKG-TOOL-THUMB-06 | diff --git a/refactor/risks.json b/refactor/risks.json index da4bc67c1..3690f2a55 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -3863,7 +3863,11 @@ "evidence": [ "refactor/baselines/thumbnail-contract.md", "refactor/baselines/thumbnail-release.json", - "refactor/scripts/thumbnail-contract.test.mjs" + "refactor/scripts/thumbnail-contract.test.mjs", + "refactor/baselines/thumbnail-behavior-validation.json", + "refactor/changes/2026-09-13-PKG-TOOL-THUMB-02-behavior.md", + "test/thumbnail.test.js", + "test/browser/thumbnail-tool.spec.js" ] }, { @@ -3875,13 +3879,17 @@ "PKG-TOOL-THUMB-03", "PKG-TOOL-THUMB-05" ], - "compatibleResolution": "Drop and duplicate-destroy defects are reproduced; reproduce source-observed extraction/timer/blob/input ownership gaps before fixes, then introduce owned cleanup without changing public shape.", + "compatibleResolution": "36 frozen behavior tests reproduce timer/input/source/callback ownership gaps and historical error ordering. Actual Chromium/Firefox native Blob callbacks prove late update and URL leaks after destroy. Task 03 must add corrected candidate regressions and owned cleanup; 05 retains real integration acceptance.", "closureCriteria": "Deterministic old failure/candidate success for replacement, error and destruction plus real media/browser resource validation.", "status": "open", "evidence": [ "refactor/baselines/thumbnail-contract.md", "refactor/baselines/thumbnail-release.json", - "refactor/scripts/thumbnail-contract.test.mjs" + "refactor/scripts/thumbnail-contract.test.mjs", + "refactor/baselines/thumbnail-behavior-validation.json", + "refactor/changes/2026-09-13-PKG-TOOL-THUMB-02-behavior.md", + "test/thumbnail.test.js", + "test/browser/thumbnail-tool.spec.js" ] }, { @@ -3900,6 +3908,24 @@ "refactor/baselines/thumbnail-release.json", "refactor/scripts/thumbnail-contract.test.mjs" ] + }, + { + "id": "THUMB-MEDIA-01", + "title": "Windows WebKit native Blob URLs cannot load tested MP4 media despite successful HTTP controls", + "status": "open", + "confirmation": "reproduced", + "owners": [ + "PKG-TOOL-THUMB-05", + "PKG-TOOL-THUMB-06" + ], + "evidence": [ + "refactor/baselines/thumbnail-behavior-validation.json", + "refactor/changes/2026-09-13-PKG-TOOL-THUMB-02-behavior.md", + "test/thumbnail.test.js", + "test/browser/thumbnail-tool.spec.js" + ], + "compatibleResolution": "Preserve independent HTTP/Blob capability controls; obtain actual supported WebKit/Safari file extraction evidence before release acceptance. Do not count the 12 unavailable controls as successful thumbnail generation or silently replace local files with HTTP media.", + "closureCriteria": "Actual generated PNG/extraction and lifecycle proof in supported Safari/WebKit environments, or explicit supported-environment decision with migration documentation; Windows limitation stays identifiable." } ] } diff --git a/refactor/tasks.json b/refactor/tasks.json index 6738cf4b5..17ae968fb 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -3765,11 +3765,14 @@ "ENG-03", "ENG-05" ], - "status": "todo", + "status": "done", "risk": "H", "deliverable": "文件输入/拖放、抽帧网格、begin/end、进度/失败/重复任务", "acceptance": "旧版本行为可重跑,成功/失败/切源/销毁有必要断言", - "evidence": [] + "evidence": [ + "baselines/thumbnail-behavior-validation.json", + "changes/2026-09-13-PKG-TOOL-THUMB-02-behavior.md" + ] }, { "id": "PKG-TOOL-THUMB-03", diff --git a/test/browser/media/thumbnail-pattern.mp4 b/test/browser/media/thumbnail-pattern.mp4 new file mode 100644 index 000000000..759c9d32a Binary files /dev/null and b/test/browser/media/thumbnail-pattern.mp4 differ diff --git a/test/browser/server.mjs b/test/browser/server.mjs index de902b4fa..2a9cbcd9a 100644 --- a/test/browser/server.mjs +++ b/test/browser/server.mjs @@ -75,6 +75,7 @@ const iframeCoreBytes = readMember(await ensureArchive(iframeCore), iframeCoreMe assert.equal(hash(iframeCoreBytes), iframeCore.files[iframeCoreMember], 'Historical Iframe core differs from frozen release') add('/published-4.5.9/artplayer.js', iframeCoreBytes, { kind: 'npm-release', version: iframeCore.version, integrity: iframeCore.integrity, member: iframeCoreMember }) add('/test/audio-tone.m4a', fs.readFileSync(path.join(workspace, 'test/browser/media/audio-tone.m4a')), { kind: 'generated-media', file: 'test/browser/media/audio-tone.m4a' }) +add('/test/thumbnail-pattern.mp4', fs.readFileSync(path.join(workspace, 'test/browser/media/thumbnail-pattern.mp4')), { kind: 'generated-media', file: 'test/browser/media/thumbnail-pattern.mp4' }) const mime = { '.html': 'text/html', '.js': 'text/javascript', '.mjs': 'text/javascript', '.json': 'application/json', '.css': 'text/css', '.mp4': 'video/mp4', '.m4a': 'audio/mp4', '.webm': 'video/webm', '.vtt': 'text/vtt', '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg' } add('/test/declaration-cues.vtt', fs.readFileSync(path.join(workspace, 'test/browser/media/declaration-cues.vtt')), { kind: 'test-subtitles', file: 'test/browser/media/declaration-cues.vtt' }) diff --git a/test/browser/thumbnail-native.spec.js b/test/browser/thumbnail-native.spec.js new file mode 100644 index 000000000..f7d21c550 --- /dev/null +++ b/test/browser/thumbnail-native.spec.js @@ -0,0 +1,49 @@ +import { expect, test } from './fixtures.js' + +test('Thumbnail native HTTP and Blob media controls', async ({ page, browserName }, testInfo) => { + await page.goto('/test/player.html?core=published') + const results = await page.evaluate(async () => { + const result = [] + for (const url of ['/test/pattern.mp4', '/test/thumbnail-pattern.mp4', '/assets/sample/video.mp4']) { + const blob = await (await fetch(url)).blob() + for (const transport of ['http', 'blob']) { + const video = document.createElement('video') + video.muted = true + video.controls = true + document.body.append(video) + const source = transport === 'blob' ? URL.createObjectURL(blob) : url + const state = await new Promise((resolve) => { + const timeout = setTimeout(() => finish('timeout'), 1500) + function finish(outcome) { + clearTimeout(timeout) + resolve({ outcome, readyState: video.readyState, error: video.error?.code || 0, duration: video.duration, width: video.videoWidth }) + } + video.onloadeddata = () => finish('loaded') + video.onerror = () => finish('error') + video.src = source + }) + result.push({ url, type: blob.type, transport, ...state }) + video.removeAttribute('src') + video.load() + video.remove() + if (transport === 'blob') + URL.revokeObjectURL(source) + } + } + return result + }) + await testInfo.attach('thumbnail-native-media-controls', { contentType: 'application/json', body: JSON.stringify(results) }) + expect(results).toHaveLength(6) + expect(results.every(item => item.outcome !== 'timeout')).toBe(true) + for (const item of results) { + if (item.transport === 'blob' && item.outcome === 'error') { + expect(browserName).toBe('webkit') + expect(item.error).toBe(4) + testInfo.annotations.push({ type: 'capability-control', description: `Native Blob URL unavailable for ${item.url}; HTTP comparison loads. This is not successful extraction.` }) + } + else { + expect(item.outcome).toBe('loaded') + expect(item.width).toBeGreaterThan(0) + } + } +}) diff --git a/test/browser/thumbnail-tool.spec.js b/test/browser/thumbnail-tool.spec.js new file mode 100644 index 000000000..d522e7561 --- /dev/null +++ b/test/browser/thumbnail-tool.spec.js @@ -0,0 +1,215 @@ +import { Buffer } from 'node:buffer' +import path from 'node:path' +import process from 'node:process' +import { fileURLToPath } from 'node:url' +import { hash } from '../../refactor/scripts/releases.mjs' +import { thumbnailHistorical } from '../helpers/thumbnail.js' +import { expect, test } from './fixtures.js' + +const sample = fileURLToPath(new URL('./media/thumbnail-pattern.mp4', import.meta.url)) + +async function capabilityControl(page, testInfo) { + const native = await page.evaluate(() => window.thumbnailNative) + if (native.blob === 'loaded') + return false + expect(testInfo.project.name).toBe('webkit') + expect(native).toEqual({ http: 'loaded', blob: 'error', error: 4 }) + await page.locator('#input').setInputFiles(sample) + await expect.poll(() => page.evaluate(() => tool.video.error?.code || 0)).toBe(4) + const state = await page.evaluate(() => { + const state = { errors: observed.filter(item => item.event === 'error').length, processing: tool.processing, duration: Number.isNaN(tool.video.duration) ? null : tool.video.duration } + tool.destroy() + return state + }) + expect(state).toEqual({ errors: 0, processing: false, duration: null }) + testInfo.annotations.push({ type: 'capability-control', description: 'Native HTTP succeeds but native Blob and tool Blob return media error 4. Extraction scenario unavailable; no successful extraction claimed.' }) + await testInfo.attach('thumbnail-blob-unavailable', { contentType: 'application/json', body: JSON.stringify({ native, tool: state }) }) + return true +} + +for (const implementation of thumbnailHistorical()) { + test.describe(`Thumbnail tool ${implementation.name}`, () => { + test.beforeEach(async ({ page }, testInfo) => { + await page.goto('/test/player.html?core=published') + await page.setContent('
') + await page.addScriptTag({ content: implementation.code }) + await page.evaluate(async () => { + const blob = await (await fetch('/test/thumbnail-pattern.mp4')).blob() + const blobUrl = URL.createObjectURL(blob) + window.thumbnailNative = {} + for (const [kind, src] of [['http', '/test/thumbnail-pattern.mp4'], ['blob', blobUrl]]) { + const video = document.createElement('video') + video.muted = true + document.body.append(video) + window.thumbnailNative[kind] = await new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error('Native media control did not settle')), 5000) + video.onloadeddata = () => { + clearTimeout(timeout) + resolve('loaded') + } + video.onerror = () => { + clearTimeout(timeout) + window.thumbnailNative.error = video.error.code + resolve('error') + } + video.src = src + }) + video.removeAttribute('src') + video.load() + video.remove() + } + URL.revokeObjectURL(blobUrl) + }) + await page.evaluate(() => { + window.created = [] + window.revoked = [] + const create = URL.createObjectURL.bind(URL) + const revoke = URL.revokeObjectURL.bind(URL) + URL.createObjectURL = (value) => { + const url = create(value) + created.push(url) + return url + } + URL.revokeObjectURL = (url) => { + revoked.push(url) + revoke(url) + } + window.tool = new ArtplayerToolThumbnail({ fileInput: document.querySelector('#input'), number: 10, width: 80, height: 30, column: 3, begin: 0, end: 20 }) + window.observed = [] + for (const event of ['file', 'video', 'canvas', 'update', 'done', 'error', 'destroy']) + tool.on(event, (...args) => observed.push({ event, progress: event === 'update' ? args[1] : null, processing: tool.processing })) + }) + await testInfo.attach('thumbnail-runtime', { contentType: 'application/json', body: JSON.stringify({ name: implementation.name, sha256: hash(implementation.code), recoveredIndividualFile: implementation.legacy, originalNpmArchive: false, media: path.relative(process.cwd(), sample) }) }) + }) + + test('native file selection, seek extraction, PNG pixels and repeat generation', async ({ page }, testInfo) => { + test.setTimeout(60000) + if (await capabilityControl(page, testInfo)) + return + await page.locator('#input').setInputFiles(sample) + await expect.poll(() => page.evaluate(() => tool.video.readyState)).toBeGreaterThanOrEqual(2) + await expect.poll(() => page.evaluate(() => observed.some(item => item.event === 'video'))).toBe(true) + const result = await page.evaluate(async () => { + await tool.start() + const first = tool.thumbnailUrl + const blob = await (await fetch(first)).blob() + const image = new Image() + image.src = first + await image.decode() + const canvas = document.createElement('canvas') + canvas.width = image.width + canvas.height = image.height + const context = canvas.getContext('2d') + context.drawImage(image, 0, 0) + const pixels = context.getImageData(0, 0, 80, tool.option.height).data + let nonBlack = 0 + for (let i = 0; i < pixels.length; i += 4) { + if (pixels[i] + pixels[i + 1] + pixels[i + 2] > 30 && pixels[i + 3] === 255) + nonBlack++ + } + const initialEvents = observed.slice() + await tool.start() + const second = tool.thumbnailUrl + tool.destroy() + return { width: image.width, height: image.height, sourceWidth: tool.video.videoWidth, sourceHeight: tool.video.videoHeight, cellHeight: tool.option.height, mime: blob.type, bytes: blob.size, nonBlack, initialEvents, events: observed, first, second, created, revoked, connected: tool.video.isConnected } + }) + expect(result.width).toBe(240) + expect(result.cellHeight).toBe(implementation.legacy ? 30 : result.sourceHeight / result.sourceWidth * 80) + expect(result.height).toBe(Math.trunc(result.cellHeight * 4 + 30)) + expect(result.mime).toBe('image/png') + expect(result.bytes).toBeGreaterThan(100) + expect(result.nonBlack).toBeGreaterThan(100) + expect(result.initialEvents.filter(item => item.event === 'update').map(item => item.progress)).toEqual(Array.from({ length: 10 }, (_, i) => (i + 1) / 10)) + expect(result.events.filter(item => item.event === 'done')).toHaveLength(2) + expect(result.first).not.toBe(result.second) + expect([...result.created].sort()).toEqual([...result.revoked].sort()) + expect(result.connected).toBe(false) + await testInfo.attach('thumbnail-extraction', { contentType: 'application/json', body: JSON.stringify(result) }) + }) + + test('DOM drop dispatch exposes missing listener while callable ondrop works', async ({ page }, testInfo) => { + if (await capabilityControl(page, testInfo)) + return + const result = await page.evaluate(async () => { + const bytes = await (await fetch('/test/thumbnail-pattern.mp4')).blob() + const file = new File([bytes], 'dropped.mp4', { type: 'video/mp4' }) + const dataTransfer = new DataTransfer() + dataTransfer.items.add(file) + const event = new Event('drop', { cancelable: true }) + Object.defineProperty(event, 'dataTransfer', { value: dataTransfer }) + tool.option.fileInput.dispatchEvent(event) + const fromListener = { files: observed.filter(item => item.event === 'file').length, prevented: event.defaultPrevented } + tool.ondrop(event) + return { fromListener, afterDirect: observed.filter(item => item.event === 'file').length, prevented: event.defaultPrevented } + }) + expect(result).toEqual({ fromListener: { files: 0, prevented: false }, afterDirect: 1, prevented: true }) + await expect.poll(() => page.evaluate(() => tool.video.readyState)).toBeGreaterThanOrEqual(2) + await page.evaluate(() => tool.destroy()) + }) + + test('corrupt media, replacement URLs and repeated destroy reproduce baseline gaps', async ({ page }, testInfo) => { + if (await capabilityControl(page, testInfo)) + return + await page.locator('#input').setInputFiles({ name: 'corrupt.mp4', mimeType: 'video/mp4', buffer: Buffer.from('not a media container') }) + await expect.poll(() => page.evaluate(() => tool.video.error?.code || 0)).toBeGreaterThan(0) + const failed = await page.evaluate(() => ({ code: tool.video.error.code, toolErrors: observed.filter(item => item.event === 'error').length, first: tool.videoUrl })) + expect(failed.toolErrors).toBe(0) + await page.locator('#input').setInputFiles(sample) + await expect.poll(() => page.evaluate(() => tool.video.readyState)).toBeGreaterThanOrEqual(2) + const result = await page.evaluate(() => { + const value = tool.option.fileInput.value + tool.destroy() + let repeated + try { + tool.destroy() + } + catch (error) { repeated = error.name } + return { value, created, revoked, repeated } + }) + expect(result.created).toHaveLength(2) + expect(result.revoked).toEqual([result.created[1]]) + expect(result.revoked).not.toContain(failed.first) + expect(result.repeated).toBe('NotFoundError') + expect(result.value === '').toBe(!implementation.legacy) + await testInfo.attach('thumbnail-media-failure', { contentType: 'application/json', body: JSON.stringify({ failed, result }) }) + }) + + test('native PNG callback delivered after destroy still emits update and creates a URL', async ({ page }, testInfo) => { + if (await capabilityControl(page, testInfo)) + return + await page.locator('#input').setInputFiles(sample) + await expect.poll(() => page.evaluate(() => tool.video.readyState)).toBeGreaterThanOrEqual(2) + await page.evaluate(() => { + const original = HTMLCanvasElement.prototype.toBlob + HTMLCanvasElement.prototype.toBlob = function (callback, ...args) { + original.call(this, (blob) => { + window.nativeBlob = blob + window.releaseBlob = () => callback(blob) + }, ...args) + } + window.pendingState = 'pending' + tool.start().then(() => { + window.pendingState = 'resolved' + }, () => { + window.pendingState = 'rejected' + }) + }) + await expect.poll(() => page.evaluate(() => typeof window.releaseBlob)).toBe('function') + const result = await page.evaluate(async () => { + tool.destroy() + const count = created.length + releaseBlob() + await new Promise(resolve => setTimeout(resolve, 0)) + return { count, created, revoked, pendingState, events: observed.map(item => item.event), connected: tool.video.isConnected, bytes: nativeBlob.size } + }) + expect(result.bytes).toBeGreaterThan(100) + expect(result.created).toHaveLength(result.count + 1) + expect(result.revoked).not.toContain(result.created.at(-1)) + expect(result.events.indexOf('update')).toBeGreaterThan(result.events.indexOf('destroy')) + expect(result.pendingState).toBe('pending') + expect(result.connected).toBe(false) + await testInfo.attach('thumbnail-late-native-blob', { contentType: 'application/json', body: JSON.stringify(result) }) + }) + }) +} +/* global created, revoked, ArtplayerToolThumbnail, tool, observed, releaseBlob, pendingState, nativeBlob */ diff --git a/test/helpers/thumbnail.js b/test/helpers/thumbnail.js index b5ac250e0..5cc3239a6 100644 --- a/test/helpers/thumbnail.js +++ b/test/helpers/thumbnail.js @@ -12,6 +12,8 @@ export function thumbnailHistorical() { export function thumbnailEnvironment(implementation, script = false) { const operations = [] const timers = new Map() + const blobs = [] + const controls = { deferBlobs: false } let nextTimer = 0 let nextUrl = 0 const context2D = Object.fromEntries(['fillRect', 'fillText', 'drawImage'].map(name => [name, (...args) => operations.push({ name, args })])) @@ -60,7 +62,12 @@ export function thumbnailEnvironment(implementation, script = false) { removeEventListener(name, callback) { this.handlers.get(name)?.delete(callback) } getContext() { return context2D } click() { operations.push({ name: 'click', download: this.download, href: this.href }) } - toBlob(callback) { callback({ kind: 'image/png' }) } + toBlob(callback) { + if (controls.deferBlobs) + blobs.push(callback) + else + callback({ kind: 'image/png' }) + } } const body = new Element('body') const module = { exports: {} } @@ -69,6 +76,8 @@ export function thumbnailEnvironment(implementation, script = false) { document: { body, createElement: tag => new Element(tag) }, URL: { createObjectURL(value) { + if (value == null) + throw new TypeError('Blob required') const url = `blob:thumbnail-${++nextUrl}` operations.push({ name: 'createURL', value, url }) return url @@ -91,5 +100,5 @@ export function thumbnailEnvironment(implementation, script = false) { vm.runInNewContext(implementation.code, box) const exported = script ? box.ArtplayerToolThumbnail : module.exports const Factory = exported.default || exported - return { box, Element, body, Factory, exported, operations, context2D, timers } + return { box, Element, body, Factory, exported, operations, context2D, timers, blobs, controls } } diff --git a/test/thumbnail.test.js b/test/thumbnail.test.js new file mode 100644 index 000000000..a46e542c7 --- /dev/null +++ b/test/thumbnail.test.js @@ -0,0 +1,259 @@ +import assert from 'node:assert/strict' +// eslint-disable-next-line test/no-import-node-test -- Historical extraction and resource baseline. +import test from 'node:test' +import { thumbnailEnvironment, thumbnailHistorical } from './helpers/thumbnail.js' + +const flush = () => new Promise(resolve => setImmediate(resolve)) +function observe(promise) { + const state = { status: 'pending' } + promise.then(value => Object.assign(state, { status: 'resolved', value }), error => Object.assign(state, { status: 'rejected', error })) + return state +} +async function tick(env) { + const next = env.timers.entries().next().value + assert(next, 'Expected a scheduled timer') + env.timers.delete(next[0]) + next[1].callback() + await flush() + return next[1].delay +} +async function frame(env, legacy) { + await flush() + if (legacy) { + await tick(env) + } + else { + assert.equal(typeof env.instance.video.oncanplay, 'function') + env.instance.video.oncanplay() + await flush() + } +} + +for (const implementation of thumbnailHistorical()) { + async function setup(options = {}) { + const env = thumbnailEnvironment(implementation) + env.instance = new env.Factory({ fileInput: new env.Element('input'), number: 10, width: 40, height: 30, column: 3, ...options }) + env.instance.loadVideo({ name: 'first.mp4', type: 'video/mp4' }) + if (implementation.legacy) + await tick(env) + env.instance.video.duration = 100 + return env + } + async function complete(env, state, count = 10) { + for (let index = 0; index < count; index++) + await frame(env, implementation.legacy) + if (implementation.legacy) { + assert.equal(state.status, 'pending') + assert.equal(await tick(env), env.instance.option.delay * 2) + } + await flush() + assert.equal(state.status, 'resolved') + assert.equal(state.value, undefined) + } + + test(`Thumbnail ${implementation.name}: sequential extraction, progress, done and rerun`, async () => { + const env = await setup({ begin: 10, end: 90 }) + const seen = [] + env.instance.on('canvas', canvas => seen.push(['canvas', canvas.width, canvas.height, env.instance.processing])) + env.instance.on('update', (url, progress) => seen.push(['update', url, progress, env.instance.processing])) + env.instance.on('done', () => seen.push(['done', env.instance.processing])) + const state = observe(env.instance.start()) + assert.equal(env.instance.duration, 80) + assert.equal(env.instance.density, 0.125) + const height = implementation.legacy ? 30 : 22.5 + assert.deepEqual(seen, [['canvas', 120, height * 4 + 30, false]]) + await complete(env, state) + assert.deepEqual(seen.filter(item => item[0] === 'update').map(item => [item[2], item[3]]), Array.from({ length: 10 }, (_, i) => [(i + 1) / 10, true])) + assert.deepEqual(seen.at(-1), ['done', false]) + const draws = env.operations.filter(item => item.name === 'drawImage') + assert.equal(draws.length, 10) + assert.deepEqual(draws.map(item => item.args.slice(1)), Array.from({ length: 10 }, (_, i) => [i % 3 * 40, Math.floor(i / 3) * height, 40, height])) + assert.equal(env.instance.video.currentTime, 86) + assert.equal(env.operations.filter(item => item.name === 'revokeURL').length, 9) + const second = observe(env.instance.start()) + await complete(env, second) + assert.equal(seen.filter(item => item[0] === 'done').length, 2) + env.instance.destroy() + assert.equal(env.operations.filter(item => item.name === 'revokeURL').length, 21) + }) + + test(`Thumbnail ${implementation.name}: range and density guards emit before throwing`, async () => { + for (const [options, pattern] of [[{ begin: 50, end: 20 }, /End time/], [{ begin: 1000 }, /End time/], [{ begin: 0, end: 5 }, /density/]]) { + const env = await setup(options) + const errors = [] + env.instance.on('error', message => errors.push(message)) + assert.throws(() => env.instance.start(), pattern) + assert.equal(errors.length, 1) + assert.equal(env.instance.processing, false) + assert.equal(env.operations.filter(item => item.name === 'drawImage').length, 0) + env.instance.destroy() + } + const env = await setup({ begin: -20, end: 200 }) + const state = observe(env.instance.start()) + assert.equal(env.instance.option.begin, 0) + assert.equal(env.instance.option.end, 100) + await complete(env, state) + }) + + test(`Thumbnail ${implementation.name}: active duplicate start/download reject without replacing extraction`, async () => { + const env = await setup() + const state = observe(env.instance.start()) + await frame(env, implementation.legacy) + assert.throws(() => env.instance.start(), /task in progress/) + assert.throws(() => env.instance.download(), /task in progress/) + await complete(env, state, 9) + }) + + test(`Thumbnail ${implementation.name}: blob callback gates subsequent frames`, async () => { + const env = await setup() + env.controls.deferBlobs = true + const state = observe(env.instance.start()) + await frame(env, implementation.legacy) + assert.equal(env.blobs.length, 1) + assert.equal(env.operations.filter(item => item.name === 'drawImage').length, 1) + assert.equal(env.instance.video.currentTime, 5) + assert.equal(state.status, 'pending') + env.controls.deferBlobs = false + env.blobs.shift()({ kind: 'image/png' }) + await flush() + await complete(env, state, 9) + }) + + test(`Thumbnail ${implementation.name}: null blob callback throws outside the unsettled extraction promise`, async () => { + const env = await setup() + env.controls.deferBlobs = true + const state = observe(env.instance.start()) + await frame(env, implementation.legacy) + assert.throws(() => env.blobs.shift()(null), /Blob required/) + await flush() + assert.equal(state.status, 'pending') + assert.equal(env.instance.processing, true) + env.instance.destroy() + await flush() + assert.equal(state.status, 'pending') + }) + + test(`Thumbnail ${implementation.name}: late blob after destroy creates an unowned URL and continues work`, async () => { + const env = await setup() + env.controls.deferBlobs = true + const events = [] + env.instance.on('destroy', () => events.push('destroy')) + env.instance.on('update', () => events.push('update')) + const state = observe(env.instance.start()) + await frame(env, implementation.legacy) + env.instance.destroy() + env.blobs.shift()({ kind: 'image/png' }) + await flush() + assert.deepEqual(events, ['destroy', 'update']) + assert.equal(state.status, 'pending') + assert.equal(env.instance.video.parentNode, null) + assert.equal(env.instance.processing, true) + const last = env.operations.filter(item => item.name === 'createURL').at(-1).url + assert.equal(env.operations.some(item => item.name === 'revokeURL' && item.url === last), false) + assert(implementation.legacy ? env.timers.size > 0 : typeof env.instance.video.oncanplay === 'function') + }) + + test(`Thumbnail ${implementation.name}: file replacement leaks old URL and preserves the running task`, async () => { + const env = await setup() + const first = env.instance.videoUrl + const state = observe(env.instance.start()) + await frame(env, implementation.legacy) + const next = { name: 'second.mp4', type: 'video/mp4' } + env.instance.loadVideo(next) + assert.notEqual(env.instance.videoUrl, first) + assert.equal(env.instance.file, next) + assert.equal(env.instance.processing, true) + assert.equal(state.status, 'pending') + env.instance.destroy() + assert.equal(env.operations.some(item => item.name === 'revokeURL' && item.url === first), false) + assert.equal(env.instance.processing, true) + }) + + test(`Thumbnail ${implementation.name}: missing metadata start and destruction do not have owned cancellation`, async () => { + const env = await setup() + env.instance.video.duration = 0 + const state = observe(env.instance.start()) + assert.equal(env.timers.size, 1) + env.instance.destroy() + assert.equal(env.timers.size, 1) + assert.equal(await tick(env), 1000) + assert.equal(state.status, implementation.legacy ? 'rejected' : 'pending') + if (implementation.legacy) + assert.equal(state.error.name, 'TypeError') + else + assert.equal(env.timers.size, 1) + }) + + test(`Thumbnail ${implementation.name}: changed input and generated wrapper remain unowned`, async () => { + const env = await setup() + const first = env.instance.option.fileInput + const wrapper = new env.Element('div') + env.instance.setup({ fileInput: wrapper }) + assert.equal(wrapper.children.length, 1) + assert.equal(env.instance.option.fileInput.handlers.size, 0) + assert.equal(first.handlers.get('change').size, 1) + env.instance.destroy() + assert.equal(first.handlers.get('change').size, 1) + assert.equal(wrapper.children.length, 1) + assert.equal(wrapper.style.position, 'relative') + }) + + test(`Thumbnail ${implementation.name}: currentTime setter failure rejects and releases processing`, async () => { + const env = await setup() + const failure = new Error('seek assignment failed') + const errors = [] + env.instance.on('error', message => errors.push(message)) + Object.defineProperty(env.instance.video, 'currentTime', { + get() { return 0 }, + set() { + throw failure + }, + }) + const state = observe(env.instance.start()) + await flush() + assert.equal(state.status, 'rejected') + assert.equal(state.error, failure) + assert.equal(env.instance.processing, false) + assert.deepEqual(errors, ['seek assignment failed']) + env.instance.destroy() + }) + + test(`Thumbnail ${implementation.name}: throwing done listener rejects with historical error multiplicity`, async () => { + const env = await setup() + const failure = new Error('done listener failed') + const errors = [] + env.instance.on('error', message => errors.push(message)) + env.instance.on('done', () => { + throw failure + }) + const state = observe(env.instance.start()) + for (let index = 0; index < 10; index++) + await frame(env, implementation.legacy) + if (implementation.legacy) + await tick(env) + await flush() + assert.equal(state.status, 'rejected') + assert.equal(state.error, failure) + assert.equal(env.instance.processing, false) + assert.deepEqual(errors, Array.from({ length: implementation.legacy ? 2 : 1 }).fill('done listener failed')) + env.instance.destroy() + }) + + test(`Thumbnail ${implementation.name}: update listener failure in deferred callback leaves work pending`, async () => { + const env = await setup() + env.controls.deferBlobs = true + const failure = new Error('update listener failed') + env.instance.on('update', () => { + throw failure + }) + const state = observe(env.instance.start()) + await frame(env, implementation.legacy) + assert.throws(() => env.blobs.shift()({ kind: 'image/png' }), error => error === failure) + await flush() + assert.equal(state.status, 'pending') + assert.equal(env.instance.processing, true) + assert.equal(typeof env.instance.thumbnailUrl, 'string') + env.instance.destroy() + assert.equal(state.status, 'pending') + }) +}