diff --git a/refactor/baselines/README.md b/refactor/baselines/README.md index 198df2d5a..e73da5763 100644 --- a/refactor/baselines/README.md +++ b/refactor/baselines/README.md @@ -36,3 +36,7 @@ BASE-02 已在内置浏览器取得核心/chapter 的同步 API 和描述符快 ## 事件与生命周期报告 BASE-03 已在内置浏览器实测播放推进、暂停/seek/切源、事件及销毁;两次 27 项断言通过,并保存 6 项历史问题。见 [覆盖与复现](lifecycle-coverage.md) 和 [原始报告](lifecycle.json)。受控拒绝与真正媒体播放分别标注,不代替全环境验收。 + +## DOM 与官方示例路径 + +BASE-04 已在内置浏览器完成两轮 16 项 DOM/样式/实际输入断言。见 [覆盖/复现/历史问题](dom-coverage.md)、[冻结报告](dom.json) 和 [29 示例/36 HTML/22 包路径](demo-inventory.json)。轻量夹具通过不代表 Monaco 或所有官方示例已验证。 diff --git a/refactor/baselines/demo-inventory.json b/refactor/baselines/demo-inventory.json new file mode 100644 index 000000000..327193559 --- /dev/null +++ b/refactor/baselines/demo-inventory.json @@ -0,0 +1,1106 @@ +{ + "schemaVersion": 1, + "task": "BASE-04", + "sourceCommit": "94893929", + "capturedAt": "2026-09-10T12:11:47.047Z", + "note": "Historical source inventory, not browser or network evidence. ScriptSources excludes comments but does not resolve inline scripts, import maps or dynamic SDK loading. Hashes use LF and describe this capture; check validates coverage, not current content identity.", + "examples": [ + { + "name": "ads", + "source": "docs/assets/example/ads.js", + "sha256Lf": "c84a97d2b1970c323a0c2538b846312bff38513e10de271fe5f239953da88cd3", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-plugin-ads/index.js&example=ads" + ], + "route": "/?libs=./uncompiled/artplayer-plugin-ads/index.js&example=ads", + "libs": [ + "./uncompiled/artplayer-plugin-ads/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "ambilight", + "source": "docs/assets/example/ambilight.js", + "sha256Lf": "f97c1404886ba6474b4e14339c563fd2de50b7d50fdc4c9d59d85a3142a1ad52", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-plugin-ambilight/index.js&example=ambilight" + ], + "route": "/?libs=./uncompiled/artplayer-plugin-ambilight/index.js&example=ambilight", + "libs": [ + "./uncompiled/artplayer-plugin-ambilight/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "asr", + "source": "docs/assets/example/asr.js", + "sha256Lf": "bb7fd5eee3df887b7e0c72a6f42f74f0253350f9bc884f73cb9c2996f0e35724", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-plugin-asr/index.js&example=asr" + ], + "route": "/?libs=./uncompiled/artplayer-plugin-asr/index.js&example=asr", + "libs": [ + "./uncompiled/artplayer-plugin-asr/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "audio.track", + "source": "docs/assets/example/audio.track.js", + "sha256Lf": "9ed26b2ee006e1a430db68b2a1b308c4801dd3a4cc87c74e8a8f5ccf3456ac8d", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-plugin-audio-track/index.js&example=audio.track" + ], + "route": "/?libs=./uncompiled/artplayer-plugin-audio-track/index.js&example=audio.track", + "libs": [ + "./uncompiled/artplayer-plugin-audio-track/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "auto.thumbnail", + "source": "docs/assets/example/auto.thumbnail.js", + "sha256Lf": "e0f706bbbab8e2d0f3201ea6b91f1d5530c6bb65f5c1745dc8ade9acc6d66295", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-plugin-auto-thumbnail/index.js&example=auto.thumbnail" + ], + "route": "/?libs=./uncompiled/artplayer-plugin-auto-thumbnail/index.js&example=auto.thumbnail", + "libs": [ + "./uncompiled/artplayer-plugin-auto-thumbnail/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "canvas", + "source": "docs/assets/example/canvas.js", + "sha256Lf": "95a971773e93cfb340a07eebcd49e333825f49ce0d167514ad98a188cd027799", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-proxy-canvas/index.js&example=canvas" + ], + "route": "/?libs=./uncompiled/artplayer-proxy-canvas/index.js&example=canvas", + "libs": [ + "./uncompiled/artplayer-proxy-canvas/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "chapter", + "source": "docs/assets/example/chapter.js", + "sha256Lf": "93f2378a1b7a105721511d34fd632ec7745b4b20c472072c410f76b25383c96b", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-plugin-chapter/index.js&example=chapter" + ], + "route": "/?libs=./uncompiled/artplayer-plugin-chapter/index.js&example=chapter", + "libs": [ + "./uncompiled/artplayer-plugin-chapter/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "chromecast", + "source": "docs/assets/example/chromecast.js", + "sha256Lf": "edd4d232a667a3ca5bae247dca3c557431975b9ae38dc56cc590b57e327d048f", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-plugin-chromecast/index.js&example=chromecast" + ], + "route": "/?libs=./uncompiled/artplayer-plugin-chromecast/index.js&example=chromecast", + "libs": [ + "./uncompiled/artplayer-plugin-chromecast/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "danmuku", + "source": "docs/assets/example/danmuku.js", + "sha256Lf": "3f91461e2466f13ed4e72e6bd69986461192d8c6a9ca866fa347599f6958e021", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-plugin-danmuku/index.js&example=danmuku" + ], + "route": "/?libs=./uncompiled/artplayer-plugin-danmuku/index.js&example=danmuku", + "libs": [ + "./uncompiled/artplayer-plugin-danmuku/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "danmuku.mask", + "source": "docs/assets/example/danmuku.mask.js", + "sha256Lf": "c0fe4f2d4cd60738583c4bfeebc24bc3365d8ca382e6f4ff86e1056f5a2729a8", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-plugin-danmuku/index.js%0A./uncompiled/artplayer-plugin-danmuku-mask/index.js&example=danmuku.mask" + ], + "route": "/?libs=./uncompiled/artplayer-plugin-danmuku/index.js%0A./uncompiled/artplayer-plugin-danmuku-mask/index.js&example=danmuku.mask", + "libs": [ + "./uncompiled/artplayer-plugin-danmuku/index.js", + "./uncompiled/artplayer-plugin-danmuku-mask/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "dash.control", + "source": "docs/assets/example/dash.control.js", + "sha256Lf": "edc4326f03262a222e06bb780aab05c903ee162829dec60970e738c1e260eef2", + "menuLinks": [ + "/?libs=https://cdnjs.cloudflare.com/ajax/libs/dashjs/5.2.1/modern/umd/dash.all.min.js%0A./uncompiled/artplayer-plugin-dash-control/index.js&example=dash.control" + ], + "route": "/?libs=https://cdnjs.cloudflare.com/ajax/libs/dashjs/5.2.1/modern/umd/dash.all.min.js%0A./uncompiled/artplayer-plugin-dash-control/index.js&example=dash.control", + "libs": [ + "https://cdnjs.cloudflare.com/ajax/libs/dashjs/5.2.1/modern/umd/dash.all.min.js", + "./uncompiled/artplayer-plugin-dash-control/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "dash", + "source": "docs/assets/example/dash.js", + "sha256Lf": "49a7837016b6f0e8ac045e6aba7bb95e1997e6fd834243b4c62d0c97a2938776", + "menuLinks": [ + "/?libs=https://cdnjs.cloudflare.com/ajax/libs/dashjs/5.2.1/modern/umd/dash.all.min.js&example=dash" + ], + "route": "/?libs=https://cdnjs.cloudflare.com/ajax/libs/dashjs/5.2.1/modern/umd/dash.all.min.js&example=dash", + "libs": [ + "https://cdnjs.cloudflare.com/ajax/libs/dashjs/5.2.1/modern/umd/dash.all.min.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "document.pip", + "source": "docs/assets/example/document.pip.js", + "sha256Lf": "2600948ff0128e2e77fe08fbefa9be96543d8fcd81421534dfbfe9cb84e91fde", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-plugin-document-pip/index.js&example=document.pip" + ], + "route": "/?libs=./uncompiled/artplayer-plugin-document-pip/index.js&example=document.pip", + "libs": [ + "./uncompiled/artplayer-plugin-document-pip/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "flv", + "source": "docs/assets/example/flv.js", + "sha256Lf": "51e052e5423318aa25c69eae90a5181c1325edf2d0975fa169308eadb6b395eb", + "menuLinks": [ + "/?libs=https://cdnjs.cloudflare.com/ajax/libs/flv.js/1.6.2/flv.min.js&example=flv" + ], + "route": "/?libs=https://cdnjs.cloudflare.com/ajax/libs/flv.js/1.6.2/flv.min.js&example=flv", + "libs": [ + "https://cdnjs.cloudflare.com/ajax/libs/flv.js/1.6.2/flv.min.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "hls.control", + "source": "docs/assets/example/hls.control.js", + "sha256Lf": "b02fd290d79999790501447fad95736642344b9b30af3ccdbe353f80c7d53a6e", + "menuLinks": [ + "/?libs=https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.17/hls.min.js%0A./uncompiled/artplayer-plugin-hls-control/index.js&example=hls.control" + ], + "route": "/?libs=https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.17/hls.min.js%0A./uncompiled/artplayer-plugin-hls-control/index.js&example=hls.control", + "libs": [ + "https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.17/hls.min.js", + "./uncompiled/artplayer-plugin-hls-control/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "hls", + "source": "docs/assets/example/hls.js", + "sha256Lf": "7f82d60442223ced637daa896199607ca9bd52ac50dabbb47c75561f84e9ff48", + "menuLinks": [ + "/?libs=https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.17/hls.min.js&example=hls" + ], + "route": "/?libs=https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.17/hls.min.js&example=hls", + "libs": [ + "https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.17/hls.min.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "iframe", + "source": "docs/assets/example/iframe.js", + "sha256Lf": "00a8441d2a9873f7be342a98b58acab1cf6aca2359414f126ffb63f6ee20c218", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-tool-iframe/index.js&example=iframe" + ], + "route": "/?libs=./uncompiled/artplayer-tool-iframe/index.js&example=iframe", + "libs": [ + "./uncompiled/artplayer-tool-iframe/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "index", + "source": "docs/assets/example/index.js", + "sha256Lf": "7fd8b85433ced107add0e0b3731ef192113435330d976166cbb63992b7625571", + "menuLinks": [], + "route": "/?example=index", + "libs": [], + "status": "not-run", + "owner": "EX-03", + "note": "No menu link; example route follows common.js loader. Required globals are not automatically loaded." + }, + { + "name": "jassub", + "source": "docs/assets/example/jassub.js", + "sha256Lf": "4c93c554aa0a65ac19c1386bb0cfd9df6545f95b8a6f65541a0db8c072975f6d", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-plugin-jassub/index.js&example=jassub" + ], + "route": "/?libs=./uncompiled/artplayer-plugin-jassub/index.js&example=jassub", + "libs": [ + "./uncompiled/artplayer-plugin-jassub/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "mediabunny", + "source": "docs/assets/example/mediabunny.js", + "sha256Lf": "dad4cecb76b2e5ce9164a4da715793911123e8fd2bc4b93f4bcc86a7053bad7d", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-proxy-mediabunny/index.js&example=mediabunny" + ], + "route": "/?libs=./uncompiled/artplayer-proxy-mediabunny/index.js&example=mediabunny", + "libs": [ + "./uncompiled/artplayer-proxy-mediabunny/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "mobile", + "source": "docs/assets/example/mobile.js", + "sha256Lf": "c117fcd0e8b0f3da3745f5b97d2544b52aea49f28a7779704b0455f2a54d00e3", + "menuLinks": [], + "route": "/?example=mobile", + "libs": [], + "status": "not-run", + "owner": "EX-03", + "note": "No menu link; example route follows common.js loader. Required globals are not automatically loaded." + }, + { + "name": "mpegts", + "source": "docs/assets/example/mpegts.js", + "sha256Lf": "2e819af74536e02fdbccbf3379467dec7b1cf6f3aff7e46c370355320485a482", + "menuLinks": [ + "/?libs=https://cdn.jsdelivr.net/npm/mpegts.js@1.7.3/dist/mpegts.min.js&example=mpegts" + ], + "route": "/?libs=https://cdn.jsdelivr.net/npm/mpegts.js@1.7.3/dist/mpegts.min.js&example=mpegts", + "libs": [ + "https://cdn.jsdelivr.net/npm/mpegts.js@1.7.3/dist/mpegts.min.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "multiple.subtitles", + "source": "docs/assets/example/multiple.subtitles.js", + "sha256Lf": "d9ccbd334d4c77f26826144870d6700e4b62f1f00b7f8f49817b3ac2583e5cc4", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-plugin-multiple-subtitles/index.js&example=multiple.subtitles" + ], + "route": "/?libs=./uncompiled/artplayer-plugin-multiple-subtitles/index.js&example=multiple.subtitles", + "libs": [ + "./uncompiled/artplayer-plugin-multiple-subtitles/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "setting.test", + "source": "docs/assets/example/setting.test.js", + "sha256Lf": "84d6489b87556df556cbf567c9e72895366e0abeba524b9bb67ef9561d1dda60", + "menuLinks": [], + "route": "/?example=setting.test", + "libs": [], + "status": "not-run", + "owner": "EX-03", + "note": "No menu link; example route follows common.js loader. Required globals are not automatically loaded." + }, + { + "name": "thumbnail", + "source": "docs/assets/example/thumbnail.js", + "sha256Lf": "e166845905f131dea0979eb77471f5531a59b93ab2dc28fef1b6791adb42fc30", + "menuLinks": [], + "route": "/?example=thumbnail", + "libs": [], + "status": "not-run", + "owner": "EX-03", + "note": "References artplayer-plugin-thumbnail, which is absent from the 22 current workspace packages; dependency and route need reconciliation." + }, + { + "name": "tool.thumbnail", + "source": "docs/assets/example/tool.thumbnail.js", + "sha256Lf": "ef830a5f499e14c09d51aa31aaa0a194ea1f910b27f14ef97c0ca8f67d529a54", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-tool-thumbnail/index.js&example=tool.thumbnail" + ], + "route": "/?libs=./uncompiled/artplayer-tool-thumbnail/index.js&example=tool.thumbnail", + "libs": [ + "./uncompiled/artplayer-tool-thumbnail/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "vast", + "source": "docs/assets/example/vast.js", + "sha256Lf": "ce93b48fabc2f1fd39fdaa8af3ae9f86a420c9aa65551e2e196d754070f36064", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-plugin-vast/index.js&example=vast" + ], + "route": "/?libs=./uncompiled/artplayer-plugin-vast/index.js&example=vast", + "libs": [ + "./uncompiled/artplayer-plugin-vast/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "vtt.thumbnail", + "source": "docs/assets/example/vtt.thumbnail.js", + "sha256Lf": "05465b0da63b23626a3032a9c1f44d3e78ed501a313aaed885ac232ab720ffee", + "menuLinks": [ + "/?libs=./uncompiled/artplayer-plugin-vtt-thumbnail/index.js&example=vtt.thumbnail" + ], + "route": "/?libs=./uncompiled/artplayer-plugin-vtt-thumbnail/index.js&example=vtt.thumbnail", + "libs": [ + "./uncompiled/artplayer-plugin-vtt-thumbnail/index.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + }, + { + "name": "webtorrent", + "source": "docs/assets/example/webtorrent.js", + "sha256Lf": "11e6541fbaeb2f854b43ed4ea2bea36f411505343dc79a3f3a9ede9d856b2226", + "menuLinks": [ + "/?libs=https://cdn.jsdelivr.net/npm/webtorrent@1/webtorrent.min.js&example=webtorrent" + ], + "route": "/?libs=https://cdn.jsdelivr.net/npm/webtorrent@1/webtorrent.min.js&example=webtorrent", + "libs": [ + "https://cdn.jsdelivr.net/npm/webtorrent@1/webtorrent.min.js" + ], + "status": "not-run", + "owner": "EX-03", + "note": "Existing menu route; SDK and media loading have not been validated." + } + ], + "pages": [ + { + "source": "docs/document/404.html", + "route": "/document/404.html", + "sha256Lf": "35330fa7a4443bb2bf2a44b7f474acc04b2d886f1acb1e800eff44e6ca8152b3", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/advanced/built-in.html", + "route": "/document/advanced/built-in.html", + "sha256Lf": "5879aef25987cedbdcbeed27b529a3db61811af4707460de4b26332197afe9fe", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/advanced/class.html", + "route": "/document/advanced/class.html", + "sha256Lf": "20253766226e1eea67923ed8b246e288973fa9274f6e2c78c9c5aabb7d094bb0", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/advanced/event.html", + "route": "/document/advanced/event.html", + "sha256Lf": "492f23ac44a57f38f5a8441a318c5db3921d35f237542f6ed75f7c299c42054c", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/advanced/global.html", + "route": "/document/advanced/global.html", + "sha256Lf": "1cde634d00316a5e66f90ad1763aa868fd84564bbc1f8ba7beded72e29a07d89", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/advanced/plugin.html", + "route": "/document/advanced/plugin.html", + "sha256Lf": "2cbc89cb96d6715995e28ab1a0ed6b74ef6e06e0b57a062d3208e1b7ca8ade8a", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/advanced/property.html", + "route": "/document/advanced/property.html", + "sha256Lf": "f020b32d5a0e67a237100cdb1bd2ad2129c829a1233608137ceb1442f4e69ae0", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/component/contextmenu.html", + "route": "/document/component/contextmenu.html", + "sha256Lf": "4b8ac7383e49262a41c842a84c946a880ff08a3defc9c1d160a109478e3bb9f8", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/component/controls.html", + "route": "/document/component/controls.html", + "sha256Lf": "c89c72f3fc06aa3b38c3793491c6cc1b2ced60823d34880c99336a741c740371", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/component/layers.html", + "route": "/document/component/layers.html", + "sha256Lf": "85d2bf5d6eecb10053bd677bff01dfe953d5e826c9e267b07a265eb51d78ce7e", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/component/setting.html", + "route": "/document/component/setting.html", + "sha256Lf": "e1910d93dfe2a4834dad24b15217d4f0e2679afa832d4ff2a8e57c0c6924136b", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/en/advanced/built-in.html", + "route": "/document/en/advanced/built-in.html", + "sha256Lf": "be5d00e9a4377b8b3e64a6ac669855b8784664c3ebaf49c2dce2bbd631d2a7b7", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/en/advanced/class.html", + "route": "/document/en/advanced/class.html", + "sha256Lf": "9a46b027c57e339ab406c48252d091b69daf51cfa423b9781cf9fed63b355145", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/en/advanced/event.html", + "route": "/document/en/advanced/event.html", + "sha256Lf": "0b59419963a461639888270048b96862abb14a6d045e2a3899567bc5aed7d854", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/en/advanced/global.html", + "route": "/document/en/advanced/global.html", + "sha256Lf": "d150fecaa5c258ac62b56d4c3f6a75ea2d7457d5da5006cb851b7cfa96e0471b", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/en/advanced/plugin.html", + "route": "/document/en/advanced/plugin.html", + "sha256Lf": "90af8fe2cb080a799cfa0a6eae7e66bd7916b59a0a456dc25ee20ffb813f9235", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/en/advanced/property.html", + "route": "/document/en/advanced/property.html", + "sha256Lf": "b38ae4ce4607dbdc70710d0615749c6760599b61e6428989cfcc2e3e1196a0da", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/en/component/contextmenu.html", + "route": "/document/en/component/contextmenu.html", + "sha256Lf": "f0426201d0dd91dce3eb1159ed4587acaba245dc393752675dc919108977d060", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/en/component/controls.html", + "route": "/document/en/component/controls.html", + "sha256Lf": "c8547522f8b9347aba1cd759159d60d067528e3d5c1431494f80381ce71531fc", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/en/component/layers.html", + "route": "/document/en/component/layers.html", + "sha256Lf": "eafb4ee60806ee346acf8bde70e96772302ea396df7afade4668e74eda01a195", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/en/component/setting.html", + "route": "/document/en/component/setting.html", + "sha256Lf": "e7687900bdcf1ba2202b57f154d32ff07c27fac821bf90f072d89deadf89555d", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/en/index.html", + "route": "/document/en/", + "sha256Lf": "f50df7a7ddd77accd512782a47a79d7d78218168ca6b6bae4c8121debd124993", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/en/start/i18n.html", + "route": "/document/en/start/i18n.html", + "sha256Lf": "cbf63c77cede2fd83221077a6600405a43b0025d61b4f87259107185c38a1bef", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/en/start/option.html", + "route": "/document/en/start/option.html", + "sha256Lf": "3bce1c34c6e56144726eb83052cf2f68b52a7e9a815101912257696b4a8bd1ba", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/index.html", + "route": "/document/", + "sha256Lf": "641d24f3fb5b98a09e193debb2b749f4d63e3708ee3fa6860efbbfd1c4722e73", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/plugin/danmuku.html", + "route": "/document/plugin/danmuku.html", + "sha256Lf": "4460414baf1a9c313ef7bba74f50fa009825ef3bcc1be3b1462e6571b47a2d67", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/start/i18n.html", + "route": "/document/start/i18n.html", + "sha256Lf": "8e219e949ab7b25b32c5c0e8f4ff8674d195492ccfa24b07e98722f27443ac97", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/document/start/option.html", + "route": "/document/start/option.html", + "sha256Lf": "5819935c8660dc958ad60a0211a2bbff1f7208c62b930d4b70a8972a1bd72c70", + "kind": "generated-docs", + "scriptSources": [ + "/document/assets/app.CkD0mcCX.js", + "/document/main.js", + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/esm.html", + "route": "/esm.html", + "sha256Lf": "bf9eaaf69b7705d53edab101fade2ce467dd63bb2830a89d4bc4cda44667ff3c", + "kind": "demo-or-editor", + "scriptSources": [], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/googlebcdef0d1e9238554.html", + "route": "/googlebcdef0d1e9238554.html", + "sha256Lf": "db372eb59ddfb090f7b09273bfeb87da90fb8d3454f7aa89b12a6c43ab27ed43", + "kind": "site-verification", + "scriptSources": [], + "status": "not-run", + "owner": "SITE-01" + }, + { + "source": "docs/i18n.html", + "route": "/i18n.html", + "sha256Lf": "4e54b71d58f85ee386698d0e53267b3e3fa6251f48087a9724dd08c07495d03b", + "kind": "demo-or-editor", + "scriptSources": [ + "./compiled/artplayer.js", + "./compiled/i18n/ar.js", + "./compiled/i18n/cs.js", + "./compiled/i18n/es.js", + "./compiled/i18n/fa.js", + "./compiled/i18n/fr.js", + "./compiled/i18n/id.js", + "./compiled/i18n/pl.js", + "./compiled/i18n/ru.js", + "./compiled/i18n/tr.js", + "./compiled/i18n/vi.js", + "./compiled/i18n/zh-tw.js" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/iframe.html", + "route": "/iframe.html", + "sha256Lf": "bc262262c66ad32459b7c91ae22a97217414dd5b18cbf68fe105c2269456f879", + "kind": "demo-or-editor", + "scriptSources": [ + "./uncompiled/artplayer/index.js", + "./uncompiled/artplayer-tool-iframe/index.js" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/index.html", + "route": "/", + "sha256Lf": "bd2e7e32efce80725b70f2054c4d9c95465de567118f245924f2e6d17567ff23", + "kind": "demo-or-editor", + "scriptSources": [ + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662", + "./assets/js/console.js", + "./assets/js/vs/loader.js" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/mobile.html", + "route": "/mobile.html", + "sha256Lf": "f6cf85608023ee57750af773908d4c8ecd95c1065b1aa587e28e2361b2cee7fd", + "kind": "demo-or-editor", + "scriptSources": [ + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662", + "./assets/js/vconsole.min.js", + "./uncompiled/artplayer/index.js", + "./assets/js/mobile.js" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/test/index.html", + "route": "/test/", + "sha256Lf": "67132147ebbb63867c05be182ec18fc6c3129df89808bf17819c92cee9a4769c", + "kind": "demo-or-editor", + "scriptSources": [ + "./chai.js", + "./mocha.js", + "../compiled/artplayer.js", + "../compiled/artplayer-plugin-ads.js", + "../compiled/artplayer-plugin-danmuku.js", + "../compiled/artplayer-plugin-dash-control.js", + "../compiled/artplayer-plugin-hls-control.js", + "../compiled/artplayer-tool-iframe.js", + "../compiled/artplayer-plugin-vtt-thumbnail.js", + "../compiled/artplayer-plugin-chapter.js", + "https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.17/hls.min.js", + "https://cdnjs.cloudflare.com/ajax/libs/dashjs/5.2.1/modern/umd/dash.all.min.js", + "https://cdnjs.cloudflare.com/ajax/libs/flv.js/1.6.2/flv.min.js", + "./test.js" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "source": "docs/upscaler/index.html", + "route": "/upscaler/", + "sha256Lf": "a04d9e364ee20cb1245c0ac9200ba28d6a3ffff51407a71725f943a1026051aa", + "kind": "standalone-tool", + "scriptSources": [ + "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8579453810848662", + "https://cdn.tailwindcss.com", + "upscaler.js" + ], + "status": "not-run", + "owner": "EX-03" + } + ], + "packages": [ + { + "name": "artplayer", + "examples": [], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-ads", + "examples": [ + "ads" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-ambilight", + "examples": [ + "ambilight" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-asr", + "examples": [ + "asr" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-audio-track", + "examples": [ + "audio.track" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-auto-thumbnail", + "examples": [ + "auto.thumbnail" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-chapter", + "examples": [ + "chapter" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-chromecast", + "examples": [ + "chromecast" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-danmuku", + "examples": [ + "danmuku", + "danmuku.mask" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-danmuku-mask", + "examples": [ + "danmuku.mask" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-dash-control", + "examples": [ + "dash.control" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-document-pip", + "examples": [ + "document.pip" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-hls-control", + "examples": [ + "hls.control" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-jassub", + "examples": [ + "jassub" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-multiple-subtitles", + "examples": [ + "multiple.subtitles" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-vast", + "examples": [ + "vast" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-plugin-vtt-thumbnail", + "examples": [ + "vtt.thumbnail" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-proxy-canvas", + "examples": [ + "canvas" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-proxy-mediabunny", + "examples": [ + "mediabunny" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-tool-iframe", + "examples": [ + "iframe" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-tool-thumbnail", + "examples": [ + "tool.thumbnail" + ], + "status": "not-run", + "owner": "EX-03" + }, + { + "name": "artplayer-vitepress", + "examples": [], + "status": "not-run", + "owner": "SITE-01" + } + ], + "editor": { + "source": "docs/assets/js/common.js", + "sha256Lf": "2db7c8e198b06986a626631f0932fe7d8a687f758f196136833acdd00b71b107", + "storage": [ + "prod", + "ts", + "code", + "log" + ], + "isolation": "Use separate test origin; record keys, do not erase user browser data.", + "core": { + "prodTrue": "./compiled/artplayer.js", + "otherwise": "./uncompiled/artplayer/index.js" + }, + "pluginSources": "URL libs parameter; independent of prod toggle", + "execution": "Run evaluates editor text; TS mode alone is not strict tsc validation.", + "runtimeStatus": "not-run" + } +} diff --git a/refactor/baselines/dom-coverage.md b/refactor/baselines/dom-coverage.md new file mode 100644 index 000000000..186832c7c --- /dev/null +++ b/refactor/baselines/dom-coverage.md @@ -0,0 +1,46 @@ +# DOM、CSS、输入与官方 demo 基线 + +BASE-04 使用固定 npm artplayer 5.4.0 / chapter 1.1.0,在 Codex 内置浏览器最终两次独立 reload 后真实点击和按键。16 项断言通过,DOM 语义快照相同,errors/unhandled 和浏览器 error/warn 日志为空。来源和时间见 [dom.json](dom.json),不是当前工作区候选或外部 Chrome 扩展验收。 + +## 已记录的扩展契约 + +| 契约 | 固定测试 ID / 数据 | 后续责任 | +| --- | --- | --- | +| API-08 模板/样式 | DOM.version-template、user-style;snapshot.template/tree/cssClasses/cssVariables | CORE-12:模板、88 个核心 CSS 类名匹配、43 个 CSS 变量;保留用户选择器和覆盖入口 | +| API-06/08 chapter | DOM.chapter-markup;chapters、chapterStylePresent | PKG-CHAPTER-01/02:两个章节、start/end/title 数据及样式注入,其他参数/悬停/销毁行为继续补测 | +| API-07 控件/图层 | DOM.control-click/update/remove、layer-remove、destroy | CORE-13:真实 click 的 this/参数、按 name 替换和清理;controls.update 返回 undefined,元素通过 controls[name] 获取 | +| API-07 设置 | DOM.setting-select/update/remove;selected | CORE-14:选择 High,当前节点 art-current,默认值 false/true,回调 this/参数和移除 | +| API-08/10 网页全屏 | DOM.fullscreen-web-enter/exit;Escape interaction | CORE-16:点击后挂到 body,Escape 恢复原容器/class;未测原生全屏或 PiP 权限 | +| API-10 输入/焦点 | DOM.keyboard-seek、input-ignores-hotkey;interactions | CORE-17/23:ArrowRight 从 0 到 5 秒,输入框相同键不 seek;Tab/焦点、可信事件来源有记录 | +| API-08 布局 | DOM.narrow-container;wide/narrow | CORE-13:640x360 和 320x180 容器边界及控件测量;不是移动 viewport/触摸/真机验证 | +| API-08/10 可访问属性 | controlAttributes、tree 的 role/aria/tabindex;实际 Tab 两次 | CORE-23:DOM 属性不是完整 accessibility tree 审计;浏览器 snapshot 中按钮为 generic,用户输入层 textbox 可用 | + +上述短 ID 均使用 DOM. 前缀,完整列表由 dom.mjs 的 domChecks 固定。模板是发布版 Artplayer.html 原文;运行时树保留节点嵌套、class、role、aria、tabindex、type,省略 SVG 内部路径和变动文本。CSS 清单从发布 STYLE 提取,不是全部伪类/组合选择器/所有主题的视觉回归。用户规则 `.player .user-hook` 和 `art.cssVar('--art-theme', ...)` 通过计算样式验证。章节样式依赖固定发布成员哈希,未另做全部插件 CSS 清单。 + +## 历史问题与界限 + +| ID | 已观察事实 | 责任与候选要求 | +| --- | --- | --- | +| BASE-DOM-01 | 实际 Tab 从外部按钮到用户 input,再到播放器后的按钮;8 个 .art-control 根节点均为 DIV / tabIndex -1 / 无 role。部分控件及子节点已有 aria-label,不能概括为完全没有名称 | CORE-23,关联 CORE-17:补主要控件/设置可达性与名称,保持旧快捷键/DOM 钩子;不要求候选重复不可达状态 | +| BASE-DOM-02 | 默认桌面控件加 Probe control,在 320x180 容器中设置和网页全屏控件越过右边界;截图目视确认裁切,player.scrollWidth 仍为 320 | CORE-13:补窄容器布局回归;CORE-14 同时检查设置面板定位;不把截图当移动端通过 | +| BASE-DEMO-01 | docs/assets/example/thumbnail.js 引用 artplayer-plugin-thumbnail,当前 22 包不存在它,首页也无该示例菜单链接 | SITE-01 / EX-03:核实历史外部包与迁移路径,不自动改成 auto-thumbnail 或删除旧示例 | + +问题只登记与分配,不在基线任务修改生产代码。dom.mjs --compare 比较同一发布基线;候选修复产生差异时应新增正向测试和解释,不能要求修复后仍保留错误,也不能覆盖历史报告消除差异。 + +## 重跑 + +1. 按 toolchain-setup.md 使用固定 Node/Yarn。运行 `node refactor/scripts/browser-server.mjs`,打开 `http://127.0.0.1:8083/fixtures/dom.html`。独立 origin 使用固定发布脚本和本地 MP4,未运行 Monaco 或读取编辑器四个开关。 +2. 等待 DOM.version-template / user-style / chapter-markup 出现。点击 Tab start,按 Tab 到 Typing probe,再按 Tab 到 Inspect narrow container,观察原始可达性。 +3. 点击 Probe control,再按 ArrowRight;点击 Typing probe,再按 ArrowRight。依次点击设置、Baseline selector、High;点击网页全屏,再按 Escape。 +4. 点击 Inspect narrow container,检查右侧裁切和章节条;点击 Save DOM baseline。报告写到 refactor/.cache/reports/dom.json,必须显示 16 checks / errors 0。 +5. 运行 `node refactor/scripts/dom.mjs --compare refactor/.cache/reports/dom.json`。重载重复实际操作,不用 dispatchEvent 代替 trusted 输入。 + +`node refactor/scripts/dom.mjs --check` 只校验冻结报告,`node --test refactor/scripts/dom.test.mjs` 验证缺输入、trusted=false、错误媒体/热键、类名/变量/布局差异等拒绝机制。报告不是抗恶意篡改证明;操作与来源记录须一起审查。小数像素宽度不作跨环境精确比较,容器边界、类名与可访问属性变化会报告差异。 + +## 全量路径台账 + +[demo-inventory.json](demo-inventory.json) 覆盖 29 个 example JS、36 个 HTML 路径与 22 包。包含 25 条首页菜单链接及 libs、4 个无菜单示例、6 个 demo/编辑器入口、28 个生成文档页面、独立 upscaler 工具页和站点验证页。核心通过所有示例使用,vitepress 通过 document 页面覆盖;两者没有插件式 libs 菜单不是缺包。 + +所有官方 docs 页面仍标 not-run,只有独立轻量夹具已实测。没有把 core/chapter 夹具通过改写成官方 chapter 示例或 Monaco 已通过。外部 SDK、媒体 URL、动态 import/脚本和无菜单示例依赖仍由 SITE-01 / EX-03 核实;scriptSources 仅列非注释的静态 script src,动态来源见 docs-browser-testing.md。 + +`node refactor/scripts/demos.mjs --check` 检查文件/HTML/菜单/包覆盖,不代表网络或浏览器通过。源码 LF 哈希描述历史采集,内容演进不自动覆盖旧值;路径增删需要显式补充和记录。负例进入 test:baseline。生成 document 页面依旧不手改。 diff --git a/refactor/baselines/dom.json b/refactor/baselines/dom.json new file mode 100644 index 000000000..eb2d9c1e5 --- /dev/null +++ b/refactor/baselines/dom.json @@ -0,0 +1,1630 @@ +{ + "schemaVersion": 1, + "task": "BASE-04", + "sourceCommit": "94893929", + "browserSurface": "Codex in-app browser", + "report": { + "kind": "dom", + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36", + "language": "zh-CN" + }, + "scripts": [ + "/releases/artplayer/dist/artplayer.js", + "/releases/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.js", + "/fixtures/dom.js" + ], + "checks": [ + { + "id": "DOM.chapter-markup", + "passed": true + }, + { + "id": "DOM.control-click", + "passed": true + }, + { + "id": "DOM.control-remove", + "passed": true + }, + { + "id": "DOM.control-update", + "passed": true + }, + { + "id": "DOM.destroy", + "passed": true + }, + { + "id": "DOM.fullscreen-web-enter", + "passed": true + }, + { + "id": "DOM.fullscreen-web-exit", + "passed": true + }, + { + "id": "DOM.input-ignores-hotkey", + "passed": true + }, + { + "id": "DOM.keyboard-seek", + "passed": true + }, + { + "id": "DOM.layer-remove", + "passed": true + }, + { + "id": "DOM.narrow-container", + "passed": true + }, + { + "id": "DOM.setting-remove", + "passed": true + }, + { + "id": "DOM.setting-select", + "passed": true + }, + { + "id": "DOM.setting-update", + "passed": true + }, + { + "id": "DOM.user-style", + "passed": true + }, + { + "id": "DOM.version-template", + "passed": true + } + ], + "errors": [], + "unhandled": [], + "interactions": [ + { + "focus": "BUTTON", + "label": null, + "id": "tab-start" + }, + { + "time": 0, + "hotkeys": 0, + "focused": false, + "target": "BUTTON", + "code": "Tab", + "trusted": true, + "afterTime": 0, + "afterHotkeys": 0, + "activeTag": "BUTTON", + "activeLabel": null + }, + { + "focus": "INPUT", + "label": "Typing probe", + "id": "" + }, + { + "time": 0, + "hotkeys": 0, + "focused": false, + "target": "INPUT", + "code": "Tab", + "trusted": true, + "afterTime": 0, + "afterHotkeys": 0, + "activeTag": "INPUT", + "activeLabel": "Typing probe" + }, + { + "focus": "BUTTON", + "label": null, + "id": "narrow" + }, + { + "time": 0, + "hotkeys": 0, + "focused": true, + "target": "BODY", + "code": "ArrowRight", + "trusted": true, + "afterTime": 5, + "afterHotkeys": 1, + "activeTag": "BODY", + "activeLabel": null + }, + { + "focus": "INPUT", + "label": "Typing probe", + "id": "" + }, + { + "time": 5, + "hotkeys": 1, + "focused": true, + "target": "INPUT", + "code": "ArrowRight", + "trusted": true, + "afterTime": 5, + "afterHotkeys": 1, + "activeTag": "INPUT", + "activeLabel": "Typing probe" + }, + { + "time": 5, + "hotkeys": 1, + "focused": true, + "target": "BODY", + "code": "Escape", + "trusted": true, + "afterTime": 5, + "afterHotkeys": 2, + "activeTag": "BODY", + "activeLabel": null + }, + { + "focus": "BUTTON", + "label": null, + "id": "narrow" + }, + { + "focus": "BUTTON", + "label": null, + "id": "finish" + } + ], + "observations": { + "wide": { + "width": 640, + "height": 360, + "scrollWidth": 640, + "controls": [ + { + "class": "art-control art-control-progress", + "width": 640, + "withinPlayer": true, + "display": "flex" + }, + { + "class": "art-control art-control-thumbnails", + "width": 0, + "withinPlayer": true, + "display": "block" + }, + { + "class": "art-control art-control-playAndPause", + "width": 46, + "withinPlayer": true, + "display": "flex" + }, + { + "class": "art-control art-control-volume", + "width": 46, + "withinPlayer": true, + "display": "flex" + }, + { + "class": "art-control art-control-time", + "width": 106.69, + "withinPlayer": true, + "display": "flex" + }, + { + "class": "art-control art-control-compat hint--rounded hint--top", + "width": 91.19, + "withinPlayer": true, + "display": "flex" + }, + { + "class": "art-control art-control-setting hint--rounded hint--top", + "width": 46, + "withinPlayer": true, + "display": "flex" + }, + { + "class": "art-control art-control-fullscreenWeb hint--rounded hint--top", + "width": 46, + "withinPlayer": true, + "display": "flex" + } + ] + }, + "selected": { + "html": "High", + "defaults": [ + false, + true + ] + }, + "narrow": { + "width": 320, + "height": 180, + "scrollWidth": 320, + "controls": [ + { + "class": "art-control art-control-progress", + "width": 300, + "withinPlayer": true, + "display": "flex" + }, + { + "class": "art-control art-control-thumbnails", + "width": 0, + "withinPlayer": true, + "display": "block" + }, + { + "class": "art-control art-control-playAndPause", + "width": 46, + "withinPlayer": true, + "display": "flex" + }, + { + "class": "art-control art-control-volume", + "width": 46, + "withinPlayer": true, + "display": "flex" + }, + { + "class": "art-control art-control-time", + "width": 106.69, + "withinPlayer": true, + "display": "flex" + }, + { + "class": "art-control art-control-compat hint--rounded hint--top", + "width": 91.19, + "withinPlayer": true, + "display": "flex" + }, + { + "class": "art-control art-control-setting hint--rounded hint--top", + "width": 46, + "withinPlayer": false, + "display": "flex" + }, + { + "class": "art-control art-control-fullscreenWeb hint--rounded hint--top", + "width": 46, + "withinPlayer": false, + "display": "flex" + } + ] + } + }, + "snapshot": { + "template": "\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
Player version:
\n
5.4.0
\n
\n
\n
Video url:
\n
\n
\n
\n
Video volume:
\n
\n
\n
\n
Video time:
\n
\n
\n
\n
Video duration:
\n
\n
\n
\n
Video resolution:
\n
\n x \n
\n
\n
\n
[x]
\n
\n
\n
\n ", + "tree": { + "tag": "div", + "attributes": { + "class": "art-video-player art-subtitle-show art-layer-show art-control-show art-backdrop art-hover artplayer-plugin-chapter art-mask-show" + }, + "children": [ + { + "tag": "video", + "attributes": { + "class": "art-video" + }, + "children": [ + { + "tag": "track", + "attributes": {}, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-poster" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-subtitle" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-danmuku" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-layers" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-layer art-layer-compat" + }, + "children": [ + { + "tag": "label", + "attributes": { + "class": "user-hook" + }, + "children": [ + { + "tag": "input", + "attributes": { + "aria-label": "Typing probe" + }, + "children": [] + } + ] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-mask" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-state" + }, + "children": [ + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-state" + }, + "children": [ + { + "tag": "svg", + "attributes": {}, + "children": [] + } + ] + }, + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-error" + }, + "children": [ + { + "tag": "svg", + "attributes": {}, + "children": [] + } + ] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-bottom" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-progress" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-control art-control-progress" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-control-progress-inner" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-progress-hover" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-progress-loaded" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-progress-played" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-progress-highlight" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-progress-indicator" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-progress-tip" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-chapters" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-chapter" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-chapter-inner" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-progress-hover" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-progress-loaded" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-progress-played" + }, + "children": [] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-chapter" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-chapter-inner" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-progress-hover" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-progress-loaded" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-progress-played" + }, + "children": [] + } + ] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-chapter-title" + }, + "children": [] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-control art-control-thumbnails" + }, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-controls" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-controls-left" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-control art-control-playAndPause" + }, + "children": [ + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-play hint--rounded hint--top", + "aria-label": "Play" + }, + "children": [ + { + "tag": "svg", + "attributes": {}, + "children": [] + } + ] + }, + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-pause hint--rounded hint--top", + "aria-label": "Pause" + }, + "children": [ + { + "tag": "svg", + "attributes": {}, + "children": [] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-control art-control-volume" + }, + "children": [ + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-volume" + }, + "children": [ + { + "tag": "svg", + "attributes": {}, + "children": [] + } + ] + }, + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-volumeClose" + }, + "children": [ + { + "tag": "svg", + "attributes": {}, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-volume-panel" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-volume-inner" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-volume-val" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-volume-slider" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-volume-handle" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-volume-loaded" + }, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-volume-indicator" + }, + "children": [] + } + ] + } + ] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-control art-control-time" + }, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-controls-center" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-controls-right" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-control art-control-compat hint--rounded hint--top", + "aria-label": "Probe control" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-control art-control-setting hint--rounded hint--top", + "aria-label": "Show Setting" + }, + "children": [ + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-setting" + }, + "children": [ + { + "tag": "svg", + "attributes": {}, + "children": [] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-control art-control-fullscreenWeb hint--rounded hint--top", + "aria-label": "Web Fullscreen" + }, + "children": [ + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-fullscreenWebOn" + }, + "children": [ + { + "tag": "svg", + "attributes": { + "class": "icon" + }, + "children": [] + } + ] + }, + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-fullscreenWebOff" + }, + "children": [ + { + "tag": "svg", + "attributes": { + "class": "icon" + }, + "children": [] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-loading" + }, + "children": [ + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-loading" + }, + "children": [ + { + "tag": "svg", + "attributes": { + "class": "uil-default" + }, + "children": [] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-notice" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-notice-inner" + }, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-settings" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-setting-panel art-current" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-setting-item" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-setting-item-left" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-setting-item-left-icon" + }, + "children": [ + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-playbackRate" + }, + "children": [ + { + "tag": "svg", + "attributes": {}, + "children": [] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-setting-item-left-text" + }, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-setting-item-right" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-setting-item-right-tooltip" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-setting-item-right-icon" + }, + "children": [ + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-arrowRight" + }, + "children": [ + { + "tag": "svg", + "attributes": {}, + "children": [] + } + ] + } + ] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-setting-item" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-setting-item-left" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-setting-item-left-icon" + }, + "children": [ + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-aspectRatio" + }, + "children": [ + { + "tag": "svg", + "attributes": {}, + "children": [] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-setting-item-left-text" + }, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-setting-item-right" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-setting-item-right-tooltip" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-setting-item-right-icon" + }, + "children": [ + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-arrowRight" + }, + "children": [ + { + "tag": "svg", + "attributes": {}, + "children": [] + } + ] + } + ] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-setting-item" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-setting-item-left" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-setting-item-left-icon" + }, + "children": [ + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-config" + }, + "children": [ + { + "tag": "svg", + "attributes": {}, + "children": [] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-setting-item-left-text" + }, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-setting-item-right" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-setting-item-right-tooltip" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-setting-item-right-icon" + }, + "children": [ + { + "tag": "i", + "attributes": { + "class": "art-icon art-icon-arrowRight" + }, + "children": [ + { + "tag": "svg", + "attributes": {}, + "children": [] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-info" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-info-panel" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-info-item" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-info-title" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-info-content" + }, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-info-item" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-info-title" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-info-content" + }, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-info-item" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-info-title" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-info-content" + }, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-info-item" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-info-title" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-info-content" + }, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-info-item" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-info-title" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-info-content" + }, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-info-item" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-info-title" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-info-content" + }, + "children": [ + { + "tag": "span", + "attributes": {}, + "children": [] + }, + { + "tag": "span", + "attributes": {}, + "children": [] + } + ] + } + ] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-info-close" + }, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-contextmenus" + }, + "children": [ + { + "tag": "div", + "attributes": { + "class": "art-contextmenu art-contextmenu-playbackRate" + }, + "children": [ + { + "tag": "span", + "attributes": {}, + "children": [] + }, + { + "tag": "span", + "attributes": {}, + "children": [] + }, + { + "tag": "span", + "attributes": { + "class": "art-current" + }, + "children": [] + }, + { + "tag": "span", + "attributes": {}, + "children": [] + }, + { + "tag": "span", + "attributes": {}, + "children": [] + }, + { + "tag": "span", + "attributes": {}, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-contextmenu art-contextmenu-aspectRatio" + }, + "children": [ + { + "tag": "span", + "attributes": { + "class": "art-current" + }, + "children": [] + }, + { + "tag": "span", + "attributes": {}, + "children": [] + }, + { + "tag": "span", + "attributes": {}, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-contextmenu art-contextmenu-info" + }, + "children": [] + }, + { + "tag": "div", + "attributes": { + "class": "art-contextmenu art-contextmenu-version" + }, + "children": [ + { + "tag": "a", + "attributes": {}, + "children": [] + } + ] + }, + { + "tag": "div", + "attributes": { + "class": "art-contextmenu art-contextmenu-close" + }, + "children": [] + } + ] + } + ] + }, + "cssVariables": [ + "--art-background-color", + "--art-border-radius", + "--art-bottom-gap", + "--art-bottom-height", + "--art-bottom-offset", + "--art-contextmenus-min-width", + "--art-control-height", + "--art-control-icon-scale", + "--art-control-icon-size", + "--art-control-opacity", + "--art-font-color", + "--art-fullscreen-web-index", + "--art-highlight-color", + "--art-highlight-width", + "--art-hover-color", + "--art-indicator-scale", + "--art-indicator-size", + "--art-loaded-color", + "--art-lock-size", + "--art-mini-progress-height", + "--art-padding", + "--art-progress-color", + "--art-progress-height", + "--art-progress-top-gap", + "--art-scrollbar-background", + "--art-scrollbar-background-hover", + "--art-scrollbar-size", + "--art-selector-max-height", + "--art-settings-icon-size", + "--art-settings-max-height", + "--art-state-opacity", + "--art-state-size", + "--art-subtitle-border", + "--art-subtitle-bottom", + "--art-subtitle-font-size", + "--art-subtitle-gap", + "--art-text-shadow-color", + "--art-theme", + "--art-tip-background", + "--art-transition-duration", + "--art-volume-handle-size", + "--art-volume-height", + "--art-widget-background" + ], + "cssClasses": [ + ".art-auto-playback-close", + ".art-auto-playback-jump", + ".art-backdrop", + ".art-backdrop-filter", + ".art-bg-cover", + ".art-bottom", + ".art-bottom-gradient", + ".art-contextmenu", + ".art-contextmenu-show", + ".art-contextmenus", + ".art-control", + ".art-control-progress", + ".art-control-progress-inner", + ".art-control-selector", + ".art-control-show", + ".art-control-thumbnails", + ".art-control-volume", + ".art-controls", + ".art-controls-center", + ".art-controls-left", + ".art-controls-right", + ".art-current", + ".art-danmuku", + ".art-fullscreen", + ".art-fullscreen-web", + ".art-hide-cursor", + ".art-hover", + ".art-icon", + ".art-icon-check", + ".art-info", + ".art-info-close", + ".art-info-content", + ".art-info-item", + ".art-info-panel", + ".art-info-show", + ".art-info-title", + ".art-layer", + ".art-layer-auto-playback", + ".art-layer-lock", + ".art-layer-show", + ".art-layers", + ".art-loading", + ".art-loading-show", + ".art-lock", + ".art-mask", + ".art-mask-show", + ".art-mini-close", + ".art-mini-dragging", + ".art-mini-popup", + ".art-mini-progress-bar", + ".art-mini-state", + ".art-mobile", + ".art-notice", + ".art-notice-inner", + ".art-notice-show", + ".art-poster", + ".art-progress", + ".art-progress-highlight", + ".art-progress-hover", + ".art-progress-indicator", + ".art-progress-loaded", + ".art-progress-played", + ".art-progress-tip", + ".art-selector-item", + ".art-selector-list", + ".art-setting-item", + ".art-setting-item-back", + ".art-setting-item-left", + ".art-setting-item-left-icon", + ".art-setting-item-right", + ".art-setting-item-right-icon", + ".art-setting-item-right-tooltip", + ".art-setting-panel", + ".art-setting-range", + ".art-setting-show", + ".art-settings", + ".art-state", + ".art-subtitle", + ".art-subtitle-show", + ".art-truncate", + ".art-video", + ".art-video-player", + ".art-volume-handle", + ".art-volume-indicator", + ".art-volume-inner", + ".art-volume-loaded", + ".art-volume-panel", + ".art-volume-slider" + ], + "chapters": [ + { + "class": "art-chapter", + "start": 0, + "end": 10, + "title": "First" + }, + { + "class": "art-chapter", + "start": 10, + "end": 90.046009, + "title": "Second" + } + ], + "controlAttributes": [ + { + "class": "art-control art-control-progress", + "tag": "DIV", + "tabIndex": -1, + "role": null, + "ariaLabel": null, + "tooltip": null, + "title": null + }, + { + "class": "art-control art-control-thumbnails", + "tag": "DIV", + "tabIndex": -1, + "role": null, + "ariaLabel": null, + "tooltip": null, + "title": null + }, + { + "class": "art-control art-control-playAndPause", + "tag": "DIV", + "tabIndex": -1, + "role": null, + "ariaLabel": null, + "tooltip": null, + "title": null + }, + { + "class": "art-control art-control-volume", + "tag": "DIV", + "tabIndex": -1, + "role": null, + "ariaLabel": null, + "tooltip": null, + "title": null + }, + { + "class": "art-control art-control-time", + "tag": "DIV", + "tabIndex": -1, + "role": null, + "ariaLabel": null, + "tooltip": null, + "title": null + }, + { + "class": "art-control art-control-compat hint--rounded hint--top", + "tag": "DIV", + "tabIndex": -1, + "role": null, + "ariaLabel": "Probe control", + "tooltip": null, + "title": null + }, + { + "class": "art-control art-control-setting hint--rounded hint--top", + "tag": "DIV", + "tabIndex": -1, + "role": null, + "ariaLabel": "Show Setting", + "tooltip": null, + "title": null + }, + { + "class": "art-control art-control-fullscreenWeb hint--rounded hint--top", + "tag": "DIV", + "tabIndex": -1, + "role": null, + "ariaLabel": "Web Fullscreen", + "tooltip": null, + "title": null + } + ], + "chapterStylePresent": true + }, + "capture": { + "capturedAt": "2026-09-10T12:09:18.280Z", + "releases": [ + { + "name": "artplayer", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js", + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f" + }, + { + "name": "artplayer-plugin-chapter", + "version": "1.1.0", + "integrity": "sha512-ji66Lr2K2S+OjFMuMby3yq+pefWlOC51Sbpop/DIr2N6w3F0smrF+YZbshN1Dy8/Tx1TYpT7N6WkkZWy5Jix1w==", + "member": "package/dist/artplayer-plugin-chapter.js", + "sha256": "c2aaee11bf1db765433b01d9f8dd3978e41fa4d39655db00ebd03fc0be3e35e0" + } + ], + "fixtureHashAlgorithm": "sha256-lf", + "fixtures": { + "dom.html": "cb2891446f74fb0e3fcb974d41170decb35977522f1ee5bfeb032f4f9b3e1fec", + "dom.js": "2a847ba117133046916a46ed586744785dc6e8b024805a69be7a7f3425b0d89b" + }, + "media": { + "video.mp4": "30a2de667ba267b6bc605edcee1c4bd728c048ed76f500ea0e73d1f1a6f8f980" + } + } + }, + "verification": { + "repeatCapturedAt": "2026-09-10T12:10:09.257Z", + "profileSha256": "89c7551f40086b22be89c2d6574e90bc25f31b3fde755af6c19bce1bf7f5f45f" + } +} diff --git a/refactor/changes/2026-09-10-BASE-04-dom.md b/refactor/changes/2026-09-10-BASE-04-dom.md new file mode 100644 index 000000000..2a5131fa6 --- /dev/null +++ b/refactor/changes/2026-09-10-BASE-04-dom.md @@ -0,0 +1,24 @@ +# BASE-04:DOM、样式、输入与 demo 路径 + +日期 2026-09-10;分支 codex/compatible-modernization;起点 94893929。 + +新增 dom.html/js 及 /reports/dom 白名单,使用固定 core 5.4.0 / chapter 1.1.0 和本地视频。保存模板、运行时节点树、88 个核心 CSS 类名、43 个变量、控件可访问属性、章节数据、布局及可信键盘交互,绑定来源、媒体和夹具哈希。 + +新增 dom.mjs 验证和比较器,两次最终页面重载、真实 Tab/点击/ArrowRight/Escape 操作得到相同 profile,16 项检查通过。无意外错误/拒绝,浏览器 error/warn 日志为空;320px 容器已经截图目视检查。比较忽略小数像素宽度,保留节点/类名/变量和布局边界差异。 + +新增 demo-inventory.json/demos.mjs,覆盖 29 示例、36 HTML 和 22 包,保留首页 25 条菜单链接、插件 libs、静态脚本与历史 LF 指纹。补充 upscaler、站点验证页、无菜单示例和生成文档路径;所有官方页面仍为 not-run。当前开发命令同步为 Yarn。 + +## 发现与兼容决定 + +- controls.update 返回 undefined,必须保留;替换元素通过 controls[name] 访问。早期夹具错误假设已修正后重新完成两次最终采集,没有修改播放器来迁就测试。 +- BASE-DOM-01:Tab 跳过内部控件,分配 CORE-23/17;已有部分 aria-label 仍记录。 +- BASE-DOM-02:带自定义文字控件的 320px 容器裁切设置/网页全屏按钮,分配 CORE-13/14;不是移动设备结论。 +- BASE-DEMO-01:thumbnail 示例依赖 workspace 不存在的历史包,分配 SITE-01/EX-03。 + +详见 [覆盖与复现](../baselines/dom-coverage.md)、[报告](../baselines/dom.json) 和 [路径清单](../baselines/demo-inventory.json)。历史缺陷不成为候选必须复现的兼容行为。未测原生全屏/PiP、屏幕阅读器、触摸/拖动、Monaco Run、全部插件及远端 SDK。 + +## 验证与回退 + +新增 3 项 Node 测试,负例包括缺路径/包/菜单、虚假输入、错误媒体/热键结果、DOM/CSS/布局差异。完整 ci:check、计划/链接和 diff 检查通过。未修改生产代码、依赖、类型或生成分发;服务可撤销此提交回到上版,历史报告保留于 Git。 + +本任务独立本地提交,主题含 BASE-04;不推送或发布。下一任务 BASE-05:发布入口、资源与类型消费者基线,再继续最小试点所需工具及类型基础。 diff --git a/refactor/docs-browser-testing.md b/refactor/docs-browser-testing.md index f567f6436..fa4701cd7 100644 --- a/refactor/docs-browser-testing.md +++ b/refactor/docs-browser-testing.md @@ -13,7 +13,7 @@ | `/iframe.html` | [iframe.html](../docs/iframe.html)、[iframe 示例](../docs/assets/example/iframe.js) | uncompiled 核心与 iframe tool,子页面 inject;父示例创建 iframe 并通信 | 父子通信、全屏及卸载;只打开子页面不能完成集成验收 | | `/test/` | [test/index.html](../docs/test/index.html)、[build-test.js](../scripts/build-test.js) | Mocha/Chai,compiled 核心及部分插件,生成的文档用例与外部播放依赖 | 文档 smoke 的现有入口,补充状态断言与自动化结果读取 | -开发入口继续采用仓库脚本:`npm run dev` 交互选择包,在 `http://localhost:8082` 验证。[dev.js](../scripts/dev.js) 当前只构建所选包到 `docs/uncompiled//index.js`,不能假设所有插件同时更新。正常构建产生分发文件,不手改 compiled/uncompiled/dist 或生成的 test.js。 +开发入口继续采用仓库脚本:`yarn dev` 交互选择包,在 `http://localhost:8082` 验证。[dev.js](../scripts/dev.js) 当前只构建所选包到 `docs/uncompiled//index.js`,不能假设所有插件同时更新。正常构建产生分发文件,不手改 compiled/uncompiled/dist 或生成的 test.js。 chapter 试点可使用 `http://localhost:8082/?libs=./uncompiled/artplayer-plugin-chapter/index.js&example=chapter`,测试前确认核心及插件已构建。这是已有路由的用法,不表示当前服务已启动或页面已通过。 @@ -49,3 +49,9 @@ chapter 试点可使用 `http://localhost:8082/?libs=./uncompiled/artplayer-plug | REVIEW-02/03 | 分别复查真实浏览器覆盖与待发布内容的一致性 | 页面行为和媒体验证按 [测试规范](testing.md) 留证,发布结论按 [多轮复盘](release-reviews.md) 核对。该清单不新增重复实施任务;真实状态以 tasks.json 为准。 + +## BASE-04 已落地的记录 + +[完整路径台账](baselines/demo-inventory.json) 已覆盖 29 个示例、36 个 HTML 和 22 包,包括生成文档、独立 upscaler 与站点验证页面。源脚本/菜单参数与 editor prod/ts/code/log 规则已有记录;这些官方入口尚未运行,运行时实际存储值和网络来源仍待 EX-03 采集。 + +独立发布包夹具的 DOM/CSS、Tab/热键、设置与网页全屏已在内置浏览器两次实测,见 [覆盖及问题](baselines/dom-coverage.md)。它没有运行 Monaco,不代表编辑器 Run 或官方 chapter demo 通过。thumbnail 无菜单示例依赖不在当前 workspace 的旧插件,已交 SITE-01/EX-03 核实。 diff --git a/refactor/fixtures/dom.html b/refactor/fixtures/dom.html new file mode 100644 index 000000000..3498ff73d --- /dev/null +++ b/refactor/fixtures/dom.html @@ -0,0 +1,22 @@ + + + +ArtPlayer published DOM baseline + +

Published DOM and input baseline

+

Use Probe control, ArrowRight, Typing probe + ArrowRight, settings Baseline selector / High, and web fullscreen + Escape. Then inspect narrow layout and save.

+ +
+ + +

Loading published player

+

+
+
+
+
diff --git a/refactor/fixtures/dom.js b/refactor/fixtures/dom.js
new file mode 100644
index 000000000..5c6807bd4
--- /dev/null
+++ b/refactor/fixtures/dom.js
@@ -0,0 +1,119 @@
+/* global Artplayer, artplayerPluginChapter */
+(() => {
+  const report = {
+    kind: 'dom', environment: { userAgent: navigator.userAgent, language: navigator.language },
+    scripts: [...document.scripts].map(script => new URL(script.src).pathname),
+    checks: [], errors: [], unhandled: [], interactions: [], observations: {},
+  }
+  const status = document.querySelector('#status')
+  const check = (id, passed) => {
+    if (report.checks.some(item => item.id === id)) return
+    report.checks.push({ id, passed: Boolean(passed) })
+    if (!passed) throw new Error(id)
+    status.textContent = report.checks.map(item => item.id).join(' | ')
+  }
+  addEventListener('error', event => report.errors.push(event.message))
+  addEventListener('unhandledrejection', event => report.unhandled.push(String(event.reason)))
+  function tree(element) {
+    return {
+      tag: element.tagName.toLowerCase(),
+      attributes: Object.fromEntries([...element.attributes].filter(attr => /^(class|role|aria-.+|tabindex|data-tooltip|type)$/.test(attr.name)).map(attr => [attr.name, attr.value])),
+      children: element.tagName.toLowerCase() === 'svg' ? [] : [...element.children].map(tree),
+    }
+  }
+  function layout() {
+    const player = art.template.$player
+    const rect = player.getBoundingClientRect()
+    return {
+      width: rect.width, height: rect.height, scrollWidth: player.scrollWidth,
+      controls: [...player.querySelectorAll('.art-control')].map(el => {
+        const r = el.getBoundingClientRect()
+        return { class: el.className, width: Math.round(r.width * 100) / 100, withinPlayer: r.left >= rect.left - 1 && r.right <= rect.right + 1, display: getComputedStyle(el).display }
+      }),
+    }
+  }
+  const art = new Artplayer({
+    container: '.player', url: '/assets/sample/video.mp4', muted: true, lang: 'en',
+    setting: true, playbackRate: true, aspectRatio: true, fullscreenWeb: true, hotkey: true,
+    controls: [{ name: 'compat', position: 'right', html: 'Probe control', tooltip: 'Probe control', click(component, event) {
+      check('DOM.control-click', this === art && component === art.controls && event.isTrusted)
+    } }],
+    layers: [{ name: 'compat', html: '', style: { top: '12px', left: '12px' } }],
+    settings: [{ name: 'compat-setting', html: 'Baseline selector', selector: [{ html: 'Low', default: true }, { html: 'High' }], onSelect(item, element, event) {
+      check('DOM.setting-select', this === art && item.html === 'High' && element.classList.contains('art-current') && event.isTrusted)
+      report.observations.selected = { html: item.html, defaults: item.$parent.selector.map(value => value.default) }
+      return item.html
+    } }],
+    plugins: [artplayerPluginChapter({ chapters: [{ start: 0, end: 10, title: 'First' }, { start: 10, end: Infinity, title: 'Second' }] })],
+  })
+  let ready = false
+  art.once('ready', () => {
+    check('DOM.version-template', Artplayer.version === '5.4.0' && art.query('.art-video') === art.video)
+    art.cssVar('--art-theme', 'rgb(1, 2, 3)')
+    check('DOM.user-style', getComputedStyle(art.query('.user-hook')).color === 'rgb(12, 34, 56)' && art.cssVar('--art-theme').trim() === 'rgb(1, 2, 3)')
+    const chapters = [...art.query('.art-chapters').children].map(el => ({ class: el.className, start: Number(el.dataset.start), end: Number(el.dataset.end), title: el.dataset.title }))
+    check('DOM.chapter-markup', chapters.length === 2 && chapters[0].title === 'First' && chapters[1].title === 'Second')
+    report.snapshot = {
+      template: Artplayer.html, tree: tree(art.template.$player),
+      cssVariables: [...new Set(Artplayer.STYLE.match(/--art-[\w-]+/g))].sort(),
+      cssClasses: [...new Set(Artplayer.STYLE.match(/\.art-[\w-]+/g))].sort(),
+      chapters,
+      controlAttributes: [...art.template.$player.querySelectorAll('.art-control')].map(el => ({ class: el.className, tag: el.tagName, tabIndex: el.tabIndex, role: el.getAttribute('role'), ariaLabel: el.getAttribute('aria-label'), tooltip: el.getAttribute('data-tooltip'), title: el.getAttribute('title') })),
+      chapterStylePresent: Boolean(document.getElementById('artplayer-plugin-chapter')),
+    }
+    report.observations.wide = layout()
+    ready = true
+  })
+  let beforeKey
+  let hotkeys = 0
+  document.addEventListener('keydown', event => {
+    beforeKey = { time: art.currentTime, hotkeys, focused: art.isFocus, target: event.target.tagName, code: event.code, trusted: event.isTrusted }
+  }, true)
+  art.on('hotkey', () => hotkeys++)
+  document.addEventListener('keydown', event => {
+    if (!ready) return
+    const after = { ...beforeKey, afterTime: art.currentTime, afterHotkeys: hotkeys, activeTag: document.activeElement.tagName, activeLabel: document.activeElement.getAttribute('aria-label') }
+    report.interactions.push(after)
+    if (event.code === 'ArrowRight' && beforeKey.target !== 'INPUT') check('DOM.keyboard-seek', beforeKey.trusted && beforeKey.focused && hotkeys === beforeKey.hotkeys + 1 && Math.abs(art.currentTime - beforeKey.time - Artplayer.SEEK_STEP) < 0.05)
+    if (event.code === 'ArrowRight' && beforeKey.target === 'INPUT') check('DOM.input-ignores-hotkey', beforeKey.trusted && beforeKey.focused && hotkeys === beforeKey.hotkeys && art.currentTime === beforeKey.time)
+  })
+  document.addEventListener('focusin', event => report.interactions.push({ focus: event.target.tagName, label: event.target.getAttribute('aria-label'), id: event.target.id }))
+  art.on('fullscreenWeb', value => {
+    if (value) check('DOM.fullscreen-web-enter', art.template.$player.parentElement === document.body && art.template.$player.classList.contains('art-fullscreen-web'))
+    else check('DOM.fullscreen-web-exit', art.template.$player.parentElement === art.template.$container && !art.template.$player.classList.contains('art-fullscreen-web'))
+  })
+  document.querySelector('#narrow').onclick = () => {
+    document.querySelector('.player').style.cssText = 'width:320px;height:180px'
+    requestAnimationFrame(() => {
+      report.observations.narrow = layout()
+      check('DOM.narrow-container', report.observations.narrow.width === 320 && report.observations.narrow.height === 180)
+    })
+  }
+  document.querySelector('#finish').onclick = async () => {
+    try {
+      const previous = art.controls.compat
+      const updateResult = art.controls.update({ name: 'compat', html: 'Updated control' })
+      const replacement = art.controls.compat
+      check('DOM.control-update', updateResult === undefined && replacement instanceof HTMLElement && !previous.isConnected && art.template.$player.querySelectorAll('.art-control-compat').length === 1)
+      art.controls.remove('compat')
+      check('DOM.control-remove', !replacement.isConnected && !art.controls.compat)
+      art.setting.update({ name: 'compat-setting', html: 'Updated selector' })
+      check('DOM.setting-update', art.setting.find('compat-setting').html === 'Updated selector')
+      art.setting.remove('compat-setting')
+      check('DOM.setting-remove', !art.setting.find('compat-setting'))
+      art.layers.remove('compat')
+      check('DOM.layer-remove', !art.query('.user-hook'))
+      art.destroy()
+      check('DOM.destroy', document.querySelector('.player').children.length === 0 && Artplayer.instances.length === 0)
+    }
+    catch (error) {
+      report.errors.push(error.message)
+      if (Artplayer.instances.includes(art)) art.destroy()
+    }
+    report.checks.sort((a, b) => a.id.localeCompare(b.id))
+    document.querySelector('#result').textContent = JSON.stringify(report, null, 2)
+    const response = await fetch('/reports/dom', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(report) })
+    status.textContent = response.ok ? `Saved ${report.checks.length} checks; errors ${report.errors.length}` : `Save failed: ${response.status}`
+    document.querySelector('#finish').disabled = true
+  }
+})()
diff --git a/refactor/plan.md b/refactor/plan.md
index fd5a1447f..b3c967daf 100644
--- a/refactor/plan.md
+++ b/refactor/plan.md
@@ -4,7 +4,7 @@
 
 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 214 项,范围 22 个包及工作区/示例。
 
-状态:todo 194 / doing 0 / blocked 0 / done 20 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
+状态:todo 193 / doing 0 / blocked 0 / done 21 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
 
 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
 
@@ -63,7 +63,7 @@
 | BASE-HARNESS-01 | workspace
建立最小发布包浏览器采集夹具与服务 | BASE-01 | 固定 tarball 静态服务、本地媒体 Range、公开 API 采集页面及受控报告路径 | HTTP 精确内容/范围和路由隔离测试、夹具语法通过;浏览器采集仍由 BASE-02 负责 | M | done | | BASE-02 | workspace
捕获公共 API 和属性描述符 | BASE-01, BASE-HARNESS-01 | 核心与试点的构造/默认值/静态和实例 API 快照,其余包由 01/02 步补齐;登记契约/版本/固定测试 ID/命令/报告与负责任务 | 真实发布包反射与旧用户调用可重跑,未验证项明确 | H | done | | BASE-03 | workspace
捕获事件、异步和生命周期 | BASE-02 | ready/restart/destroy、Promise、切源/插件事件 trace;登记契约/版本/固定测试 ID/命令/报告与负责任务 | 成功、失败、重入和多实例有断言,历史缺陷有标记;使用已发布包和最小可重跑探针启动,不等待后续 ENG 服务 | H | done | -| BASE-04 | workspace
捕获 DOM、CSS 和官方 demo | BASE-02 | 核心模板/类名/变量、键盘/焦点/可访问名称基线及全 demo 路径映射;docs-browser-testing.md 中 HTML/编辑器的脚本来源与存储状态;登记契约/版本/固定测试 ID/命令/报告与负责任务 | 核心/试点用户样式和输入用法已记录,其他 demo 运行状态与未知项明确;使用已发布包和最小可重跑探针启动,不等待后续 ENG 服务 | M | todo | +| BASE-04 | workspace
捕获 DOM、CSS 和官方 demo | BASE-02 | 核心模板/类名/变量、键盘/焦点/可访问名称基线及全 demo 路径映射;docs-browser-testing.md 中 HTML/编辑器的脚本来源与存储状态;登记契约/版本/固定测试 ID/命令/报告与负责任务 | 核心/试点用户样式和输入用法已记录,其他 demo 运行状态与未知项明确;使用已发布包和最小可重跑探针启动,不等待后续 ENG 服务 | M | done | | BASE-05 | workspace
捕获包入口、资源和类型消费 | BASE-01 | 全包 manifest/资源路径清单;核心/试点的 UMD/AMD/ESM/CJS/legacy/i18n 和类型消费者基线;登记契约/版本/固定测试 ID/命令/报告与负责任务;区分库/npm 与站点实际分发类别,SSR import/模板/非浏览器构造错误分别捕获 | 核心/试点隔离消费可运行,其他包由各自契约任务验证,thumbnail tool 差异已登记 | H | todo | | BASE-06 | workspace
记录性能与资源基线 | BASE-03, BASE-04 | 固定媒体/设备测量、包体积、反复装卸资源报告 | 多次采样可重跑,指标和审查阈值确定,不承诺未经测量的收益;使用已发布包和最小可重跑探针启动,不等待后续 ENG 服务 | M | todo | | BASE-07 | workspace
建立差异和风险台账 | BASE-03, BASE-05 | 销毁/切源/类型差异、vendored 许可和外部 SDK 清单 | 已确认与待复现分开,每项有负责任务及兼容处理路线 | H | todo | @@ -116,8 +116,8 @@ | CORE-10 | artplayer
迁移播放与时间/状态属性 | CORE-09 | play/pause/toggle/seek/volume/rate/duration 等模块 | 旧返回值、拒绝、mutex、位置恢复、属性描述符与事件通过 | H | todo | | CORE-11 | artplayer
整理媒体事件、ready 与重连 | CORE-10 | 媒体转发、重连、UI 响应职责分离 | readiness 次数/顺序、过期重连、原错误参数有回归 | H | todo | | CORE-12 | artplayer
迁移模板及公开资源入口 | CORE-06, CORE-04, BASE-04 | template/icons/i18n/style、SSR 与 proxy 挂载 | DOM/CSS/global/样式注入、i18n 子路径及 SSR import 保持;不把 SSR import/useSSR 保持扩大为服务端构造播放器 | H | todo | -| CORE-13 | artplayer
迁移组件注册与控件 | CORE-12, CORE-08 | Component/control/layer/contextmenu 的资源与 TS 边界 | add/update/remove、name、selector、mounted/beforeUnmount 通过 | H | todo | -| CORE-14 | artplayer
拆分设置树、渲染和布局 | CORE-13 | setting model/selection/render/layout 的分批实现 | 不强行统一控件返回值,嵌套选择、默认高亮、更新清理通过 | H | todo | +| CORE-13 | artplayer
迁移组件注册与控件 | CORE-12, CORE-08 | Component/control/layer/contextmenu 的资源与 TS 边界 | add/update/remove、name、selector、mounted/beforeUnmount 通过;处理 BASE-DOM-02 窄容器控件裁切,保留 controls.update 的旧 undefined 返回值 | H | todo | +| CORE-14 | artplayer
拆分设置树、渲染和布局 | CORE-13 | setting model/selection/render/layout 的分批实现 | 不强行统一控件返回值,嵌套选择、默认高亮、更新清理通过;结合 BASE-DOM-02 验证窄容器设置定位 | H | todo | | CORE-15 | artplayer
迁移字幕获取、解析和渲染 | CORE-11, CORE-13 | subtitle 请求/track/DOM/URL 生命周期与类型 | 过期结果、偏移、cue 数组、原生全屏及错误路径通过 | H | todo | | CORE-16 | artplayer
迁移全屏、PiP、mini 与尺寸模式 | CORE-11, CORE-12 | 显示模式服务和原公开属性门面 | 模式互斥/恢复位置、手势权限、移动方向和退出失败通过 | H | todo | | CORE-17 | artplayer
迁移键盘、手势、焦点和全局事件 | CORE-13, CORE-16 | events/hotkey 的明确 document 与资源归属 | 跨 document 重绑、触摸/键盘、快捷键及销毁无重复响应 | H | todo | @@ -125,7 +125,7 @@ | CORE-19 | artplayer
整理进度、质量、缩略图和截图 | CORE-10, CORE-13, CORE-16 | 相关 player/control 功能与纯计算分离 | 截图跨域失败、缩略图布局、quality/进度边界保持 | M | todo | | CORE-20 | artplayer
收敛核心入口与依赖方向 | CORE-14, CORE-15, CORE-18, CORE-19 | Artplayer 门面、初始化依赖与最终目录 | 所有核心自有源码迁移,公开描述符/静态接口/事件差分通过 | H | todo | | CORE-21 | artplayer
生成核心声明并校验包内容 | CORE-20, ENG-07 | 由 TS 生成的既有入口声明与三种发布产物 | 内部类型不泄漏,旧 JS/TS 消费者与 i18n 通过 | H | todo | -| CORE-23 | artplayer
检查并补齐键盘、焦点与可访问名称 | CORE-13, CORE-14, CORE-17, ENG-05, BASE-04 | 主要控件/设置/模式退出的键盘与焦点回归、名称和字幕可用性检查及必要兼容修正 | 保持旧快捷键和 DOM/CSS 钩子;真实浏览器验证,不以静态属性检查代替交互 | H | todo | +| CORE-23 | artplayer
检查并补齐键盘、焦点与可访问名称 | CORE-13, CORE-14, CORE-17, ENG-05, BASE-04 | 主要控件/设置/模式退出的键盘与焦点回归、名称和字幕可用性检查及必要兼容修正 | 保持旧快捷键和 DOM/CSS 钩子;真实浏览器验证,不以静态属性检查代替交互;处理 BASE-DOM-01 的主要控件 Tab 不可达,保留既有名称与快捷键 | H | todo | | CORE-22 | artplayer
核心阶段完整验收 | CORE-21, CORE-23, PILOT-01, ENG-08, ENG-10 | 核心与旧插件的可自动化完整回归、资源/性能及明确外部验证缺口 | 核心自动化和公开差异处置通过;真实环境缺口链接包集成/REL 门槛,阶段完成不代表可公开发布 | H | todo | ## 5 包迁移:artplayer-plugin-chapter @@ -359,7 +359,7 @@ | ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 | | --- | --- | --- | --- | --- | --- | --- | -| SITE-01 | artplayer-vitepress
清点文档/示例/生成链 | BASE-04, BASE-05 | 中英文文档、插件页面、demo URL、编辑器声明与生成目录清单;六类 HTML 入口、prod/libs/code/example 加载行为及移动重定向 | 所有公开 API/插件有对应页面或明确补充任务 | M | todo | +| SITE-01 | artplayer-vitepress
清点文档/示例/生成链 | BASE-04, BASE-05 | 中英文文档、插件页面、demo URL、编辑器声明与生成目录清单;六类 HTML 入口、prod/libs/code/example 加载行为及移动重定向 | 所有公开 API/插件有对应页面或明确补充任务;核实 BASE-DEMO-01 的历史 thumbnail 插件来源及 29 示例/36 HTML 路径 | M | todo | | SITE-02 | artplayer-vitepress
整理声明与示例生成器 | SITE-01, ENG-04, ENG-06 | build-ts/build-test 生成链的可验证 TS 脚本 | 不靠字符串拼接掩盖声明错误,生成示例有真实断言或仅标 smoke;替换固定 100ms 成功判定,明确异步错误、清理和生成覆盖限制 | M | todo | | SITE-03 | artplayer-vitepress
整理 i18n/文档/LLM 生成流程 | SITE-02 | build-i18n/build-docs/build-llm/trans-docs 的任务边界和错误处理 | 原命令兼容、生成可复现,翻译步骤不隐式运行远程服务 | M | todo | | SITE-04 | artplayer-vitepress
交叉核对逐包持续维护的文档 | CORE-21, SITE-03, PKG-CHAPTER-04, PKG-AMBILIGHT-04, PKG-AUDIO-04, PKG-AUTO-THUMB-04, PKG-VTT-THUMB-04, PKG-HLS-04, PKG-DASH-04, PKG-MULTI-SUB-04, PKG-JASSUB-04, PKG-MASK-04, PKG-ASR-04, PKG-ADS-04, PKG-VAST-04, PKG-CAST-04, PKG-DPIP-04, PKG-CANVAS-04, PKG-IFRAME-04, PKG-TOOL-THUMB-04, PKG-DANMUKU-06, PKG-MB-08 | 已随实现更新的中文/英文 API、包内实现地图、旧 JS 示例及已知能力限制的全包核对 | 未把缺环境的能力写成已验证,静态核对不等待设备任务;最终 demo 仍由 EX-03 验收 | M | todo | @@ -367,7 +367,7 @@ | SITE-06 | artplayer-vitepress
文档站交付验收 | SITE-05 | 维护指南和站点变更记录 | 未手改 generated 目录,旧 URL 可用、部署与检查分离 | M | todo | | EX-01 | example/react.js
验证 React 消费者与 TS | CORE-22, ENG-07 | React 挂载/卸载/重挂载、引用和插件样例 | 真实 tarball + TS 消费通过,保留既有 React 集成 API | M | todo | | EX-02 | example/vue.js
验证 Vue 消费者与更新卸载 | CORE-22, ENG-07 | Vue 实例/ref、参数更新、卸载及插件样例 | 旧 JS 组件用法无需修改,重复挂载不泄漏 | M | todo | -| EX-03 | workspace
验证全部原生 demo 与外部播放集成 | EX-01, EX-02, PKG-CHAPTER-06, PKG-AMBILIGHT-06, PKG-AUDIO-06, PKG-AUTO-THUMB-06, PKG-VTT-THUMB-06, PKG-HLS-06, PKG-DASH-06, PKG-MULTI-SUB-06, PKG-JASSUB-06, PKG-MASK-06, PKG-ASR-06, PKG-ADS-06, PKG-VAST-06, PKG-CAST-06, PKG-DPIP-06, PKG-DANMUKU-09, PKG-CANVAS-06, PKG-MB-10, PKG-IFRAME-06, PKG-TOOL-THUMB-06 | 8082 全 demo、HLS/DASH/FLV/MPEGTS/WebTorrent 集成记录 | 旧 URL/参数/脚本加载保持;网络/SDK 限制明确,不静默跳过;覆盖编辑器重复 Run 和 ESM/i18n/mobile/iframe,记录实际候选脚本及状态隔离证据 | H | todo | +| EX-03 | workspace
验证全部原生 demo 与外部播放集成 | EX-01, EX-02, PKG-CHAPTER-06, PKG-AMBILIGHT-06, PKG-AUDIO-06, PKG-AUTO-THUMB-06, PKG-VTT-THUMB-06, PKG-HLS-06, PKG-DASH-06, PKG-MULTI-SUB-06, PKG-JASSUB-06, PKG-MASK-06, PKG-ASR-06, PKG-ADS-06, PKG-VAST-06, PKG-CAST-06, PKG-DPIP-06, PKG-DANMUKU-09, PKG-CANVAS-06, PKG-MB-10, PKG-IFRAME-06, PKG-TOOL-THUMB-06 | 8082 全 demo、HLS/DASH/FLV/MPEGTS/WebTorrent 集成记录 | 旧 URL/参数/脚本加载保持;网络/SDK 限制明确,不静默跳过;覆盖编辑器重复 Run 和 ESM/i18n/mobile/iframe,记录实际候选脚本及状态隔离证据;依据 BASE-04 路径台账逐项消除 not-run,处理 BASE-DEMO-01 | H | todo | ## 7 工具链与性能 @@ -420,6 +420,7 @@ - BASE-HARNESS-01: [记录](changes/2026-09-10-BASE-HARNESS-01-browser-fixture.md) - BASE-02: [记录](changes/2026-09-10-BASE-02-public-api.md) [记录](baselines/public-api.json) [记录](baselines/api-coverage.md) - BASE-03: [记录](changes/2026-09-10-BASE-03-lifecycle.md) [记录](baselines/lifecycle.json) [记录](baselines/lifecycle-coverage.md) +- BASE-04: [记录](changes/2026-09-10-BASE-04-dom.md) [记录](baselines/dom.json) [记录](baselines/dom-coverage.md) [记录](baselines/demo-inventory.json) - ENG-01: [记录](changes/2026-09-10-ENG-01-reproducible-toolchain.md) [记录](baselines/toolchain-validation.json) - ENG-PM-01: [记录](changes/2026-09-10-ENG-PM-01-yarn-toolchain.md) [记录](baselines/yarn-validation.json) - ENG-02: [记录](changes/2026-09-10-ENG-02-readonly-ci.md) [记录](baselines/ci-validation.json) diff --git a/refactor/progress.md b/refactor/progress.md index b6a8ce5a0..b8d56d055 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,11 @@ # 进度与证据 +## 当前实施:BASE-04 DOM 与输入基线完成 + +内置浏览器最终两轮 16 项检查通过,模板/类名/变量、用户样式、控件/设置、Tab/热键与网页全屏已留证。320px 裁切和控件 Tab 不可达已分配 CORE-13/14/23,未把历史问题当成正常兼容要求。29 示例、36 HTML 和 22 包的路径已映射,官方 docs 页面仍待真实运行。见 [BASE-04](changes/2026-09-10-BASE-04-dom.md)。 + +当前 214 项任务,21 完成、193 待办。BASE-03 已提交 94893929;本轮独立提交 BASE-04。没有生产 TS 迁移、推送或发布。下一任务 BASE-05:入口、资源与类型消费者基线。 + ## 当前实施:BASE-03 生命周期基线完成 固定发布包在内置浏览器最终两次执行 27 项断言、真实媒体时间推进与关键事件顺序均通过,语义观察相同,保存 64 条事件 trace 和六项历史问题。受控恢复拒绝单独登记,其余 errors/unhandled 为空;无把历史缺陷当作正常兼容要求。见 [BASE-03](changes/2026-09-10-BASE-03-lifecycle.md) 和 [问题/覆盖台账](baselines/lifecycle-coverage.md)。CORE-04/08/09 验收已链接相应复现。 diff --git a/refactor/scripts/browser-server.mjs b/refactor/scripts/browser-server.mjs index 5dc956308..9353cdfe5 100644 --- a/refactor/scripts/browser-server.mjs +++ b/refactor/scripts/browser-server.mjs @@ -37,7 +37,7 @@ function sendBytes(req, res, bytes, filename) { const server = http.createServer(async (req, res) => { try { const url = new URL(req.url, `http://127.0.0.1:${port}`) - const reportName = { '/reports/api': 'api', '/reports/lifecycle': 'lifecycle' }[url.pathname] + const reportName = { '/reports/api': 'api', '/reports/lifecycle': 'lifecycle', '/reports/dom': 'dom' }[url.pathname] if (req.method === 'POST' && reportName) { assert.equal(req.headers.origin, `http://127.0.0.1:${port}`, 'Unexpected report origin') const chunks = [] @@ -48,7 +48,7 @@ const server = http.createServer(async (req, res) => { chunks.push(chunk) } const report = JSON.parse(Buffer.concat(chunks).toString('utf8')) - assert.equal(report.kind, reportName === 'api' ? 'public-api' : 'lifecycle') + assert.equal(report.kind, reportName === 'api' ? 'public-api' : reportName) report.capture = { capturedAt: new Date().toISOString(), releases: baseline.releases.map(release => ({ @@ -62,6 +62,7 @@ const server = http.createServer(async (req, res) => { if (reportName === 'lifecycle') { report.capture.media = Object.fromEntries(['video.mp4', 'video2.mp4'].map(name => [name, hash(fs.readFileSync(path.join(root, 'docs/assets/sample', name)))])) } + if (reportName === 'dom') report.capture.media = { 'video.mp4': hash(fs.readFileSync(path.join(root, 'docs/assets/sample/video.mp4'))) } const output = path.join(refactorDir, '.cache/reports') fs.mkdirSync(output, { recursive: true }) fs.writeFileSync(path.join(output, `${reportName}.json`), `${JSON.stringify(report, null, 2)}\n`) diff --git a/refactor/scripts/demos.mjs b/refactor/scripts/demos.mjs new file mode 100644 index 000000000..c9e372050 --- /dev/null +++ b/refactor/scripts/demos.mjs @@ -0,0 +1,70 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { hash, refactorDir } from './releases.mjs' + +const root = path.resolve(refactorDir, '..') +const read = name => fs.readFileSync(path.join(root, name), 'utf8').replaceAll('\r\n', '\n') +function htmlFiles(dir = 'docs') { + return fs.readdirSync(path.join(root, dir), { withFileTypes: true }).flatMap(entry => { + const name = `${dir}/${entry.name}` + if (entry.isDirectory()) return ['compiled', 'uncompiled', 'node_modules'].includes(entry.name) ? [] : htmlFiles(name) + return entry.name.endsWith('.html') ? [name] : [] + }).sort() +} +function menuLinks() { + return [...read('docs/index.html').matchAll(/href="([^"\n]*[?&]example=[^"\n]*)"/g)].map(match => match[1].replaceAll('&', '&')) +} +export function captureDemoInventory() { + const links = menuLinks() + const inventory = JSON.parse(read('refactor/package-inventory.json')) + const examples = fs.readdirSync(path.join(root, 'docs/assets/example')).filter(name => name.endsWith('.js')).sort().map(file => { + const name = file.slice(0, -3) + const source = `docs/assets/example/${file}` + const linked = links.filter(link => new URL(link, 'http://localhost:8082').searchParams.get('example') === name) + const libs = [...new Set(linked.flatMap(link => (new URL(link, 'http://localhost:8082').searchParams.get('libs') || '').split('\n').filter(Boolean)))] + return { name, source, sha256Lf: hash(read(source)), menuLinks: linked, route: linked[0] || `/?example=${name}`, libs, + status: 'not-run', owner: 'EX-03', + note: name === 'thumbnail' ? 'References artplayer-plugin-thumbnail, which is absent from the 22 current workspace packages; dependency and route need reconciliation.' : linked.length ? 'Existing menu route; SDK and media loading have not been validated.' : 'No menu link; example route follows common.js loader. Required globals are not automatically loaded.' } + }) + const pages = htmlFiles().map(source => { + const text = read(source) + const uncommented = text.replace(//g, '') + return { source, route: `/${source.slice(5)}`.replace(/index\.html$/, ''), sha256Lf: hash(text), + kind: source.startsWith('docs/document/') ? 'generated-docs' : source.includes('googlebc') ? 'site-verification' : source === 'docs/upscaler/index.html' ? 'standalone-tool' : 'demo-or-editor', + scriptSources: [...uncommented.matchAll(/]*\bsrc=["']([^"']+)["']/g)].map(match => match[1]), + status: 'not-run', owner: source.includes('googlebc') ? 'SITE-01' : 'EX-03' } + }) + return { schemaVersion: 1, task: 'BASE-04', sourceCommit: '94893929', capturedAt: new Date().toISOString(), + note: 'Historical source inventory, not browser or network evidence. ScriptSources excludes comments but does not resolve inline scripts, import maps or dynamic SDK loading. Hashes use LF and describe this capture; check validates coverage, not current content identity.', + examples, pages, + packages: inventory.packages.map(pkg => ({ name: pkg.name, examples: examples.filter(example => example.libs.some(lib => lib.includes(`/${pkg.name}/`))).map(example => example.name), status: 'not-run', owner: pkg.name === 'artplayer-vitepress' ? 'SITE-01' : 'EX-03' })), + editor: { source: 'docs/assets/js/common.js', sha256Lf: hash(read('docs/assets/js/common.js')), storage: ['prod', 'ts', 'code', 'log'], isolation: 'Use separate test origin; record keys, do not erase user browser data.', core: { prodTrue: './compiled/artplayer.js', otherwise: './uncompiled/artplayer/index.js' }, pluginSources: 'URL libs parameter; independent of prod toggle', execution: 'Run evaluates editor text; TS mode alone is not strict tsc validation.', runtimeStatus: 'not-run' }, + } +} +export function validateDemoInventory(inventory) { + assert.equal(inventory.schemaVersion, 1) + assert.deepEqual(inventory.examples.map(item => item.source).sort(), fs.readdirSync(path.join(root, 'docs/assets/example')).filter(name => name.endsWith('.js')).map(name => `docs/assets/example/${name}`).sort(), 'Demo file coverage drift') + assert.deepEqual(inventory.pages.map(item => item.source).sort(), htmlFiles(), 'HTML route coverage drift') + assert.deepEqual(inventory.examples.flatMap(item => item.menuLinks).sort(), menuLinks().sort(), 'Editor link coverage drift') + assert.deepEqual(inventory.packages.map(item => item.name).sort(), JSON.parse(read('refactor/package-inventory.json')).packages.map(item => item.name).sort(), 'Package demo coverage drift') + const names = new Set(inventory.examples.map(item => item.name)) + for (const pkg of inventory.packages) for (const name of pkg.examples) assert(names.has(name), `Unknown example: ${name}`) + assert(inventory.examples.find(item => item.name === 'thumbnail').note.includes('absent'), 'Missing historical plugin exception') +} +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const filename = path.join(refactorDir, 'baselines/demo-inventory.json') + if (process.argv.includes('--capture')) { + assert(!fs.existsSync(filename), 'Do not overwrite the historical inventory; reconcile changes explicitly') + const inventory = captureDemoInventory() + validateDemoInventory(inventory) + fs.writeFileSync(filename, `${JSON.stringify(inventory, null, 2)}\n`) + } + else { + assert(process.argv.slice(2).every(arg => arg === '--check'), 'Use --capture or --check') + const inventory = JSON.parse(fs.readFileSync(filename, 'utf8')) + validateDemoInventory(inventory) + console.log(`Demo coverage: ${inventory.examples.length} examples, ${inventory.pages.length} HTML paths, ${inventory.packages.length} packages; no browser pass implied`) + } +} diff --git a/refactor/scripts/demos.test.mjs b/refactor/scripts/demos.test.mjs new file mode 100644 index 000000000..9557ee544 --- /dev/null +++ b/refactor/scripts/demos.test.mjs @@ -0,0 +1,21 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import path from 'node:path' +import test from 'node:test' +import { validateDemoInventory } from './demos.mjs' +import { refactorDir } from './releases.mjs' + +test('Demo inventory rejects missing files, routes and ecosystem packages', () => { + const inventory = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/demo-inventory.json'), 'utf8')) + validateDemoInventory(inventory) + for (const mutate of [ + value => value.examples.pop(), + value => value.pages.pop(), + value => value.examples.find(item => item.menuLinks.length).menuLinks.pop(), + value => value.packages.pop(), + ]) { + const changed = structuredClone(inventory) + mutate(changed) + assert.throws(() => validateDemoInventory(changed)) + } +}) diff --git a/refactor/scripts/dom.mjs b/refactor/scripts/dom.mjs new file mode 100644 index 000000000..c755aa8a4 --- /dev/null +++ b/refactor/scripts/dom.mjs @@ -0,0 +1,78 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { validatePublishedCapture } from './api.mjs' +import { hash, refactorDir } from './releases.mjs' + +export const domChecks = [ + 'DOM.chapter-markup', 'DOM.control-click', 'DOM.control-remove', 'DOM.control-update', + 'DOM.destroy', 'DOM.fullscreen-web-enter', 'DOM.fullscreen-web-exit', 'DOM.input-ignores-hotkey', + 'DOM.keyboard-seek', 'DOM.layer-remove', 'DOM.narrow-container', 'DOM.setting-remove', + 'DOM.setting-select', 'DOM.setting-update', 'DOM.user-style', 'DOM.version-template', +] + +export function validateDomReport(report) { + assert.equal(report.kind, 'dom') + assert.deepEqual(report.errors, [], 'DOM capture failed') + assert.deepEqual(report.unhandled, [], 'Unexpected DOM rejection') + assert.deepEqual(report.checks, domChecks.map(id => ({ id, passed: true })), 'Missing DOM checks') + validatePublishedCapture(report, 'dom') + assert.equal(report.capture.media['video.mp4'], hash(fs.readFileSync(path.resolve(refactorDir, '../docs/assets/sample/video.mp4'))), 'DOM media changed') + const s = report.snapshot + assert(s.template.includes('art-video-player') && s.tree.children.length && s.cssVariables.length > 0 && s.cssClasses.length > 0, 'Incomplete DOM/CSS snapshot') + assert.equal(s.chapterStylePresent, true) + assert(s.controlAttributes.length >= 8) + assert.deepEqual(report.observations.selected, { html: 'High', defaults: [false, true] }) + for (const [name, width, height] of [['wide', 640, 360], ['narrow', 320, 180]]) { + assert.equal(report.observations[name].width, width) + assert.equal(report.observations[name].height, height) + assert(report.observations[name].controls.length >= 8, 'Missing layout measurement') + } + const keys = report.interactions.filter(item => item.code) + assert(keys.every(item => item.trusted), 'Input was not trusted browser interaction') + const seek = keys.find(item => item.code === 'ArrowRight' && item.target !== 'INPUT') + assert(seek?.focused && seek.afterTime === seek.time + 5 && seek.afterHotkeys === seek.hotkeys + 1, 'Missing real keyboard seek') + const input = keys.find(item => item.code === 'ArrowRight' && item.target === 'INPUT') + assert(input?.focused && input.afterTime === input.time && input.afterHotkeys === input.hotkeys, 'Input consumed a player hotkey') + assert(keys.some(item => item.code === 'Escape' && item.focused && item.afterHotkeys === item.hotkeys + 1), 'Missing Escape interaction') + assert.equal(keys.filter(item => item.code === 'Tab').length, 2, 'Missing Tab traversal') + const focus = report.interactions.filter(item => item.focus).slice(0, 3) + assert.deepEqual(focus, [{ focus: 'BUTTON', label: null, id: 'tab-start' }, { focus: 'INPUT', label: 'Typing probe', id: '' }, { focus: 'BUTTON', label: null, id: 'narrow' }], 'Published focus traversal changed; assess accessibility improvements explicitly') +} + +export function domProfile(report) { + return { + snapshot: report.snapshot, + checks: report.checks, + selected: report.observations.selected, + layouts: Object.fromEntries(['wide', 'narrow'].map(name => { + const { width, height, controls } = report.observations[name] + return [name, { width, height, controls: controls.map(({ class: className, withinPlayer, display }) => ({ class: className, withinPlayer, display })) }] + })), + } +} + +export function verifyFrozenDom() { + const frozen = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/dom.json'), 'utf8')) + assert.equal(frozen.schemaVersion, 1) + validateDomReport(frozen.report) + assert.equal(hash(JSON.stringify(domProfile(frozen.report))), frozen.verification.profileSha256, 'Frozen DOM profile changed') + return frozen +} + +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const frozen = verifyFrozenDom() + const index = process.argv.indexOf('--compare') + if (index !== -1) { + assert(process.argv[index + 1], '--compare requires a report path') + const captured = JSON.parse(fs.readFileSync(process.argv[index + 1], 'utf8')) + validateDomReport(captured) + assert.deepEqual(domProfile(captured), domProfile(frozen.report), 'Published DOM profile differs; review the change') + console.log('Published DOM capture matches baseline; this comparison is not a candidate accessibility gate') + } + else { + assert(process.argv.slice(2).every(arg => arg === '--check'), 'Use --check or --compare ') + console.log(`Frozen DOM verified: ${domChecks.length} checks; this command does not rerun the browser`) + } +} diff --git a/refactor/scripts/dom.test.mjs b/refactor/scripts/dom.test.mjs new file mode 100644 index 000000000..bad16de6e --- /dev/null +++ b/refactor/scripts/dom.test.mjs @@ -0,0 +1,37 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import { domProfile, validateDomReport, verifyFrozenDom } from './dom.mjs' + +test('DOM baseline rejects absent interaction, fake input and wrong media', () => { + const frozen = verifyFrozenDom().report + for (const mutate of [ + report => report.checks.pop(), + report => report.interactions.find(item => item.code === 'ArrowRight').trusted = false, + report => report.interactions.find(item => item.code === 'ArrowRight').afterTime = 0, + report => report.interactions.find(item => item.code === 'ArrowRight' && item.target === 'INPUT').afterHotkeys++, + report => report.capture.media['video.mp4'] = 'wrong', + report => report.unhandled.push('unexpected'), + ]) { + const changed = structuredClone(frozen) + mutate(changed) + assert.throws(() => validateDomReport(changed)) + } +}) + +test('DOM comparison detects class, variable and layout changes without freezing subpixel widths', () => { + const frozen = verifyFrozenDom().report + const expected = domProfile(frozen) + const jitter = structuredClone(frozen) + jitter.observations.wide.controls[0].width += 0.01 + assert.deepEqual(domProfile(jitter), expected) + for (const mutate of [ + report => report.snapshot.tree.attributes.class = 'renamed', + report => report.snapshot.cssVariables.pop(), + report => report.snapshot.controlAttributes[0].role = 'button', + report => report.observations.narrow.controls[0].withinPlayer = false, + ]) { + const changed = structuredClone(frozen) + mutate(changed) + assert.notDeepEqual(domProfile(changed), expected) + } +}) diff --git a/refactor/tasks.json b/refactor/tasks.json index 87fb47d3a..8d513a035 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -325,11 +325,16 @@ "dependsOn": [ "BASE-02" ], - "status": "todo", + "status": "done", "risk": "M", "deliverable": "核心模板/类名/变量、键盘/焦点/可访问名称基线及全 demo 路径映射;docs-browser-testing.md 中 HTML/编辑器的脚本来源与存储状态;登记契约/版本/固定测试 ID/命令/报告与负责任务", "acceptance": "核心/试点用户样式和输入用法已记录,其他 demo 运行状态与未知项明确;使用已发布包和最小可重跑探针启动,不等待后续 ENG 服务", - "evidence": [] + "evidence": [ + "changes/2026-09-10-BASE-04-dom.md", + "baselines/dom.json", + "baselines/dom-coverage.md", + "baselines/demo-inventory.json" + ] }, { "id": "BASE-05", @@ -911,7 +916,7 @@ "status": "todo", "risk": "H", "deliverable": "Component/control/layer/contextmenu 的资源与 TS 边界", - "acceptance": "add/update/remove、name、selector、mounted/beforeUnmount 通过", + "acceptance": "add/update/remove、name、selector、mounted/beforeUnmount 通过;处理 BASE-DOM-02 窄容器控件裁切,保留 controls.update 的旧 undefined 返回值", "evidence": [] }, { @@ -927,7 +932,7 @@ "status": "todo", "risk": "H", "deliverable": "setting model/selection/render/layout 的分批实现", - "acceptance": "不强行统一控件返回值,嵌套选择、默认高亮、更新清理通过", + "acceptance": "不强行统一控件返回值,嵌套选择、默认高亮、更新清理通过;结合 BASE-DOM-02 验证窄容器设置定位", "evidence": [] }, { @@ -1071,7 +1076,7 @@ "BASE-04" ], "deliverable": "主要控件/设置/模式退出的键盘与焦点回归、名称和字幕可用性检查及必要兼容修正", - "acceptance": "保持旧快捷键和 DOM/CSS 钩子;真实浏览器验证,不以静态属性检查代替交互" + "acceptance": "保持旧快捷键和 DOM/CSS 钩子;真实浏览器验证,不以静态属性检查代替交互;处理 BASE-DOM-01 的主要控件 Tab 不可达,保留既有名称与快捷键" }, { "id": "CORE-22", @@ -3318,7 +3323,7 @@ "status": "todo", "risk": "M", "deliverable": "中英文文档、插件页面、demo URL、编辑器声明与生成目录清单;六类 HTML 入口、prod/libs/code/example 加载行为及移动重定向", - "acceptance": "所有公开 API/插件有对应页面或明确补充任务", + "acceptance": "所有公开 API/插件有对应页面或明确补充任务;核实 BASE-DEMO-01 的历史 thumbnail 插件来源及 29 示例/36 HTML 路径", "evidence": [] }, { @@ -3494,7 +3499,7 @@ "status": "todo", "risk": "H", "deliverable": "8082 全 demo、HLS/DASH/FLV/MPEGTS/WebTorrent 集成记录", - "acceptance": "旧 URL/参数/脚本加载保持;网络/SDK 限制明确,不静默跳过;覆盖编辑器重复 Run 和 ESM/i18n/mobile/iframe,记录实际候选脚本及状态隔离证据", + "acceptance": "旧 URL/参数/脚本加载保持;网络/SDK 限制明确,不静默跳过;覆盖编辑器重复 Run 和 ESM/i18n/mobile/iframe,记录实际候选脚本及状态隔离证据;依据 BASE-04 路径台账逐项消除 not-run,处理 BASE-DEMO-01", "evidence": [] }, {