char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
b7bd857
main
1import { 2ArrayPrototypePush as push , 3Error , 4ObjectCreate , 5ObjectDefineProperty as define , 6ReflectApply as apply , 7ReflectConstruct as construct , 8TypeError , 9WeakMap , 10WeakMapPrototypeGet as get , 11WeakMapPrototypeSet as set , 12} from "ext:core/primordials" ; 13import { promiseThen as then } from "ext:core/promises" ; 14import { enqueueTask } from "ext:core/tasks" ; 15import { onRuntimeStart } from "ext:core/bootstrap" ; 16import { clone } from "ext:core/structured_clone" ; 17import { 18op_worker_base , 19op_worker_close , 20op_worker_create , 21op_worker_metadata , 22op_worker_terminate , 23op_worker_wait , 24} from "ext:core/ops" ; 25import { array , dictionary , required , usv } from "ext:web/idl.js" ; 26import { installInterface } from "ext:web/interface.js" ; 27import { 28addListener , 29dataCloneError , 30dispatch , 31ErrorEvent , 32EventTarget , 33preventDefault , 34removeListener , 35reportErrorValue , 36} from "ext:web/events.js" ; 37import { 38adoptPort , 39dispatchMessage , 40isMessagePort , 41messageData , 42MessageEvent , 43MessagePort , 44messagePorts , 45startPort , 46transferList , 47} from "ext:web/messaging.js" ; 48import { 49classify , 50registerUncloneable , 51transferCallbacks , 52} from "ext:web/serialization.js" ; 53import { avril } from "ext:base/main.js" ; 54import { preparePledge } from "ext:base/pledge.js" ; 55import { URL } from "ext:url/main.js" ; 56 57type HandlerName = "onmessage" | "onmessageerror" | "onerror" ; 58type HandlerState = Partial < 59& Record < HandlerName , Function | null > 60& Record < 61 `${HandlerName } Listener`, 62(( event :import ( "ext:web/events.js" ). Event ) => void ) | null 63> 64>; 65interface WorkerState extends HandlerState { 66id :number ; 67port :MessagePort ; 68terminated :boolean ; 69} 70type LocationState = Record < 71| "href" 72| "origin" 73| "protocol" 74| "host" 75| "hostname" 76| "port" 77| "pathname" 78| "search" 79| "hash" , 80string 81>; 82const workers = new WeakMap < object , WorkerState >(), 83locations = new WeakMap < object , LocationState >(); 84registerUncloneable ( workers ); 85registerUncloneable ( locations ); 86const post = MessagePort . prototype . postMessage ; 87const closePort = MessagePort . prototype . close ; 88 89function state ( value :unknown ) :WorkerState { 90const result = get ( workers , value ); 91if ( ! result ) throw new TypeError ( "Illegal invocation" ); 92return result ; 93} 94 95function handler ( 96target :unknown , 97current :HandlerState , 98name :HandlerName , 99type :string , 100value :unknown , 101) :void { 102value = typeof value === "function" ?value :null ; 103current [ name ] = value as Function | null ; 104const key = name + "Listener" as `${HandlerName } Listener`; 105if ( ! value && current [ key ]) { 106removeListener ( target , type , current [ key ]); 107current [ key ] = null ; 108} else if ( value && ! current [ key ]) { 109current [ key ] = ( event ) => { 110if ( current [ name ] && apply ( current [ name ] ! , target , [ event ]) === false ) { 111preventDefault ( event ); 112} 113}; 114addListener ( target , type , current [ key ]); 115} 116} 117 118class Worker extends EventTarget { 119// @ts-expect-error Reflect.construct preserves new.target without a guest-replaceable super() call. 120constructor ( scriptURL :unknown , options :unknown = {}) { 121required ( arguments . length , 1 ); 122const specifier = usv ( scriptURL ), init = dictionary ( options ); 123const type = init . type === undefined ?"classic" :usv ( init . type ); 124if ( type !== "module" ) { 125throw new TypeError ( "avril supports module workers only" ); 126} 127const name = init . name === undefined ?"" :usv ( init . name ); 128const extension = dictionary ( init [ avril ]); 129const restrictions = extension . caps === undefined 130 ?[] 131 :preparePledge ( extension . caps ); 132const self = construct ( EventTarget , [], new . target ) as Worker ; 133const created = op_worker_create ([ 134specifier , 135op_worker_base (), 136name , 137restrictions , 138]); 139const port = adoptPort ( created [ 1 ]); 140const current = ObjectCreate ( null ) as WorkerState ; 141current . id = created [ 0 ]; 142current . port = port ; 143current . terminated = false ; 144set ( workers , self , current ); 145addListener ( port , "message" , ( event ) => { 146if ( ! current . terminated ) { 147dispatchMessage ( self , messageData ( event ), messagePorts ( event )); 148} 149}); 150addListener ( port , "messageerror" , () => { 151if ( ! current . terminated ) { 152dispatch ( self , new MessageEvent ( "messageerror" , ObjectCreate ( null ))); 153} 154}); 155startPort ( port ); 156then ( op_worker_wait ( current . id ), ( message ) => { 157enqueueTask (() => { 158if ( ! current . terminated && message !== null ) { 159const init = ObjectCreate ( null ); 160init . message = message ; 161init . cancelable = true ; 162const event = new ErrorEvent ( "error" , init ); 163if ( dispatch ( self , event )) reportErrorValue ( new Error ( message )); 164} 165current . terminated = true ; 166apply ( closePort , port , []); 167}, () => true ); 168}); 169return self ; 170} 171postMessage ( message :unknown , options :unknown = undefined ) { 172required ( arguments . length , 1 ); 173apply ( post , state ( this ). port , [ message , options ]); 174} 175terminate () { 176const current = state ( this ); 177current . terminated = true ; 178apply ( closePort , current . port , []); 179op_worker_terminate ( current . id ); 180} 181get onmessage () { 182return state ( this ). onmessage ?? null ; 183} 184set onmessage ( value :unknown ) { 185handler ( this , state ( this ), "onmessage" , "message" , value ); 186} 187get onmessageerror () { 188return state ( this ). onmessageerror ?? null ; 189} 190set onmessageerror ( value :unknown ) { 191handler ( this , state ( this ), "onmessageerror" , "messageerror" , value ); 192} 193get onerror () { 194return state ( this ). onerror ?? null ; 195} 196set onerror ( value :unknown ) { 197handler ( this , state ( this ), "onerror" , "error" , value ); 198} 199} 200installInterface ( "Worker" , Worker ); 201 202class WorkerLocation { 203private constructor () { 204throw new TypeError ( "Illegal constructor" ); 205} 206get href () { 207return get ( locations , this ) ! . href ; 208} 209get origin () { 210return get ( locations , this ) ! . origin ; 211} 212get protocol () { 213return get ( locations , this ) ! . protocol ; 214} 215get host () { 216return get ( locations , this ) ! . host ; 217} 218get hostname () { 219return get ( locations , this ) ! . hostname ; 220} 221get port () { 222return get ( locations , this ) ! . port ; 223} 224get pathname () { 225return get ( locations , this ) ! . pathname ; 226} 227get search () { 228return get ( locations , this ) ! . search ; 229} 230get hash () { 231return get ( locations , this ) ! . hash ; 232} 233toString () { 234return get ( locations , this ) ! . href ; 235} 236} 237 238const global = globalThis , current :HandlerState = ObjectCreate ( null ); 239for ( const type of [ "message" , "messageerror" ] as const ) { 240define ( global , "on" + type , { 241get :() => current [ "on" + type as HandlerName ] ?? null , 242set :( value ) => 243handler ( global , current , "on" + type as HandlerName , type , value ), 244enumerable :true , 245configurable :true , 246}); 247} 248onRuntimeStart (() => { 249const metadata = op_worker_metadata (); 250if ( metadata !== null ) { 251const port = adoptPort ( metadata [ 0 ]); 252addListener ( 253port , 254"message" , 255( event ) => 256dispatchMessage ( global , messageData ( event ), messagePorts ( event )), 257); 258addListener ( 259port , 260"messageerror" , 261() => 262dispatch ( global , new MessageEvent ( "messageerror" , ObjectCreate ( null ))), 263); 264startPort ( port , true ); 265installInterface ( "WorkerLocation" , WorkerLocation ); 266const location = ObjectCreate ( WorkerLocation . prototype ); 267const url = new URL ( metadata [ 1 ]), 268locationState :LocationState = ObjectCreate ( null ); 269for ( 270const key of [ 271"href" , 272"origin" , 273"protocol" , 274"host" , 275"hostname" , 276"port" , 277"pathname" , 278"search" , 279"hash" , 280] as const 281) locationState [ key ] = url [ key ]; 282set ( locations , location , locationState ); 283define ( global , "location" , { 284get :() => location , 285enumerable :true , 286configurable :true , 287}); 288define ( global , "name" , { 289value :metadata [ 2 ], 290enumerable :true , 291configurable :true , 292}); 293define ( global , "postMessage" , { 294value :{ 295postMessage ( message :unknown , options :unknown = undefined ) { 296required ( arguments . length , 1 ); 297apply ( post , port , [ message , options ]); 298}, 299}. postMessage , 300writable :true , 301enumerable :true , 302configurable :true , 303}); 304define ( global , "close" , { 305value :function close () { 306apply ( closePort , port , []); 307op_worker_close (); 308}, 309writable :true , 310enumerable :true , 311configurable :true , 312}); 313} else { 314define ( global , "postMessage" , { 315value :{ 316postMessage ( message :unknown , options :unknown = undefined ) { 317required ( arguments . length , 1 ); 318const transfer = transferList ( options ), ports = array (); 319for ( let i = 0 ; i < transfer . length ; i ++ ) { 320if ( isMessagePort ( transfer [ i ])) push ( ports , transfer [ i ]); 321} 322const callbacks = transferCallbacks ( transfer , dataCloneError ); 323let packet :{ data :unknown ; ports :MessagePort [] }; 324try { 325packet = clone ( 326{ __proto__ :null , data :message , ports}, 327transfer , 328classify , 329callbacks . cloneHost , 330dataCloneError , 331callbacks . prepare , 332callbacks . validate , 333callbacks . commit , 334) as typeof packet ; 335} finally { 336callbacks . clear (); 337} 338enqueueTask ( 339() => dispatchMessage ( global , packet . data , packet . ports ), 340() => true , 341); 342}, 343}. postMessage , 344writable :true , 345enumerable :true , 346configurable :true , 347}); 348} 349}); 350 351export type { Worker , WorkerLocation };