From b8d03e7366f421b3616e3338c570c1efd92f1104 Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Sat, 12 Sep 2026 23:41:48 +0800 Subject: [PATCH] fix(iframe): [PKG-IFRAME-03] own pending requests and cancellation --- docs/compiled/artplayer-tool-iframe.js | 2 +- docs/compiled/artplayer-tool-iframe.legacy.js | 2 +- docs/compiled/artplayer-tool-iframe.mjs | 96 +- package.json | 4 +- .../artplayer-tool-iframe/ARCHITECTURE.md | 78 + packages/artplayer-tool-iframe/README.md | 5 + .../dist/artplayer-tool-iframe.js | 2 +- .../dist/artplayer-tool-iframe.legacy.js | 2 +- .../dist/artplayer-tool-iframe.mjs | 96 +- .../src/{index.js => index.ts} | 61 +- .../artplayer-tool-iframe/src/requests.ts | 94 + packages/artplayer-tool-iframe/tsconfig.json | 5 + .../baselines/iframe-requests-checkpoint.json | 3049 +++++++++++++++++ ...09-12-PKG-IFRAME-03-requests-checkpoint.md | 82 + refactor/plan.md | 5 +- refactor/progress.md | 13 + refactor/risks.json | 6 +- refactor/tasks.json | 7 +- test/browser/iframe.spec.js | 60 +- test/helpers/iframe.js | 9 + test/iframe-lifecycle.test.js | 255 ++ 21 files changed, 3826 insertions(+), 107 deletions(-) create mode 100644 packages/artplayer-tool-iframe/ARCHITECTURE.md rename packages/artplayer-tool-iframe/src/{index.js => index.ts} (74%) create mode 100644 packages/artplayer-tool-iframe/src/requests.ts create mode 100644 packages/artplayer-tool-iframe/tsconfig.json create mode 100644 refactor/baselines/iframe-requests-checkpoint.json create mode 100644 refactor/changes/2026-09-12-PKG-IFRAME-03-requests-checkpoint.md create mode 100644 test/iframe-lifecycle.test.js diff --git a/docs/compiled/artplayer-tool-iframe.js b/docs/compiled/artplayer-tool-iframe.js index 8180b1f34..810ccc3df 100644 --- a/docs/compiled/artplayer-tool-iframe.js +++ b/docs/compiled/artplayer-tool-iframe.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(t.ArtplayerToolIframe=t(),define(function(){return t.ArtplayerToolIframe})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolIframe=t()}(this,function(){"use strict";class e{static get iframe(){return window.top!==window}static postMessage({type:t,data:s,id:i=0}){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.postMessage" method can only be used in iframe');window.parent.postMessage({type:t,data:s,id:i},"*")}static async onMessage(t){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.onMessage" method can only be used in iframe');const{type:s,data:i,id:o}=t.data;if("commit"===s)try{if(i.match(/\bresolve\((.*?)\)/)){const t=`return new Promise(function(resolve){\n${i}\n})`,s=await new Function(t)();e.postMessage({type:"response",data:s,id:o})}else{const t=new Function(i)();e.postMessage({type:"response",data:t,id:o})}}catch(n){throw e.postMessage({type:"error",data:n.message,id:o}),n}}static inject(){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.inject" method can only be used in iframe');e.postMessage({type:"inject"}),window.addEventListener("message",e.onMessage)}constructor({iframe:e,url:t}){if(e instanceof HTMLIFrameElement===!1)throw new TypeError('"option.iframe" needs to be a HTMLIFrameElement');if("string"!==typeof t)throw new TypeError('"option.url" needs to be a string');this.url=t,this.$iframe=e,this.promises={},this.injected=!1,this.destroyed=!1,this.messageCallback=()=>null,this.onMessage=this.onMessage.bind(this),window.addEventListener("message",this.onMessage),this.$iframe.src=this.url}onMessage(e){const{type:t,data:s,id:i}=e.data;if("inject"===t)this.injected=!0;this.promises[i]&&("error"===t?this.promises[i].reject(new Error(s)):this.promises[i].resove(s),delete this.promises[i]),this.messageCallback&&this.messageCallback({type:t,data:s})}postMessage({type:e,data:t}){return new Promise((s,i)=>{(function o(){if(this.destroyed)i(new Error("The instance has been destroyed"));else if(this.injected){const o=Date.now();this.promises[o]={resove:s,reject:i},this.$iframe.contentWindow.postMessage({type:e,data:t,id:o},"*")}else setTimeout(o.bind(this),200)}).call(this)})}commit(e){if("function"!==typeof e)throw new TypeError('"commit.callback" needs to be a function');const t=e.toString(),s=t.substring(t.indexOf("{")+1,t.lastIndexOf("}"));return this.postMessage({type:"commit",data:s})}message(e){if("function"!==typeof e)throw new TypeError('"message.callback" needs to be a function');this.messageCallback=e}destroy(){this.destroyed=!0,window.removeEventListener("message",this.onMessage)}}return e}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolIframe=t(),define(function(){return e.ArtplayerToolIframe})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolIframe=t()}(this,function(){"use strict";const e=new WeakMap;let t=0;class s{static get iframe(){return window.top!==window}static postMessage({type:e,data:t,id:o=0}){if(!s.iframe)throw new Error('The "ArtplayerToolIframe.postMessage" method can only be used in iframe');window.parent.postMessage({type:e,data:t,id:o},"*")}static async onMessage(e){if(!s.iframe)throw new Error('The "ArtplayerToolIframe.onMessage" method can only be used in iframe');const{type:t,data:o,id:i}=e.data;if("commit"===t)try{if(o.match(/\bresolve\((.*?)\)/)){const e=`return new Promise(function(resolve){\n${o}\n})`,t=await new Function(e)();s.postMessage({type:"response",data:t,id:i})}else{const e=new Function(o)();s.postMessage({type:"response",data:e,id:i})}}catch(r){throw s.postMessage({type:"error",data:r.message,id:i}),r}}static inject(){if(!s.iframe)throw new Error('The "ArtplayerToolIframe.inject" method can only be used in iframe');s.postMessage({type:"inject"}),window.addEventListener("message",s.onMessage)}constructor({iframe:e,url:t}){if(e instanceof HTMLIFrameElement===!1)throw new TypeError('"option.iframe" needs to be a HTMLIFrameElement');if("string"!==typeof t)throw new TypeError('"option.url" needs to be a string');this.url=t,this.$iframe=e,this.promises={},this.injected=!1,this.destroyed=!1,this.messageCallback=()=>null,this.onMessage=this.onMessage.bind(this),window.addEventListener("message",this.onMessage),this.$iframe.src=this.url}onMessage(e){if(this.destroyed)return;const{type:t,data:s,id:o}=e.data;if("inject"===t)this.injected=!0;void 0!==o&&Object.prototype.hasOwnProperty.call(this.promises,o)&&this.promises[o]&&("error"===t?this.promises[o].reject(new Error(s)):this.promises[o].resove(s),delete this.promises[o]),this.messageCallback&&this.messageCallback({type:t,data:s})}postMessage(s){return function(s,{type:o,data:i}){return new Promise((r,n)=>{if(s.destroyed)return void n(new Error("The instance has been destroyed"));const a=e.get(s)||new Set;e.set(s,a);const d=t=>!!a.delete(t)&&(void 0!==t.timer&&clearTimeout(t.timer),void 0!==t.id&&delete s.promises[t.id],a.size||e.delete(s),!0),c={callbacks:{resove(e){d(c)&&r(e)},reject(e){d(c)&&n(e)}}};a.add(c);const l=()=>{if(c.timer=void 0,a.has(c))if(s.destroyed)c.callbacks.reject(new Error("The instance has been destroyed"));else try{if(s.injected){const e=Math.max(Date.now(),t+1);t=e,c.id=e,s.promises[e]=c.callbacks,s.$iframe.contentWindow.postMessage({type:o,data:i,id:e},"*")}else c.timer=setTimeout(l,200)}catch(e){c.callbacks.reject(e)}};l()})}(this,s)}commit(e){if("function"!==typeof e)throw new TypeError('"commit.callback" needs to be a function');const t=e.toString(),s=t.substring(t.indexOf("{")+1,t.lastIndexOf("}"));return this.postMessage({type:"commit",data:s})}message(e){if("function"!==typeof e)throw new TypeError('"message.callback" needs to be a function');this.messageCallback=e}destroy(){this.destroyed=!0,window.removeEventListener("message",this.onMessage),function(t){const s=e.get(t);if(s)for(const e of[...s])e.callbacks.reject(new Error("The instance has been destroyed"))}(this)}}return s}); diff --git a/docs/compiled/artplayer-tool-iframe.legacy.js b/docs/compiled/artplayer-tool-iframe.legacy.js index 4d398aa1e..78a7c6e14 100644 --- a/docs/compiled/artplayer-tool-iframe.legacy.js +++ b/docs/compiled/artplayer-tool-iframe.legacy.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(t.ArtplayerToolIframe=t(),define(function(){return t.ArtplayerToolIframe})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolIframe=t()}(this,function(){"use strict";class e{static get iframe(){return window.top!==window}static postMessage({type:t,data:s,id:o=0}){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.postMessage" method can only be used in iframe');window.parent.postMessage({type:t,data:s,id:o},"*")}static onMessage(t){return s=this,o=null,i=function*(){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.onMessage" method can only be used in iframe');const{type:s,data:o,id:i}=t.data;if("commit"===s)try{if(o.match(/\bresolve\((.*?)\)/)){const t=`return new Promise(function(resolve){\n${o}\n})`,s=yield new Function(t)();e.postMessage({type:"response",data:s,id:i})}else{const t=new Function(o)();e.postMessage({type:"response",data:t,id:i})}}catch(n){throw e.postMessage({type:"error",data:n.message,id:i}),n}},new Promise((e,t)=>{var n=e=>{try{a(i.next(e))}catch(s){t(s)}},r=e=>{try{a(i.throw(e))}catch(s){t(s)}},a=t=>t.done?e(t.value):Promise.resolve(t.value).then(n,r);a((i=i.apply(s,o)).next())});var s,o,i}static inject(){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.inject" method can only be used in iframe');e.postMessage({type:"inject"}),window.addEventListener("message",e.onMessage)}constructor({iframe:e,url:t}){if(e instanceof HTMLIFrameElement===!1)throw new TypeError('"option.iframe" needs to be a HTMLIFrameElement');if("string"!==typeof t)throw new TypeError('"option.url" needs to be a string');this.url=t,this.$iframe=e,this.promises={},this.injected=!1,this.destroyed=!1,this.messageCallback=()=>null,this.onMessage=this.onMessage.bind(this),window.addEventListener("message",this.onMessage),this.$iframe.src=this.url}onMessage(e){const{type:t,data:s,id:o}=e.data;if("inject"===t)this.injected=!0;this.promises[o]&&("error"===t?this.promises[o].reject(new Error(s)):this.promises[o].resove(s),delete this.promises[o]),this.messageCallback&&this.messageCallback({type:t,data:s})}postMessage({type:e,data:t}){return new Promise((s,o)=>{(function i(){if(this.destroyed)o(new Error("The instance has been destroyed"));else if(this.injected){const i=Date.now();this.promises[i]={resove:s,reject:o},this.$iframe.contentWindow.postMessage({type:e,data:t,id:i},"*")}else setTimeout(i.bind(this),200)}).call(this)})}commit(e){if("function"!==typeof e)throw new TypeError('"commit.callback" needs to be a function');const t=e.toString(),s=t.substring(t.indexOf("{")+1,t.lastIndexOf("}"));return this.postMessage({type:"commit",data:s})}message(e){if("function"!==typeof e)throw new TypeError('"message.callback" needs to be a function');this.messageCallback=e}destroy(){this.destroyed=!0,window.removeEventListener("message",this.onMessage)}}return e}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolIframe=t(),define(function(){return e.ArtplayerToolIframe})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolIframe=t()}(this,function(){"use strict";const e=new WeakMap;let t=0;class s{static get iframe(){return window.top!==window}static postMessage({type:e,data:t,id:o=0}){if(!s.iframe)throw new Error('The "ArtplayerToolIframe.postMessage" method can only be used in iframe');window.parent.postMessage({type:e,data:t,id:o},"*")}static onMessage(e){return t=this,o=null,r=function*(){if(!s.iframe)throw new Error('The "ArtplayerToolIframe.onMessage" method can only be used in iframe');const{type:t,data:o,id:r}=e.data;if("commit"===t)try{if(o.match(/\bresolve\((.*?)\)/)){const e=`return new Promise(function(resolve){\n${o}\n})`,t=yield new Function(e)();s.postMessage({type:"response",data:t,id:r})}else{const e=new Function(o)();s.postMessage({type:"response",data:e,id:r})}}catch(i){throw s.postMessage({type:"error",data:i.message,id:r}),i}},new Promise((e,s)=>{var i=e=>{try{a(r.next(e))}catch(t){s(t)}},n=e=>{try{a(r.throw(e))}catch(t){s(t)}},a=t=>t.done?e(t.value):Promise.resolve(t.value).then(i,n);a((r=r.apply(t,o)).next())});var t,o,r}static inject(){if(!s.iframe)throw new Error('The "ArtplayerToolIframe.inject" method can only be used in iframe');s.postMessage({type:"inject"}),window.addEventListener("message",s.onMessage)}constructor({iframe:e,url:t}){if(e instanceof HTMLIFrameElement===!1)throw new TypeError('"option.iframe" needs to be a HTMLIFrameElement');if("string"!==typeof t)throw new TypeError('"option.url" needs to be a string');this.url=t,this.$iframe=e,this.promises={},this.injected=!1,this.destroyed=!1,this.messageCallback=()=>null,this.onMessage=this.onMessage.bind(this),window.addEventListener("message",this.onMessage),this.$iframe.src=this.url}onMessage(e){if(this.destroyed)return;const{type:t,data:s,id:o}=e.data;if("inject"===t)this.injected=!0;void 0!==o&&Object.prototype.hasOwnProperty.call(this.promises,o)&&this.promises[o]&&("error"===t?this.promises[o].reject(new Error(s)):this.promises[o].resove(s),delete this.promises[o]),this.messageCallback&&this.messageCallback({type:t,data:s})}postMessage(s){return function(s,{type:o,data:r}){return new Promise((i,n)=>{if(s.destroyed)return void n(new Error("The instance has been destroyed"));const a=e.get(s)||new Set;e.set(s,a);const d=t=>!!a.delete(t)&&(void 0!==t.timer&&clearTimeout(t.timer),void 0!==t.id&&delete s.promises[t.id],a.size||e.delete(s),!0),c={callbacks:{resove(e){d(c)&&i(e)},reject(e){d(c)&&n(e)}}};a.add(c);const l=()=>{if(c.timer=void 0,a.has(c))if(s.destroyed)c.callbacks.reject(new Error("The instance has been destroyed"));else try{if(s.injected){const e=Math.max(Date.now(),t+1);t=e,c.id=e,s.promises[e]=c.callbacks,s.$iframe.contentWindow.postMessage({type:o,data:r,id:e},"*")}else c.timer=setTimeout(l,200)}catch(e){c.callbacks.reject(e)}};l()})}(this,s)}commit(e){if("function"!==typeof e)throw new TypeError('"commit.callback" needs to be a function');const t=e.toString(),s=t.substring(t.indexOf("{")+1,t.lastIndexOf("}"));return this.postMessage({type:"commit",data:s})}message(e){if("function"!==typeof e)throw new TypeError('"message.callback" needs to be a function');this.messageCallback=e}destroy(){this.destroyed=!0,window.removeEventListener("message",this.onMessage),function(t){const s=e.get(t);if(s)for(const e of[...s])e.callbacks.reject(new Error("The instance has been destroyed"))}(this)}}return s}); diff --git a/docs/compiled/artplayer-tool-iframe.mjs b/docs/compiled/artplayer-tool-iframe.mjs index 3e99c0366..504da4eec 100644 --- a/docs/compiled/artplayer-tool-iframe.mjs +++ b/docs/compiled/artplayer-tool-iframe.mjs @@ -4,6 +4,72 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ +const requests = /* @__PURE__ */ new WeakMap(); +let lastId = 0; +function postRequest(host, { type, data }) { + return new Promise((resolve, reject) => { + if (host.destroyed) { + reject(new Error("The instance has been destroyed")); + return; + } + const pending = requests.get(host) || /* @__PURE__ */ new Set(); + requests.set(host, pending); + const finish = (request2) => { + if (!pending.delete(request2)) + return false; + if (request2.timer !== void 0) + clearTimeout(request2.timer); + if (request2.id !== void 0) + delete host.promises[request2.id]; + if (!pending.size) + requests.delete(host); + return true; + }; + const request = { + callbacks: { + resove(value) { + if (finish(request)) + resolve(value); + }, + reject(error) { + if (finish(request)) + reject(error); + } + } + }; + pending.add(request); + const loop = () => { + request.timer = void 0; + if (!pending.has(request)) + return; + if (host.destroyed) { + request.callbacks.reject(new Error("The instance has been destroyed")); + return; + } + try { + if (host.injected) { + const id = Math.max(Date.now(), lastId + 1); + lastId = id; + request.id = id; + host.promises[id] = request.callbacks; + host.$iframe.contentWindow.postMessage({ type, data, id }, "*"); + } else { + request.timer = setTimeout(loop, 200); + } + } catch (error) { + request.callbacks.reject(error); + } + }; + loop(); + }); +} +function cancelRequests(host) { + const pending = requests.get(host); + if (!pending) + return; + for (const request of [...pending]) + request.callbacks.reject(new Error("The instance has been destroyed")); +} class ArtplayerToolIframe { static get iframe() { return window.top !== window; @@ -71,13 +137,15 @@ ${data} this.$iframe.src = this.url; } onMessage(event) { + if (this.destroyed) + return; const { type, data, id } = event.data; switch (type) { case "inject": this.injected = true; break; } - if (this.promises[id]) { + if (id !== void 0 && Object.prototype.hasOwnProperty.call(this.promises, id) && this.promises[id]) { if (type === "error") { this.promises[id].reject(new Error(data)); } else { @@ -89,29 +157,8 @@ ${data} this.messageCallback({ type, data }); } } - postMessage({ type, data }) { - return new Promise((resove, reject) => { - (function loop() { - if (this.destroyed) { - reject(new Error("The instance has been destroyed")); - } else { - if (this.injected) { - const id = Date.now(); - this.promises[id] = { resove, reject }; - this.$iframe.contentWindow.postMessage( - { - type, - data, - id - }, - "*" - ); - } else { - setTimeout(loop.bind(this), 200); - } - } - }).call(this); - }); + postMessage(message) { + return postRequest(this, message); } commit(callback) { if (typeof callback !== "function") { @@ -130,6 +177,7 @@ ${data} destroy() { this.destroyed = true; window.removeEventListener("message", this.onMessage); + cancelRequests(this); } } export { diff --git a/package.json b/package.json index 5b5e73652..590c33b74 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports", "test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js", "typecheck": "node scripts/typecheck.mjs", - "test:unit": "node --test test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js", + "test:unit": "node --test test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js", "test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs", "test": "yarn test:node && yarn test:baseline", "test:browser": "playwright test", @@ -80,7 +80,7 @@ "test:mediabunny-types-package": "node refactor/scripts/mb-package-types.mjs", "test:mediabunny-soak": "playwright test --config playwright.soak.config.js", "generate:media:hls": "node scripts/generate-hls-fixture.mjs", - "test:iframe": "node --test test/iframe.test.js refactor/scripts/iframe-contract.test.mjs" + "test:iframe": "node --test test/iframe.test.js test/iframe-lifecycle.test.js refactor/scripts/iframe-contract.test.mjs" }, "browserslist": "last 1 Chrome version", "devDependencies": { diff --git a/packages/artplayer-tool-iframe/ARCHITECTURE.md b/packages/artplayer-tool-iframe/ARCHITECTURE.md new file mode 100644 index 000000000..911ef858b --- /dev/null +++ b/packages/artplayer-tool-iframe/ARCHITECTURE.md @@ -0,0 +1,78 @@ +# Iframe maintenance map + +`src/index.ts` owns the existing class, constructor validation, bound parent +message listener, child injection/execution protocol and public callbacks. +`src/requests.ts` owns request IDs, pending promises, injection polling and +settlement/cancellation. Its host interface uses only the iframe, registry and +two lifecycle flags; it does not depend on ArtPlayer or import the entry class. + +The entry initializes the same seven enumerable, writable fields in the same +order. Internal request ownership lives in a module WeakMap, keeping instance +reflection unchanged. `promises` remains the same ordinary object, exposing the +historical `resove` spelling and `reject`. Do not rename `resove` or replace this +public object with a Map. The wire envelope remains `{ type, data, id }`, with +numeric IDs and wildcard target origin. Non-error responses with a matching ID +are still accepted; `message` receives `{ type, data }` with the instance as `this`. + +## Request lifecycle + +Every call owns its promise immediately. Before injection it polls every 200ms, +as before; injection does not flush the queue synchronously. On send it allocates +a numeric ID from the greater of the current timestamp and the previous ID + 1. +The counter is shared by instances in this module so two instances pointing at +one child do not settle each other's requests. It also avoids reuse after clock +rollback. Independently loaded copies of the library do not share this counter. + +The request is recorded before native postMessage. Success, remote error, native +send failure or an explicit public callback settle once and remove ownership, +timer and registry entry. Native failures retain their original Error object, +including failures reached from polling. `destroy()` first marks the instance +destroyed, removes its listener, then immediately rejects both sent and waiting +requests with `The instance has been destroyed`. Saved listener/timer callbacks +become inert. Repeated destroy is harmless. There is no implicit request timeout. + +These are deliberate defect corrections: callers holding a promise during +destroy must handle rejection; previously sent requests remained pending forever. +The public ID is a correlation number, and is no longer always equal to Date.now. +Public callback invocation now removes its completed registry entry immediately. +The own-property guard prevents inherited names such as `toString` being treated +as requests. These differences are recorded under PKG-IFRAME-03. + +## Compatibility still under review + +Child `commit` still extracts the function body and evaluates it with the old +`resolve(...)` convention. Simple commits post a response synchronously inside +the async handler; resolver commits await the result. Execution errors still +send an error packet and reject the handler. Expression arrow functions, closure +capture and CSP restrictions have not been replaced with a new RPC protocol. + +This checkpoint does not fix navigation/reinjection, malformed message handling +or source/origin validation. The executable commit protocol is not a sandbox; +the current listener still accepts messages from unrelated windows. IFRAME-LIFE-01 +and IFRAME-TRUST-01 remain open. Continue in PKG-IFRAME-03/05 before release. + +Public declarations remain in `types/artplayer-tool-iframe.d.ts`; source typing +does not yet establish historical declaration/entry compatibility. PKG-IFRAME-04 +must reconcile the old npm plugin name, namespace/default and extra helper +protocol separately. PKG-IFRAME-06 owns final distribution. No version bump or +publication is implied by this source migration. + +## Verification + +- `yarn test:iframe`: frozen historical contract/defect assertions and candidate + lifecycle assertions. Historical failures are not candidate acceptance. +- `yarn typecheck`: strict source and existing consumers; full package declaration + consumers are still a separate PKG-IFRAME-04 gate. +- `yarn build artplayer-tool-iframe`: normal main/legacy/ESM production output and + generated docs copies. Never hand-edit those files. +- `yarn test:browser test/browser/iframe.spec.js`: real same/cross-origin windows + in Chromium, Firefox and WebKit. This tool-only fixture creates no player. +- Set `ARTPLAYER_IFRAME_LIFECYCLE_ONLY=1` for candidate lifecycle browser rows; + `ARTPLAYER_IFRAME_ARTIFACT` selects an actual built file. For an unchanged + candidate test against the old workspace, set `ARTPLAYER_IFRAME_BASELINE=1`. + Expected historical failures must be archived, not suppressed. + +The existing local demo remains at +`http://localhost:8082/?libs=./uncompiled/artplayer-tool-iframe/index.js&example=iframe`. +Full demo/player integration, native devices and final release checks are later +gates; the controlled iframe fixture is not evidence that those have passed. diff --git a/packages/artplayer-tool-iframe/README.md b/packages/artplayer-tool-iframe/README.md index 766bbf6b5..b26edffc0 100644 --- a/packages/artplayer-tool-iframe/README.md +++ b/packages/artplayer-tool-iframe/README.md @@ -6,6 +6,11 @@ Iframe tool for ArtPlayer [https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-tool-iframe/index.js&example=iframe) +## Maintenance + +See [ARCHITECTURE.md](ARCHITECTURE.md) for the TypeScript module map, request +cleanup, compatibility constraints, executable checks and remaining migration work. + ## License MIT © Harvey Zhao diff --git a/packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.js b/packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.js index 8180b1f34..810ccc3df 100644 --- a/packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.js +++ b/packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(t.ArtplayerToolIframe=t(),define(function(){return t.ArtplayerToolIframe})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolIframe=t()}(this,function(){"use strict";class e{static get iframe(){return window.top!==window}static postMessage({type:t,data:s,id:i=0}){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.postMessage" method can only be used in iframe');window.parent.postMessage({type:t,data:s,id:i},"*")}static async onMessage(t){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.onMessage" method can only be used in iframe');const{type:s,data:i,id:o}=t.data;if("commit"===s)try{if(i.match(/\bresolve\((.*?)\)/)){const t=`return new Promise(function(resolve){\n${i}\n})`,s=await new Function(t)();e.postMessage({type:"response",data:s,id:o})}else{const t=new Function(i)();e.postMessage({type:"response",data:t,id:o})}}catch(n){throw e.postMessage({type:"error",data:n.message,id:o}),n}}static inject(){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.inject" method can only be used in iframe');e.postMessage({type:"inject"}),window.addEventListener("message",e.onMessage)}constructor({iframe:e,url:t}){if(e instanceof HTMLIFrameElement===!1)throw new TypeError('"option.iframe" needs to be a HTMLIFrameElement');if("string"!==typeof t)throw new TypeError('"option.url" needs to be a string');this.url=t,this.$iframe=e,this.promises={},this.injected=!1,this.destroyed=!1,this.messageCallback=()=>null,this.onMessage=this.onMessage.bind(this),window.addEventListener("message",this.onMessage),this.$iframe.src=this.url}onMessage(e){const{type:t,data:s,id:i}=e.data;if("inject"===t)this.injected=!0;this.promises[i]&&("error"===t?this.promises[i].reject(new Error(s)):this.promises[i].resove(s),delete this.promises[i]),this.messageCallback&&this.messageCallback({type:t,data:s})}postMessage({type:e,data:t}){return new Promise((s,i)=>{(function o(){if(this.destroyed)i(new Error("The instance has been destroyed"));else if(this.injected){const o=Date.now();this.promises[o]={resove:s,reject:i},this.$iframe.contentWindow.postMessage({type:e,data:t,id:o},"*")}else setTimeout(o.bind(this),200)}).call(this)})}commit(e){if("function"!==typeof e)throw new TypeError('"commit.callback" needs to be a function');const t=e.toString(),s=t.substring(t.indexOf("{")+1,t.lastIndexOf("}"));return this.postMessage({type:"commit",data:s})}message(e){if("function"!==typeof e)throw new TypeError('"message.callback" needs to be a function');this.messageCallback=e}destroy(){this.destroyed=!0,window.removeEventListener("message",this.onMessage)}}return e}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolIframe=t(),define(function(){return e.ArtplayerToolIframe})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolIframe=t()}(this,function(){"use strict";const e=new WeakMap;let t=0;class s{static get iframe(){return window.top!==window}static postMessage({type:e,data:t,id:o=0}){if(!s.iframe)throw new Error('The "ArtplayerToolIframe.postMessage" method can only be used in iframe');window.parent.postMessage({type:e,data:t,id:o},"*")}static async onMessage(e){if(!s.iframe)throw new Error('The "ArtplayerToolIframe.onMessage" method can only be used in iframe');const{type:t,data:o,id:i}=e.data;if("commit"===t)try{if(o.match(/\bresolve\((.*?)\)/)){const e=`return new Promise(function(resolve){\n${o}\n})`,t=await new Function(e)();s.postMessage({type:"response",data:t,id:i})}else{const e=new Function(o)();s.postMessage({type:"response",data:e,id:i})}}catch(r){throw s.postMessage({type:"error",data:r.message,id:i}),r}}static inject(){if(!s.iframe)throw new Error('The "ArtplayerToolIframe.inject" method can only be used in iframe');s.postMessage({type:"inject"}),window.addEventListener("message",s.onMessage)}constructor({iframe:e,url:t}){if(e instanceof HTMLIFrameElement===!1)throw new TypeError('"option.iframe" needs to be a HTMLIFrameElement');if("string"!==typeof t)throw new TypeError('"option.url" needs to be a string');this.url=t,this.$iframe=e,this.promises={},this.injected=!1,this.destroyed=!1,this.messageCallback=()=>null,this.onMessage=this.onMessage.bind(this),window.addEventListener("message",this.onMessage),this.$iframe.src=this.url}onMessage(e){if(this.destroyed)return;const{type:t,data:s,id:o}=e.data;if("inject"===t)this.injected=!0;void 0!==o&&Object.prototype.hasOwnProperty.call(this.promises,o)&&this.promises[o]&&("error"===t?this.promises[o].reject(new Error(s)):this.promises[o].resove(s),delete this.promises[o]),this.messageCallback&&this.messageCallback({type:t,data:s})}postMessage(s){return function(s,{type:o,data:i}){return new Promise((r,n)=>{if(s.destroyed)return void n(new Error("The instance has been destroyed"));const a=e.get(s)||new Set;e.set(s,a);const d=t=>!!a.delete(t)&&(void 0!==t.timer&&clearTimeout(t.timer),void 0!==t.id&&delete s.promises[t.id],a.size||e.delete(s),!0),c={callbacks:{resove(e){d(c)&&r(e)},reject(e){d(c)&&n(e)}}};a.add(c);const l=()=>{if(c.timer=void 0,a.has(c))if(s.destroyed)c.callbacks.reject(new Error("The instance has been destroyed"));else try{if(s.injected){const e=Math.max(Date.now(),t+1);t=e,c.id=e,s.promises[e]=c.callbacks,s.$iframe.contentWindow.postMessage({type:o,data:i,id:e},"*")}else c.timer=setTimeout(l,200)}catch(e){c.callbacks.reject(e)}};l()})}(this,s)}commit(e){if("function"!==typeof e)throw new TypeError('"commit.callback" needs to be a function');const t=e.toString(),s=t.substring(t.indexOf("{")+1,t.lastIndexOf("}"));return this.postMessage({type:"commit",data:s})}message(e){if("function"!==typeof e)throw new TypeError('"message.callback" needs to be a function');this.messageCallback=e}destroy(){this.destroyed=!0,window.removeEventListener("message",this.onMessage),function(t){const s=e.get(t);if(s)for(const e of[...s])e.callbacks.reject(new Error("The instance has been destroyed"))}(this)}}return s}); diff --git a/packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.legacy.js b/packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.legacy.js index 4d398aa1e..78a7c6e14 100644 --- a/packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.legacy.js +++ b/packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.legacy.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(t.ArtplayerToolIframe=t(),define(function(){return t.ArtplayerToolIframe})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolIframe=t()}(this,function(){"use strict";class e{static get iframe(){return window.top!==window}static postMessage({type:t,data:s,id:o=0}){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.postMessage" method can only be used in iframe');window.parent.postMessage({type:t,data:s,id:o},"*")}static onMessage(t){return s=this,o=null,i=function*(){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.onMessage" method can only be used in iframe');const{type:s,data:o,id:i}=t.data;if("commit"===s)try{if(o.match(/\bresolve\((.*?)\)/)){const t=`return new Promise(function(resolve){\n${o}\n})`,s=yield new Function(t)();e.postMessage({type:"response",data:s,id:i})}else{const t=new Function(o)();e.postMessage({type:"response",data:t,id:i})}}catch(n){throw e.postMessage({type:"error",data:n.message,id:i}),n}},new Promise((e,t)=>{var n=e=>{try{a(i.next(e))}catch(s){t(s)}},r=e=>{try{a(i.throw(e))}catch(s){t(s)}},a=t=>t.done?e(t.value):Promise.resolve(t.value).then(n,r);a((i=i.apply(s,o)).next())});var s,o,i}static inject(){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.inject" method can only be used in iframe');e.postMessage({type:"inject"}),window.addEventListener("message",e.onMessage)}constructor({iframe:e,url:t}){if(e instanceof HTMLIFrameElement===!1)throw new TypeError('"option.iframe" needs to be a HTMLIFrameElement');if("string"!==typeof t)throw new TypeError('"option.url" needs to be a string');this.url=t,this.$iframe=e,this.promises={},this.injected=!1,this.destroyed=!1,this.messageCallback=()=>null,this.onMessage=this.onMessage.bind(this),window.addEventListener("message",this.onMessage),this.$iframe.src=this.url}onMessage(e){const{type:t,data:s,id:o}=e.data;if("inject"===t)this.injected=!0;this.promises[o]&&("error"===t?this.promises[o].reject(new Error(s)):this.promises[o].resove(s),delete this.promises[o]),this.messageCallback&&this.messageCallback({type:t,data:s})}postMessage({type:e,data:t}){return new Promise((s,o)=>{(function i(){if(this.destroyed)o(new Error("The instance has been destroyed"));else if(this.injected){const i=Date.now();this.promises[i]={resove:s,reject:o},this.$iframe.contentWindow.postMessage({type:e,data:t,id:i},"*")}else setTimeout(i.bind(this),200)}).call(this)})}commit(e){if("function"!==typeof e)throw new TypeError('"commit.callback" needs to be a function');const t=e.toString(),s=t.substring(t.indexOf("{")+1,t.lastIndexOf("}"));return this.postMessage({type:"commit",data:s})}message(e){if("function"!==typeof e)throw new TypeError('"message.callback" needs to be a function');this.messageCallback=e}destroy(){this.destroyed=!0,window.removeEventListener("message",this.onMessage)}}return e}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolIframe=t(),define(function(){return e.ArtplayerToolIframe})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolIframe=t()}(this,function(){"use strict";const e=new WeakMap;let t=0;class s{static get iframe(){return window.top!==window}static postMessage({type:e,data:t,id:o=0}){if(!s.iframe)throw new Error('The "ArtplayerToolIframe.postMessage" method can only be used in iframe');window.parent.postMessage({type:e,data:t,id:o},"*")}static onMessage(e){return t=this,o=null,r=function*(){if(!s.iframe)throw new Error('The "ArtplayerToolIframe.onMessage" method can only be used in iframe');const{type:t,data:o,id:r}=e.data;if("commit"===t)try{if(o.match(/\bresolve\((.*?)\)/)){const e=`return new Promise(function(resolve){\n${o}\n})`,t=yield new Function(e)();s.postMessage({type:"response",data:t,id:r})}else{const e=new Function(o)();s.postMessage({type:"response",data:e,id:r})}}catch(i){throw s.postMessage({type:"error",data:i.message,id:r}),i}},new Promise((e,s)=>{var i=e=>{try{a(r.next(e))}catch(t){s(t)}},n=e=>{try{a(r.throw(e))}catch(t){s(t)}},a=t=>t.done?e(t.value):Promise.resolve(t.value).then(i,n);a((r=r.apply(t,o)).next())});var t,o,r}static inject(){if(!s.iframe)throw new Error('The "ArtplayerToolIframe.inject" method can only be used in iframe');s.postMessage({type:"inject"}),window.addEventListener("message",s.onMessage)}constructor({iframe:e,url:t}){if(e instanceof HTMLIFrameElement===!1)throw new TypeError('"option.iframe" needs to be a HTMLIFrameElement');if("string"!==typeof t)throw new TypeError('"option.url" needs to be a string');this.url=t,this.$iframe=e,this.promises={},this.injected=!1,this.destroyed=!1,this.messageCallback=()=>null,this.onMessage=this.onMessage.bind(this),window.addEventListener("message",this.onMessage),this.$iframe.src=this.url}onMessage(e){if(this.destroyed)return;const{type:t,data:s,id:o}=e.data;if("inject"===t)this.injected=!0;void 0!==o&&Object.prototype.hasOwnProperty.call(this.promises,o)&&this.promises[o]&&("error"===t?this.promises[o].reject(new Error(s)):this.promises[o].resove(s),delete this.promises[o]),this.messageCallback&&this.messageCallback({type:t,data:s})}postMessage(s){return function(s,{type:o,data:r}){return new Promise((i,n)=>{if(s.destroyed)return void n(new Error("The instance has been destroyed"));const a=e.get(s)||new Set;e.set(s,a);const d=t=>!!a.delete(t)&&(void 0!==t.timer&&clearTimeout(t.timer),void 0!==t.id&&delete s.promises[t.id],a.size||e.delete(s),!0),c={callbacks:{resove(e){d(c)&&i(e)},reject(e){d(c)&&n(e)}}};a.add(c);const l=()=>{if(c.timer=void 0,a.has(c))if(s.destroyed)c.callbacks.reject(new Error("The instance has been destroyed"));else try{if(s.injected){const e=Math.max(Date.now(),t+1);t=e,c.id=e,s.promises[e]=c.callbacks,s.$iframe.contentWindow.postMessage({type:o,data:r,id:e},"*")}else c.timer=setTimeout(l,200)}catch(e){c.callbacks.reject(e)}};l()})}(this,s)}commit(e){if("function"!==typeof e)throw new TypeError('"commit.callback" needs to be a function');const t=e.toString(),s=t.substring(t.indexOf("{")+1,t.lastIndexOf("}"));return this.postMessage({type:"commit",data:s})}message(e){if("function"!==typeof e)throw new TypeError('"message.callback" needs to be a function');this.messageCallback=e}destroy(){this.destroyed=!0,window.removeEventListener("message",this.onMessage),function(t){const s=e.get(t);if(s)for(const e of[...s])e.callbacks.reject(new Error("The instance has been destroyed"))}(this)}}return s}); diff --git a/packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.mjs b/packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.mjs index 3e99c0366..504da4eec 100644 --- a/packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.mjs +++ b/packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.mjs @@ -4,6 +4,72 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ +const requests = /* @__PURE__ */ new WeakMap(); +let lastId = 0; +function postRequest(host, { type, data }) { + return new Promise((resolve, reject) => { + if (host.destroyed) { + reject(new Error("The instance has been destroyed")); + return; + } + const pending = requests.get(host) || /* @__PURE__ */ new Set(); + requests.set(host, pending); + const finish = (request2) => { + if (!pending.delete(request2)) + return false; + if (request2.timer !== void 0) + clearTimeout(request2.timer); + if (request2.id !== void 0) + delete host.promises[request2.id]; + if (!pending.size) + requests.delete(host); + return true; + }; + const request = { + callbacks: { + resove(value) { + if (finish(request)) + resolve(value); + }, + reject(error) { + if (finish(request)) + reject(error); + } + } + }; + pending.add(request); + const loop = () => { + request.timer = void 0; + if (!pending.has(request)) + return; + if (host.destroyed) { + request.callbacks.reject(new Error("The instance has been destroyed")); + return; + } + try { + if (host.injected) { + const id = Math.max(Date.now(), lastId + 1); + lastId = id; + request.id = id; + host.promises[id] = request.callbacks; + host.$iframe.contentWindow.postMessage({ type, data, id }, "*"); + } else { + request.timer = setTimeout(loop, 200); + } + } catch (error) { + request.callbacks.reject(error); + } + }; + loop(); + }); +} +function cancelRequests(host) { + const pending = requests.get(host); + if (!pending) + return; + for (const request of [...pending]) + request.callbacks.reject(new Error("The instance has been destroyed")); +} class ArtplayerToolIframe { static get iframe() { return window.top !== window; @@ -71,13 +137,15 @@ ${data} this.$iframe.src = this.url; } onMessage(event) { + if (this.destroyed) + return; const { type, data, id } = event.data; switch (type) { case "inject": this.injected = true; break; } - if (this.promises[id]) { + if (id !== void 0 && Object.prototype.hasOwnProperty.call(this.promises, id) && this.promises[id]) { if (type === "error") { this.promises[id].reject(new Error(data)); } else { @@ -89,29 +157,8 @@ ${data} this.messageCallback({ type, data }); } } - postMessage({ type, data }) { - return new Promise((resove, reject) => { - (function loop() { - if (this.destroyed) { - reject(new Error("The instance has been destroyed")); - } else { - if (this.injected) { - const id = Date.now(); - this.promises[id] = { resove, reject }; - this.$iframe.contentWindow.postMessage( - { - type, - data, - id - }, - "*" - ); - } else { - setTimeout(loop.bind(this), 200); - } - } - }).call(this); - }); + postMessage(message) { + return postRequest(this, message); } commit(callback) { if (typeof callback !== "function") { @@ -130,6 +177,7 @@ ${data} destroy() { this.destroyed = true; window.removeEventListener("message", this.onMessage); + cancelRequests(this); } } export { diff --git a/packages/artplayer-tool-iframe/src/index.js b/packages/artplayer-tool-iframe/src/index.ts similarity index 74% rename from packages/artplayer-tool-iframe/src/index.js rename to packages/artplayer-tool-iframe/src/index.ts index cfe9f3619..6a7e6309a 100644 --- a/packages/artplayer-tool-iframe/src/index.js +++ b/packages/artplayer-tool-iframe/src/index.ts @@ -1,9 +1,19 @@ +import type { Callbacks, Packet } from './requests' +import { cancelRequests, postRequest } from './requests' + export default class ArtplayerToolIframe { + declare url: string + declare $iframe: HTMLIFrameElement + declare promises: Record + declare injected: boolean + declare destroyed: boolean + declare messageCallback: ((...args: any[]) => any) | null + static get iframe() { return window.top !== window } - static postMessage({ type, data, id = 0 }) { + static postMessage({ type, data, id = 0 }: Packet) { if (!ArtplayerToolIframe.iframe) { throw new Error('The "ArtplayerToolIframe.postMessage" method can only be used in iframe') } @@ -18,7 +28,7 @@ export default class ArtplayerToolIframe { ) } - static async onMessage(event) { + static async onMessage(event: MessageEvent) { if (!ArtplayerToolIframe.iframe) { throw new Error('The "ArtplayerToolIframe.onMessage" method can only be used in iframe') } @@ -40,7 +50,7 @@ export default class ArtplayerToolIframe { } } catch (error) { - ArtplayerToolIframe.postMessage({ type: 'error', data: error.message, id }) + ArtplayerToolIframe.postMessage({ type: 'error', data: (error as { message: string }).message, id }) throw error } break @@ -58,7 +68,7 @@ export default class ArtplayerToolIframe { window.addEventListener('message', ArtplayerToolIframe.onMessage) } - constructor({ iframe, url }) { + constructor({ iframe, url }: { iframe: HTMLIFrameElement, url: string }) { if (iframe instanceof HTMLIFrameElement === false) { throw new TypeError('"option.iframe" needs to be a HTMLIFrameElement') } @@ -78,7 +88,10 @@ export default class ArtplayerToolIframe { this.$iframe.src = this.url } - onMessage(event) { + onMessage(event: MessageEvent) { + if (this.destroyed) + return + const { type, data, id } = event.data switch (type) { @@ -89,12 +102,12 @@ export default class ArtplayerToolIframe { break } - if (this.promises[id]) { + if (id !== undefined && Object.prototype.hasOwnProperty.call(this.promises, id) && this.promises[id]) { if (type === 'error') { - this.promises[id].reject(new Error(data)) + this.promises[id]!.reject(new Error(data)) } else { - this.promises[id].resove(data) + this.promises[id]!.resove(data) } delete this.promises[id] } @@ -104,34 +117,11 @@ export default class ArtplayerToolIframe { } } - postMessage({ type, data }) { - return new Promise((resove, reject) => { - (function loop() { - if (this.destroyed) { - reject(new Error('The instance has been destroyed')) - } - else { - if (this.injected) { - const id = Date.now() - this.promises[id] = { resove, reject } - this.$iframe.contentWindow.postMessage( - { - type, - data, - id, - }, - '*', - ) - } - else { - setTimeout(loop.bind(this), 200) - } - } - }).call(this) - }) + postMessage(message: Packet): Promise { + return postRequest(this, message) } - commit(callback) { + commit any>(callback: T): Promise> { if (typeof callback !== 'function') { throw new TypeError('"commit.callback" needs to be a function') } @@ -140,7 +130,7 @@ export default class ArtplayerToolIframe { return this.postMessage({ type: 'commit', data: bodyString }) } - message(callback) { + message(callback: (...args: any[]) => any) { if (typeof callback !== 'function') { throw new TypeError('"message.callback" needs to be a function') } @@ -150,5 +140,6 @@ export default class ArtplayerToolIframe { destroy() { this.destroyed = true window.removeEventListener('message', this.onMessage) + cancelRequests(this) } } diff --git a/packages/artplayer-tool-iframe/src/requests.ts b/packages/artplayer-tool-iframe/src/requests.ts new file mode 100644 index 000000000..2855a545c --- /dev/null +++ b/packages/artplayer-tool-iframe/src/requests.ts @@ -0,0 +1,94 @@ +export interface Packet { + type: string + data?: any + id?: number +} + +export interface Callbacks { + resove: (value?: any) => void + reject: (reason?: any) => void +} + +export interface RequestHost { + $iframe: HTMLIFrameElement + promises: Record + injected: boolean + destroyed: boolean +} + +interface Pending { + id?: number + timer?: ReturnType + callbacks: Callbacks +} + +const requests = new WeakMap>() +let lastId = 0 + +export function postRequest(host: RequestHost, { type, data }: Packet): Promise { + return new Promise((resolve, reject) => { + if (host.destroyed) { + reject(new Error('The instance has been destroyed')) + return + } + const pending = requests.get(host) || new Set() + requests.set(host, pending) + const finish = (request: Pending) => { + if (!pending.delete(request)) + return false + if (request.timer !== undefined) + clearTimeout(request.timer) + if (request.id !== undefined) + delete host.promises[request.id] + if (!pending.size) + requests.delete(host) + return true + } + const request: Pending = { + callbacks: { + resove(value) { + if (finish(request)) + resolve(value) + }, + reject(error) { + if (finish(request)) + reject(error) + }, + }, + } + pending.add(request) + const loop = () => { + request.timer = undefined + if (!pending.has(request)) + return + if (host.destroyed) { + request.callbacks.reject(new Error('The instance has been destroyed')) + return + } + try { + if (host.injected) { + const id = Math.max(Date.now(), lastId + 1) + lastId = id + request.id = id + host.promises[id] = request.callbacks + host.$iframe.contentWindow!.postMessage({ type, data, id }, '*') + } + else { + request.timer = setTimeout(loop, 200) + } + } + catch (error) { + request.callbacks.reject(error) + } + } + loop() + }) +} + +export function cancelRequests(host: RequestHost): void { + const pending = requests.get(host) + if (!pending) + return + for (const request of [...pending]) + request.callbacks.reject(new Error('The instance has been destroyed')) +} diff --git a/packages/artplayer-tool-iframe/tsconfig.json b/packages/artplayer-tool-iframe/tsconfig.json new file mode 100644 index 000000000..285047f94 --- /dev/null +++ b/packages/artplayer-tool-iframe/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "allowJs": false }, + "include": ["src/**/*.ts", "types/**/*.d.ts"] +} diff --git a/refactor/baselines/iframe-requests-checkpoint.json b/refactor/baselines/iframe-requests-checkpoint.json new file mode 100644 index 000000000..d80b78a5c --- /dev/null +++ b/refactor/baselines/iframe-requests-checkpoint.json @@ -0,0 +1,3049 @@ +{ + "task": "PKG-IFRAME-03", + "status": "doing", + "checkpoint": "verified-request-ownership", + "sourceSha256LF": { + "package.json": "71ab122e3c1eea872920b1269d72c7112de5d9871b260c13b9015295f55c67aa", + "packages/artplayer-tool-iframe/src/index.ts": "a698c3cf690c4fcfb508cd4fc89a0c3e439a87e8312108a4f863a1cb5c9deb67", + "packages/artplayer-tool-iframe/src/requests.ts": "f84dceace6b0c88cc2d1e8545cf0017960b0bcb58093909447f5861b0557abe6", + "packages/artplayer-tool-iframe/tsconfig.json": "712a3fa32ed57692d16bf854a0e77ffaecd04236efa0f557905aab7291e0aef6", + "packages/artplayer-tool-iframe/types/artplayer-tool-iframe.d.ts": "4f02aa22a6b373393ead533ae3421ad5acf0de7116057f351021c72d3448bd1e", + "packages/artplayer-tool-iframe/README.md": "7d862afc9e3177e33cb04ba51695883ae5b412a77d27551232dce60bddf8f146", + "packages/artplayer-tool-iframe/ARCHITECTURE.md": "635e3be6814d5e0e3fd07201fa213c5d48e23f6749239d8fbbd10e7ec1ab1483", + "test/iframe-lifecycle.test.js": "96273d46077c28069504643a989a4d628e875c9aa49fb62e28ac160f7e7aaae1", + "test/browser/iframe.spec.js": "50add3cec28e94947ac48466860056cb43b9f47b0d08ded816124805567484d0", + "test/helpers/iframe.js": "0aae4805e1bfa8d6cddf1e558daa080fc67f7af8f10b92bc5e8df0d4d28f63f0" + }, + "artifacts": { + "packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.js": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.legacy.js": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.mjs": "1690846fea7a1b13cc0eac2a91bdce72f7dd6029535678b60b3584714cd0900c" + }, + "historical": { + "node": { + "tests": 16, + "passed": 5, + "failed": 11, + "log": "refactor/.cache/iframe03-before-node.log" + }, + "browser": { + "tests": 10, + "passed": 2, + "failed": 8, + "report": "refactor/.cache/iframe03-before-results/report.json", + "results": "refactor/.cache/iframe03-before-results/results" + }, + "emptyRegistryComparison": "refactor/.cache/iframe03-empty-record-before.log" + }, + "node": [ + { + "kind": "source", + "tests": 16, + "passed": 16, + "log": "refactor/.cache/iframe03-source-node-final.log" + }, + { + "kind": "main", + "tests": 16, + "passed": 16, + "log": "refactor/.cache/iframe03-main-node-final.log" + }, + { + "kind": "legacy", + "tests": 16, + "passed": 16, + "log": "refactor/.cache/iframe03-legacy-node-final.log" + } + ], + "browser": [ + { + "kind": "main", + "total": 174, + "candidateLifecycle": 30, + "historicalAssertions": 144, + "report": "refactor/.cache/iframe03-final-main-results/report.json", + "sha256": "9941c27277b5e4614df64fc44daa193841143066b2ab62d4d833e9054e9964bc", + "results": "refactor/.cache/iframe03-final-main-results/results", + "records": [ + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "round-trip", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "sync": 7, + "async": 8, + "error": "iframe baseline expected error", + "recovery": 9, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 7, + "id": 1789227465525, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 8, + "id": 1789227465526, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "error", + "data": "iframe baseline expected error", + "id": 1789227465536, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 9, + "id": 1789227465537, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "same-tick", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "states": { + "first": { + "status": "resolved", + "value": 1 + }, + "second": { + "status": "resolved", + "value": 2 + } + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 1, + "id": 1234, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 2, + "id": 1235, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "destroy-pending", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "destroyed": true, + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-held", + "data": 1789227465834, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": "late", + "id": 1789227465834, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "destroy-wait", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "clone-failure", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "failure": "DataCloneError", + "deferred": { + "status": "rejected", + "error": "Failed to execute 'postMessage' on 'Window': () => 'not cloneable' could not be cloned." + }, + "pending": 0, + "uncaught": [] + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "round-trip", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "sync": 7, + "async": 8, + "error": "iframe baseline expected error", + "recovery": 9, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 7, + "id": 1789227466161, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 8, + "id": 1789227466162, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "error", + "data": "iframe baseline expected error", + "id": 1789227466174, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 9, + "id": 1789227466175, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "same-tick", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "states": { + "first": { + "status": "resolved", + "value": 1 + }, + "second": { + "status": "resolved", + "value": 2 + } + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 1, + "id": 1234, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 2, + "id": 1235, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "destroy-pending", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "destroyed": true, + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-held", + "data": 1789227466469, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": "late", + "id": 1789227466469, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "destroy-wait", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "clone-failure", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "failure": "DataCloneError", + "deferred": { + "status": "rejected", + "error": "Failed to execute 'postMessage' on 'Window': () => 'not cloneable' could not be cloned." + }, + "pending": 0, + "uncaught": [] + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "round-trip", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "sync": 7, + "async": 8, + "error": "iframe baseline expected error", + "recovery": 9, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 7, + "id": 1789227483728, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 8, + "id": 1789227483729, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "error", + "data": "iframe baseline expected error", + "id": 1789227483739, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 9, + "id": 1789227483740, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "same-tick", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "states": { + "first": { + "status": "resolved", + "value": 1 + }, + "second": { + "status": "resolved", + "value": 2 + } + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 1, + "id": 1234, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 2, + "id": 1235, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "destroy-pending", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "destroyed": true, + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-held", + "data": 1789227484296, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": "late", + "id": 1789227484296, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "destroy-wait", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "clone-failure", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "failure": "DataCloneError", + "deferred": { + "status": "rejected", + "error": "Function object could not be cloned." + }, + "pending": 0, + "uncaught": [] + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "round-trip", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "sync": 7, + "async": 8, + "error": "iframe baseline expected error", + "recovery": 9, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 7, + "id": 1789227485008, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 8, + "id": 1789227485009, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "error", + "data": "iframe baseline expected error", + "id": 1789227485019, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 9, + "id": 1789227485020, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "same-tick", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "states": { + "first": { + "status": "resolved", + "value": 1 + }, + "second": { + "status": "resolved", + "value": 2 + } + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 1, + "id": 1234, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 2, + "id": 1235, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "destroy-pending", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "destroyed": true, + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-held", + "data": 1789227485716, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": "late", + "id": 1789227485716, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "destroy-wait", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "clone-failure", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "failure": "DataCloneError", + "deferred": { + "status": "rejected", + "error": "Function object could not be cloned." + }, + "pending": 0, + "uncaught": [] + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "round-trip", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "sync": 7, + "async": 8, + "error": "iframe baseline expected error", + "recovery": 9, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 7, + "id": 1789227504497, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 8, + "id": 1789227504511, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "error", + "data": "iframe baseline expected error", + "id": 1789227504557, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 9, + "id": 1789227504587, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "same-tick", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "states": { + "first": { + "status": "resolved", + "value": 1 + }, + "second": { + "status": "resolved", + "value": 2 + } + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 1, + "id": 1234, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 2, + "id": 1235, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "destroy-pending", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "destroyed": true, + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-held", + "data": 1789227505258, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": "late", + "id": 1789227505258, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "destroy-wait", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "same-origin", + "scenario": "clone-failure", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "failure": "DataCloneError", + "deferred": { + "status": "rejected", + "error": "The object can not be cloned." + }, + "pending": 0, + "uncaught": [] + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "round-trip", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "sync": 7, + "async": 8, + "error": "iframe baseline expected error", + "recovery": 9, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 7, + "id": 1789227505888, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 8, + "id": 1789227505904, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "error", + "data": "iframe baseline expected error", + "id": 1789227505951, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 9, + "id": 1789227505981, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "same-tick", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "states": { + "first": { + "status": "resolved", + "value": 1 + }, + "second": { + "status": "resolved", + "value": 2 + } + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 1, + "id": 1234, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 2, + "id": 1235, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "destroy-pending", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "destroyed": true, + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-held", + "data": 1789227506637, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": "late", + "id": 1789227506637, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "destroy-wait", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "307c84030a0c9eff44437b2b9a8af58f06c3350bc97a4c318aca831263028624", + "relation": "cross-origin", + "scenario": "clone-failure", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "failure": "DataCloneError", + "deferred": { + "status": "rejected", + "error": "The object can not be cloned." + }, + "pending": 0, + "uncaught": [] + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + } + ] + }, + { + "kind": "legacy", + "total": 30, + "candidateLifecycle": 30, + "historicalAssertions": 0, + "report": "refactor/.cache/iframe03-final-legacy-results/report.json", + "sha256": "b9d4bea4bf5a3f9f15d3cf603cd79835c6bfaaec64427a16da7af863585506f7", + "results": "refactor/.cache/iframe03-final-legacy-results/results", + "records": [ + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "round-trip", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "sync": 7, + "async": 8, + "error": "iframe baseline expected error", + "recovery": 9, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 7, + "id": 1789227562622, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 8, + "id": 1789227562623, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "error", + "data": "iframe baseline expected error", + "id": 1789227562634, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 9, + "id": 1789227562635, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "same-tick", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "states": { + "first": { + "status": "resolved", + "value": 1 + }, + "second": { + "status": "resolved", + "value": 2 + } + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 1, + "id": 1234, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 2, + "id": 1235, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "destroy-pending", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "destroyed": true, + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-held", + "data": 1789227562622, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": "late", + "id": 1789227562622, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "destroy-wait", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "clone-failure", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "failure": "DataCloneError", + "deferred": { + "status": "rejected", + "error": "Failed to execute 'postMessage' on 'Window': () => 'not cloneable' could not be cloned." + }, + "pending": 0, + "uncaught": [] + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "round-trip", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "sync": 7, + "async": 8, + "error": "iframe baseline expected error", + "recovery": 9, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 7, + "id": 1789227563039, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 8, + "id": 1789227563040, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "error", + "data": "iframe baseline expected error", + "id": 1789227563050, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 9, + "id": 1789227563051, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "same-tick", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "states": { + "first": { + "status": "resolved", + "value": 1 + }, + "second": { + "status": "resolved", + "value": 2 + } + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 1, + "id": 1234, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 2, + "id": 1235, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "destroy-pending", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "destroyed": true, + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-held", + "data": 1789227563362, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": "late", + "id": 1789227563362, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "destroy-wait", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "chromium", + "browser": "153.0.8010.12", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "clone-failure", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "failure": "DataCloneError", + "deferred": { + "status": "rejected", + "error": "Failed to execute 'postMessage' on 'Window': () => 'not cloneable' could not be cloned." + }, + "pending": 0, + "uncaught": [] + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "round-trip", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "sync": 7, + "async": 8, + "error": "iframe baseline expected error", + "recovery": 9, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 7, + "id": 1789227567305, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 8, + "id": 1789227567306, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "error", + "data": "iframe baseline expected error", + "id": 1789227567327, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 9, + "id": 1789227567328, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "same-tick", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "states": { + "first": { + "status": "resolved", + "value": 1 + }, + "second": { + "status": "resolved", + "value": 2 + } + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 1, + "id": 1234, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 2, + "id": 1235, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "destroy-pending", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "destroyed": true, + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-held", + "data": 1789227568861, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": "late", + "id": 1789227568861, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "destroy-wait", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "clone-failure", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "failure": "DataCloneError", + "deferred": { + "status": "rejected", + "error": "Function object could not be cloned." + }, + "pending": 0, + "uncaught": [] + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "round-trip", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "sync": 7, + "async": 8, + "error": "iframe baseline expected error", + "recovery": 9, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 7, + "id": 1789227570517, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 8, + "id": 1789227570518, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "error", + "data": "iframe baseline expected error", + "id": 1789227570529, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 9, + "id": 1789227570530, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "same-tick", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "states": { + "first": { + "status": "resolved", + "value": 1 + }, + "second": { + "status": "resolved", + "value": 2 + } + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 1, + "id": 1234, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 2, + "id": 1235, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "destroy-pending", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "destroyed": true, + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-held", + "data": 1789227571357, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": "late", + "id": 1789227571357, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "destroy-wait", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "firefox", + "browser": "155.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "clone-failure", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "failure": "DataCloneError", + "deferred": { + "status": "rejected", + "error": "Function object could not be cloned." + }, + "pending": 0, + "uncaught": [] + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "round-trip", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "sync": 7, + "async": 8, + "error": "iframe baseline expected error", + "recovery": 9, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 7, + "id": 1789227577975, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 8, + "id": 1789227577984, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "error", + "data": "iframe baseline expected error", + "id": 1789227578030, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 9, + "id": 1789227578061, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "same-tick", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "states": { + "first": { + "status": "resolved", + "value": 1 + }, + "second": { + "status": "resolved", + "value": 2 + } + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 1, + "id": 1234, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": 2, + "id": 1235, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "destroy-pending", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "destroyed": true, + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "fixture-held", + "data": 1789227578107, + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "response", + "data": "late", + "id": 1789227578107, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "destroy-wait", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "same-origin", + "scenario": "clone-failure", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://127.0.0.1:8084", + "foreignOrigin": "http://localhost:8084", + "result": { + "failure": "DataCloneError", + "deferred": { + "status": "rejected", + "error": "The object can not be cloned." + }, + "pending": 0, + "uncaught": [] + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://127.0.0.1:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://127.0.0.1:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "round-trip", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "sync": 7, + "async": 8, + "error": "iframe baseline expected error", + "recovery": 9, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 7, + "id": 1789227579702, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 8, + "id": 1789227579712, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "error", + "data": "iframe baseline expected error", + "id": 1789227579758, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 9, + "id": 1789227579789, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "same-tick", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "states": { + "first": { + "status": "resolved", + "value": 1 + }, + "second": { + "status": "resolved", + "value": 2 + } + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 1, + "id": 1234, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": 2, + "id": 1235, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "destroy-pending", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "destroyed": true, + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-ready", + "data": "?auto=1&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "fixture-held", + "data": 1789227580236, + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "response", + "data": "late", + "id": 1789227580236, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "destroy-wait", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "state": { + "status": "rejected", + "error": "The instance has been destroyed" + }, + "pending": 0 + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + }, + { + "project": "webkit", + "browser": "26.6", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "platform": "win32", + "implementation": "candidate-artifact", + "sha256": "dc72986d86f8b5959acd673450c67cef7fe5e6384a586ba3ee5496e755046f52", + "relation": "cross-origin", + "scenario": "clone-failure", + "parentOrigin": "http://127.0.0.1:8084", + "childOrigin": "http://localhost:8084", + "foreignOrigin": "http://127.0.0.1:8084", + "result": { + "failure": "DataCloneError", + "deferred": { + "status": "rejected", + "error": "The object can not be cloned." + }, + "pending": 0, + "uncaught": [] + }, + "packets": [ + { + "type": "fixture-ready", + "data": "?auto=0&epoch=1", + "origin": "http://localhost:8084", + "fromChild": true + }, + { + "type": "inject", + "id": 0, + "origin": "http://localhost:8084", + "fromChild": true + } + ], + "lifecycleAcceptance": true, + "scope": "Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet." + } + ] + } + ], + "checks": [ + { + "command": "yarn ci:check", + "result": "pass", + "exitCode": 0, + "log": "refactor/.cache/iframe03-ci-final.log", + "tests": 1505, + "unit": 1331, + "engineering": 14, + "baseline": 160, + "repeatedContracts": 44, + "productionTypeScriptFiles": 326 + }, + { + "command": "yarn test:imports", + "result": "pass", + "exitCode": 0, + "tests": 3, + "log": "refactor/.cache/iframe03-imports-final.log" + } + ], + "priorCandidate": [ + "refactor/.cache/iframe03-main-results/report.json", + "refactor/.cache/iframe03-legacy-results/report.json", + "refactor/.cache/iframe03-ci.log" + ], + "build": { + "failed": "refactor/.cache/iframe03-build-final.log", + "failure": "Windows copyfile UNKNOWN while copying legacy output into docs; cause not established", + "recovered": "refactor/.cache/iframe03-build-recovered.log", + "result": "normal build rerun passed; all three docs copies byte-identical" + }, + "limitations": [ + "Navigation/reinjection and malformed-message handling remain open.", + "Source/origin trust policy is unchanged and unsafe foreign-window behavior remains open.", + "No player/media/demo integration is claimed by tool-only iframe fixtures.", + "Full old-name/helper/declaration/distribution consumers remain separate PKG-IFRAME-04/05/06 gates.", + "Independent module copies do not share request ID allocation." + ] +} diff --git a/refactor/changes/2026-09-12-PKG-IFRAME-03-requests-checkpoint.md b/refactor/changes/2026-09-12-PKG-IFRAME-03-requests-checkpoint.md new file mode 100644 index 000000000..281503e18 --- /dev/null +++ b/refactor/changes/2026-09-12-PKG-IFRAME-03-requests-checkpoint.md @@ -0,0 +1,82 @@ +# PKG-IFRAME-03 request ownership checkpoint (doing) + +The old single JS file is now a strict TS public entry plus a request ownership +module. Entry methods and fields remain in place; the private WeakMap does not +add reflective instance keys. Source types are checked by a new package tsconfig, +without changing published declarations or installing dependencies. Package +ARCHITECTURE.md explains state, ownership, compatibility and follow-up work. + +## Corrections and compatibility + +| Boundary | Previous result | Candidate result | +| --- | --- | --- | +| Same millisecond / clock rollback | Callbacks overwrite each other or old IDs recur | Module-wide increasing numeric IDs preserve independent responses | +| Multiple instances, one frame | Each instance can settle from the same response ID | Instances from the same module allocate distinct IDs | +| Destroy sent/waiting request | Sent promise hangs; waiting poll remains until its next tick | Immediate rejection with the existing destroyed Error message, timers/registry released | +| Saved callback after destroy | Can settle or notify | Inert | +| Native send failure | Immediate rejection leaks record; delayed failure escapes callback | Same original error rejects in both paths, all owned state removed | +| Public resove/reject | Promise settles but its registry entry remains | Promise and corresponding owned registry entry released once | +| Inherited response ID | `toString` can be mistaken for a request and throws | Only own registry entries participate, generic message callback still runs | + +These are deliberate fixes under API-02/03/04/05/12, not a new wire format. +Constructor validation, seven field descriptors/order, bound onMessage, callback +receiver/packet shape, numeric envelopes, `resove`, ordinary registry identity, +wildcard target origin, 200ms polling, generic non-error responses, function-body +commit serialization, child sync/resolve timing and child error channel remain. +An ID is now a correlation number rather than an exact clock reading. Callers +must handle pending request rejection on destroy; old promises hung indefinitely. +No automatic timeout, new public option, SDK or version change is introduced. + +The counter is local to a library module; separately evaluated copies are not +coordinated. Source typing uses `any` only for the existing arbitrary message +payload/callback/result boundary; ownership and state are explicit. The child +error `.message` assertion intentionally retains the old error serialization, +including its existing behavior for unusual thrown values. The contentWindow +assertion does not add an availability guarantee: a null window throws inside the +send boundary and rejects the request rather than leaving it owned. + +## Validation + +The same 16 candidate Node assertions against the frozen workspace pass 5 and +fail 11; candidate source passes all 16. The unchanged candidate browser checks +against that old artifact pass 2 round trips and fail 8 lifecycle cases across +same/cross-origin Chromium. Full failure reports/results/traces are archived in +`refactor/.cache/iframe03-before-results`; Node log is `iframe03-before-node.log`. +This contrasts candidate acceptance with the separate historical defect assertions. + +During the independent diff review, the new own-property guard was found to +throw for consumer-cleared `promises[id] = undefined`, whereas the old workspace +ignored it. `iframe03-empty-record-before.log` records that old/current contrast. +The guard now retains the old truthiness check as well; the existing regression +case covers this edge. Earlier passing CI/browser records predate this fix and +remain archived; final candidate builds and checks are rerun against the new bytes. + +Final production/browser/CI results and hashes are recorded in the checkpoint +validation file after execution. No player is created by these iframe-only +fixtures; they do not establish full demo/core/media integration. + +## Remaining work and rollback + +PKG-IFRAME-03 remains doing. Navigation/reinjection, malformed packets, constructor +setup failure cleanup and the independent source/origin policy still need work. +IFRAME-LIFE-01, IFRAME-TRUST-01 and IFRAME-DIST-01 remain open. Trust policy has not +silently changed; current foreign-message behavior is still a known defect. +PKG-IFRAME-04 owns declaration/helper/old-name compatibility and 05/06 own complete +integration/distribution. This checkpoint is not release acceptance. + +Rollback by reverting this checkpoint and rebuilding `artplayer-tool-iframe` +through the normal script. The frozen artifacts and previous evidence remain +immutable. Commit subject: `fix(iframe): [PKG-IFRAME-03] own pending requests and cancellation`. + +Final source/main/legacy Node checks each pass 16. Final main browser matrix +passes 174 (144 historical defect assertions + 30 candidate acceptance), and +final legacy passes 30 candidate rows. All three engines execute real windows; +there are no skips/capability substitutes. CI passes 1505 (1331 unit + 14 +engineering + 160 baseline), 44 repeated contracts and 326 production TS files; +3 import/SSR files pass after the final rebuild. All hashes match archived +reports and the final source/artifacts; docs copies are byte-identical. + +One normal build attempt failed copying the legacy file into docs with Windows +copyfile UNKNOWN; the cause is not established. The failure log is retained, +and a normal build rerun succeeded before final checks. No manual artifact edit +or ignored build failure was used. Status stays doing and all three risks open. diff --git a/refactor/plan.md b/refactor/plan.md index 7dd79d9df..b170773b4 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -4,7 +4,7 @@ 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 222 项,范围 22 个包及工作区/示例。 -状态:todo 106 / doing 9 / blocked 0 / done 107 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 105 / doing 10 / blocked 0 / done 107 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -346,7 +346,7 @@ | --- | --- | --- | --- | --- | --- | --- | | PKG-IFRAME-01 | artplayer-tool-iframe
核对包契约与历史用法 | BASE-05 | constructor/commit/message/inject、postMessage 协议与历史公开拼写 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done | | PKG-IFRAME-02 | artplayer-tool-iframe
建立特有行为与错误测试 | PKG-IFRAME-01, ENG-03, ENG-05 | 跨窗口消息、ID 匹配、请求失败、重复 inject、销毁中请求 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done | -| PKG-IFRAME-03 | artplayer-tool-iframe
整理内部职责与资源 | PKG-IFRAME-02, CORE-02, BASE-07 | 请求注册/响应匹配/监听清理分离;origin/source 安全边界独立决策 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | todo | +| PKG-IFRAME-03 | artplayer-tool-iframe
整理内部职责与资源 | PKG-IFRAME-02, CORE-02, BASE-07 | 请求注册/响应匹配/监听清理分离;origin/source 安全边界独立决策 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | doing | | PKG-IFRAME-04 | artplayer-tool-iframe
迁移自有源码和公开类型 | PKG-IFRAME-03, ENG-04, ENG-06, CORE-07 | 消息联合类型、回调/Promise 推导、旧公开字段兼容;旧npm export=与实际namespace、额外helper协议分别核验 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo | | PKG-IFRAME-05 | artplayer-tool-iframe
验证新旧核心和组合 | PKG-IFRAME-04, CORE-22 | 真实同源/跨源 iframe、既有 commit 协议;安全变化有独立结论 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo | | PKG-IFRAME-06 | artplayer-tool-iframe
验证分发并同步文档 | PKG-IFRAME-05, ENG-07 | iframe.js、示例集成和原 script/class 导出验证;旧包名/额外helper深入口与新工具名的迁移结论 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo | @@ -532,4 +532,5 @@ - PKG-MB-09: [记录](changes/2026-09-12-PKG-MB-09-native-pip-checkpoint.md) [记录](baselines/mb-native-pip-checkpoint.json) [记录](changes/2026-09-12-PKG-MB-09-sustained-playback-checkpoint.md) [记录](baselines/mb-sustained-validation.json) [记录](baselines/mb-sustained-media.json) - PKG-IFRAME-01: [记录](changes/2026-09-12-PKG-IFRAME-01-contract.md) [记录](baselines/iframe-release.json) [记录](baselines/iframe-contract.md) [记录](baselines/iframe-contract-validation.json) - PKG-IFRAME-02: [记录](changes/2026-09-12-PKG-IFRAME-02-behavior.md) [记录](baselines/iframe-behavior-validation.json) +- PKG-IFRAME-03: [记录](changes/2026-09-12-PKG-IFRAME-03-requests-checkpoint.md) [记录](baselines/iframe-requests-checkpoint.json) - PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md) diff --git a/refactor/progress.md b/refactor/progress.md index 42e95c457..e7324ec3b 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,18 @@ # 进度与证据 +## PKG-IFRAME-03 请求资源与TS入口检查点(doing) + +入口和请求管理拆为2个严格TS模块,保留7个公开字段及resove/回调/消息协议。 +修复ID冲突/回拨、跨实例串线、销毁悬挂、轮询发送错误和公开回调的资源泄漏。 +同16断言旧版5通过/11失败,候选源码/main/legacy各16通过;旧版真实Chromium +候选断言2通过/8失败。最终main浏览器174(历史144+候选30)、legacy候选30通过, +完整CI1505与44重复契约、3导入/SSR文件通过,326生产TS。审查发现并修复空记录 +兼容回归;一次Windows复制失败经正常重建恢复,全部旧/失败/最终报告保留。 +见[变更](changes/2026-09-12-PKG-IFRAME-03-requests-checkpoint.md)和 +[验证](baselines/iframe-requests-checkpoint.json)。222项:107 done、10 doing、105 todo。 +本地检查点提交;IFRAME-03和三个风险仍未关闭。下一步导航/重新注入、构造失败清理、 +畸形消息与独立来源信任策略,随后04处理公开声明与旧helper/分发兼容。 + ## PKG-IFRAME-02 完成:历史生命周期与真实窗口缺陷复现 52项Node和144项Chromium/Firefox/WebKit真实窗口检查通过,覆盖4份历史产物、同源/跨源。 diff --git a/refactor/risks.json b/refactor/risks.json index 6377016a2..8e9b040f1 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -3775,14 +3775,16 @@ ], "status": "open", "confirmation": "reproduced", - "compatibleResolution": "Reproduce same-tick request collision, pending injection, response errors and destroy behavior before changing internal ownership; preserve public promises[id].resove.", + "compatibleResolution": "Request ownership checkpoint fixes timestamp collisions, polling/send failures and terminal destruction while preserving public resove/registry/envelopes. Navigation/reinjection and setup failure cleanup remain for PKG-IFRAME-03; keep open until complete actual iframe lifecycle acceptance.", "closureCriteria": "Old behavior and candidate fixes have deterministic race/resource tests plus actual iframe integration.", "evidence": [ "refactor/baselines/iframe-contract.md", "refactor/baselines/iframe-release.json", "refactor/changes/2026-09-12-PKG-IFRAME-01-contract.md", "refactor/baselines/iframe-behavior-validation.json", - "refactor/changes/2026-09-12-PKG-IFRAME-02-behavior.md" + "refactor/changes/2026-09-12-PKG-IFRAME-02-behavior.md", + "refactor/changes/2026-09-12-PKG-IFRAME-03-requests-checkpoint.md", + "refactor/baselines/iframe-requests-checkpoint.json" ] }, { diff --git a/refactor/tasks.json b/refactor/tasks.json index c4ed7bf89..6bdeeeec2 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -3656,11 +3656,14 @@ "CORE-02", "BASE-07" ], - "status": "todo", + "status": "doing", "risk": "H", "deliverable": "请求注册/响应匹配/监听清理分离;origin/source 安全边界独立决策", "acceptance": "结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过", - "evidence": [] + "evidence": [ + "changes/2026-09-12-PKG-IFRAME-03-requests-checkpoint.md", + "baselines/iframe-requests-checkpoint.json" + ] }, { "id": "PKG-IFRAME-04", diff --git a/test/browser/iframe.spec.js b/test/browser/iframe.spec.js index dc1747a84..66b558e90 100644 --- a/test/browser/iframe.spec.js +++ b/test/browser/iframe.spec.js @@ -1,13 +1,20 @@ +import process from 'node:process' import { hash } from '../../refactor/scripts/releases.mjs' -import { iframeHistorical } from '../helpers/iframe.js' +import { iframeCandidate, iframeHistorical } from '../helpers/iframe.js' import { expect, test } from './fixtures.js' -const implementations = (await iframeHistorical()).filter(item => item.global !== 'ArtplayerHelperIframe') +const candidate = { ...await iframeCandidate(), lifecycle: true } +const implementations = process.env.ARTPLAYER_IFRAME_LIFECYCLE_ONLY === '1' + ? [candidate] + : [...(await iframeHistorical()).filter(item => item.global !== 'ArtplayerHelperIframe'), candidate] for (const implementation of implementations) { for (const relation of ['same-origin', 'cross-origin']) { - for (const scenario of ['round-trip', 'same-tick', 'destroy-pending', 'foreign-message', 'navigation', 'clone-failure']) { - test(`Iframe ${implementation.name}/${relation}: ${scenario} historical behavior`, async ({ page }, testInfo) => { + const scenarios = implementation.lifecycle + ? ['round-trip', 'same-tick', 'destroy-pending', 'destroy-wait', 'clone-failure'] + : ['round-trip', 'same-tick', 'destroy-pending', 'foreign-message', 'navigation', 'clone-failure'] + for (const scenario of scenarios) { + test(`Iframe ${implementation.name}/${relation}: ${scenario} ${implementation.lifecycle ? 'lifecycle acceptance' : 'historical behavior'}`, async ({ page }, testInfo) => { await page.goto('/test/player.html?core=published') const parentOrigin = new URL(page.url()).origin const alternate = parentOrigin.replace('127.0.0.1', 'localhost') @@ -33,7 +40,7 @@ for (const implementation of implementations) { ` })) await page.route('**/iframe-baseline-foreign', route => route.fulfill({ contentType: 'text/html', body: '' })) await page.addScriptTag({ content: `(() => { const module = { exports: {} }; const exports = module.exports; ${implementation.code}; window.IframeFactory = module.exports.default || module.exports; })();` }) - const manualInjection = scenario === 'foreign-message' || scenario === 'clone-failure' + const manualInjection = scenario === 'foreign-message' || scenario === 'clone-failure' || scenario === 'destroy-wait' const url = `${childOrigin}/iframe-baseline-child?auto=${manualInjection ? 0 : 1}&epoch=1` await page.evaluate((url) => { window.packets = [] @@ -90,9 +97,11 @@ for (const implementation of implementations) { } finally { Date.now = now } }) - await expect.poll(() => page.evaluate(() => window.packets.filter(packet => packet.type === 'response' && packet.id === 1234).length)).toBe(2) + await expect.poll(() => page.evaluate(() => window.packets.filter(packet => packet.type === 'response').length)).toBe(2) result = await page.evaluate(() => ({ states: window.states, pending: Object.keys(window.tool.promises).length })) - expect(result).toEqual({ states: { first: { status: 'pending' }, second: { status: 'resolved', value: 1 } }, pending: 0 }) + expect(result).toEqual(implementation.lifecycle + ? { states: { first: { status: 'resolved', value: 1 }, second: { status: 'resolved', value: 2 } }, pending: 0 } + : { states: { first: { status: 'pending' }, second: { status: 'resolved', value: 1 } }, pending: 0 }) } else if (scenario === 'destroy-pending') { await page.evaluate(() => window.track('held', window.tool.postMessage({ type: 'hold' }))) @@ -102,7 +111,22 @@ for (const implementation of implementations) { await child.evaluate(() => window.Tool.postMessage({ type: 'response', data: 'late', id: window.held.id })) await expect.poll(() => page.evaluate(() => window.packets.some(packet => packet.type === 'response' && packet.data === 'late'))).toBe(true) result = await page.evaluate(() => ({ destroyed: window.tool.destroyed, state: window.states.held, pending: Object.keys(window.tool.promises).length })) - expect(result).toEqual({ destroyed: true, state: { status: 'pending' }, pending: 1 }) + expect(result).toEqual(implementation.lifecycle + ? { destroyed: true, state: { status: 'rejected', error: 'The instance has been destroyed' }, pending: 0 } + : { destroyed: true, state: { status: 'pending' }, pending: 1 }) + } + else if (scenario === 'destroy-wait') { + result = await page.evaluate(async () => { + window.track('waiting', window.tool.postMessage({ type: 'hold' })) + window.tool.destroy() + await Promise.resolve() + return { state: window.states.waiting, pending: Object.keys(window.tool.promises).length } + }) + expect(result).toEqual({ state: { status: 'rejected', error: 'The instance has been destroyed' }, pending: 0 }) + const child = page.frames().find(frame => frame.url() === url) + await child.evaluate(() => window.Tool.inject()) + await expect.poll(() => page.evaluate(() => window.packets.some(packet => packet.type === 'inject'))).toBe(true) + expect(await page.evaluate(() => window.tool.injected)).toBe(false) } else if (scenario === 'foreign-message') { expect(await page.evaluate(() => window.tool.injected)).toBe(false) @@ -143,9 +167,12 @@ for (const implementation of implementations) { }) const child = page.frames().find(frame => frame.url() === url) await child.evaluate(() => window.Tool.inject()) - await expect.poll(() => page.evaluate(() => window.cloneErrors.length)).toBe(1) + if (implementation.lifecycle) + await expect.poll(() => page.evaluate(() => window.states.deferredClone.status)).toBe('rejected') + else + await expect.poll(() => page.evaluate(() => window.cloneErrors.length)).toBe(1) result = await page.evaluate(async () => { - const deferredId = Object.keys(window.tool.promises)[0] + const deferredId = Object.keys(window.tool.promises)[0] || Date.now() const now = Date.now Date.now = () => Number(deferredId) + 1 let failure @@ -156,7 +183,16 @@ for (const implementation of implementations) { finally { Date.now = now } return { failure, deferred: window.states.deferredClone, pending: Object.keys(window.tool.promises).length, uncaught: window.cloneErrors } }) - expect(result).toEqual({ failure: 'DataCloneError', deferred: { status: 'pending' }, pending: 2, uncaught: ['DataCloneError'] }) + if (implementation.lifecycle) { + expect(result.failure).toBe('DataCloneError') + expect(result.deferred.status).toBe('rejected') + expect(result.deferred.error).toMatch(/clon/i) + expect(result.pending).toBe(0) + expect(result.uncaught).toEqual([]) + } + else { + expect(result).toEqual({ failure: 'DataCloneError', deferred: { status: 'pending' }, pending: 2, uncaught: ['DataCloneError'] }) + } } else { const nextUrl = `${childOrigin}/iframe-baseline-child?auto=0&epoch=2` @@ -177,7 +213,7 @@ for (const implementation of implementations) { } const packets = await page.evaluate(() => window.packets) expect(packets.filter(packet => packet.fromChild).every(packet => packet.origin === childOrigin)).toBe(true) - await testInfo.attach('iframe-behavior', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), relation, scenario, parentOrigin, childOrigin, foreignOrigin, result, packets, scope: 'Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted; known historical defects are asserted, not fixed or waived.' }) }) + await testInfo.attach('iframe-behavior', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), relation, scenario, parentOrigin, childOrigin, foreignOrigin, result, packets, lifecycleAcceptance: Boolean(implementation.lifecycle), scope: 'Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet.' }) }) } finally { await page.evaluate(() => { diff --git a/test/helpers/iframe.js b/test/helpers/iframe.js index b63d8e654..a739678f3 100644 --- a/test/helpers/iframe.js +++ b/test/helpers/iframe.js @@ -1,6 +1,15 @@ +import fs from 'node:fs' +import process from 'node:process' import vm from 'node:vm' import { verifyIframeContract } from '../../refactor/scripts/iframe-contract.mjs' import { readMember } from '../../refactor/scripts/releases.mjs' +import { compilePackage } from './load.js' + +export async function iframeCandidate() { + if (process.env.ARTPLAYER_IFRAME_BASELINE === '1') + return (await iframeHistorical()).find(item => item.name === 'workspace.js') + return { name: process.env.ARTPLAYER_IFRAME_ARTIFACT ? 'candidate-artifact' : 'candidate-source', global: 'ArtplayerToolIframe', namespace: false, code: process.env.ARTPLAYER_IFRAME_ARTIFACT ? fs.readFileSync(process.env.ARTPLAYER_IFRAME_ARTIFACT, 'utf8') : await compilePackage('artplayer-tool-iframe', 'umd') } +} export async function iframeHistorical() { const { baseline, archive, sources } = await verifyIframeContract() diff --git a/test/iframe-lifecycle.test.js b/test/iframe-lifecycle.test.js new file mode 100644 index 000000000..a823eb3d7 --- /dev/null +++ b/test/iframe-lifecycle.test.js @@ -0,0 +1,255 @@ +import assert from 'node:assert/strict' +// eslint-disable-next-line test/no-import-node-test -- Candidate assertions also run unchanged against the frozen workspace. +import test from 'node:test' +import { iframeCandidate, iframeEnvironment } from './helpers/iframe.js' + +const implementation = await iframeCandidate() +const flush = () => new Promise(resolve => setImmediate(resolve)) +function observe(promise) { + const state = { status: 'pending' } + promise.then(value => Object.assign(state, { status: 'resolved', value }), error => Object.assign(state, { status: 'rejected', error: error.message })) + return state +} +function setup(injected = true) { + const env = iframeEnvironment(implementation) + const instance = new env.Factory({ iframe: new env.Frame(), url: '/first' }) + if (injected) + env.dispatch({ type: 'inject' }) + return { ...env, instance } +} +function tick(env) { + for (const [id, timer] of [...env.timers]) { + assert.equal(timer.delay, 200) + env.timers.delete(id) + timer.callback() + } +} + +test('Iframe candidate preserves instance shape, descriptors, binding and listener-before-src order', () => { + const env = setup(false) + const instance = env.instance + assert.deepEqual(Object.getOwnPropertyNames(env.Factory.prototype), ['constructor', 'onMessage', 'postMessage', 'commit', 'message', 'destroy']) + assert.deepEqual(Object.keys(instance), ['url', '$iframe', 'promises', 'injected', 'destroyed', 'messageCallback', 'onMessage']) + for (const key of Object.keys(instance)) { + const descriptor = Object.getOwnPropertyDescriptor(instance, key) + assert(descriptor.writable && descriptor.enumerable && descriptor.configurable) + } + assert.deepEqual(env.order, ['listen:message', 'src']) + assert.equal(instance.messageCallback(), null) + const receiver = instance.onMessage + receiver({ data: { type: 'inject' } }) + assert.equal(instance.injected, true) + assert.equal(instance.destroy(), undefined) +}) + +test('Iframe candidate preserves numeric envelopes, resove spelling and generic response callback receiver', async () => { + const env = setup() + const registry = env.instance.promises + const seen = [] + env.instance.message(function (packet) { + seen.push({ receiver: this === env.instance, ...packet }) + }) + const pending = env.instance.postMessage({ type: 'custom-query', data: 3, id: 900 }) + assert.deepEqual(Object.keys(registry[1234]), ['resove', 'reject']) + assert.deepEqual(JSON.parse(JSON.stringify(env.sent)), [{ packet: { type: 'custom-query', data: 3, id: 1234 }, origin: '*' }]) + env.dispatch({ type: 'custom-result', data: 4, id: 1234 }) + assert.equal(await pending, 4) + assert.equal(registry, env.instance.promises) + assert.deepEqual(Object.keys(registry), []) + assert.deepEqual(seen, [{ receiver: true, type: 'custom-result', data: 4 }]) + env.instance.destroy() +}) + +test('Iframe candidate matches simultaneous requests independently and out of order', async () => { + const env = setup() + const first = observe(env.instance.postMessage({ type: 'one' })) + const second = observe(env.instance.postMessage({ type: 'two' })) + assert.deepEqual(env.sent.map(item => item.packet.id), [1234, 1235]) + env.dispatch({ type: 'response', data: 2, id: 1235 }) + env.dispatch({ type: 'response', data: 1, id: 1234 }) + await flush() + assert.deepEqual([first, second], [{ status: 'resolved', value: 1 }, { status: 'resolved', value: 2 }]) + assert.equal(Object.keys(env.instance.promises).length, 0) + env.instance.destroy() +}) + +test('Iframe candidate never reuses a settled ID when the clock moves backwards', async () => { + const env = setup() + const first = env.instance.postMessage({ type: 'one' }) + env.dispatch({ type: 'response', data: 1, id: 1234 }) + assert.equal(await first, 1) + env.now(1000) + const second = observe(env.instance.postMessage({ type: 'two' })) + assert.equal(env.sent.at(-1).packet.id, 1235) + env.dispatch({ type: 'response', data: 'duplicate', id: 1234 }) + await flush() + assert.equal(second.status, 'pending') + env.dispatch({ type: 'response', data: 2, id: 1235 }) + await flush() + assert.deepEqual(second, { status: 'resolved', value: 2 }) + env.instance.destroy() +}) + +test('Iframe candidate separates two instances sharing the same iframe window', async () => { + const env = setup() + const second = new env.Factory({ iframe: env.instance.$iframe, url: '/first' }) + env.dispatch({ type: 'inject' }) + const one = observe(env.instance.postMessage({ type: 'one' })) + const two = observe(second.postMessage({ type: 'two' })) + assert.deepEqual(env.sent.map(item => item.packet.id), [1234, 1235]) + env.dispatch({ type: 'response', data: 1, id: 1234 }) + env.dispatch({ type: 'response', data: 2, id: 1235 }) + await flush() + assert.deepEqual([one, two], [{ status: 'resolved', value: 1 }, { status: 'resolved', value: 2 }]) + env.instance.destroy() + second.destroy() +}) + +test('Iframe candidate destroys sent requests immediately and ignores saved receivers', async () => { + const env = setup() + const state = observe(env.instance.postMessage({ type: 'one' })) + const receiver = env.instance.onMessage + let calls = 0 + env.instance.message(() => calls++) + env.instance.destroy() + receiver({ data: { type: 'response', data: 'late', id: 1234 } }) + await flush() + assert.deepEqual(state, { status: 'rejected', error: 'The instance has been destroyed' }) + assert.equal(Object.keys(env.instance.promises).length, 0) + assert.equal(calls, 0) + assert.equal(env.handlers.get('message').size, 0) + assert.equal(env.instance.destroy(), undefined) +}) + +test('Iframe candidate destroys waiting requests immediately and cancels every polling timer', async () => { + const env = setup(false) + const states = Array.from({ length: 3 }, () => observe(env.instance.postMessage({ type: 'wait' }))) + const late = [...env.timers.values()].map(timer => timer.callback) + env.instance.destroy() + await flush() + assert.equal(env.timers.size, 0) + assert(states.every(state => state.status === 'rejected' && state.error === 'The instance has been destroyed')) + late.forEach(callback => callback()) + assert.equal(env.timers.size, 0) + assert.equal(env.sent.length, 0) +}) + +test('Iframe candidate preserves 200ms injection waiting and allocates unique IDs after handshake', async () => { + const env = setup(false) + const one = observe(env.instance.postMessage({ type: 'one' })) + const two = observe(env.instance.postMessage({ type: 'two' })) + tick(env) + assert.equal(env.sent.length, 0) + assert.equal(env.timers.size, 2) + env.dispatch({ type: 'inject' }) + assert.equal(env.sent.length, 0) + tick(env) + assert.equal(env.timers.size, 0) + assert.deepEqual(env.sent.map(item => item.packet.id), [1234, 1235]) + env.dispatch({ type: 'response', data: 1, id: 1234 }) + env.dispatch({ type: 'error', data: 'remote failure', id: 1235 }) + await flush() + assert.deepEqual([one, two], [{ status: 'resolved', value: 1 }, { status: 'rejected', error: 'remote failure' }]) + env.instance.destroy() +}) + +for (const deferred of [false, true]) { + test(`Iframe candidate clears ${deferred ? 'deferred' : 'immediate'} send failure and preserves the exact rejection`, async () => { + const env = setup(!deferred) + const failure = new Error('clone failure') + env.instance.$iframe.contentWindow.postMessage = () => { + throw failure + } + const promise = env.instance.postMessage({ type: 'query' }) + const rejected = assert.rejects(promise, error => error === failure) + if (deferred) { + env.dispatch({ type: 'inject' }) + tick(env) + } + await rejected + assert.equal(Object.keys(env.instance.promises).length, 0) + assert.equal(env.timers.size, 0) + env.instance.destroy() + }) +} + +test('Iframe candidate rejects after destroy without allocating a request or timer', async () => { + const env = setup(false) + env.instance.destroy() + await assert.rejects(env.instance.postMessage({ type: 'late' }), /The instance has been destroyed/) + assert.equal(env.timers.size, 0) + assert.equal(env.sent.length, 0) + assert.equal(Object.keys(env.instance.promises).length, 0) +}) + +test('Iframe candidate permits public resove/reject callbacks and releases their owned requests', async () => { + const env = setup() + const first = env.instance.postMessage({ type: 'one' }) + env.instance.promises[1234].resove(5) + assert.equal(await first, 5) + assert.equal(Object.keys(env.instance.promises).length, 0) + const second = env.instance.postMessage({ type: 'two' }) + const rejected = assert.rejects(second, /external rejection/) + env.instance.promises[env.sent.at(-1).packet.id].reject(new Error('external rejection')) + await rejected + assert.equal(Object.keys(env.instance.promises).length, 0) + env.instance.destroy() +}) + +test('Iframe candidate cancels owned promises even when public records were removed by a consumer', async () => { + const env = setup() + const state = observe(env.instance.postMessage({ type: 'one' })) + delete env.instance.promises[1234] + env.instance.destroy() + await flush() + assert.deepEqual(state, { status: 'rejected', error: 'The instance has been destroyed' }) +}) + +test('Iframe candidate ignores inherited request keys without changing generic message callbacks', () => { + const env = setup() + const seen = [] + env.instance.message(packet => seen.push(packet.type)) + assert.doesNotThrow(() => env.dispatch({ type: 'custom', id: 'toString' })) + assert.deepEqual(seen, ['custom']) + env.instance.promises[7] = undefined + assert.doesNotThrow(() => env.dispatch({ type: 'empty', id: 7 })) + assert.deepEqual(seen, ['custom', 'empty']) + env.instance.destroy() +}) + +test('Iframe candidate preserves commit serialization, synchronous response and async resolver behavior', async () => { + const parent = setup() + const promise = parent.instance.commit(() => { + return 7 + }) + const packet = parent.sent[0].packet + assert.equal(packet.data.trim(), 'return 7') + const child = iframeEnvironment(implementation, true) + const handling = child.Factory.onMessage({ data: packet }) + assert.equal(child.sent[0].packet.data, 7) + await handling + parent.dispatch(child.sent[0].packet) + assert.equal(await promise, 7) + const async = child.Factory.onMessage({ data: { type: 'commit', data: 'resolve(8)', id: 18 } }) + assert.equal(child.sent.length, 1) + await async + assert.equal(child.sent[1].packet.data, 8) + parent.instance.destroy() +}) + +test('Iframe candidate preserves child errors, injection repetition and argument validation', async () => { + const parent = setup() + assert.throws(() => new parent.Factory({ iframe: {}, url: '/' }), /needs to be a HTMLIFrameElement/) + assert.throws(() => parent.instance.commit(null), /needs to be a function/) + assert.throws(() => parent.instance.message(null), /needs to be a function/) + assert.throws(() => parent.Factory.inject(), /can only be used in iframe/) + const child = iframeEnvironment(implementation, true) + child.Factory.inject() + child.Factory.inject() + assert.equal(child.handlers.get('message').size, 1) + assert.deepEqual(child.sent.map(item => item.packet.id), [0, 0]) + await assert.rejects(child.Factory.onMessage({ data: { type: 'commit', data: 'throw new Error("child failure")', id: 19 } }), /child failure/) + assert.equal(child.sent.at(-1).packet.type, 'error') + assert.equal(child.sent.at(-1).packet.data, 'child failure') + parent.instance.destroy() +})