mod common; use common::run; #[test] fn message_channel_schedules_fifo_events_and_preserves_event_brands() { let output = run( r#" if (!(new MessageChannel() instanceof Object) || !(new MessageEvent('message') instanceof Event)) { throw Error('Message interfaces'); } try { new MessagePort(); throw Error('MessagePort constructor'); } catch (error) { if (!(error instanceof TypeError)) throw error; } const legacy = new MessageEvent('old', {data: 1, ports: []}); legacy.initMessageEvent('new', false, true, 2); if (legacy.type !== 'new' || legacy.data !== 2 || !legacy.cancelable) throw Error('initMessageEvent'); const channel = new MessageChannel(), seen = []; if (Object.hasOwn(channel, 'port1') || Object.hasOwn(channel, 'port2') || channel.port1 === channel.port2) throw Error('MessageChannel attributes'); try { structuredClone(channel); throw Error('Cloned MessageChannel'); } catch (error) { if (error.name !== 'DataCloneError') throw error; } channel.port2.addEventListener('message', event => { const data = event.data; event.initMessageEvent('changed', false, false, 'changed'); if (event.type !== 'message' || event.data !== data) throw Error('initMessageEvent during dispatch'); seen.push([data, event.isTrusted, event.target === channel.port2, event.currentTarget === channel.port2, event.ports]); }); const snapshot = {value: 1}; channel.port1.postMessage(snapshot); snapshot.value = 3; channel.port1.postMessage(2); await new Promise(resolve => setTimeout(resolve, 0)); if (seen.length !== 0) throw Error('addEventListener started the port'); channel.port2.start(); await new Promise(resolve => setTimeout(resolve, 10)); if (seen.length !== 2 || seen[0][0].value !== 1 || seen[1][0] !== 2 || seen.some(event => !event[1] || !event[2] || !event[3] || !Array.isArray(event[4]) || Object.getPrototypeOf(event[4]) !== Array.prototype || !Object.isFrozen(event[4]))) { throw Error('Message task order or event state'); } console.log('message FIFO'); "#, &[], ); assert_eq!(output.stdout, b"message FIFO\n"); } #[test] fn message_port_transfer_preserves_graph_identity_and_moves_queued_messages() { let output = run( r#" const channel = new MessageChannel(), peer = new MessageChannel(); const payload = {port: peer.port1}; payload.self = payload; channel.port2.onmessage = event => { if (event.data !== event.data.self || event.ports.length !== 1 || event.ports[0] !== event.data.port || !Object.isFrozen(event.ports)) { throw Error('Transferred port identity'); } event.ports[0].postMessage('reply'); }; peer.port2.onmessage = event => { if (event.data !== 'reply') throw Error('Transferred endpoint'); console.log('message transfer'); }; channel.port1.postMessage(payload, [peer.port1]); peer.port1.postMessage('detached source'); try { structuredClone({}, {transfer: [peer.port1]}); throw Error('Transferred port reused'); } catch (error) { if (error.name !== 'DataCloneError') throw error; } const queued = new MessageChannel(), relay = new MessageChannel(); queued.port1.postMessage('queued before transfer'); relay.port2.onmessage = event => { const received = event.data; received.onmessage = message => { if (message.data !== 'queued before transfer') throw Error('Queued endpoint message'); console.log('queued transfer'); }; }; relay.port1.postMessage(queued.port2, [queued.port2]); await new Promise(resolve => setTimeout(resolve, 20)); "#, &[], ); assert_eq!(output.stdout, b"message transfer\nqueued transfer\n"); } #[test] fn message_port_clone_failures_leave_transferables_usable_and_close_drops_messages() { let output = run( r#" const channel = new MessageChannel(), buffer = new ArrayBuffer(2), movable = new MessageChannel(); let stillAttached = false; try { channel.port1.postMessage({bad: () => {}}, [buffer, movable.port2]); throw Error('Clone failure'); } catch (error) { if (!(error instanceof DOMException) || error.name !== 'DataCloneError') throw error; } if (buffer.detached) throw Error('Clone failure detached buffer'); movable.port2.onmessage = event => { if (event.data !== 'still attached') throw Error('Clone failure moved port'); stillAttached = true; }; movable.port1.postMessage('still attached'); try { channel.port1.postMessage(channel.port1, [channel.port1]); throw Error('Self transfer'); } catch (error) { if (!(error instanceof DOMException) || error.name !== 'DataCloneError') throw error; } const other = new MessageChannel(); for (const options of [undefined, [other.port2, other.port2]]) { try { channel.port1.postMessage(other.port2, options); throw Error('Port transfer validation'); } catch (error) { if (!(error instanceof DOMException) || error.name !== 'DataCloneError') throw error; } } other.port2.onmessage = event => { if (event.data !== 'still entangled') throw Error('Failed transfer changed port'); }; other.port1.postMessage('still entangled'); channel.port2.onmessage = () => { throw Error('Closed message dispatched'); }; channel.port1.postMessage('discarded'); channel.port2.close(); await new Promise(resolve => setTimeout(resolve, 10)); if (buffer.byteLength !== 2 || !stillAttached) throw Error('Transfer changed'); console.log('message failure and close'); "#, &[], ); assert_eq!(output.stdout, b"message failure and close\n"); } #[test] fn transferred_ports_restart_queues_and_closed_ports_still_serialize() { let out = run( r#" const channel=new MessageChannel(); let called=0; channel.port2.onmessage=()=>called++; const moved=structuredClone(channel.port2,{transfer:[channel.port2]}); moved.addEventListener('message',event=>{ if(event.data!==undefined)throw Error('Undefined message changed'); called++; }); channel.port1.postMessage(undefined); await new Promise(r=>setTimeout(r,5)); if(called!==0)throw Error('Transferred queue already enabled'); moved.start(); await new Promise(r=>setTimeout(r,5)); if(called!==1)throw Error('Restarted queue not delivered'); moved.close(); const buffer=new ArrayBuffer(1); channel.port1.postMessage(null,[buffer]); if(!buffer.detached)throw Error('Closed port did not serialize'); try{channel.port1.postMessage(()=>{});throw Error('Closed port skipped clone');} catch(e){if(e.name!=='DataCloneError')throw e;} const order=[],attributes=new MessageChannel(); attributes.port2.onmessage=()=>order.push('old'); attributes.port2.addEventListener('message',()=>order.push('listener')); attributes.port2.onmessage=()=>order.push('replacement'); attributes.port1.postMessage(1); await new Promise(r=>setTimeout(r,5)); if(order.join()!=='replacement,listener')throw Error('Handler replacement changed position'); const event=new MessageEvent('message',{get data(){called++;return called;},origin:'\ud800'}); if(called!==2||event.data!==2||event.origin!=='\ufffd')throw Error('Dictionary conversion'); console.log('port lifecycle'); "#, &[], ); assert_eq!(out.stdout, b"port lifecycle\n"); } #[test] fn host_message_delivery_does_not_consult_guest_dictionary_or_iterator_prototypes() { let out = run( r#" const channel=new MessageChannel(); channel.port2.onmessage=event=>{ if(event.data!=='safe'||event.bubbles||event.cancelable||event.composed||event.ports.length!==0)throw Error('Guest conversion of host event'); console.log('private delivery'); }; channel.port1.postMessage('safe'); for(const key of ['bubbles','cancelable','composed','ports'])Object.defineProperty(Object.prototype,key,{get(){throw Error('Guest dictionary');},configurable:true}); Array.prototype[Symbol.iterator]=()=>{throw Error('Guest iterator');}; await new Promise(r=>setTimeout(r,5)); "#, &[], ); assert_eq!(out.stdout, b"private delivery\n"); }