mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-20 20:21:20 -08:00
test(canvas): [PKG-CANVAS-02] reproduce frame leaks and native pixel boundaries
This commit is contained in:
1 parent
5e26d2298d
commit
e931520e66
11 files changed
+642
-12
No files matched your search
+3
-2
@@ -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/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/canvas.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",
|
||||
@@ -71,7 +71,8 @@
|
||||
"check:contracts": "node refactor/scripts/contracts.mjs",
|
||||
"test:contracts": "node refactor/scripts/run-contracts.mjs",
|
||||
"test:ambilight": "node --test test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js",
|
||||
"test:ambilight-types-package": "node refactor/scripts/ambilight-package-types.mjs"
|
||||
"test:ambilight-types-package": "node refactor/scripts/ambilight-package-types.mjs",
|
||||
"test:canvas": "node --test test/canvas.test.js"
|
||||
},
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"task": "PKG-CANVAS-02",
|
||||
"status": "done",
|
||||
"checks": [
|
||||
{
|
||||
"command": "node --test test/canvas.test.js",
|
||||
"result": "pass",
|
||||
"tests": 33,
|
||||
"scope": "11 historical normal/error/ownership categories across three frozen implementations"
|
||||
},
|
||||
{
|
||||
"command": "playwright test test/browser/canvas.spec.js",
|
||||
"result": "pass",
|
||||
"tests": 42,
|
||||
"skipped": 0,
|
||||
"browsers": [
|
||||
{
|
||||
"project": "chromium",
|
||||
"version": "153.0.8010.12",
|
||||
"platform": "win32"
|
||||
},
|
||||
{
|
||||
"project": "firefox",
|
||||
"version": "155.0",
|
||||
"platform": "win32"
|
||||
},
|
||||
{
|
||||
"project": "webkit",
|
||||
"version": "26.6",
|
||||
"platform": "win32"
|
||||
}
|
||||
],
|
||||
"scope": "36 archived/frozen proxy checks plus 6 independent native attached/detached controls; expected historical WebKit transparency and post-destroy RAF remain defects"
|
||||
},
|
||||
{
|
||||
"command": "focused eslint --no-fix",
|
||||
"result": "pass"
|
||||
},
|
||||
{
|
||||
"command": "yarn ci:check",
|
||||
"result": "pass",
|
||||
"tests": {
|
||||
"unit": 914,
|
||||
"engineering": 14,
|
||||
"baseline": 96,
|
||||
"mainTotal": 1024,
|
||||
"repeatedContractExecutions": 44
|
||||
},
|
||||
"strictProductionTypeScriptFiles": 278
|
||||
}
|
||||
],
|
||||
"limitations": [
|
||||
"Historical failure assertions are not candidate fixes or rendering acceptance.",
|
||||
"WebKit detached-video transparency is scoped to recorded Windows Playwright browser and media inputs, not all Safari devices.",
|
||||
"Controlled Node DOM and bitmap promises do not prove native pixel, codec or device behavior.",
|
||||
"No VAST VPN exception or browser skip used."
|
||||
],
|
||||
"sourceCommit": "5e26d2298d4fc7568b208962d2aa069b788a8a78",
|
||||
"preservedBrowserAttempts": [
|
||||
{
|
||||
"file": "refactor/.cache/canvas02-first-results/report.json",
|
||||
"sha256": "de5c0195c7de8a1551b064fd4213c089cfe20d6aecc5bdba4558938ac7b2758e",
|
||||
"stats": {
|
||||
"startTime": "2026-09-12T04:24:44.219Z",
|
||||
"duration": 29128.137000000002,
|
||||
"expected": 30,
|
||||
"skipped": 0,
|
||||
"unexpected": 6,
|
||||
"flaky": 0
|
||||
},
|
||||
"resultsDirectory": "refactor/.cache/canvas02-first-results/results"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas02-webkit-wait-results/report.json",
|
||||
"sha256": "43a7b4497d89213244201ecfd052b0fa0dedabea71d88239756df12998e3039e",
|
||||
"stats": {
|
||||
"startTime": "2026-09-12T04:26:11.586Z",
|
||||
"duration": 22140.993,
|
||||
"expected": 0,
|
||||
"skipped": 0,
|
||||
"unexpected": 6,
|
||||
"flaky": 0
|
||||
},
|
||||
"resultsDirectory": "refactor/.cache/canvas02-webkit-wait-results/results"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas02-pixel-probe-results/report.json",
|
||||
"sha256": "dd441cc8e223fdaeecdb39dac5297c5f5cb1008a2c396d3b9caac5fa3a43ed76",
|
||||
"stats": {
|
||||
"startTime": "2026-09-12T04:27:46.306Z",
|
||||
"duration": 11620.739,
|
||||
"expected": 0,
|
||||
"skipped": 0,
|
||||
"unexpected": 1,
|
||||
"flaky": 0
|
||||
},
|
||||
"resultsDirectory": "refactor/.cache/canvas02-pixel-probe-results/results"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas02-attach-probe-results/report.json",
|
||||
"sha256": "5f12808970c4cb65ffed6cb265c3d3b595bdea0eb1ea79fa3e524a77a22070b7",
|
||||
"stats": {
|
||||
"startTime": "2026-09-12T04:29:43.968Z",
|
||||
"duration": 11748.322,
|
||||
"expected": 0,
|
||||
"skipped": 0,
|
||||
"unexpected": 1,
|
||||
"flaky": 0
|
||||
},
|
||||
"resultsDirectory": "refactor/.cache/canvas02-attach-probe-results/results"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas02-native-control-results/report.json",
|
||||
"sha256": "655cd06111a6f9389bee5b7bd56b1f50b81482635bb69ad58462051a721235a5",
|
||||
"stats": {
|
||||
"startTime": "2026-09-12T04:34:13.618Z",
|
||||
"duration": 6111.419,
|
||||
"expected": 3,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"resultsDirectory": "refactor/.cache/canvas02-native-control-results/results"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas02-native-detached-results/report.json",
|
||||
"sha256": "1db5932b7979980177dc0d158090cec3d967cce33c2e4f9a4d2f1c7d6ca94f95",
|
||||
"stats": {
|
||||
"startTime": "2026-09-12T04:35:12.367Z",
|
||||
"duration": 11861.622,
|
||||
"expected": 0,
|
||||
"skipped": 0,
|
||||
"unexpected": 1,
|
||||
"flaky": 0
|
||||
},
|
||||
"resultsDirectory": "refactor/.cache/canvas02-native-detached-results/results"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas02-pixel-boundary-results/report.json",
|
||||
"sha256": "ac1768227986cb716d40231a856bab8ae9dcb35ee0161516a42cb47e5df1e687",
|
||||
"stats": {
|
||||
"startTime": "2026-09-12T04:36:16.598Z",
|
||||
"duration": 30244.336,
|
||||
"expected": 42,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"resultsDirectory": "refactor/.cache/canvas02-pixel-boundary-results/results"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas02-final-results/report.json",
|
||||
"sha256": "28886ac131df9f5f871fb9c66b0985a73cd9e84dd5542d028db68e28f374b649",
|
||||
"stats": {
|
||||
"startTime": "2026-09-12T04:37:05.676Z",
|
||||
"duration": 28495.888,
|
||||
"expected": 42,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"resultsDirectory": "refactor/.cache/canvas02-final-results/results"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"file": "test/canvas.test.js",
|
||||
"sha256LF": "df1b1aaee15f18b6b9808f48e759ef2ef22912e7a20a0b831e51bee4d84ee436"
|
||||
},
|
||||
{
|
||||
"file": "test/browser/canvas.spec.js",
|
||||
"sha256LF": "e34a4463574f09057af7f307e8564cbcd8f03415a270f362b6c83de69359f652"
|
||||
},
|
||||
{
|
||||
"file": "test/helpers/canvas.js",
|
||||
"sha256LF": "cc617b6d1053a9ed9caa5dad03088abaaff4344f078e23d007ee1a935f2a6c3c"
|
||||
},
|
||||
{
|
||||
"file": "refactor/baselines/canvas-release.json",
|
||||
"sha256LF": "7f7b229a5c0350a5d02be101d0095548c7ef1e76ce95c3b7152b415b0117398c"
|
||||
}
|
||||
],
|
||||
"logs": [
|
||||
{
|
||||
"file": "refactor/.cache/canvas02-node.log",
|
||||
"sha256": "7558fb6922ba2e2490c586051081375f98069048d234f4cd7fda22b85b746bfd"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas02-lint-final.log",
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas02-browser-readiness.log",
|
||||
"sha256": "ba10158100c211a7f5ee5e05e917f7ffc20c8968e8372b7d99f668e16e4b7970"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas02-ci-before-risk-index.log",
|
||||
"sha256": "0a0452a062448c3a2092c4d62ed84b5a255f9016cf4ef86c108825952d45a45e"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas02-ci.log",
|
||||
"sha256": "e4137711c5902d943ddfdae260f895959223e9ef1040e464197f8f59209d403f"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -15,9 +15,27 @@ play/pause、destroy前后订阅、缺context和尺寸异常;保留历史失
|
||||
|
||||
当前已有的Ambilight代理颜色测试会直接加载proxy的src/index.js;代理迁移文件入口
|
||||
或模块拆分时必须同步改为实际候选入口加载,不能用旧dist掩盖改名或遗漏模块。
|
||||
Canvas本身的正式历史浏览器矩阵尚未建立;没有在01使用新浏览器结果作包整体验收。
|
||||
Canvas正式历史浏览器矩阵在02建立;01没有使用新浏览器结果作包整体验收。
|
||||
|
||||
01最终13项Canvas专项、7项契约索引检查与991项完整CI通过;证据见
|
||||
[发布契约验证](baselines/canvas-contract-validation.json) 和
|
||||
[实施记录](changes/2026-09-12-PKG-CANVAS-01-contract.md)。三个新增风险继续open,
|
||||
生命周期仅source-observed,02需先复现再推进03源码修复。
|
||||
01时生命周期仅source-observed,02复现以后推进03源码修复。
|
||||
|
||||
## 02 历史失败与真实浏览器
|
||||
|
||||
`yarn test:canvas`运行33项历史Node检查,已接入test:unit。运行
|
||||
`yarn test:browser test/browser/canvas.spec.js`核验42项:三实现×两种实际支持proxy
|
||||
的核心×三引擎×两场景=36,加三个引擎分别挂载/未挂载原生video的6项独立对照。
|
||||
就绪事件顺序、暂停/seek/resize/切源及回调内destroy后继续RAF均有断言。历史npm
|
||||
5.1.7不支持proxy,不假装把它计入此矩阵。
|
||||
|
||||
WebKit历史代理输出透明是明确的失败基线;测试成功表示重现了该失败。原生video
|
||||
播放前挂载可读到像素,未挂载对照为透明;播放后才挂载旧代理的video未恢复。
|
||||
没有替换像素、bitmap或媒体来绕过它,03需以候选真实像素关闭CANVAS-PIXEL-01。
|
||||
既有Ambilight组合测试由回调填充九格调色板,只证明输出buffer几何,不证明代理
|
||||
已从视频画出有效帧。Windows Playwright WebKit证据不能直接等同真实Safari设备。
|
||||
|
||||
失败尝试与最终报告均保留report.json和完整results,哈希与范围见
|
||||
[02验证证据](baselines/canvas-behavior-validation.json);问题处理细节见
|
||||
[02实施记录](changes/2026-09-12-PKG-CANVAS-02-tests.md)。本任务不修改生产源码。
|
||||
@@ -0,0 +1,32 @@
|
||||
# PKG-CANVAS-02 历史行为与错误回归
|
||||
|
||||
对实际npm1.0.0、1.1.0和01冻结工作区运行33项Node测试,覆盖挂起bitmap在pause/
|
||||
destroy之后完成、重复play产生并发帧、旧帧覆盖新帧、drawImage异常未close、bitmap
|
||||
拒绝、用户回调异常、回调中同步销毁、延迟订阅在销毁后安装、注册失败未回滚、已销毁
|
||||
宿主、零尺寸和缺失2D context。正常回调错误语义与资源缺陷分别记录;假Canvas的
|
||||
NaN仅描述输入赋值,不声称真实Canvas能存储NaN尺寸。
|
||||
|
||||
真实浏览器直接执行两个归档及冻结JS,组合实际npm核心5.4.0/候选核心和Chromium/
|
||||
Firefox/WebKit,36项代理检查加6项独立原生对照。代理检查包含loadedmetadata→
|
||||
loadeddata→canplay顺序、播放/暂停/seek、resize、切源,以及回调中destroy以后
|
||||
原生RAF仍被调用。RAF观察器只包装代理的词法引用,仍委托浏览器调度;媒体、Canvas、
|
||||
bitmap、回调和事件均为实际执行。
|
||||
|
||||
首次矩阵30通过/6失败:WebKit的六个正常代理场景透明,视频时间仍推进。等待更多帧、
|
||||
对同一视频直接drawImage、将其在播放后挂载到页面均未恢复。独立原生video播放前
|
||||
挂载则三个引擎都读到alpha255;未挂载的WebKit对照为alpha0,另两个引擎仍255。
|
||||
这将问题收窄到当前WebKit环境的未挂载媒体路径,没有据此宣称所有Safari设备相同。
|
||||
最终测试明确断言历史透明结果;它们证明旧失败可复现,不是WebKit渲染成功或skip。
|
||||
03必须以候选实际像素验证修复,不能把原生对照当作代理修复证据。
|
||||
|
||||
新增test:canvas并接入test:unit,源码、类型、依赖和构建产物保持本任务开始时状态。
|
||||
验证结果及保留的失败/最终报告见[证据](../baselines/canvas-behavior-validation.json)。
|
||||
生命周期风险升级为reproduced,新增未挂载视频像素风险,均继续open交给03~05。
|
||||
|
||||
VAST脚本VPN例外继续只适用于VAST;本任务没有使用例外,也没有跳过浏览器场景。
|
||||
完成检查后本任务独立本地提交,不推送/发布。
|
||||
|
||||
首次完整CI在风险索引一致性检查失败:新增风险的JSON台账已更新,生成的risk-table.md
|
||||
尚未同步。使用risk-register.mjs --write重新生成并重跑CI,保留首次失败日志。
|
||||
|
||||
最终专项lint、33项历史Node、42项浏览器及完整CI1024项通过(914单元+14工程+96基线),另44项重复契约观察不计为新增。278个生产TS文件严格检查;状态done,准备独立提交。
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 221 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 125 / doing 6 / blocked 0 / done 90 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 124 / doing 6 / blocked 0 / done 91 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -319,7 +319,7 @@
|
||||
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| PKG-CANVAS-01 | artplayer-proxy-canvas<br>核对包契约与历史用法 | BASE-05 | callback、canvas 原方法、转发 media 属性/方法/事件 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done |
|
||||
| PKG-CANVAS-02 | artplayer-proxy-canvas<br>建立特有行为与错误测试 | PKG-CANVAS-01, ENG-03, ENG-05 | ready/loadeddata/canplay、play/seek、回调、resize 与销毁 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | todo |
|
||||
| PKG-CANVAS-02 | artplayer-proxy-canvas<br>建立特有行为与错误测试 | PKG-CANVAS-01, ENG-03, ENG-05 | ready/loadeddata/canplay、play/seek、回调、resize 与销毁 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
|
||||
| PKG-CANVAS-03 | artplayer-proxy-canvas<br>整理内部职责与资源 | PKG-CANVAS-02, CORE-06, CORE-11, CORE-16 | video adapter/原 canvas 方法/RAF 绘制/事件订阅分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | todo |
|
||||
| PKG-CANVAS-04 | artplayer-proxy-canvas<br>迁移自有源码和公开类型 | PKG-CANVAS-03, ENG-04, ENG-06, CORE-07 | canvas 与媒体能力的精确组合类型,保持 Result | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
|
||||
| PKG-CANVAS-05 | artplayer-proxy-canvas<br>验证新旧核心和组合 | PKG-CANVAS-04, CORE-22 | 真实 video/canvas 绘制、字幕和 document PiP 恢复 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
@@ -507,3 +507,4 @@
|
||||
- PKG-VAST-02: [记录](changes/2026-09-12-PKG-VAST-02-tests.md) [记录](baselines/vast-behavior-validation.json) [记录](baselines/vast-core.json) [记录](vast-validation.md)
|
||||
- PKG-VAST-03: [记录](changes/2026-09-12-PKG-VAST-03-lifecycle.md) [记录](baselines/vast-lifecycle-validation.json) [记录](vast-compatibility-decision.md)
|
||||
- PKG-CANVAS-01: [记录](changes/2026-09-12-PKG-CANVAS-01-contract.md) [记录](baselines/canvas-contract.md) [记录](baselines/canvas-release.json) [记录](canvas-validation.md) [记录](baselines/canvas-contract-validation.json)
|
||||
- PKG-CANVAS-02: [记录](changes/2026-09-12-PKG-CANVAS-02-tests.md) [记录](baselines/canvas-behavior-validation.json) [记录](canvas-validation.md)
|
||||
@@ -1,5 +1,16 @@
|
||||
# 进度与证据
|
||||
|
||||
## 最新完成:Canvas-02 历史错误与原生浏览器对照
|
||||
|
||||
33项Node回归与42项真实浏览器检查通过,就绪事件顺序、播放/暂停/seek/resize/切源
|
||||
及销毁后RAF均有断言。WebKit六个旧代理场景首次透明失败;独立原生对照表明播放前
|
||||
挂载可读到像素、未挂载透明。最终矩阵明确重现历史失败,不把它当作渲染成功。
|
||||
完整CI1024项通过,另44项重复观察;未修改生产源码。CANVAS-LIFE-01升级reproduced,
|
||||
新增CANVAS-PIXEL-01待03修复。当前221项:91 done、6 doing、124 todo。下一步03
|
||||
拆分严格TS职责并以候选回归关闭生命周期/像素问题。见
|
||||
[本步记录](changes/2026-09-12-PKG-CANVAS-02-tests.md) 和
|
||||
[验证证据](baselines/canvas-behavior-validation.json)。独立本地提交,无推送/发布。
|
||||
|
||||
## 最新完成:Canvas-01 发布契约与正常基线
|
||||
|
||||
冻结npm1.0.0/1.1.0共12成员及工作区6个Git输入,运行三实现的同步返回、媒体属性/
|
||||
|
||||
@@ -155,6 +155,7 @@
|
||||
| AMBILIGHT-TYPE-01 | resolved / 已复现 | Ambilight declarations disagree with optional factory argument and differ across published generations | PKG-AMBILIGHT-04, PKG-AMBILIGHT-06 |
|
||||
| AMBILIGHT-DIST-01 | resolved / 已复现 | Ambilight 1.0.0 CommonJS default namespace differs from callable 1.1.0 | PKG-AMBILIGHT-04, PKG-AMBILIGHT-06 |
|
||||
| AMBILIGHT-PROXY-01 | resolved / 已复现 | Canvas proxy output pixels differ from forwarded video dimensions | PKG-AMBILIGHT-PROXY-01, PKG-AMBILIGHT-05 |
|
||||
| CANVAS-LIFE-01 | open / 源码/产物事实 | Canvas proxy pending draw, bitmap and delayed subscriptions lack terminal ownership | PKG-CANVAS-02, PKG-CANVAS-03, PKG-CANVAS-05 |
|
||||
| CANVAS-LIFE-01 | open / 已复现 | Canvas proxy pending draw, bitmap and delayed subscriptions lack terminal ownership | PKG-CANVAS-02, PKG-CANVAS-03, PKG-CANVAS-05 |
|
||||
| CANVAS-TYPE-01 | open / 源码/产物事实 | Canvas proxy declarations change required callback across generations and omit forwarded media capabilities | PKG-CANVAS-04, PKG-CANVAS-06 |
|
||||
| CANVAS-DIST-01 | open / 已复现 | Canvas proxy published CommonJS namespace differs from current callable export | PKG-CANVAS-04, PKG-CANVAS-06 |
|
||||
| CANVAS-PIXEL-01 | open / 已复现 | Historical Canvas proxy produces transparent frames with detached native video in tested WebKit | PKG-CANVAS-03, PKG-CANVAS-05 |
|
||||
+23
-3
@@ -3343,7 +3343,7 @@
|
||||
{
|
||||
"id": "CANVAS-LIFE-01",
|
||||
"title": "Canvas proxy pending draw, bitmap and delayed subscriptions lack terminal ownership",
|
||||
"confirmation": "source-observed",
|
||||
"confirmation": "reproduced",
|
||||
"owners": [
|
||||
"PKG-CANVAS-02",
|
||||
"PKG-CANVAS-03",
|
||||
@@ -3355,8 +3355,11 @@
|
||||
"evidence": [
|
||||
"refactor/baselines/canvas-contract.md",
|
||||
"refactor/baselines/canvas-release.json",
|
||||
"refactor/changes/2026-09-12-PKG-CANVAS-01-contract.md"
|
||||
]
|
||||
"refactor/changes/2026-09-12-PKG-CANVAS-01-contract.md",
|
||||
"refactor/changes/2026-09-12-PKG-CANVAS-02-tests.md",
|
||||
"refactor/baselines/canvas-behavior-validation.json"
|
||||
],
|
||||
"workspaceState": "33 historical Node checks reproduce pending bitmap, concurrent RAF, close omission, reentrant destroy and delayed/partial registration leaks; 18 real browser core/proxy combinations observe RAF continuing after destroy inside callback. Candidate fixes pending task 03."
|
||||
},
|
||||
{
|
||||
"id": "CANVAS-TYPE-01",
|
||||
@@ -3391,6 +3394,23 @@
|
||||
"refactor/baselines/canvas-release.json",
|
||||
"refactor/changes/2026-09-12-PKG-CANVAS-01-contract.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "CANVAS-PIXEL-01",
|
||||
"title": "Historical Canvas proxy produces transparent frames with detached native video in tested WebKit",
|
||||
"status": "open",
|
||||
"confirmation": "reproduced",
|
||||
"owners": [
|
||||
"PKG-CANVAS-03",
|
||||
"PKG-CANVAS-05"
|
||||
],
|
||||
"compatibleResolution": "Verify actual pixel recovery with explicit backing media ownership and stable public canvas surface; do not infer that attaching after playback fixes it.",
|
||||
"closureCriteria": "Candidate native proxy pixels, pause/seek/source switch and terminal cleanup pass all three engines; device limitations remain explicit.",
|
||||
"evidence": [
|
||||
"refactor/changes/2026-09-12-PKG-CANVAS-02-tests.md",
|
||||
"refactor/baselines/canvas-behavior-validation.json"
|
||||
],
|
||||
"workspaceState": "All six historical WebKit proxy combinations produce alpha0 while time advances. Separate native video mounted before play yields alpha255; detached native control alpha0. Chromium/Firefox both paths yield255. These historical observations do not prove candidate fix or all Safari device behavior."
|
||||
}
|
||||
]
|
||||
}
|
||||
+6
-2
@@ -3268,11 +3268,15 @@
|
||||
"ENG-03",
|
||||
"ENG-05"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"risk": "H",
|
||||
"deliverable": "ready/loadeddata/canplay、play/seek、回调、resize 与销毁",
|
||||
"acceptance": "旧版本行为可重跑,成功/失败/切源/销毁有必要断言",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-12-PKG-CANVAS-02-tests.md",
|
||||
"baselines/canvas-behavior-validation.json",
|
||||
"canvas-validation.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-CANVAS-03",
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import { transform } from 'esbuild'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { canvasHistorical } from '../helpers/canvas.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const implementations = await Promise.all((await canvasHistorical()).map(async item => ({ ...item, code: item.format === 'source' ? (await transform(item.source, { format: 'cjs', target: 'es2020' })).code : item.source })))
|
||||
|
||||
async function setup(page, core, implementation, testInfo) {
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
await page.addScriptTag({ content: `(() => {
|
||||
const pending = new Set();
|
||||
const state = { requested: 0, fired: 0, cancelled: 0 };
|
||||
const requestAnimationFrame = callback => {
|
||||
const id = window.requestAnimationFrame(time => { pending.delete(id); state.fired++; callback(time); });
|
||||
pending.add(id); state.requested++; return id;
|
||||
};
|
||||
const cancelAnimationFrame = id => { pending.delete(id); state.cancelled++; window.cancelAnimationFrame(id); };
|
||||
const module = { exports: {} }; const exports = module.exports;
|
||||
${implementation.code};
|
||||
window.canvasFactory = module.exports.default || module.exports;
|
||||
window.canvasFrames = { state, pending, stopAll() { for (const id of pending) window.cancelAnimationFrame(id); pending.clear(); } };
|
||||
})();` })
|
||||
await page.evaluate(() => {
|
||||
window.proxyDraws = 0
|
||||
window.proxyEvents = []
|
||||
window.proxyErrors = []
|
||||
window.art = new window.Artplayer({
|
||||
container: '.player',
|
||||
url: '/test/pattern.mp4',
|
||||
muted: true,
|
||||
loop: true,
|
||||
proxy: window.canvasFactory((context, video) => {
|
||||
window.proxyContext = context
|
||||
window.proxyVideo = video
|
||||
window.proxyDraws++
|
||||
if (window.destroyInNextCallback) {
|
||||
window.destroyInNextCallback = false
|
||||
window.art.destroy(false)
|
||||
window.destroyedInCallback = true
|
||||
}
|
||||
}),
|
||||
})
|
||||
for (const name of ['video:loadedmetadata', 'video:loadeddata', 'video:canplay', 'video:play', 'video:pause', 'video:seeked', 'artplayerProxyCanvas:draw', 'destroy'])
|
||||
window.art.on(name, () => window.proxyEvents.push(name))
|
||||
window.art.on('artplayerProxyCanvas:error', error => window.proxyErrors.push({ name: error.name, message: error.message }))
|
||||
document.querySelector('#play').onclick = () => window.art.play()
|
||||
document.querySelector('#pause').onclick = () => window.art.pause()
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
await page.click('#play')
|
||||
await expect.poll(() => page.evaluate(() => window.proxyDraws)).toBeGreaterThan(1)
|
||||
await expect.poll(() => page.evaluate(() => window.proxyVideo.currentTime)).toBeGreaterThan(0)
|
||||
await testInfo.attach('canvas-historical-inputs', { contentType: 'application/json', body: JSON.stringify({ core, implementation: implementation.name, sha256: hash(implementation.code), nativeBitmapAPI: await page.evaluate(() => typeof createImageBitmap), scope: 'actual archived proxy or frozen workspace, native video/Canvas/bitmap and lexical native RAF observer' }) })
|
||||
}
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await testInfo.attach('canvas-native-pixel-control', { contentType: 'application/json', body: JSON.stringify(await page.evaluate(async () => {
|
||||
const video = window.proxyVideo
|
||||
const output = window.proxyContext
|
||||
if (!video || !output)
|
||||
return null
|
||||
const pixel = (context, x, y) => [...context.getImageData(x, y, 1, 1).data]
|
||||
const probe = document.createElement('canvas')
|
||||
probe.width = video.videoWidth || 320
|
||||
probe.height = video.videoHeight || 180
|
||||
const context = probe.getContext('2d')
|
||||
const state = { output: { width: output.canvas.width, height: output.canvas.height, corner: pixel(output, 0, 0), center: pixel(output, Math.floor(output.canvas.width / 2), Math.floor(output.canvas.height / 2)) } }
|
||||
try {
|
||||
context.drawImage(video, 0, 0, probe.width, probe.height)
|
||||
state.direct = { corner: pixel(context, 0, 0), center: pixel(context, Math.floor(probe.width / 2), Math.floor(probe.height / 2)) }
|
||||
if (typeof createImageBitmap === 'function') {
|
||||
const bitmap = await createImageBitmap(video)
|
||||
try {
|
||||
context.clearRect(0, 0, probe.width, probe.height)
|
||||
context.drawImage(bitmap, 0, 0, probe.width, probe.height)
|
||||
state.bitmap = { width: bitmap.width, height: bitmap.height, corner: pixel(context, 0, 0), center: pixel(context, Math.floor(probe.width / 2), Math.floor(probe.height / 2)) }
|
||||
}
|
||||
finally { bitmap.close() }
|
||||
}
|
||||
}
|
||||
catch (error) { state.error = { name: error.name, message: error.message } }
|
||||
return state
|
||||
})) })
|
||||
await testInfo.attach('canvas-historical-state', { contentType: 'application/json', body: JSON.stringify(await page.evaluate(() => ({ version: window.Artplayer?.version, host: window.art?.template.$video.nodeName, source: window.proxyVideo?.currentSrc, width: window.proxyVideo?.videoWidth, currentTime: window.proxyVideo?.currentTime, destroyed: window.art?.isDestroy, frames: window.canvasFrames?.state, pending: window.canvasFrames?.pending.size, draws: window.proxyDraws, errors: window.proxyErrors, events: window.proxyEvents }))) })
|
||||
await page.evaluate(() => {
|
||||
window.proxyVideo?.pause()
|
||||
if (window.art && !window.art.isDestroy)
|
||||
window.art.destroy()
|
||||
window.canvasFrames?.stopAll()
|
||||
})
|
||||
})
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
for (const implementation of implementations) {
|
||||
const label = `${core} / ${implementation.name}`
|
||||
test(`${label}: native proxy control flow and detached-video pixel boundary`, async ({ page, browserName }, testInfo) => {
|
||||
await setup(page, core, implementation, testInfo)
|
||||
expect(await page.evaluate(() => window.art.template.$video.nodeName)).toBe('CANVAS')
|
||||
await expect.poll(() => page.evaluate(() => window.proxyEvents.includes('video:canplay'))).toBe(true)
|
||||
const readiness = await page.evaluate(() => ['video:loadedmetadata', 'video:loadeddata', 'video:canplay'].map(name => window.proxyEvents.indexOf(name)))
|
||||
expect(readiness[0]).toBeGreaterThanOrEqual(0)
|
||||
expect(readiness[1]).toBeGreaterThan(readiness[0])
|
||||
expect(readiness[2]).toBeGreaterThan(readiness[1])
|
||||
if (browserName === 'webkit') {
|
||||
const detached = await page.evaluate(() => ({ connected: window.proxyVideo.isConnected, alpha: window.proxyContext.getImageData(0, 0, 1, 1).data[3], currentTime: window.proxyVideo.currentTime }))
|
||||
expect(detached.connected).toBe(false)
|
||||
expect(detached.alpha).toBe(0)
|
||||
await testInfo.attach('webkit-historical-pixel-failure', { contentType: 'application/json', body: JSON.stringify({ state: detached, scope: 'expected historical transparent output, not successful rendering; independent attached and detached native controls run separately' }) })
|
||||
}
|
||||
else {
|
||||
await expect.poll(() => page.evaluate(() => window.proxyContext.getImageData(0, 0, 1, 1).data[3])).toBe(255)
|
||||
}
|
||||
await page.click('#pause')
|
||||
await expect.poll(() => page.evaluate(() => window.proxyVideo.paused)).toBe(true)
|
||||
await page.evaluate(() => window.art.currentTime = 0.5)
|
||||
await expect.poll(() => page.evaluate(() => window.proxyEvents.filter(name => name === 'video:seeked').length)).toBeGreaterThan(0)
|
||||
await page.evaluate(() => {
|
||||
const player = window.art.template.$player
|
||||
player.style.width = '320px'
|
||||
player.style.height = '480px'
|
||||
window.art.emit('resize')
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.art.template.$video.width)).toBe(320)
|
||||
expect(await page.evaluate(() => window.art.template.$video.style.padding)).toBe('150px 0px')
|
||||
await page.evaluate(() => window.art.switchUrl('/assets/sample/video.mp4'))
|
||||
await page.click('#play')
|
||||
await expect.poll(() => page.evaluate(() => window.proxyVideo.currentSrc.endsWith('/assets/sample/video.mp4') && !window.proxyVideo.paused && window.proxyVideo.currentTime > 0)).toBe(true)
|
||||
expect(await page.evaluate(() => window.proxyEvents.filter(name => name === 'video:loadedmetadata').length)).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
test(`${label}: historical destroy inside the real draw callback leaves native RAF work running`, async ({ page }, testInfo) => {
|
||||
await setup(page, core, implementation, testInfo)
|
||||
await page.evaluate(() => window.destroyInNextCallback = true)
|
||||
await expect.poll(() => page.evaluate(() => window.destroyedInCallback && window.art.isDestroy)).toBe(true)
|
||||
await expect.poll(() => page.evaluate(() => window.canvasFrames.pending.size)).toBeGreaterThan(0)
|
||||
const fired = await page.evaluate(() => window.canvasFrames.state.fired)
|
||||
await expect.poll(() => page.evaluate(() => window.canvasFrames.state.fired)).toBeGreaterThan(fired)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const attached of [true, false]) {
|
||||
test(`native video pixel control without proxy: attached=${attached}`, async ({ page, browserName }, testInfo) => {
|
||||
await page.goto('/test/player.html')
|
||||
await page.evaluate(async (attached) => {
|
||||
const video = document.createElement('video')
|
||||
video.src = '/test/pattern.mp4'
|
||||
video.muted = true
|
||||
video.loop = true
|
||||
if (attached)
|
||||
document.body.appendChild(video)
|
||||
window.proxyVideo = video
|
||||
window.proxyContext = document.createElement('canvas').getContext('2d')
|
||||
await video.play()
|
||||
}, attached)
|
||||
await expect.poll(() => page.evaluate(() => window.proxyVideo.currentTime)).toBeGreaterThan(0)
|
||||
await expect.poll(() => page.evaluate(() => {
|
||||
window.proxyContext.drawImage(window.proxyVideo, 0, 0, 300, 150)
|
||||
return window.proxyContext.getImageData(0, 0, 1, 1).data[3]
|
||||
})).toBe(browserName === 'webkit' && !attached ? 0 : 255)
|
||||
await testInfo.attach('native-control', { contentType: 'application/json', body: JSON.stringify({ attached, scope: 'native video, direct drawImage, no proxy or bitmap before assertion; detached WebKit transparency is a historical environment observation, not rendering acceptance' }) })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
import assert from 'node:assert/strict'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Node historical regression runner.
|
||||
import test from 'node:test'
|
||||
import { canvasEnvironment, canvasHistorical } from './helpers/canvas.js'
|
||||
|
||||
function deferred() {
|
||||
let resolve, reject
|
||||
const promise = new Promise((yes, no) => {
|
||||
resolve = yes
|
||||
reject = no
|
||||
})
|
||||
return { promise, resolve, reject }
|
||||
}
|
||||
|
||||
for (const implementation of await canvasHistorical()) {
|
||||
test(`Canvas ${implementation.name}: historical pending bitmap draws and reschedules after pause or destroy`, async () => {
|
||||
for (const operation of ['video:pause', 'destroy']) {
|
||||
const pending = deferred()
|
||||
let closed = 0
|
||||
const bitmap = { close() {
|
||||
closed++
|
||||
} }
|
||||
const env = await canvasEnvironment(implementation, { createImageBitmap: () => pending.promise })
|
||||
env.factory()(env.art)
|
||||
env.art.emit('video:play')
|
||||
env.art.emit(operation)
|
||||
assert.equal(env.frames.size, 0)
|
||||
pending.resolve(bitmap)
|
||||
await env.flush()
|
||||
assert.equal(env.draws.length, 1)
|
||||
assert.equal(closed, 1)
|
||||
assert.equal(env.frames.size, 1)
|
||||
assert.equal(env.emitted.at(-1).name, 'artplayerProxyCanvas:draw')
|
||||
}
|
||||
})
|
||||
|
||||
test(`Canvas ${implementation.name}: historical repeated play creates concurrent stale bitmap loops`, async () => {
|
||||
const pending = [deferred(), deferred()]
|
||||
let index = 0
|
||||
const env = await canvasEnvironment(implementation, { createImageBitmap: () => pending[index++].promise })
|
||||
env.factory()(env.art)
|
||||
env.art.emit('video:play')
|
||||
env.art.emit('video:play')
|
||||
const newer = { close() {} }
|
||||
const older = { close() {} }
|
||||
pending[1].resolve(newer)
|
||||
await env.flush()
|
||||
pending[0].resolve(older)
|
||||
await env.flush()
|
||||
assert.deepEqual(env.draws.map(args => args[0]), [newer, older])
|
||||
assert.equal(env.frames.size, 2)
|
||||
env.art.destroy()
|
||||
assert.equal(env.frames.size, 1, 'Only the latest stored RAF handle is cancelled')
|
||||
})
|
||||
|
||||
test(`Canvas ${implementation.name}: historical draw failure emits its error but leaks the acquired bitmap`, async () => {
|
||||
const failure = new Error('draw failed')
|
||||
let closed = 0
|
||||
const bitmap = { close() {
|
||||
closed++
|
||||
} }
|
||||
const env = await canvasEnvironment(implementation, { createImageBitmap: () => Promise.resolve(bitmap), onDraw() {
|
||||
throw failure
|
||||
} })
|
||||
env.factory()(env.art)
|
||||
env.art.emit('video:play')
|
||||
await env.flush()
|
||||
assert.equal(closed, 0)
|
||||
assert.equal(env.emitted.at(-1).name, 'artplayerProxyCanvas:error')
|
||||
assert.equal(env.emitted.at(-1).args[0], failure)
|
||||
assert.equal(env.frames.size, 1)
|
||||
})
|
||||
|
||||
test(`Canvas ${implementation.name}: historical bitmap rejection emits the original error without direct-video fallback`, async () => {
|
||||
const failure = new Error('bitmap rejected')
|
||||
const env = await canvasEnvironment(implementation, { createImageBitmap: () => Promise.reject(failure) })
|
||||
env.factory()(env.art)
|
||||
env.art.emit('video:play')
|
||||
await env.flush()
|
||||
assert.equal(env.draws.length, 0)
|
||||
assert.equal(env.emitted.at(-1).name, 'artplayerProxyCanvas:error')
|
||||
assert.equal(env.emitted.at(-1).args[0], failure)
|
||||
assert.equal(env.frames.size, 1)
|
||||
})
|
||||
|
||||
test(`Canvas ${implementation.name}: callback errors occur after bitmap close and replace the draw event with an error`, async () => {
|
||||
const failure = new Error('callback failed')
|
||||
let closed = 0
|
||||
const env = await canvasEnvironment(implementation, { createImageBitmap: () => Promise.resolve({ close() {
|
||||
closed++
|
||||
} }) })
|
||||
env.factory(() => {
|
||||
throw failure
|
||||
})(env.art)
|
||||
env.art.emit('resize')
|
||||
await env.flush()
|
||||
assert.equal(closed, 1)
|
||||
assert.equal(env.emitted.filter(event => event.name === 'artplayerProxyCanvas:draw').length, 0)
|
||||
assert.equal(env.emitted.at(-1).args[0], failure)
|
||||
})
|
||||
|
||||
test(`Canvas ${implementation.name}: historical callback destruction still emits draw and queues a new frame`, async () => {
|
||||
const env = await canvasEnvironment(implementation)
|
||||
env.factory(() => env.art.destroy())(env.art)
|
||||
env.art.emit('video:play')
|
||||
await env.flush()
|
||||
assert.deepEqual(env.emitted.map(event => event.name), ['video:play', 'destroy', 'artplayerProxyCanvas:draw'])
|
||||
assert.equal(env.frames.size, 1)
|
||||
})
|
||||
|
||||
test(`Canvas ${implementation.name}: historical destroy before setup timeout retains host handlers and attaches media listeners later`, async () => {
|
||||
const env = await canvasEnvironment(implementation)
|
||||
env.factory()(env.art)
|
||||
env.art.destroy()
|
||||
assert.equal(env.timers.size, 1)
|
||||
assert.equal([...env.listeners.values()].reduce((count, set) => count + set.size, 0), 5)
|
||||
env.flushTimers()
|
||||
assert.equal(env.proxies.length, 5)
|
||||
env.emitVideo({ type: 'play' })
|
||||
await env.flush()
|
||||
assert.equal(env.frames.size, 1)
|
||||
})
|
||||
|
||||
test(`Canvas ${implementation.name}: historical late attachment to a destroyed host still registers and draws`, async () => {
|
||||
const env = await canvasEnvironment(implementation)
|
||||
env.art.destroy()
|
||||
const result = env.factory()(env.art)
|
||||
assert.equal(result, env.canvas)
|
||||
env.flushTimers()
|
||||
env.emitVideo({ type: 'play' })
|
||||
await env.flush()
|
||||
assert.equal(env.proxies.length, 5)
|
||||
assert.equal(env.draws.length, 1)
|
||||
assert.equal(env.frames.size, 1)
|
||||
})
|
||||
|
||||
test(`Canvas ${implementation.name}: historical setup subscription failure leaves deferred work and the partial host registration`, async () => {
|
||||
const env = await canvasEnvironment(implementation)
|
||||
const on = env.art.on
|
||||
const failure = new Error('subscription failed')
|
||||
env.art.on = (...args) => {
|
||||
on(...args)
|
||||
throw failure
|
||||
}
|
||||
assert.throws(() => env.factory()(env.art), error => error === failure)
|
||||
assert.equal(env.timers.size, 1)
|
||||
assert.equal([...env.listeners.values()].reduce((count, set) => count + set.size, 0), 1)
|
||||
env.flushTimers()
|
||||
assert.equal(env.proxies.length, 5)
|
||||
})
|
||||
|
||||
test(`Canvas ${implementation.name}: historical zero media dimensions reach unguarded resize and drawing`, async () => {
|
||||
const env = await canvasEnvironment(implementation)
|
||||
env.video.videoWidth = 0
|
||||
env.video.videoHeight = 0
|
||||
env.factory()(env.art)
|
||||
env.art.emit('resize')
|
||||
await env.flush()
|
||||
assert(Number.isNaN(env.canvas.height), 'This controlled element does not apply native unsigned dimension coercion')
|
||||
assert.equal(env.canvas.style.padding, 'NaNpx 0px')
|
||||
assert.equal(env.draws.length, 1)
|
||||
})
|
||||
|
||||
test(`Canvas ${implementation.name}: historical missing 2D context repeats a TypeError path with no capability guard`, async () => {
|
||||
const env = await canvasEnvironment(implementation, { noContext: true })
|
||||
env.factory()(env.art)
|
||||
env.art.emit('video:play')
|
||||
await env.flush()
|
||||
assert.equal(env.emitted.at(-1).name, 'artplayerProxyCanvas:error')
|
||||
assert.equal(env.emitted.at(-1).args[0].name, 'TypeError')
|
||||
assert.equal(env.frames.size, 1)
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user