mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
refactor(core): [CORE-22] validate compatible core and defer initial layout
This commit is contained in:
1 parent
5efc7f7699
commit
adec20213f
24 files changed
+77377
-50
No files matched your search
@@ -67,4 +67,4 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var n=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(n(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(o=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),c=r(t);if("object"===s){if("object"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(c,"'"));Object.keys(e).forEach(function(r){var s=t[r],c=e[r],l=n.slice();l.push(r),i(s,c,l),a(s,c,l),o(s,c,l)})}if("array"===s){if("array"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(c,"'"));t.forEach(function(r,s){var c=t[s],l=e[s]||e[0],d=n.slice();d.push(s),i(c,l,d),a(c,l,d),o(c,l,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),r.exports);const o=t(n),i="5.4.1";function a(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(r)}):(document.head||document.documentElement).appendChild(r)),r.textContent=e}const s=globalThis.CUSTOM_USER_AGENT??("undefined"===typeof navigator?"":navigator.userAgent),c=/^(?:(?!chrome|android).)*safari/i.test(s),l=/iPad|iPhone|iPod/i.test(s)&&("undefined"===typeof window||!window.MSStream),d=l||s.includes("Macintosh")&&"undefined"!==typeof navigator&&navigator.maxTouchPoints>=1,u=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(s)||d,p="undefined"!==typeof window&&"undefined"!==typeof document;const f={properties:["audioTracks","autoplay","buffered","controller","controlLine truncated
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var n=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(n(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(o=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),c=r(t);if("object"===s){if("object"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(c,"'"));Object.keys(e).forEach(function(r){var s=t[r],c=e[r],l=n.slice();l.push(r),i(s,c,l),a(s,c,l),o(s,c,l)})}if("array"===s){if("array"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(c,"'"));t.forEach(function(r,s){var c=t[s],l=e[s]||e[0],d=n.slice();d.push(s),i(c,l,d),a(c,l,d),o(c,l,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),r.exports);const o=t(n),i="5.4.1";function a(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(r)}):(document.head||document.documentElement).appendChild(r)),r.textContent=e}const s=globalThis.CUSTOM_USER_AGENT??("undefined"===typeof navigator?"":navigator.userAgent),c=/^(?:(?!chrome|android).)*safari/i.test(s),l=/iPad|iPhone|iPod/i.test(s)&&("undefined"===typeof window||!window.MSStream),d=l||s.includes("Macintosh")&&"undefined"!==typeof navigator&&navigator.maxTouchPoints>=1,u=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(s)||d,p="undefined"!==typeof window&&"undefined"!==typeof document;const f={properties:["audioTracks","autoplay","buffered","controller","controlLine truncated
|
||||
@@ -67,4 +67,4 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.defineProperties,r=Object.getOwnPropertyDescriptors,n=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,a=(e,r,n)=>r in e?t(e,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[r]=n,s=(t,e)=>{for(var r in e||(e={}))o.call(e,r)&&a(t,r,e[r]);if(n)for(var r of n(e))i.call(e,r)&&a(t,r,e[r]);return t},l=(t,n)=>e(t,r(n)),c=(t,e,r)=>new Promise((n,o)=>{var i=t=>{try{s(r.next(t))}catch(e){o(e)}},a=t=>{try{s(r.throw(t))}catch(e){o(e)}},s=t=>t.done?n(t.value):Promise.resolve(t.value).then(i,a);s((r=r.apply(t,e)).next())});function d(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var u,p={exports:{}};var f=(u||(u=1,p.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(n(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(o=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=n.slice();c.push(r),i(s,l,c),a(s,l,c),o(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=n.slice();d.push(s),i(l,c,d),a(l,c,d),o(l,c,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),p.exports);const h=d(f),m="5.4.1";function v(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.apLine truncated
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.defineProperties,r=Object.getOwnPropertyDescriptors,n=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,a=(e,r,n)=>r in e?t(e,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[r]=n,s=(t,e)=>{for(var r in e||(e={}))o.call(e,r)&&a(t,r,e[r]);if(n)for(var r of n(e))i.call(e,r)&&a(t,r,e[r]);return t},l=(t,n)=>e(t,r(n)),c=(t,e,r)=>new Promise((n,o)=>{var i=t=>{try{s(r.next(t))}catch(e){o(e)}},a=t=>{try{s(r.throw(t))}catch(e){o(e)}},s=t=>t.done?n(t.value):Promise.resolve(t.value).then(i,a);s((r=r.apply(t,e)).next())});function d(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var u,p={exports:{}};var f=(u||(u=1,p.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(n(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(o=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=n.slice();c.push(r),i(s,l,c),a(s,l,c),o(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=n.slice();d.push(s),i(l,c,d),a(l,c,d),o(l,c,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),p.exports);const h=d(f),m="5.4.1";function v(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.apLine truncated
|
||||
@@ -2809,6 +2809,7 @@ function observeControlLayout(art) {
|
||||
scope.add(() => {
|
||||
observer.disconnect();
|
||||
});
|
||||
return;
|
||||
} else if (typeof MutationObserver !== "undefined") {
|
||||
const observer = new MutationObserver(update);
|
||||
observer.observe($controls, { childList: true, subtree: true, attributes: true, characterData: true });
|
||||
@@ -7733,12 +7734,22 @@ function flip(art) {
|
||||
}
|
||||
};
|
||||
}
|
||||
const focusSelector = '[tabindex="0"],button,input,select,textarea,a[href],[contenteditable]';
|
||||
function available(element) {
|
||||
if (!element.isConnected || element.matches(":disabled") || element.closest("[inert]") || element.getAttribute("aria-disabled") === "true" || !element.getClientRects().length)
|
||||
return false;
|
||||
const visibility = element.ownerDocument.defaultView?.getComputedStyle(element).visibility;
|
||||
return element.isConnected && !element.matches(":disabled") && !element.closest("[inert]") && element.getAttribute("aria-disabled") !== "true" && element.getClientRects().length > 0 && visibility !== "hidden" && visibility !== "collapse";
|
||||
return visibility !== "hidden" && visibility !== "collapse";
|
||||
}
|
||||
function settingFocusTargets(panel) {
|
||||
return Array.from(panel.querySelectorAll('[tabindex="0"],button,input,select,textarea,a[href],[contenteditable]')).filter((element) => element.getAttribute("tabindex") !== "-1" && available(element));
|
||||
return Array.from(panel.querySelectorAll(focusSelector)).filter((element) => element.getAttribute("tabindex") !== "-1" && available(element));
|
||||
}
|
||||
function selectSettingTarget(panel, preferred, last = false) {
|
||||
if (preferred && preferred !== panel && panel.contains(preferred) && preferred.matches(focusSelector) && preferred.getAttribute("tabindex") !== "-1" && available(preferred))
|
||||
return preferred;
|
||||
const targets = settingFocusTargets(panel);
|
||||
const selected = panel.querySelector(".art-setting-item.art-current");
|
||||
return targets.find((element) => preferred?.contains(element)) || (last ? targets[targets.length - 1] : targets.find((element) => selected?.contains(element)) || targets.find((element) => !element.classList.contains("art-setting-item-back")) || targets[0]);
|
||||
}
|
||||
function returnSettingFocus(setting2) {
|
||||
const { art, $parent } = setting2;
|
||||
@@ -7758,9 +7769,7 @@ function focusSettingPanel(setting2, preferred, last = false) {
|
||||
const panel = setting2.cache.get(setting2.active);
|
||||
if (!panel || !settingScopeActive(settingPanelScope(panel)))
|
||||
return;
|
||||
const targets = settingFocusTargets(panel);
|
||||
const selected = panel.querySelector(".art-setting-item.art-current");
|
||||
const target = targets.find((element) => preferred === element || preferred?.contains(element)) || (last ? targets[targets.length - 1] : targets.find((element) => selected?.contains(element)) || targets.find((element) => !element.classList.contains("art-setting-item-back")) || targets[0]);
|
||||
const target = selectSettingTarget(panel, preferred, last);
|
||||
target?.focus({ preventScroll: true });
|
||||
if (!isClosing(setting2.art))
|
||||
target?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
||||
@@ -7833,6 +7842,8 @@ function installSettingKeyboard(setting2) {
|
||||
close2();
|
||||
return;
|
||||
}
|
||||
if (!["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key))
|
||||
return;
|
||||
const row = target.closest(".art-setting-item");
|
||||
if (!row || target !== row && !target.matches('button,a[href],[role="button"]'))
|
||||
return;
|
||||
@@ -7841,8 +7852,7 @@ function installSettingKeyboard(setting2) {
|
||||
return;
|
||||
const currentItem = setting2.active?.find((item) => item.$item === row);
|
||||
if (!settingScopeActive(settingPanelScope(panel)) || currentItem && !settingScopeActive(settingScope(currentItem))) {
|
||||
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key))
|
||||
claimKey(event);
|
||||
claimKey(event);
|
||||
return;
|
||||
}
|
||||
const targets = settingFocusTargets(panel);
|
||||
|
||||
@@ -811,12 +811,18 @@ into HTML attributes; normal text and marker positions retain their behavior.
|
||||
controls.less allows groups to wrap inside narrow players. Individual control height
|
||||
continues to use --art-control-height; control/layout.ts observes total layout height
|
||||
through offsetHeight and records --art-controls-height for subtitle/panel offsets.
|
||||
ResizeObserver is preferred; the fallback combines core resize and MutationObserver.
|
||||
ResizeObserver is preferred; its initial notification measures after constructor
|
||||
DOM writes, so observing controls does not synchronously force layout during
|
||||
construction. The derived inline --art-controls-height is populated at that first
|
||||
layout notification, not promised synchronously to constructor/plugin callbacks.
|
||||
CSS retains its --art-control-height fallback until then. Explicit core resize
|
||||
events still update immediately. Without ResizeObserver, the initial measurement
|
||||
remains synchronous and subsequent updates combine core resize and MutationObserver.
|
||||
Both observers and subscriptions are owned by the bottom controls scope. Without
|
||||
ResizeObserver, unrelated parent CSS resizing is not independently detected until a
|
||||
core resize or control mutation occurs. The 640/320/240px tests include real 16:9
|
||||
heights and verify all buttons remain visible. This does not promise arbitrary custom
|
||||
minimum widths will fit. Narrow setting-tree/panel behavior remains CORE-14 work.
|
||||
minimum widths will fit. Setting panel bounds are handled by setting/layout.ts.
|
||||
|
||||
Use test/component-resources.test.js for ownership, reentry and quality settlement,
|
||||
test/types/components.ts for source contracts, and test/browser/components.spec.js
|
||||
@@ -1391,6 +1397,17 @@ task, since a microtask can observe body during the browser's focus transition.
|
||||
Pointer opening does not move focus into the panel. Existing click callbacks and
|
||||
selection/update generations remain the mutation boundaries.
|
||||
|
||||
Navigation reads a fresh target list for each relevant arrow/Home/End event; it
|
||||
does not cache availability across events. Focus restoration first checks whether
|
||||
the preferred element is still a focusable descendant of the active live panel.
|
||||
When it is, only that element is revalidated instead of scanning the panel again.
|
||||
An unavailable preferred element or a non-focusable wrapper still uses the normal
|
||||
descendant/selected-item fallback. Eligibility checks reject detached, disabled,
|
||||
inert and boxless elements before reading computed visibility. Unrelated keys
|
||||
never enumerate the panel. The long-panel browser case records query/style work
|
||||
and checks live hide/disable/restore/remove behavior; these operation counts are
|
||||
not constructor timings or evidence that the bundle-size risk is closed.
|
||||
|
||||
`accessibility/moved-focus.ts` restores a connected focused node when moving the
|
||||
player for web fullscreen makes the browser focus body. It never overrides focus
|
||||
deliberately assigned elsewhere and is not used during destruction cleanup. Mode
|
||||
|
||||
Vendored
+1
-1
@@ -67,4 +67,4 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var n=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(n(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(o=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),c=r(t);if("object"===s){if("object"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(c,"'"));Object.keys(e).forEach(function(r){var s=t[r],c=e[r],l=n.slice();l.push(r),i(s,c,l),a(s,c,l),o(s,c,l)})}if("array"===s){if("array"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(c,"'"));t.forEach(function(r,s){var c=t[s],l=e[s]||e[0],d=n.slice();d.push(s),i(c,l,d),a(c,l,d),o(c,l,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),r.exports);const o=t(n),i="5.4.1";function a(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(r)}):(document.head||document.documentElement).appendChild(r)),r.textContent=e}const s=globalThis.CUSTOM_USER_AGENT??("undefined"===typeof navigator?"":navigator.userAgent),c=/^(?:(?!chrome|android).)*safari/i.test(s),l=/iPad|iPhone|iPod/i.test(s)&&("undefined"===typeof window||!window.MSStream),d=l||s.includes("Macintosh")&&"undefined"!==typeof navigator&&navigator.maxTouchPoints>=1,u=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(s)||d,p="undefined"!==typeof window&&"undefined"!==typeof document;const f={properties:["audioTracks","autoplay","buffered","controller","controlLine truncated
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var n=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(n(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(o=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),c=r(t);if("object"===s){if("object"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(c,"'"));Object.keys(e).forEach(function(r){var s=t[r],c=e[r],l=n.slice();l.push(r),i(s,c,l),a(s,c,l),o(s,c,l)})}if("array"===s){if("array"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(c,"'"));t.forEach(function(r,s){var c=t[s],l=e[s]||e[0],d=n.slice();d.push(s),i(c,l,d),a(c,l,d),o(c,l,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),r.exports);const o=t(n),i="5.4.1";function a(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(r)}):(document.head||document.documentElement).appendChild(r)),r.textContent=e}const s=globalThis.CUSTOM_USER_AGENT??("undefined"===typeof navigator?"":navigator.userAgent),c=/^(?:(?!chrome|android).)*safari/i.test(s),l=/iPad|iPhone|iPod/i.test(s)&&("undefined"===typeof window||!window.MSStream),d=l||s.includes("Macintosh")&&"undefined"!==typeof navigator&&navigator.maxTouchPoints>=1,u=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(s)||d,p="undefined"!==typeof window&&"undefined"!==typeof document;const f={properties:["audioTracks","autoplay","buffered","controller","controlLine truncated
|
||||
+1
-1
@@ -67,4 +67,4 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.defineProperties,r=Object.getOwnPropertyDescriptors,n=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,a=(e,r,n)=>r in e?t(e,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[r]=n,s=(t,e)=>{for(var r in e||(e={}))o.call(e,r)&&a(t,r,e[r]);if(n)for(var r of n(e))i.call(e,r)&&a(t,r,e[r]);return t},l=(t,n)=>e(t,r(n)),c=(t,e,r)=>new Promise((n,o)=>{var i=t=>{try{s(r.next(t))}catch(e){o(e)}},a=t=>{try{s(r.throw(t))}catch(e){o(e)}},s=t=>t.done?n(t.value):Promise.resolve(t.value).then(i,a);s((r=r.apply(t,e)).next())});function d(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var u,p={exports:{}};var f=(u||(u=1,p.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(n(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(o=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=n.slice();c.push(r),i(s,l,c),a(s,l,c),o(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=n.slice();d.push(s),i(l,c,d),a(l,c,d),o(l,c,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),p.exports);const h=d(f),m="5.4.1";function v(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.apLine truncated
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.defineProperties,r=Object.getOwnPropertyDescriptors,n=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,a=(e,r,n)=>r in e?t(e,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[r]=n,s=(t,e)=>{for(var r in e||(e={}))o.call(e,r)&&a(t,r,e[r]);if(n)for(var r of n(e))i.call(e,r)&&a(t,r,e[r]);return t},l=(t,n)=>e(t,r(n)),c=(t,e,r)=>new Promise((n,o)=>{var i=t=>{try{s(r.next(t))}catch(e){o(e)}},a=t=>{try{s(r.throw(t))}catch(e){o(e)}},s=t=>t.done?n(t.value):Promise.resolve(t.value).then(i,a);s((r=r.apply(t,e)).next())});function d(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var u,p={exports:{}};var f=(u||(u=1,p.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(n(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(o=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=n.slice();c.push(r),i(s,l,c),a(s,l,c),o(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=n.slice();d.push(s),i(l,c,d),a(l,c,d),o(l,c,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),p.exports);const h=d(f),m="5.4.1";function v(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.apLine truncated
|
||||
+17
-7
@@ -2809,6 +2809,7 @@ function observeControlLayout(art) {
|
||||
scope.add(() => {
|
||||
observer.disconnect();
|
||||
});
|
||||
return;
|
||||
} else if (typeof MutationObserver !== "undefined") {
|
||||
const observer = new MutationObserver(update);
|
||||
observer.observe($controls, { childList: true, subtree: true, attributes: true, characterData: true });
|
||||
@@ -7733,12 +7734,22 @@ function flip(art) {
|
||||
}
|
||||
};
|
||||
}
|
||||
const focusSelector = '[tabindex="0"],button,input,select,textarea,a[href],[contenteditable]';
|
||||
function available(element) {
|
||||
if (!element.isConnected || element.matches(":disabled") || element.closest("[inert]") || element.getAttribute("aria-disabled") === "true" || !element.getClientRects().length)
|
||||
return false;
|
||||
const visibility = element.ownerDocument.defaultView?.getComputedStyle(element).visibility;
|
||||
return element.isConnected && !element.matches(":disabled") && !element.closest("[inert]") && element.getAttribute("aria-disabled") !== "true" && element.getClientRects().length > 0 && visibility !== "hidden" && visibility !== "collapse";
|
||||
return visibility !== "hidden" && visibility !== "collapse";
|
||||
}
|
||||
function settingFocusTargets(panel) {
|
||||
return Array.from(panel.querySelectorAll('[tabindex="0"],button,input,select,textarea,a[href],[contenteditable]')).filter((element) => element.getAttribute("tabindex") !== "-1" && available(element));
|
||||
return Array.from(panel.querySelectorAll(focusSelector)).filter((element) => element.getAttribute("tabindex") !== "-1" && available(element));
|
||||
}
|
||||
function selectSettingTarget(panel, preferred, last = false) {
|
||||
if (preferred && preferred !== panel && panel.contains(preferred) && preferred.matches(focusSelector) && preferred.getAttribute("tabindex") !== "-1" && available(preferred))
|
||||
return preferred;
|
||||
const targets = settingFocusTargets(panel);
|
||||
const selected = panel.querySelector(".art-setting-item.art-current");
|
||||
return targets.find((element) => preferred?.contains(element)) || (last ? targets[targets.length - 1] : targets.find((element) => selected?.contains(element)) || targets.find((element) => !element.classList.contains("art-setting-item-back")) || targets[0]);
|
||||
}
|
||||
function returnSettingFocus(setting2) {
|
||||
const { art, $parent } = setting2;
|
||||
@@ -7758,9 +7769,7 @@ function focusSettingPanel(setting2, preferred, last = false) {
|
||||
const panel = setting2.cache.get(setting2.active);
|
||||
if (!panel || !settingScopeActive(settingPanelScope(panel)))
|
||||
return;
|
||||
const targets = settingFocusTargets(panel);
|
||||
const selected = panel.querySelector(".art-setting-item.art-current");
|
||||
const target = targets.find((element) => preferred === element || preferred?.contains(element)) || (last ? targets[targets.length - 1] : targets.find((element) => selected?.contains(element)) || targets.find((element) => !element.classList.contains("art-setting-item-back")) || targets[0]);
|
||||
const target = selectSettingTarget(panel, preferred, last);
|
||||
target?.focus({ preventScroll: true });
|
||||
if (!isClosing(setting2.art))
|
||||
target?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
||||
@@ -7833,6 +7842,8 @@ function installSettingKeyboard(setting2) {
|
||||
close2();
|
||||
return;
|
||||
}
|
||||
if (!["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key))
|
||||
return;
|
||||
const row = target.closest(".art-setting-item");
|
||||
if (!row || target !== row && !target.matches('button,a[href],[role="button"]'))
|
||||
return;
|
||||
@@ -7841,8 +7852,7 @@ function installSettingKeyboard(setting2) {
|
||||
return;
|
||||
const currentItem = setting2.active?.find((item) => item.$item === row);
|
||||
if (!settingScopeActive(settingPanelScope(panel)) || currentItem && !settingScopeActive(settingScope(currentItem))) {
|
||||
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key))
|
||||
claimKey(event);
|
||||
claimKey(event);
|
||||
return;
|
||||
}
|
||||
const targets = settingFocusTargets(panel);
|
||||
|
||||
@@ -21,6 +21,8 @@ export function observeControlLayout(art: ControlHost): void {
|
||||
scope.add(() => {
|
||||
observer.disconnect()
|
||||
})
|
||||
// Its initial delivery measures after the remaining constructor DOM writes.
|
||||
return
|
||||
}
|
||||
else if (typeof MutationObserver !== 'undefined') {
|
||||
const observer = new MutationObserver(update)
|
||||
|
||||
@@ -4,16 +4,29 @@ import { isClosing } from '../lifecycle/instance'
|
||||
import { settingScopeActive } from './activity'
|
||||
import { settingPanelScope } from './panels'
|
||||
|
||||
const focusSelector = '[tabindex="0"],button,input,select,textarea,a[href],[contenteditable]'
|
||||
|
||||
function available(element: HTMLElement): boolean {
|
||||
if (!element.isConnected || element.matches(':disabled') || element.closest('[inert]') || element.getAttribute('aria-disabled') === 'true' || !element.getClientRects().length)
|
||||
return false
|
||||
const visibility = element.ownerDocument.defaultView?.getComputedStyle(element).visibility
|
||||
return element.isConnected && !element.matches(':disabled') && !element.closest('[inert]') && element.getAttribute('aria-disabled') !== 'true' && element.getClientRects().length > 0 && visibility !== 'hidden' && visibility !== 'collapse'
|
||||
return visibility !== 'hidden' && visibility !== 'collapse'
|
||||
}
|
||||
|
||||
export function settingFocusTargets(panel: HTMLElement): HTMLElement[] {
|
||||
return Array.from(panel.querySelectorAll<HTMLElement>('[tabindex="0"],button,input,select,textarea,a[href],[contenteditable]'))
|
||||
return Array.from(panel.querySelectorAll<HTMLElement>(focusSelector))
|
||||
.filter(element => element.getAttribute('tabindex') !== '-1' && available(element))
|
||||
}
|
||||
|
||||
function selectSettingTarget(panel: HTMLElement, preferred?: HTMLElement, last = false): HTMLElement | undefined {
|
||||
if (preferred && preferred !== panel && panel.contains(preferred) && preferred.matches(focusSelector) && preferred.getAttribute('tabindex') !== '-1' && available(preferred))
|
||||
return preferred
|
||||
const targets = settingFocusTargets(panel)
|
||||
const selected = panel.querySelector('.art-setting-item.art-current')
|
||||
return targets.find(element => preferred?.contains(element))
|
||||
|| (last ? targets[targets.length - 1] : targets.find(element => selected?.contains(element)) || targets.find(element => !element.classList.contains('art-setting-item-back')) || targets[0])
|
||||
}
|
||||
|
||||
export function returnSettingFocus(setting: SettingManager): void {
|
||||
const { art, $parent } = setting
|
||||
const player = art.template.$player
|
||||
@@ -33,10 +46,7 @@ export function focusSettingPanel(setting: SettingManager, preferred?: HTMLEleme
|
||||
const panel = setting.cache.get(setting.active)
|
||||
if (!panel || !settingScopeActive(settingPanelScope(panel)))
|
||||
return
|
||||
const targets = settingFocusTargets(panel)
|
||||
const selected = panel.querySelector('.art-setting-item.art-current')
|
||||
const target = targets.find(element => preferred === element || preferred?.contains(element))
|
||||
|| (last ? targets[targets.length - 1] : targets.find(element => selected?.contains(element)) || targets.find(element => !element.classList.contains('art-setting-item-back')) || targets[0])
|
||||
const target = selectSettingTarget(panel, preferred, last)
|
||||
target?.focus({ preventScroll: true })
|
||||
if (!isClosing(setting.art))
|
||||
target?.scrollIntoView({ block: 'nearest', inline: 'nearest' })
|
||||
|
||||
@@ -68,6 +68,8 @@ export function installSettingKeyboard(setting: SettingManager): void {
|
||||
close()
|
||||
return
|
||||
}
|
||||
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key))
|
||||
return
|
||||
const row = target.closest<HTMLElement>('.art-setting-item')
|
||||
if (!row || (target !== row && !target.matches('button,a[href],[role="button"]')))
|
||||
return
|
||||
@@ -76,8 +78,7 @@ export function installSettingKeyboard(setting: SettingManager): void {
|
||||
return
|
||||
const currentItem = setting.active?.find(item => item.$item === row)
|
||||
if (!settingScopeActive(settingPanelScope(panel)) || (currentItem && !settingScopeActive(settingScope(currentItem)))) {
|
||||
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key))
|
||||
claimKey(event)
|
||||
claimKey(event)
|
||||
return
|
||||
}
|
||||
const targets = settingFocusTargets(panel)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
| [执行门槛与分批交付](execution-gates.md) | 最小启动基线、早期试点、历史失败、设备门槛和分包准入 |
|
||||
| [兼容性契约](compatibility.md) | 不可静默改变的接口、行为、类型、样式与包分发约定 |
|
||||
| [核心设计](core-design.md) | 核心各模块的目标职责、迁移方式和生命周期设计 |
|
||||
| [核心阶段验收范围](core-acceptance.md) | 核心契约、已处置差异、性能风险与后续生态/发布门槛 |
|
||||
| [包迁移规范](packages.md) | 全生态统一流程、组合矩阵及包特有风险 |
|
||||
| [测试与验收](testing.md) | 测试层次、浏览器/消费者矩阵、证据和回归门槛 |
|
||||
| [已落实的测试可靠性规则](test-reliability.md) | 历史失败、候选回归、设备缺口、精确异常及等待/重试的执行规则 |
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -33,3 +33,26 @@ scripts/build-analysis.mjs 负责报告;projects.js 解析只对 build 有效
|
||||
目录迁移会改变分组,例如 display 从旧 player 中移出,不能只按新目录的正增量计算功能成本。当前最大源码模块是 setting/render.ts,报告中的模块 code 为 11933 UTF-8 字节;Rollup 记录的渲染长度为 11598 代码单元。后续审查应结合它的失败回滚、更新冲突、挂载回调和焦点契约,不能为减小体积删除已有回归所保护的路径。
|
||||
|
||||
没有发现 TS、c8、Playwright 或 Vite 运行时进入核心产物。此轮仅完成归因工具和证据,尚未优化生产行为;ENG-PERF-01/02 保持 open,CORE-22 继续审查可避免的开销和构造耗时。不要把解释了来源等同接受了全部成本。
|
||||
|
||||
## CORE-22 构造/销毁热点定位
|
||||
|
||||
使用固定 Node 和已有 Playwright,运行 `node refactor/scripts/profile-core-layout.mjs`。
|
||||
它启动自身拥有的 8086 本地测试服务和 headless Chromium,加载当前源码的未压缩构建,
|
||||
先预热 10 次,再对 60 次构造、真实本地视频 ready/play/首个解码帧及销毁进行 CPU 采样。
|
||||
产物、manifest、原始 cpuprofile、分组摘要及服务日志保存在 refactor/.cache/core22-profile-*。
|
||||
脚本在 finally 中关闭浏览器和自身服务,没有使用连接的 Chrome,也没有新依赖或修改正式性能夹具。
|
||||
|
||||
只有调用栈位于 constructProfilePlayer / destroyProfilePlayer 内的样本才计入对应分组;
|
||||
其余样本独立统计。请求采样间隔 100 微秒不表示平台实际达到该分辨率,采样空隙和原生调用
|
||||
可能计入 JS 调用方。摘要中的耗时是带分析器的诊断数据,不得拿来替代未插桩的新旧配对门槛。
|
||||
|
||||
两次修改前采样均把 control/layout.ts 的初始 update 定位为构造热点。它在控件 DOM 创建后
|
||||
同步读取 offsetHeight,之后还会收到 ResizeObserver 的初始通知。现在原生 observer 路径
|
||||
使用首次布局通知完成测量,避免在后续构造 DOM 写入前额外强制布局;显式 resize 仍同步,
|
||||
缺少 ResizeObserver 时仍立即测量。新增浏览器用例先复现一次同步读取,修改后三引擎为零,
|
||||
并检查首轮渲染的窄屏高度、字幕偏移、fallback 和首次通知前销毁。修改后采样不再在构造栈
|
||||
出现该 update 热点,这一事实不等于整个播放器已经达到性能发布门槛。
|
||||
|
||||
销毁采样主要落在原生 video.load() 和模板 DOM 移除,资源作用域清理占比较低。
|
||||
保留 reset 的 removeAttribute('src') / load 顺序:不能为了缩短同步计时留下媒体资源,
|
||||
也不能把清理挪到计时窗口外制造收益。原始计时波动仍须通过正式配对与后续 MOD-03/REVIEW-01 复核。
|
||||
@@ -1,6 +1,6 @@
|
||||
# CORE-22 核心阶段完整验收
|
||||
|
||||
起点为 ENG-08 提交 4a9a26d4,工作区干净,CORE-21/23、PILOT-01、ENG-08/10 前置均已完成。本任务尚在实施,不能视为核心或 npm 发布验收完成。
|
||||
首轮起点为 ENG-08 提交 4a9a26d4;ENG-11 提交 5efc7f76 后恢复核心验收。下文保留实施过程,最终阶段结论见文末。核心阶段不等于全项目或 npm 发布验收。
|
||||
|
||||
首先处理 ENG-PERF-01/02:确认核心体积增长的源码/构建来源,保留旧版与候选真实字节和相同格式;对间歇构造耗时继续同环境配对取证。不得通过提高阈值、删除兼容功能或忽略采样差异让报告通过。
|
||||
|
||||
@@ -13,3 +13,62 @@
|
||||
可复用的正式构建分析从本任务拆为 ENG-11,下游依赖已更新;在该前置交付前本任务暂回 todo,保留初步工作,不重复宣称工具实现。当前三格式产物没有变化。详见 [归因说明](../build-analysis.md) 和 [完整数据](../baselines/bundle-attribution.json)。
|
||||
|
||||
同工具链下,起点源码构建现代包 gzip 为 35703 字节,当前为 58374 字节;主要增量仍来自实现而非换编译工具。已提交产物对照中,键盘/焦点、设置树和显示模式三阶段合计约占净 gzip 增量六成。接下来审查这些模块的可避免开销与构造耗时,不删除已有回归保护的兼容、清理和错误恢复路径。
|
||||
|
||||
## 设置导航:减少重复 DOM 工作
|
||||
|
||||
ENG-11 提交 5efc7f76 后恢复 doing。本阶段修改两个生产 TS 模块,不改变公共入口、回调、点击语义或声明。`keyboard.ts` 在读取条目和布局前排除无关按键;`keyboard-focus.ts` 为仍在活动面板内且可聚焦的 preferred 元素直接复核可用性,免去第二次全表扫描。若元素失效或只是包装节点,仍走原来的后代/选中项回退路径。每次按键仍重新读取条目,不跨事件缓存。断开、禁用、inert、无布局盒的元素先被排除,再读取计算样式。
|
||||
|
||||
40 个原生按钮的真实 Chromium 修改前证据显示:正常向下导航调用两次 querySelectorAll、80 次 getComputedStyle;修改后三引擎均为一次扫描、41 次样式读取。无关字符键从一次扫描/39 次样式读取变为零。浏览器测试还验证即时隐藏、inert、原生 disabled、tabindex=-1、恢复和公开 remove 后的实际焦点。记录的是操作次数,不据此估算毫秒收益。原有嵌套面板、更新失败恢复、原生范围、暂停作用域等回归保留。
|
||||
|
||||
构建后的现代包为 208030 raw / 58429 gzip9 字节;与 ENG-08 的 207875 / 58374 相比增加 155 / 55 字节。legacy 为 212211 / 59683,ESM 为 361484 / 77137。该小幅代码成本换取避免重复扫描,不能算作体积优化;ENG-PERF-01 仍 open。架构说明同步到包内 ARCHITECTURE.md。产物全部由正式 build 和 build:i18n 生成,没有手改产物或公开声明。
|
||||
|
||||
新安装包 run-UElxNR 的三引擎配对均完整,54 次候选资源清理探针通过。Chromium 第一组 core/chapter 销毁分别增加 3.1/2.5 ms,第三组 chapter 构造增加 2.8 ms;WebKit 第一组 chapter 销毁从 71 到 98 ms,其他组未重复该销毁信号;Firefox 本轮没有计时信号。固定计时夹具使用默认关闭的设置面板,不能把这些波动归因于本次导航路径。原始样本保留,ENG-PERF-02 扩展为构造及销毁计时待复核,门槛不变。
|
||||
|
||||
阶段证据见 [设置导航与配对报告](../baselines/core-setting-navigation-partial.json)。本阶段不是 CORE-22 完整验收;完整安装包浏览器矩阵、核心差异结论以及性能归因仍未完成。不复用上一任务的全量结果宣称当前源码已全部通过。本任务完成时再统一独立提交;当前没有新的完成任务,也没有 push/tag/publish/merge。
|
||||
|
||||
验证:完整本地 CI 476 项(440 单元、11 工程、25 基线),249 个生产 TS 严格检查通过;源码构建三引擎设置/网页全屏/快捷键组合 327 项通过。新安装包 34 个运行时、5 组旧类型、8 组精确类型通过;107 个工作区 dist/types 文件以及三份 docs/compiled 核心产物与安装包相同。三个导入检查通过,安装包 modern 与 legacy 的设置键盘专项各 36 项通过,均无失败、重试或跳过。未为本次小改动重复宣称覆盖率测量或远端 CI 成果。
|
||||
|
||||
## 构造布局测量与最终核心阶段验收
|
||||
|
||||
两次真实 Chromium CPU 采样将默认构造热点定位到 control/layout.ts 的同步 offsetHeight 读取。
|
||||
支持 ResizeObserver 时现在使用其首次布局通知,在构造剩余 DOM 写入后测量;旧环境立即测量,
|
||||
显式 resize 仍同步。修改前的原生 observer 探针稳定记录一次同步读取,修改后为零,三引擎
|
||||
15 项初始布局、fallback、窄屏/字幕位置和首次通知前销毁检查通过。最初的 fallback 测试在字幕
|
||||
CSS 过渡未结束时断言最终位置,已改为等待实际状态,保留原失败报告,没有改变位置门槛。
|
||||
|
||||
两次修改后 CPU 采样不再在构造栈出现该 update 热点。诊断脚本已保存到
|
||||
refactor/scripts/profile-core-layout.mjs,明确限定为 Chromium 未压缩源码构建的带分析器定位,
|
||||
不是新旧性能门槛。Chromium 销毁采样主要落在原生 load 和 DOM 移除;保留释放顺序,不能将其
|
||||
延后到计时区间外。WebKit 的销毁信号不能仅用 Chromium 调用栈解释。
|
||||
|
||||
最终安装包 run-vynq7l 的结果如下,详见 [最终冻结证据](../baselines/core-acceptance-validation.json):
|
||||
|
||||
| 验收项 | 结果与范围 |
|
||||
| --- | --- |
|
||||
| 源码与声明 | 自有核心源码 TS 迁移完成,249 个生产 TS(含 chapter)严格检查,37 个生成核心声明无漂移;唯一保留的 src JS 是有来源记录的 screenfull |
|
||||
| 本地 CI | 476 项:440 单元、11 工程、25 基线;新增诊断脚本另行 lint 和实际执行通过 |
|
||||
| 安装包消费 | 34 运行时、5 组旧类型、8 组精确类型通过;107 个工作区分发/声明文件及三份 docs 核心产物与安装包匹配,三个导入检查通过 |
|
||||
| 完整浏览器矩阵 | modern 1761、legacy 1761;每种格式每引擎 587 项,零失败/重试/跳过;真实媒体、核心 Monaco、已有 chapter 四组合和自定义插件均包含在当前套件 |
|
||||
| 最新覆盖率 | 224 运行时文件、26 明确排除,四个关键生命周期/操作文件的行/分支/函数均 100%;全体 Node 行 60.90%、分支 86.55%、函数 66.62%,不与浏览器数量合并为覆盖率 |
|
||||
| 性能/资源 | 每引擎三组正式配对均完成,54 次候选资源探针通过;本轮三引擎均无构造计时信号,Chromium/Firefox 无任何计时信号,WebKit 两组销毁仍超阈值 |
|
||||
| 分发体积 | 现代 208037 raw / 58434 gzip9 / 53372 Brotli6;legacy 212218 / 59686 / 54412;ESM 361496 / 77141 / 70030。相较 ENG-08,现代增加 60 gzip 字节,不能称作体积缩减 |
|
||||
|
||||
SDK-12 的实际同步集成范围已补齐:固定 option-validator 2.0.6 与文件哈希,真实新旧安装包
|
||||
验证正常/非法配置、首错、挂载前失败、容器复用、媒体 ready/customType 和销毁;纯同步依赖本身
|
||||
不创建网络、切源任务或设备资源。它的直接测试范围与核心完整矩阵对应,不套用外部媒体 SDK 的
|
||||
设备/网络门槛,也不声称已经审核整个第三方项目。
|
||||
|
||||
CORE-22 完成核心结构、兼容差异处置和当前固定消费者的自动化阶段。具体 API-01 至 API-12
|
||||
入口及旧声明/切源/生命周期差异的结论见 [核心验收范围](../core-acceptance.md)。现有方法、类型
|
||||
兼容入口、事件/Promise、DOM/CSS hooks 和包分发回归通过;新派生控件高度值的首次测量时机有
|
||||
明确说明和浏览器验证。没有把 chapter 或受控能力场景当作所有插件/物理设备通过。
|
||||
|
||||
ENG-PERF-01/02 保持 open,由 MOD-03 和 REVIEW-01 继续复核:本轮 WebKit 第一组 chapter
|
||||
销毁增加 44 ms,第二组 core 增加 30 ms,第三组未触发;保存全部样本,不将其归为已证明的主机
|
||||
噪声。体积增长虽已归因,仍需后续性能与发布复盘处理。此阶段交付不放宽阈值、不豁免发布,也
|
||||
不删除后续性能依赖。全插件/外部 SDK、EX/SITE、物理设备、完整 CI/CD、各包 major 和三轮复盘
|
||||
仍由原任务负责;完整目标继续推进。
|
||||
|
||||
本任务以独立本地 commit 交付,包含三处生产模块改进、回归、正式生成产物、诊断脚本、风险和
|
||||
文档/状态。回退时整体 revert 本任务 commit,保留其父提交 5efc7f76 的运行时/产物对应关系,
|
||||
不要只回退 TS 或只回退 dist。提交 SHA 在提交后由 Git 核对;没有 push/tag/publish/merge。
|
||||
@@ -0,0 +1,59 @@
|
||||
# 核心阶段验收范围
|
||||
|
||||
CORE-22 汇总核心自有源码、公开声明、生成产物和当前固定消费者的自动化证据。
|
||||
本文件不能作为全部生态包或 npm 发布放行。实际执行结果、源码/产物指纹和剩余事项见
|
||||
[交付记录](changes/2026-09-11-CORE-22-core-acceptance.md);任务状态以 tasks.json 为准。
|
||||
|
||||
## 契约与回归入口
|
||||
|
||||
| 契约 | 本阶段检查与可重跑入口 | 明确边界 |
|
||||
| --- | --- | --- |
|
||||
| API-01 构造/配置 | options、initialization、entry 单元测试;浏览器 options、initialization、template-resources;实际 option-validator 2.0.6 | 保留 getter 读取、默认值、容器身份、错误种类/首错、挂载前失败与回滚;非浏览器构造仍抛错 |
|
||||
| API-02/03 属性与返回 | playback-properties、facade-properties、public-behavior;浏览器 declarations、runtime-types、initialization;安装 runtime.cjs | 保留描述符、绑定/身份、同步返回及 Promise;只写命令不虚构 getter |
|
||||
| API-04 事件 | emitter、media-events、listener-registry、event-scheduling、source 及对应浏览器用例 | 比较实际语义顺序与 payload,不冻结浏览器媒体时钟或平台允许的原生事件差异 |
|
||||
| API-05 生命周期 | resource-scope、instance-lifecycle、initialization;全部浏览器销毁/回滚用例;正式性能资源探针 | 包含多实例、失败构造、连续操作及延迟回调;不等于原生 heap/GPU 泄漏认证 |
|
||||
| API-06/07 扩展 | plugins、component/setting 的正常/异常/重入用例;published/current chapter 四组合;安装旧插件类型消费者 | 可控自定义插件和 chapter 已有固定发布基线;其余插件的实际 SDK 组合由各包 01/05 步补齐 |
|
||||
| API-08 DOM/CSS | components、setting 系列、display 系列、字幕/缩略图/截图、accessibility 系列 | 保留原 hooks、鼠标/点击入口,新增键盘和焦点行为;UA/触摸模拟不代表物理设备验收 |
|
||||
| API-09/10 分发/浏览器 | 隔离 tarball 安装、UMD/AMD/global/CJS/ESM/legacy/i18n/runtime 入口,modern/legacy 三引擎完整矩阵 | es2015 语法不为 WebCodecs/PiP 等能力提供 polyfill;正式目标版本由 REL-09/02 再验 |
|
||||
| API-11 类型 | check:types、249 个生产 TS 文件严格检查、五组旧消费者、八组精确消费者、真实 Monaco worker 用例 | 旧根入口保留 TS 4.3.5 及历史可编译赋值;可选 runtime 入口提供准确类型且复用原 JS 构造器 |
|
||||
| API-12 持久状态/资源 | storage、source、subtitle、thumbnails、gesture 单元与浏览器回归 | 保留存储格式和调用方 URL 所有权;远端 SDK/服务协议仍须由对应包验证 |
|
||||
|
||||
统一命令是 yarn ci:check、yarn test:package、指定该安装包 ARTPLAYER_BROWSER_ARTIFACTS 的
|
||||
yarn test:browser,以及同一安装包的 yarn test:performance。legacy 必须映射安装后的
|
||||
*.legacy.js,不能让测试服务回退到源码。原始冻结报告中的历史结果不自动覆盖新源码。
|
||||
|
||||
## 已处置的兼容差异
|
||||
|
||||
- 旧切源 Promise 被替代或销毁后以 undefined 兑现,避免新增 AbortError;当前源的真实失败仍拒绝。
|
||||
Promise 结束不表示该源曾激活,调用方继续通过 URL/媒体事件确认。见 CORE-09 交付记录。
|
||||
- 同步插件注册仍同步可见;销毁后延迟结果不再写回实例。重复销毁、失败挂载、移除后的监听器和
|
||||
过期异步 UI 更新属于已复现缺陷修正,不是新公开调用形式。
|
||||
- root 类型保留历史接受范围,可选 artplayer/runtime 精确描述真实返回、访问器和事件数组。
|
||||
不使用交叉返回伪装矛盾,也不包装运行时 Promise。见 core-public-types.md 与包内 types/COMPATIBILITY.md。
|
||||
- 新键盘/焦点和窄屏布局保持原类名、模板、点击回调及普通快捷键。设置树的资源归属、更新回滚和
|
||||
显示模式恢复保护真实重入/异常路径,不为压缩体积删除这些已被回归用例约束的行为。
|
||||
- --art-controls-height 是此次重构增加的派生布局值。原生 ResizeObserver 下改在首次布局通知填充,
|
||||
CSS 继续使用单行高度 fallback;旧环境及显式 resize 仍同步测量。它不是对历史公开 API 的删改。
|
||||
|
||||
## 性能处置和继续实施条件
|
||||
|
||||
模块归因确认主要体积增长来自键盘/焦点、设置树和显示模式的能力及异常路径,TS 与测试工具没有
|
||||
进入运行时包。CORE-22 已移除设置导航的重复扫描和构造中的一次强制控件布局;不夸大其收益,
|
||||
也不将原始 gzip/计时阈值上调。native load/DOM 销毁成本不通过异步延迟清理转移出计时区间。
|
||||
|
||||
ENG-PERF-01/02 的后续核心性能工作归 MOD-03,并在 REVIEW-01 重新审查。它们保持 open,
|
||||
表示不能批准发布,也不能宣称性能已经全面达标;保留相同设备媒体三组配对和原始样本。
|
||||
核心结构及兼容自动化阶段的交付不删除这些任务、风险或后续依赖。
|
||||
|
||||
## 仍需全项目完成的范围
|
||||
|
||||
- 各 PKG-*-01/05/06:完整发布基线、支持范围旧核心/新插件、新核心/旧插件、SDK/组合/分发;
|
||||
chapter 的本轮四组合不能代替所有包,也不能代替它自己的最终进度/质量/全屏组合任务。
|
||||
- EX-01/02/03 与 SITE:React/Vue、全部 demo/在线编辑器及远端资源;核心 Monaco 场景不是全站验收。
|
||||
- REL-03、REVIEW-02:物理 Apple/Android、原生 PiP/AirPlay/方向/触摸、外部 SDK、codec/模型及压力环境。
|
||||
- ENG/MOD 后续任务:完整 GitHub CI/CD、覆盖/提交索引、剩余脚本、性能优化及工具链采用决策。
|
||||
- REL-09/02 和 REVIEW-01/02/03:逐包下一个 major、最终 tarball、三轮全项目复盘及发布批准。
|
||||
|
||||
包内 ARCHITECTURE.md 是实际源码地图;本目录保存迁移与验收记录。
|
||||
核心 src 下唯一保留的 JS 为有来源记录的第三方 screenfull 文件,不能冒充待迁移的自有实现,
|
||||
也不能把本阶段完成理解为全部 22 包已迁移。
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 215 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 155 / doing 0 / blocked 0 / done 60 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 154 / doing 0 / blocked 0 / done 61 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
| CORE-20 | artplayer<br>收敛核心入口与依赖方向 | CORE-14, CORE-15, CORE-18, CORE-19 | Artplayer 门面、初始化依赖与最终目录 | 所有核心自有源码迁移,公开描述符/静态接口/事件差分通过 | H | done |
|
||||
| CORE-21 | artplayer<br>生成核心声明并校验包内容 | CORE-20, ENG-07 | 由 TS 生成的既有入口声明与三种发布产物 | 内部类型不泄漏,旧 JS/TS 消费者与 i18n 通过;逐项完成 CORE-07 留下的 BASE-TYPE-04/05/07 声明方案,旧合法消费者与实际返回分别留证;不能以虚假的交叉返回类型或改变运行时来掩盖冲突,未决项阻止核心声明出口;处理 CORE-10 / BASE-TYPE-08 的 setter-only 属性虚构 getter 声明,并复核 PlaybackControls 精确视图与旧 toggle 声明共存策略 | H | done |
|
||||
| CORE-23 | artplayer<br>检查并补齐键盘、焦点与可访问名称 | CORE-13, CORE-14, CORE-17, ENG-05, BASE-04 | 主要控件/设置/模式退出的键盘与焦点回归、名称和字幕可用性检查及必要兼容修正 | 保持旧快捷键和 DOM/CSS 钩子;真实浏览器验证,不以静态属性检查代替交互;处理 BASE-DOM-01 的主要控件 Tab 不可达,保留既有名称与快捷键 | H | done |
|
||||
| CORE-22 | artplayer<br>核心阶段完整验收 | CORE-21, CORE-23, PILOT-01, ENG-08, ENG-10, ENG-11 | 核心与旧插件的可自动化完整回归、资源/性能及明确外部验证缺口 | 核心自动化和公开差异处置通过;真实环境缺口链接包集成/REL 门槛,阶段完成不代表可公开发布 | H | todo |
|
||||
| CORE-22 | artplayer<br>核心阶段完整验收 | CORE-21, CORE-23, PILOT-01, ENG-08, ENG-10, ENG-11 | 核心与旧插件的可自动化完整回归、资源/性能及明确外部验证缺口 | 核心自动化和公开差异处置通过;真实环境缺口链接包集成/REL 门槛,阶段完成不代表可公开发布 | H | done |
|
||||
|
||||
## 5 包迁移:artplayer-plugin-chapter
|
||||
|
||||
@@ -460,7 +460,7 @@
|
||||
- CORE-20: [记录](changes/2026-09-11-CORE-20-entry.md) [记录](baselines/entry-validation.json)
|
||||
- CORE-21: [记录](changes/2026-09-11-CORE-21-public-declarations.md) [记录](baselines/core-declarations-validation.json) [记录](core-public-types.md) [记录](typechecking.md)
|
||||
- CORE-23: [记录](changes/2026-09-11-CORE-23-keyboard-focus.md) [记录](baselines/keyboard-validation.json)
|
||||
- CORE-22: [记录](changes/2026-09-11-CORE-22-core-acceptance.md) 初步体积归因已执行;先独立交付新增前置 ENG-11 分析工具,再恢复核心完整验收。
|
||||
- CORE-22: [记录](changes/2026-09-11-CORE-22-core-acceptance.md) [记录](baselines/core-setting-navigation-partial.json) [记录](core-acceptance.md) [记录](baselines/core-acceptance-validation.json)
|
||||
- PKG-CHAPTER-01: [记录](changes/2026-09-10-PKG-CHAPTER-01-contract.md) [记录](baselines/chapter-contract.md)
|
||||
- PKG-CHAPTER-02: [记录](changes/2026-09-10-PKG-CHAPTER-02-tests.md) [记录](baselines/chapter-validation.json)
|
||||
- PKG-CHAPTER-03: [记录](changes/2026-09-10-PKG-CHAPTER-03-typescript-modules.md) [记录](baselines/chapter-migration-validation.json)
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# 进度与证据
|
||||
|
||||
## 最新完成:CORE-22 核心结构与兼容自动化验收
|
||||
|
||||
核心阶段完成,完整目标继续覆盖全部 22 包。本任务在设置导航优化之外,按实际 CPU 热点移除了原生 ResizeObserver 环境中构造时的额外控件布局读取;首次布局结果、fallback、窄屏字幕与销毁都有三引擎回归。三个生产 TS 模块、生成产物、诊断脚本及包内架构说明同步交付。
|
||||
|
||||
最终安装包 run-vynq7l:modern/legacy 全量各 1761 项(每引擎 587 项)通过,零失败/重试/跳过;34 个运行时、5 组旧类型、8 组精确类型通过,107 个分发/声明文件与三份 docs 核心产物匹配。完整 CI 476 项、249 个生产 TS 严格检查和三个导入检查通过。最新 Node 覆盖率四个关键文件的行/分支/函数均 100%,总体行 60.90%;浏览器数量不合并为覆盖率。
|
||||
|
||||
新三引擎三组配对均完整,54 次候选资源清理通过,本轮没有构造计时信号;WebKit 第一组 chapter 销毁增加 44 ms、第二组 core 增加 30 ms,仍需复核。现代 gzip 58434 字节,体积风险保持开放。ENG-PERF-01/02 由 MOD-03/REVIEW-01 接续,不改变历史阈值或豁免发布。SDK-12 的同步 validator 集成范围已补齐并关闭;其余插件、外部 SDK、真机、全站/消费者、完整 CI/CD、逐包 major 和三轮复盘仍待原任务完成。
|
||||
|
||||
见 [核心验收范围](core-acceptance.md)、[最终证据](baselines/core-acceptance-validation.json) 和 [实施记录](changes/2026-09-11-CORE-22-core-acceptance.md)。当前 61 done、154 todo;本任务独立本地 commit,提交后以 Git 核对。下一步 PKG-HLS-01,先固定 HLS control 的发布契约,再迁移与验证该插件;不把当前 chapter 四组合当作全部旧插件已通过。没有 push/tag/publish/merge。下方记录保留各阶段当时的状态。
|
||||
|
||||
## 当前实施:CORE-22 设置导航优化与新安装包复核
|
||||
|
||||
已修改设置键盘与焦点 TS 模块:无关按键不再扫描面板;选定目标实时复核后直接聚焦,保留失效目标的原回退。40 按钮探针的正常导航从两次扫描/80 次样式读取降至一次/41 次;三引擎结果一致,动态隐藏/inert/禁用/tabindex、恢复及移除后的实际焦点通过。没有跨事件缓存或公开 API/声明变更。
|
||||
|
||||
完整本地 CI 476 项、249 个生产 TS 文件通过;设置/布局/所有权/交互/网页全屏/快捷键三引擎组合 327 项通过。新安装包 run-UElxNR 通过 34 运行时、5 组旧类型、8 组精确类型,107 个工作区分发/声明文件和三份 docs 产物与安装包匹配;三个导入检查通过。安装包 modern/legacy 的设置键盘专项各 36 项通过,无失败、重试或跳过。这些是专项证据,不是当前产物的全量浏览器验收。
|
||||
|
||||
现代包 gzip 为 58429 字节,比 ENG-08 增加 55 字节;减少重复 DOM 工作不等于体积优化。新三引擎三组配对均完整,54 次候选资源清理通过,但 Chromium 个别组有构造/销毁信号,WebKit 一组有销毁信号;Firefox 本轮无计时信号。固定计时夹具默认关闭设置面板,不能用这组计时解释导航改动的收益。已保留样本,并将 ENG-PERF-02 扩展为构造及销毁计时待复核;两个性能风险继续 open。
|
||||
|
||||
详见 [阶段冻结证据](baselines/core-setting-navigation-partial.json) 与 [实施记录](changes/2026-09-11-CORE-22-core-acceptance.md)。下一步继续默认构造/销毁路径的实际开销归因、设置树/显示模式的可避免体积审查,再完成核心公开差异与新安装包全量矩阵。CORE-22 仍 doing,60 done、1 doing、154 todo;本任务尚未验收完成,无新的完成 commit,未推送或发布。
|
||||
|
||||
## 当前实施:CORE-22 恢复核心完整验收
|
||||
|
||||
ENG-11 提交 5efc7f76 已核实,三个格式的核心产物字节未变。CORE-22 的新增前置已完成,恢复 doing;60 done、1 doing、154 todo。接下来审查设置树/显示模式开销、间歇构造耗时和核心完整兼容性,现有两项性能风险仍 open。
|
||||
|
||||
## 最新完成:ENG-11 构建归因;CORE-22 保留待验收
|
||||
|
||||
新增正式构建 --analyze 开关,三个格式的产物字节不变,报告仅写本地缓存。实际构建夹具覆盖 TS/JS/Less/SVG/worker,并验证分析前后内容和清单一致。476 项 CI 通过,最终报告计量修正后另跑 lint 与实际构建测试通过;当前核心三格式均与 ENG-08 指纹一致。
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
| ID | 状态 / 证据等级 | 条目 | 后续任务 |
|
||||
| --- | --- | --- | --- |
|
||||
| ENG-PERF-01 | open / 源码/产物事实 | Candidate core bundles exceed the BASE-06 size review threshold in all three formats | CORE-22, REVIEW-01 |
|
||||
| ENG-PERF-02 | open / 待取证 | Intermittent constructor timing signals require follow-up paired measurement | CORE-22, REVIEW-01 |
|
||||
| ENG-PERF-01 | open / 源码/产物事实 | Candidate core bundles exceed the BASE-06 size review threshold in all three formats | CORE-22, MOD-03, REVIEW-01 |
|
||||
| ENG-PERF-02 | open / 待取证 | Intermittent constructor and destruction timing signals require follow-up paired measurement | CORE-22, MOD-03, REVIEW-01 |
|
||||
| BASE-LIFE-06 | resolved / 已复现 | Published switch leaves an unhandled rejection and an unsettled promise when resume fails | CORE-09 |
|
||||
| BASE-LIFE-01 | resolved / 已复现 | Superseded switch resolves along with the latest source | CORE-09 |
|
||||
| BASE-LIFE-02 | resolved / 已复现 | Switch remains unsettled during the bounded observation after destroy | CORE-09 |
|
||||
@@ -44,7 +44,7 @@
|
||||
| SDK-09 | open / 待取证 | jassub-worker-wasm-fonts 实际集成验证范围 | PKG-JASSUB-01, PKG-JASSUB-05 |
|
||||
| SDK-10 | open / 待取证 | mediabunny 实际集成验证范围 | PKG-MB-01, PKG-MB-09 |
|
||||
| SDK-11 | open / 待取证 | asr-caller-service 实际集成验证范围 | PKG-ASR-01, PKG-ASR-05, EX-03 |
|
||||
| SDK-12 | open / 待取证 | option-validator 实际集成验证范围 | CORE-01, CORE-07 |
|
||||
| SDK-12 | resolved / 源码/产物事实 | option-validator 实际集成验证范围 | CORE-01, CORE-07, CORE-22 |
|
||||
| BUILD-AMD-01 | resolved / 已复现 | AMD 全局导出补丁误将固定参数 t 当作全局对象 | ENG-06 |
|
||||
| UTIL-MERGE-01 | resolved / 已复现 | mergeDeep lets a JSON __proto__ key replace the result prototype | CORE-01 |
|
||||
| UTIL-DOWNLOAD-01 | resolved / 源码/产物事实 | Temporary download anchor remains if click throws | CORE-01 |
|
||||
|
||||
+32
-11
@@ -12,34 +12,44 @@
|
||||
"status": "open",
|
||||
"owners": [
|
||||
"CORE-22",
|
||||
"MOD-03",
|
||||
"REVIEW-01"
|
||||
],
|
||||
"evidence": [
|
||||
"refactor/coverage-performance.md",
|
||||
"refactor/baselines/quality-validation.json",
|
||||
"refactor/changes/2026-09-11-ENG-08-quality-reports.md"
|
||||
"refactor/changes/2026-09-11-ENG-08-quality-reports.md",
|
||||
"refactor/baselines/core-setting-navigation-partial.json",
|
||||
"refactor/changes/2026-09-11-CORE-22-core-acceptance.md",
|
||||
"refactor/core-acceptance.md",
|
||||
"refactor/baselines/core-acceptance-validation.json"
|
||||
],
|
||||
"compatibleResolution": "Attribute the growth to actual modules and build output, remove avoidable overhead while preserving APIs, and document any justified remaining cost against matching published entries.",
|
||||
"closureCriteria": "Fresh isolated artifacts have reproducible raw/gzip9/Brotli6 results and an explicit size review; do not raise or overwrite the historical threshold to pass.",
|
||||
"workspaceState": "First ENG-08 modern core measurement: 134037 to 207875 raw bytes, 35486 to 58374 gzip9, 32533 to 53326 Brotli6. Chapter remains below the combined relative/absolute review threshold. This is a review requirement, not a release approval."
|
||||
"workspaceState": "First ENG-08 modern core measurement: 134037 to 207875 raw bytes, 35486 to 58374 gzip9, 32533 to 53326 Brotli6. Chapter remains below the combined relative/absolute review threshold. This is a review requirement, not a release approval. CORE-22 navigation phase: 208030 raw / 58429 gzip9 / 53361 Brotli6 modern bytes. Preferred-target revalidation removes a second panel scan at a cost of 55 gzip bytes over ENG-08; it does not resolve total bundle growth. Final CORE-22: modern 208037 raw / 58434 gzip9 / 53372 Brotli6; legacy 212218 / 59686 / 54412; ESM 361496 / 77141 / 70030. Navigation and initial-layout work were reduced, but total size remains above the historical threshold. MOD-03 and REVIEW-01 must review this before release."
|
||||
},
|
||||
{
|
||||
"id": "ENG-PERF-02",
|
||||
"title": "Intermittent constructor timing signals require follow-up paired measurement",
|
||||
"title": "Intermittent constructor and destruction timing signals require follow-up paired measurement",
|
||||
"confirmation": "unverified",
|
||||
"status": "open",
|
||||
"owners": [
|
||||
"CORE-22",
|
||||
"MOD-03",
|
||||
"REVIEW-01"
|
||||
],
|
||||
"evidence": [
|
||||
"refactor/coverage-performance.md",
|
||||
"refactor/baselines/quality-validation.json",
|
||||
"refactor/changes/2026-09-11-ENG-08-quality-reports.md"
|
||||
"refactor/changes/2026-09-11-ENG-08-quality-reports.md",
|
||||
"refactor/baselines/core-setting-navigation-partial.json",
|
||||
"refactor/changes/2026-09-11-CORE-22-core-acceptance.md",
|
||||
"refactor/core-acceptance.md",
|
||||
"refactor/baselines/core-acceptance-validation.json"
|
||||
],
|
||||
"compatibleResolution": "Repeat paired measurements on the same environment, distinguish initialization work from host jitter, and optimize only after identifying a reproducible cost.",
|
||||
"compatibleResolution": "Repeat paired measurements on the same environment, distinguish initialization and teardown work from host jitter, and optimize only after identifying a reproducible cost.",
|
||||
"closureCriteria": "Explain repeat-group timing signals with retained samples and source fingerprints; do not infer a regression or improvement from a single group or expand timing allowances.",
|
||||
"workspaceState": "First ENG-08 run: Firefox group 2 had core/chapter constructor delta 3/5 ms, WebKit group 3 chapter delta 3 ms, versus 2 ms absolute allowance. Other groups did not trigger. Candidate resource checks passed independently. Final run: Chromium/Firefox had no timing signals; WebKit group 1 core/chapter each increased 3 ms, with no signals in the other two groups. The intermittent review remains open."
|
||||
"workspaceState": "First ENG-08 run: Firefox group 2 had core/chapter constructor delta 3/5 ms, WebKit group 3 chapter delta 3 ms, versus 2 ms absolute allowance. Other groups did not trigger. Candidate resource checks passed independently. Final run: Chromium/Firefox had no timing signals; WebKit group 1 core/chapter each increased 3 ms, with no signals in the other two groups. The intermittent review remains open. CORE-22 navigation phase: Chromium group 1 core/chapter destroy deltas 3.1/2.5 ms and group 3 chapter constructor delta 2.8 ms; WebKit group 1 chapter destroy delta 27 ms (71 to 98 ms); Firefox has no timing signals. Other groups do not reproduce those destroy signals. All 54 candidate resource probes pass. The timed fixture uses default settings disabled, so these measurements do not establish any timing effect from setting navigation changes. Final CORE-22: no constructor timing signals in any of the three engines; Chromium/Firefox have no timing signals. WebKit group 1 chapter destruction delta 44 ms and group 2 core delta 30 ms remain, group 3 has no signal. All 54 candidate resource probes pass. Chromium sampling identifies native load/DOM teardown cost but does not establish the cause of WebKit signals. MOD-03/REVIEW-01 retain the open review without changing allowances."
|
||||
},
|
||||
{
|
||||
"id": "BASE-LIFE-06",
|
||||
@@ -761,18 +771,29 @@
|
||||
{
|
||||
"id": "SDK-12",
|
||||
"title": "option-validator 实际集成验证范围",
|
||||
"confirmation": "unverified",
|
||||
"status": "open",
|
||||
"confirmation": "source-observed",
|
||||
"status": "resolved",
|
||||
"owners": [
|
||||
"CORE-01",
|
||||
"CORE-07"
|
||||
"CORE-07",
|
||||
"CORE-22"
|
||||
],
|
||||
"evidence": [
|
||||
"packages/artplayer/src/index.ts",
|
||||
"packages/artplayer/src/option/resolve.ts"
|
||||
"packages/artplayer/src/option/resolve.ts",
|
||||
"test/options.test.js",
|
||||
"test/browser/options.spec.js",
|
||||
"test/browser/initialization.spec.js",
|
||||
"refactor/baselines/core-acceptance-validation.json"
|
||||
],
|
||||
"compatibleResolution": "Bundled validation dependency; preserve validation/default/merge behavior and failure cleanup when typing constructor boundaries.",
|
||||
"closureCriteria": "固定 SDK/外部资源版本和环境;成功/失败/切换/销毁有真实证据,设备和网络限制逐项记录。"
|
||||
"closureCriteria": "固定 SDK/外部资源版本和环境;成功/失败/切换/销毁有真实证据,设备和网络限制逐项记录。",
|
||||
"resolutionEvidence": [
|
||||
"refactor/baselines/core-acceptance-validation.json",
|
||||
"refactor/core-acceptance.md",
|
||||
"refactor/changes/2026-09-11-CORE-22-core-acceptance.md"
|
||||
],
|
||||
"resolutionRationale": "option-validator 2.0.6 is a bundled synchronous dependency with recorded file hash and lock/version evidence. Current and published real-browser consumers retain default/merge identity, callback behavior, exact invalid-option errors before mounting, subsequent container reuse and destruction; modern/legacy full matrices and current unit tests passed. The dependency itself has no source-switch, network or physical-device lifecycle, so those external-SDK conditions do not apply; player media lifecycle is covered by the core matrix. This closes its integration scope, not an independent audit of the upstream project."
|
||||
},
|
||||
{
|
||||
"id": "BUILD-AMD-01",
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { spawn } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { chromium } from '@playwright/test'
|
||||
|
||||
const directory = fs.mkdtempSync('refactor/.cache/core22-profile-')
|
||||
const port = 8086
|
||||
const env = { ...process.env, ARTPLAYER_BROWSER_PORT: String(port) }
|
||||
delete env.ARTPLAYER_BROWSER_ARTIFACTS
|
||||
const server = spawn(process.execPath, ['test/browser/server.mjs'], { env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] })
|
||||
let serverLog = ''
|
||||
server.stdout.on('data', (chunk) => {
|
||||
serverLog += chunk
|
||||
})
|
||||
server.stderr.on('data', (chunk) => {
|
||||
serverLog += chunk
|
||||
})
|
||||
let browser
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => reject(new Error('Server startup timeout')), 60000)
|
||||
const close = (code) => {
|
||||
clearTimeout(timer)
|
||||
reject(new Error(`Server exited ${code}: ${serverLog}`))
|
||||
}
|
||||
server.once('exit', close)
|
||||
server.stdout.on('data', () => {
|
||||
if (serverLog.includes(`Browser test server: http://127.0.0.1:${port}`)) {
|
||||
clearTimeout(timer)
|
||||
server.removeListener('exit', close)
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
browser = await chromium.launch()
|
||||
const page = await browser.newPage({ viewport: { width: 960, height: 720 } })
|
||||
const errors = []
|
||||
page.on('pageerror', error => errors.push(error.message))
|
||||
await page.goto(`http://127.0.0.1:${port}/test/player.html?core=candidate&chapter=published`)
|
||||
const manifest = await (await page.request.get(`http://127.0.0.1:${port}/test/manifest.json`)).json()
|
||||
fs.writeFileSync(path.join(directory, 'candidate.js'), await (await page.request.get(`http://127.0.0.1:${port}/candidate/artplayer.js`)).body())
|
||||
await page.evaluate(() => {
|
||||
window.profileSamples = []
|
||||
window.runProfile = async function (count) {
|
||||
function constructProfilePlayer() {
|
||||
return new window.Artplayer({ container: '.player', url: '/assets/sample/video.mp4', muted: true })
|
||||
}
|
||||
function destroyProfilePlayer(art) {
|
||||
art.destroy()
|
||||
}
|
||||
for (let index = 0; index < count; index++) {
|
||||
const start = performance.now()
|
||||
const art = constructProfilePlayer()
|
||||
const constructed = performance.now()
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => reject(new Error('Ready timed out')), 10000)
|
||||
art.once('ready', () => {
|
||||
clearTimeout(timer)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
await art.play()
|
||||
await new Promise(resolve => art.video.requestVideoFrameCallback(() => resolve()))
|
||||
art.pause()
|
||||
const beforeDestroy = performance.now()
|
||||
destroyProfilePlayer(art)
|
||||
window.profileSamples.push({ constructorMs: constructed - start, destroyMs: performance.now() - beforeDestroy })
|
||||
}
|
||||
finally {
|
||||
art.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
await page.evaluate(() => window.runProfile(10))
|
||||
await page.evaluate(() => {
|
||||
window.profileSamples = []
|
||||
})
|
||||
const client = await page.context().newCDPSession(page)
|
||||
await client.send('Profiler.enable')
|
||||
await client.send('Profiler.setSamplingInterval', { interval: 100 })
|
||||
await client.send('Profiler.start')
|
||||
await page.evaluate(() => window.runProfile(60))
|
||||
const { profile } = await client.send('Profiler.stop')
|
||||
const nodes = new Map(profile.nodes.map(node => [node.id, node]))
|
||||
const parents = new Map()
|
||||
for (const node of profile.nodes) {
|
||||
for (const child of node.children || []) parents.set(child, node.id)
|
||||
}
|
||||
const totals = { constructor: 0, destroy: 0, other: 0 }
|
||||
const groups = { constructor: new Map(), destroy: new Map() }
|
||||
for (let index = 0; index < profile.samples.length; index++) {
|
||||
const leaf = nodes.get(profile.samples[index])
|
||||
let node = leaf
|
||||
let group = 'other'
|
||||
while (node) {
|
||||
if (node.callFrame.functionName === 'constructProfilePlayer') {
|
||||
group = 'constructor'
|
||||
break
|
||||
}
|
||||
if (node.callFrame.functionName === 'destroyProfilePlayer') {
|
||||
group = 'destroy'
|
||||
break
|
||||
}
|
||||
node = nodes.get(parents.get(node.id))
|
||||
}
|
||||
const duration = profile.timeDeltas[index]
|
||||
totals[group] += duration
|
||||
if (group !== 'other') {
|
||||
const frame = leaf.callFrame
|
||||
const key = `${frame.functionName || '(anonymous)'}:${frame.url}:${frame.lineNumber + 1}`
|
||||
const result = groups[group].get(key) || { frame, sampledUs: 0, samples: 0 }
|
||||
result.sampledUs += duration
|
||||
result.samples++
|
||||
groups[group].set(key, result)
|
||||
}
|
||||
}
|
||||
const summary = {
|
||||
kind: 'diagnostic-cpu-sampling',
|
||||
browser: browser.version(),
|
||||
node: process.version,
|
||||
manifest,
|
||||
intervalRequestedUs: 100,
|
||||
warmup: 10,
|
||||
repetitions: 60,
|
||||
totalsUs: totals,
|
||||
groups: Object.fromEntries(Object.entries(groups).map(([name, values]) => [name, [...values.values()].sort((a, b) => b.sampledUs - a.sampledUs)])),
|
||||
timings: await page.evaluate(() => window.profileSamples),
|
||||
errors,
|
||||
limit: 'Instrumented Chromium source-build diagnostic only; not an uninstrumented published/candidate performance comparison. Native frames and sampling gaps may be charged to their JS caller. Timings must not be used for performance acceptance.',
|
||||
}
|
||||
assert.deepEqual(errors, [])
|
||||
assert.equal(await page.evaluate(() => window.Artplayer.instances.length), 0)
|
||||
fs.writeFileSync(path.join(directory, 'profile.cpuprofile'), JSON.stringify(profile))
|
||||
fs.writeFileSync(path.join(directory, 'summary.json'), `${JSON.stringify(summary, null, 2)}\n`)
|
||||
console.log(directory)
|
||||
for (const [name, rows] of Object.entries(summary.groups)) console.log(name, totals[name], rows.slice(0, 12).map(row => ({ name: row.frame.functionName, line: row.frame.lineNumber + 1, sampledUs: row.sampledUs, samples: row.samples })))
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
await browser?.close()
|
||||
}
|
||||
finally {
|
||||
server.kill()
|
||||
fs.writeFileSync(path.join(directory, 'server.log'), serverLog)
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -1227,14 +1227,16 @@
|
||||
"ENG-10",
|
||||
"ENG-11"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"risk": "H",
|
||||
"deliverable": "核心与旧插件的可自动化完整回归、资源/性能及明确外部验证缺口",
|
||||
"acceptance": "核心自动化和公开差异处置通过;真实环境缺口链接包集成/REL 门槛,阶段完成不代表可公开发布",
|
||||
"evidence": [
|
||||
"changes/2026-09-11-CORE-22-core-acceptance.md"
|
||||
],
|
||||
"reason": "初步体积归因已执行;先独立交付新增前置 ENG-11 分析工具,再恢复核心完整验收。"
|
||||
"changes/2026-09-11-CORE-22-core-acceptance.md",
|
||||
"baselines/core-setting-navigation-partial.json",
|
||||
"core-acceptance.md",
|
||||
"baselines/core-acceptance-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-CHAPTER-01",
|
||||
|
||||
@@ -84,6 +84,73 @@ test('candidate: setting navigation ignores hidden rows and inert descendants',
|
||||
await expect(row(page, 'quality')).toBeFocused()
|
||||
})
|
||||
|
||||
test('candidate: long setting panels observe live button availability on every navigation', async ({ page }, testInfo) => {
|
||||
await setup(page)
|
||||
await page.evaluate(() => {
|
||||
const setting = window.art.setting
|
||||
for (const name of ['quality', 'loop', 'offset', 'action'])
|
||||
setting.remove(name)
|
||||
for (let index = 0; index < 40; index++)
|
||||
setting.add({ name: `row-${index}`, html: `<button id="setting-button-${index}">Action ${index}</button>` })
|
||||
})
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(page.locator('#setting-button-0')).toBeFocused()
|
||||
const measurements = []
|
||||
async function navigate(key, expected) {
|
||||
await page.evaluate(() => {
|
||||
const panel = window.art.setting.cache.get(window.art.setting.active)
|
||||
const query = panel.querySelectorAll
|
||||
const style = window.getComputedStyle
|
||||
const counts = { queries: 0, styles: 0 }
|
||||
panel.querySelectorAll = function (...args) {
|
||||
counts.queries++
|
||||
return query.apply(this, args)
|
||||
}
|
||||
window.getComputedStyle = function (element, ...args) {
|
||||
if (panel.contains(element))
|
||||
counts.styles++
|
||||
return style.call(this, element, ...args)
|
||||
}
|
||||
window.finishSettingMeasurement = () => {
|
||||
delete panel.querySelectorAll
|
||||
window.getComputedStyle = style
|
||||
delete window.finishSettingMeasurement
|
||||
return counts
|
||||
}
|
||||
})
|
||||
try {
|
||||
await page.keyboard.press(key)
|
||||
}
|
||||
finally {
|
||||
measurements.push({ key, expected, ...await page.evaluate(() => window.finishSettingMeasurement()) })
|
||||
}
|
||||
await expect(page.locator(`#setting-button-${expected}`)).toBeFocused()
|
||||
}
|
||||
await navigate('ArrowDown', 1)
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('#setting-button-2').style.visibility = 'hidden'
|
||||
document.querySelector('#setting-button-3').closest('.art-setting-item').setAttribute('inert', '')
|
||||
document.querySelector('#setting-button-4').disabled = true
|
||||
document.querySelector('#setting-button-5').tabIndex = -1
|
||||
})
|
||||
await navigate('ArrowDown', 6)
|
||||
await navigate('a', 6)
|
||||
await navigate('End', 39)
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('#setting-button-2').style.visibility = ''
|
||||
document.querySelector('#setting-button-3').closest('.art-setting-item').removeAttribute('inert')
|
||||
document.querySelector('#setting-button-4').disabled = false
|
||||
document.querySelector('#setting-button-5').removeAttribute('tabindex')
|
||||
window.art.setting.remove('row-1')
|
||||
})
|
||||
await navigate('Home', 0)
|
||||
await navigate('ArrowDown', 2)
|
||||
await navigate('ArrowDown', 3)
|
||||
await navigate('ArrowDown', 4)
|
||||
await navigate('ArrowDown', 5)
|
||||
await testInfo.attach('setting-navigation-work', { contentType: 'application/json', body: JSON.stringify(measurements, null, 2) })
|
||||
})
|
||||
|
||||
test('candidate: setting keyboard enters nested panels and returns to the originating item', async ({ page }, testInfo) => {
|
||||
await setup(page)
|
||||
await page.evaluate(() => {
|
||||
|
||||
@@ -1,5 +1,57 @@
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
for (const fallback of [false, true]) {
|
||||
test(`candidate: initial control measurement uses the browser layout phase with fallback=${fallback}`, async ({ page }, testInfo) => {
|
||||
if (fallback)
|
||||
await page.addInitScript(() => { window.ResizeObserver = undefined })
|
||||
await page.goto('/test/player.html?core=candidate&chapter=published')
|
||||
const reads = await page.evaluate(() => {
|
||||
document.querySelector('.player').style.width = '320px'
|
||||
const descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetHeight')
|
||||
let reads = 0
|
||||
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
|
||||
...descriptor,
|
||||
get() {
|
||||
if (this.classList.contains('art-controls'))
|
||||
reads++
|
||||
return descriptor.get.call(this)
|
||||
},
|
||||
})
|
||||
try {
|
||||
window.art = new window.Artplayer({ container: '.player', url: '', setting: true, fullscreenWeb: true, controls: [{ name: 'probe', position: 'right', html: 'Probe control' }] })
|
||||
}
|
||||
finally {
|
||||
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', descriptor)
|
||||
}
|
||||
return reads
|
||||
})
|
||||
await testInfo.attach('constructor-control-layout-reads', { contentType: 'application/json', body: JSON.stringify({ fallback, reads }) })
|
||||
expect(reads).toBe(fallback ? 1 : 0)
|
||||
const measured = await page.evaluate(async () => {
|
||||
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))
|
||||
const { $controls, $player } = window.art.template
|
||||
return { height: $controls.offsetHeight, recorded: Number.parseFloat($player.style.getPropertyValue('--art-controls-height')) }
|
||||
})
|
||||
expect(measured.height).toBe(92)
|
||||
expect(measured.recorded).toBe(measured.height)
|
||||
await expect.poll(() => page.evaluate(() => Number.parseFloat(getComputedStyle(window.art.template.$subtitle).bottom))).toBeGreaterThanOrEqual(measured.height)
|
||||
})
|
||||
}
|
||||
|
||||
test('candidate: destroying before initial control observation leaves no late layout write', async ({ page }) => {
|
||||
await page.goto('/test/player.html?core=candidate&chapter=published')
|
||||
const state = await page.evaluate(async () => {
|
||||
const art = new window.Artplayer({ container: '.player', url: '' })
|
||||
const player = art.template.$player
|
||||
art.destroy(false)
|
||||
const before = player.getAttribute('style')
|
||||
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))
|
||||
return { before, after: player.getAttribute('style'), instances: window.Artplayer.instances.length }
|
||||
})
|
||||
expect(state.after).toBe(state.before)
|
||||
expect(state.instances).toBe(0)
|
||||
})
|
||||
|
||||
for (const fallback of [false, true]) {
|
||||
test(`candidate: control layout follows resize and removal with observer fallback=${fallback}`, async ({ page }) => {
|
||||
if (fallback) {
|
||||
|
||||
Reference in new issue
Block a user