UNPKG

sabayon

Version:
150 lines (126 loc) 3.97 kB
// (c) Andrea Giammarchi - MIT const ACTION_INIT = 0; const ACTION_NOTIFY = 1; const ACTION_WAIT = 2; const ACTION_SW = 3; const { ArrayBuffer, Atomics: $Atomics, Promise } = globalThis; const { isArray } = Array; const { create, getPrototypeOf, values } = Object; const TypedArray = getPrototypeOf(Int32Array); const Atomics = /** @type {globalThis.Atomics} */(create($Atomics)); const dispatch = ({ currentTarget, type, origin, lastEventId, source, ports }, data) => currentTarget.dispatchEvent(new MessageEvent(type, { data, origin, lastEventId, source, ports })); const withResolvers = () => Promise.withResolvers(); let id = 0; const views = new Map; const extend = (Class, SharedArrayBuffer) => class extends Class { constructor(value, ...rest) { super(value, ...rest); if (value instanceof SharedArrayBuffer) views.set(this, [id++, 0, withResolvers()]); } }; const ignoreList = new WeakSet; /** * @template {T} * @callback PassThrough * @param {T} value * @returns {T} */ /** @type {PassThrough} */ const ignoreDirect = value => value; /** @type {PassThrough} */ const ignorePatch = value => { ignoreList.add(value); return value; }; const isChannel = (event, channel) => { const { data } = event; const yes = isArray(data) && ( data.at(0) === channel || (data.at(1) === ACTION_INIT && !channel) ); if (yes) { event.stopImmediatePropagation(); event.preventDefault(); } return yes; }; const isObject = value => ( value !== null && typeof value === 'object' && !ignoreList.has(value) ); const transferred = new WeakMap; const transferViews = (data, transfer, visited) => { if (views.has(data)) transfer.set(data, views.get(data)[0]); else if (!(data instanceof TypedArray || data instanceof ArrayBuffer)) { for (const value of values(data)) { if (isObject(value) && !visited.has(value)) { visited.add(value); transferViews(value, transfer, visited); } } } }; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/waitAsync#browser_compatibility let intAsyncPatch = 0; const waitAsyncPatch = (...args) => ({ value: new Promise(async resolve => { // fallback to a local file to avoid CSP issues // this file must contain: onmessage=e=>postMessage(!Atomics.wait(...e.data)) const url = '/__sabayon_wait_async.js'; const data = 'data:application/javascript,onmessage%3De%3D%3EpostMessage(!Atomics.wait(...e.data))'; // perform this check only once if (!intAsyncPatch) { const ok = fetch(url, { method: 'HEAD' }).then(r => r.ok, () => false); intAsyncPatch = (await ok) ? 1 : -1; } const worker = new Worker(intAsyncPatch < 0 ? data : url); worker.onmessage = () => { worker.terminate(); resolve('ok'); }; worker.postMessage(args); }) }); const waitAsyncPoly = (view, index) => { const value = views.get(view), [id, _, { promise }] = value; value[1] = index; return [id, promise]; }; const actionNotify = (_view, _id, _index) => { for (const [view, [id, index, { resolve }]] of views) { if (_id === id && _index === index) { view.set(_view); views.delete(view); resolve('ok'); break; } } }; const actionWait = (event, transfer, data) => { for (const [view, id] of transfer) transferred.set(view, [id, event.currentTarget]); dispatch(event, data); }; const postData = (CHANNEL, data) => { const transfer = new Map; if (isObject(data)) transferViews(data, transfer, new Set); return transfer.size ? [CHANNEL, ACTION_WAIT, transfer, data] : data; }; const getData = view => transferred.get(view); export { ACTION_INIT, ACTION_NOTIFY, ACTION_WAIT, ACTION_SW, ArrayBuffer, Atomics, TypedArray, actionNotify, actionWait, getData, postData, ignoreDirect, ignorePatch, waitAsyncPatch, waitAsyncPoly, dispatch, extend, isChannel, views, withResolvers, };