char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
688e96e
main
1mod common; 2 3use common:: run; 4 5# [ test ] 6fn message_channel_schedules_fifo_events_and_preserves_event_brands () { 7let output =run ( 8r#" 9if (!(new MessageChannel() instanceof Object) || !(new MessageEvent('message') instanceof Event)) { 10throw Error('Message interfaces'); 11} 12try { new MessagePort(); throw Error('MessagePort constructor'); } 13catch (error) { if (!(error instanceof TypeError)) throw error; } 14const legacy = new MessageEvent('old', {data: 1, ports: []}); 15legacy.initMessageEvent('new', false, true, 2); 16if (legacy.type !== 'new' || legacy.data !== 2 || !legacy.cancelable) throw Error('initMessageEvent'); 17const channel = new MessageChannel(), seen = []; 18if (Object.hasOwn(channel, 'port1') || Object.hasOwn(channel, 'port2') || 19channel.port1 === channel.port2) throw Error('MessageChannel attributes'); 20try { structuredClone(channel); throw Error('Cloned MessageChannel'); } 21catch (error) { if (error.name !== 'DataCloneError') throw error; } 22channel.port2.addEventListener('message', event => { 23const data = event.data; 24event.initMessageEvent('changed', false, false, 'changed'); 25if (event.type !== 'message' || event.data !== data) throw Error('initMessageEvent during dispatch'); 26seen.push([data, event.isTrusted, event.target === channel.port2, 27event.currentTarget === channel.port2, event.ports]); 28}); 29const snapshot = {value: 1}; 30channel.port1.postMessage(snapshot); 31snapshot.value = 3; 32channel.port1.postMessage(2); 33await new Promise(resolve => setTimeout(resolve, 0)); 34if (seen.length !== 0) throw Error('addEventListener started the port'); 35channel.port2.start(); 36await new Promise(resolve => setTimeout(resolve, 10)); 37if (seen.length !== 2 || seen[0][0].value !== 1 || seen[1][0] !== 2 38|| seen.some(event => !event[1] || !event[2] || !event[3] || !Array.isArray(event[4]) 39|| Object.getPrototypeOf(event[4]) !== Array.prototype || !Object.isFrozen(event[4]))) { 40throw Error('Message task order or event state'); 41} 42console.log('message FIFO'); 43"# , 44& [], 45); 46assert_eq! ( output. stdout , b"message FIFO\n" ); 47} 48 49# [ test ] 50fn message_port_transfer_preserves_graph_identity_and_moves_queued_messages () { 51let output =run ( 52r#" 53const channel = new MessageChannel(), peer = new MessageChannel(); 54const payload = {port: peer.port1}; 55payload.self = payload; 56channel.port2.onmessage = event => { 57if (event.data !== event.data.self || event.ports.length !== 1 58|| event.ports[0] !== event.data.port || !Object.isFrozen(event.ports)) { 59throw Error('Transferred port identity'); 60} 61event.ports[0].postMessage('reply'); 62}; 63peer.port2.onmessage = event => { 64if (event.data !== 'reply') throw Error('Transferred endpoint'); 65console.log('message transfer'); 66}; 67channel.port1.postMessage(payload, [peer.port1]); 68peer.port1.postMessage('detached source'); 69try { structuredClone({}, {transfer: [peer.port1]}); throw Error('Transferred port reused'); } 70catch (error) { if (error.name !== 'DataCloneError') throw error; } 71 72const queued = new MessageChannel(), relay = new MessageChannel(); 73queued.port1.postMessage('queued before transfer'); 74relay.port2.onmessage = event => { 75const received = event.data; 76received.onmessage = message => { 77if (message.data !== 'queued before transfer') throw Error('Queued endpoint message'); 78console.log('queued transfer'); 79}; 80}; 81relay.port1.postMessage(queued.port2, [queued.port2]); 82await new Promise(resolve => setTimeout(resolve, 20)); 83"# , 84& [], 85); 86assert_eq! ( output. stdout , b"message transfer\nqueued transfer\n" ); 87} 88 89# [ test ] 90fn message_port_clone_failures_leave_transferables_usable_and_close_drops_messages () { 91let output =run ( 92r#" 93const channel = new MessageChannel(), buffer = new ArrayBuffer(2), movable = new MessageChannel(); 94let stillAttached = false; 95try { channel.port1.postMessage({bad: () => {}}, [buffer, movable.port2]); throw Error('Clone failure'); } 96catch (error) { if (!(error instanceof DOMException) || error.name !== 'DataCloneError') throw error; } 97if (buffer.detached) throw Error('Clone failure detached buffer'); 98movable.port2.onmessage = event => { 99if (event.data !== 'still attached') throw Error('Clone failure moved port'); 100stillAttached = true; 101}; 102movable.port1.postMessage('still attached'); 103try { channel.port1.postMessage(channel.port1, [channel.port1]); throw Error('Self transfer'); } 104catch (error) { if (!(error instanceof DOMException) || error.name !== 'DataCloneError') throw error; } 105const other = new MessageChannel(); 106for (const options of [undefined, [other.port2, other.port2]]) { 107try { channel.port1.postMessage(other.port2, options); throw Error('Port transfer validation'); } 108catch (error) { if (!(error instanceof DOMException) || error.name !== 'DataCloneError') throw error; } 109} 110other.port2.onmessage = event => { 111if (event.data !== 'still entangled') throw Error('Failed transfer changed port'); 112}; 113other.port1.postMessage('still entangled'); 114channel.port2.onmessage = () => { throw Error('Closed message dispatched'); }; 115channel.port1.postMessage('discarded'); 116channel.port2.close(); 117await new Promise(resolve => setTimeout(resolve, 10)); 118if (buffer.byteLength !== 2 || !stillAttached) throw Error('Transfer changed'); 119console.log('message failure and close'); 120"# , 121& [], 122); 123assert_eq! ( output. stdout , b"message failure and close\n" ); 124} 125 126# [ test ] 127fn transferred_ports_restart_queues_and_closed_ports_still_serialize () { 128let out =run ( 129r#" 130const channel=new MessageChannel(); 131let called=0; 132channel.port2.onmessage=()=>called++; 133const moved=structuredClone(channel.port2,{transfer:[channel.port2]}); 134moved.addEventListener('message',event=>{ 135if(event.data!==undefined)throw Error('Undefined message changed'); 136called++; 137}); 138channel.port1.postMessage(undefined); 139await new Promise(r=>setTimeout(r,5)); 140if(called!==0)throw Error('Transferred queue already enabled'); 141moved.start(); 142await new Promise(r=>setTimeout(r,5)); 143if(called!==1)throw Error('Restarted queue not delivered'); 144moved.close(); 145const buffer=new ArrayBuffer(1); 146channel.port1.postMessage(null,[buffer]); 147if(!buffer.detached)throw Error('Closed port did not serialize'); 148try{channel.port1.postMessage(()=>{});throw Error('Closed port skipped clone');} 149catch(e){if(e.name!=='DataCloneError')throw e;} 150const order=[],attributes=new MessageChannel(); 151attributes.port2.onmessage=()=>order.push('old'); 152attributes.port2.addEventListener('message',()=>order.push('listener')); 153attributes.port2.onmessage=()=>order.push('replacement'); 154attributes.port1.postMessage(1); 155await new Promise(r=>setTimeout(r,5)); 156if(order.join()!=='replacement,listener')throw Error('Handler replacement changed position'); 157const event=new MessageEvent('message',{get data(){called++;return called;},origin:'\ud800'}); 158if(called!==2||event.data!==2||event.origin!=='\ufffd')throw Error('Dictionary conversion'); 159console.log('port lifecycle'); 160"# , 161& [], 162); 163assert_eq! ( out. stdout , b"port lifecycle\n" ); 164} 165 166# [ test ] 167fn host_message_delivery_does_not_consult_guest_dictionary_or_iterator_prototypes () { 168let out =run ( 169r#" 170const channel=new MessageChannel(); 171channel.port2.onmessage=event=>{ 172if(event.data!=='safe'||event.bubbles||event.cancelable||event.composed||event.ports.length!==0)throw Error('Guest conversion of host event'); 173console.log('private delivery'); 174}; 175channel.port1.postMessage('safe'); 176for(const key of ['bubbles','cancelable','composed','ports'])Object.defineProperty(Object.prototype,key,{get(){throw Error('Guest dictionary');},configurable:true}); 177Array.prototype[Symbol.iterator]=()=>{throw Error('Guest iterator');}; 178await new Promise(r=>setTimeout(r,5)); 179"# , 180& [], 181); 182assert_eq! ( out. stdout , b"private delivery\n" ); 183}