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 3# [ test ] 4fn web_globals_expose_self_navigator_and_performance () { 5let output = common:: run ( 6r#" 7const global = globalThis; 8if (self !== global || self !== globalThis) throw Error('self alias'); 9const selfDescriptor = Object.getOwnPropertyDescriptor(global, 'self'); 10if (selfDescriptor.get === undefined || selfDescriptor.enumerable !== true || !selfDescriptor.configurable) throw Error('self descriptor'); 11if (navigator !== global.navigator || !(navigator instanceof WorkerNavigator) || typeof Navigator !== 'undefined') throw Error('Navigator interface'); 12if (Object.prototype.toString.call(navigator) !== '[object WorkerNavigator]') throw Error('Navigator tag'); 13if (!navigator.userAgent.startsWith('avril/') || navigator.appCodeName !== 'Mozilla' || navigator.appName !== 'Netscape' || navigator.appVersion !== '4.0' || navigator.product !== 'Gecko') throw Error('Navigator identification'); 14if (!navigator.platform || navigator.language !== 'en-US' || navigator.languages !== navigator.languages || !Object.isFrozen(navigator.languages) || navigator.languages.join() !== 'en-US' || !Number.isInteger(navigator.hardwareConcurrency) || navigator.hardwareConcurrency < 1 || !navigator.onLine) throw Error('Navigator mixins'); 15const navigatorDescriptor = Object.getOwnPropertyDescriptor(global, 'navigator'); 16if (!navigatorDescriptor.get || navigatorDescriptor.enumerable !== true || !navigatorDescriptor.configurable) throw Error('navigator descriptor'); 17try { new WorkerNavigator(); throw Error('Navigator constructor was callable'); } catch (error) { if (!(error instanceof TypeError)) throw error; } 18const userAgent = Object.getOwnPropertyDescriptor(WorkerNavigator.prototype, 'userAgent').get; 19try { userAgent.call({}); throw Error('Navigator brand check'); } catch (error) { if (!(error instanceof TypeError)) throw error; } 20 21if (performance !== global.performance || !(performance instanceof Performance) || !(performance instanceof EventTarget)) throw Error('Performance identity'); 22if (Object.prototype.toString.call(performance) !== '[object Performance]') throw Error('Performance tag'); 23if (typeof performance.timeOrigin !== 'number' || performance.timeOrigin <= 0 || performance.now() < 0) throw Error('Performance clock'); 24const opsSymbol = Symbol.for('__avril.ops'); 25if (opsSymbol in global || global[opsSymbol] !== undefined || Reflect.ownKeys(global).includes(opsSymbol)) throw Error('Native op registry exposed'); 26const before = performance.now(), after = performance.now(); 27if (after < before) throw Error('Performance clock moved backwards'); 28const sample = performance.now(); 29if (Math.abs(sample * 10 - Math.round(sample * 10)) > 1e-7) throw Error('Performance precision'); 30if (Math.abs(Date.now() - (performance.timeOrigin + performance.now())) > 5000) throw Error('Time origin'); 31const start = performance.now(); 32await new Promise(resolve => setTimeout(resolve, 10)); 33if (performance.now() < start + 5) throw Error('Timer and performance clocks diverged'); 34const json = performance.toJSON(); 35if (Object.keys(json).join() !== 'timeOrigin' || json.timeOrigin !== performance.timeOrigin) throw Error('Performance JSON'); 36for (const call of [() => new Performance(), () => performance.now.call({}), () => Object.getOwnPropertyDescriptor(Performance.prototype, 'timeOrigin').get.call({}), () => Performance.prototype.toJSON.call({})]) { 37try { call(); throw Error('Performance brand or constructor'); } catch (error) { if (!(error instanceof TypeError)) throw error; } 38} 39const performanceDescriptor = Object.getOwnPropertyDescriptor(global, 'performance'); 40if (!performanceDescriptor.get || performanceDescriptor.enumerable !== true || !performanceDescriptor.configurable) throw Error('performance descriptor'); 41if ('op_navigator_metadata' in global || 'op_timer_time_origin' in global) throw Error('Native helper leaked'); 42console.log('globals'); 43"# , 44& [], 45); 46assert_eq! ( output. stdout , b"globals\n" ); 47} 48 49# [ test ] 50fn base64_globals_follow_forgiving_decode_and_latin1_encode () { 51let output = common:: run ( 52r#" 53if (btoa('') !== '' || btoa('f') !== 'Zg==' || btoa('fo') !== 'Zm8=' || btoa('foo') !== 'Zm9v' || btoa('\x00\x80\xff') !== 'AID/') throw Error('btoa vectors'); 54if (atob('') !== '' || atob(' Z m 9 v \t\n') !== 'foo' || atob('AQ') !== '\x01' || atob('AQI') !== '\x01\x02' || atob('AQIDBA==') !== '\x01\x02\x03\x04' || atob('AB==') !== '\x00') throw Error('atob vectors'); 55if (atob(btoa('binary\x00\x80\xff')) !== 'binary\x00\x80\xff' || atob('\f') !== '') throw Error('Base64 round trip'); 56const long = 'x'.repeat(10000); 57if (atob(btoa(long)) !== long) throw Error('Large Base64 round trip'); 58function fails(fn, name, code) { 59try { fn(); throw Error('Expected ' + name); } 60catch (error) { 61if (error.message === 'Expected ' + name) throw error; 62if (!(error instanceof DOMException) || error.name !== name || error.code !== code) throw error; 63} 64} 65fails(() => btoa('éĀ'), 'InvalidCharacterError', 5); 66fails(() => btoa('\ud800'), 'InvalidCharacterError', 5); 67for (const value of ['A', 'AA===', 'AQ\v', 'AQ\u00a0', 'é', '====']) fails(() => atob(value), 'InvalidCharacterError', 5); 68for (const fn of [() => atob(), () => btoa()]) { 69try { fn(); throw Error('Missing argument accepted'); } catch (error) { if (!(error instanceof TypeError)) throw error; } 70} 71try { btoa(Symbol()); throw Error('Symbol accepted'); } catch (error) { if (!(error instanceof TypeError)) throw error; } 72if (DOMException.INVALID_CHARACTER_ERR !== 5 || DOMException.prototype.INVALID_CHARACTER_ERR !== 5 || new DOMException('', 'QuotaExceededError').code !== 22 || DOMException.QUOTA_EXCEEDED_ERR !== 22 || new DOMException('', 'TypeMismatchError').code !== 17 || DOMException.TYPE_MISMATCH_ERR !== 17) throw Error('DOMException legacy codes'); 73for(const name of ['DOMStringSizeError','NoDataAllowedError','ValidationError']){ 74if(new DOMException('',name).code!==0)throw Error('Obsolete exception name got a code'); 75} 76const codeGetter=Object.getOwnPropertyDescriptor(DOMException.prototype,'code').get; 77try{codeGetter.call({name:'AbortError'});throw Error('Unbranded exception accepted');}catch(e){if(!(e instanceof TypeError))throw e;} 78const exception=new DOMException('','AbortError'); 79Object.defineProperty(exception,'name',{value:'TimeoutError'}); 80if(exception.code!==20)throw Error('Public name changed native exception code'); 81if (atob.length !== 1 || btoa.length !== 1 || Object.getOwnPropertyDescriptor(globalThis, 'atob').enumerable !== true || Object.getOwnPropertyDescriptor(globalThis, 'btoa').enumerable !== true) throw Error('Base64 descriptors'); 82console.log('base64'); 83"# , 84& [], 85); 86assert_eq! ( output. stdout , b"base64\n" ); 87} 88 89# [ test ] 90fn web_globals_capture_intrinsics_and_native_clocks () { 91let output = common:: run ( 92r#" 93const now = performance.now.bind(performance), origin = performance.timeOrigin; 94String.prototype.charCodeAt = () => {throw Error('charCodeAt');}; 95String.prototype.slice = () => {throw Error('slice');}; 96String.fromCharCode = () => {throw Error('fromCharCode');}; 97Array.prototype.push = () => {throw Error('push');}; 98Array.prototype.join = () => {throw Error('join');}; 99Date.now = () => {throw Error('Date.now');}; 100if (btoa('foo') !== 'Zm9v' || atob('Zm9v') !== 'foo' || now() < 0 || performance.timeOrigin !== origin) throw Error('Captured operations'); 101console.log('captured'); 102"# , 103& [], 104); 105assert_eq! ( output. stdout , b"captured\n" ); 106} 107 108# [ test ] 109fn base64_operations_are_not_constructors_and_replaceable_globals_can_be_reassigned () { 110let output = common:: run ( 111r#" 112for(const fn of [atob,btoa]){ 113try{new fn('');throw Error('Operation used as constructor');}catch(e){if(!(e instanceof TypeError))throw e;} 114} 115const original=self; 116self={replacement:true}; 117performance={replacement:true}; 118if(!self.replacement||!performance.replacement||globalThis!==original)throw Error('Globals not replaceable'); 119for(const name of ['self','performance']){ 120const descriptor=Object.getOwnPropertyDescriptor(globalThis,name); 121if(!descriptor.writable||!descriptor.enumerable||!descriptor.configurable||descriptor.get)throw Error('Replacement descriptor'); 122} 123console.log('replaceable'); 124"# , 125& [], 126); 127assert_eq! ( output. stdout , b"replaceable\n" ); 128} 129 130# [ test ] 131fn global_avril_symbol_marks_the_runtime_in_main_and_workers () { 132let output = common:: run ( 133r#" 134import { avril } from 'avril:std'; 135const key = Symbol.for('runtime:avril'); 136if (avril !== key) throw Error('avril symbol'); 137if (globalThis[key] !== true || globalThis[avril] !== true) throw Error('Marker'); 138const descriptor = Object.getOwnPropertyDescriptor(globalThis, key); 139if (!descriptor || descriptor.value !== true || descriptor.writable !== false || descriptor.enumerable !== false || descriptor.configurable !== false) throw Error('Descriptor'); 140if (Reflect.ownKeys(globalThis).includes(key) !== true) throw Error('Own key'); 141try { globalThis[key] = false; } catch {} 142if (globalThis[key] !== true) throw Error('Writable'); 143try { Object.defineProperty(globalThis, key, { value: false }); throw Error('Redefined'); } 144catch (error) { if (!(error instanceof TypeError)) throw error; } 145console.log('marker'); 146"# , 147& [], 148); 149assert_eq! ( output. stdout , b"marker\n" ); 150let dir = tempfile:: tempdir (). unwrap (); 151 std:: fs:: write ( 152 dir. path (). join ( "worker.js" ), 153r#" 154postMessage(globalThis[Symbol.for('runtime:avril')]); close(); 155"# , 156) 157. unwrap (); 158 std:: fs:: write ( dir. path (). join ( "main.js" ), r#" 159const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' }); 160const marker = await new Promise((resolve, reject) => { worker.onmessage = event => resolve(event.data); worker.onerror = reject; }); 161if (marker !== true) throw Error('Worker marker'); 162console.log('worker marker'); 163"# ). unwrap (); 164let output = common:: wait ( 165 std:: process:: Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 166. arg ( "run" ) 167. arg ( dir. path (). join ( "main.js" )) 168. stdout ( std:: process:: Stdio :: piped ()) 169. stderr ( std:: process:: Stdio :: piped ()) 170. spawn () 171. unwrap (), 172); 173assert! ( 174 output. status . success (), 175"{}" , 176String :: from_utf8_lossy ( & output. stderr ) 177); 178assert_eq! ( output. stdout , b"worker marker\n" ); 179}