Skip to content

Commit 2d8d0d6

Browse files
committed
Bundle structuredClone implementation for GJS
1 parent 2ce90aa commit 2d8d0d6

12 files changed

Lines changed: 1233 additions & 7 deletions

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
src/core/structuredClone.js

eslint.config.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,14 @@ export default defineConfig([{
5353
"no-debugger": "error",
5454
"no-console": "error",
5555
},
56+
}], [{
57+
files: ["scripts/lib/structuredClone.rollup.js"],
58+
languageOptions: {
59+
sourceType: "module",
60+
}
61+
}], [{
62+
files: ["src/core/structuredClone.js"],
63+
rules: {
64+
curly: "off",
65+
}
5666
}]);

lib/jasmine-core/jasmine.js

Lines changed: 293 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ const getJasmineRequireObj = (function() {
5353

5454
jRequire.base(j$, private$, globalThis);
5555
private$.util = jRequire.util(j$, private$);
56+
private$.structuredClone = jRequire.structuredClone(
57+
j$,
58+
private$,
59+
globalThis
60+
);
5661
private$.errors = jRequire.errors();
5762
private$.formatErrorMsg = jRequire.formatErrorMsg(j$, private$);
5863
private$.AllOf = jRequire.AllOf(j$, private$);
@@ -821,7 +826,7 @@ getJasmineRequireObj().util = function(j$, private$) {
821826
// Reporter events are cloned internally via structuredClone, and it's
822827
// common for reporters (including jasmine-browser-runner's) to JSON
823828
// serialize them.
824-
JSON.stringify(structuredClone(v));
829+
JSON.stringify(private$.structuredClone(v));
825830
} catch (e) {
826831
throw new Error(`${msgPrefix} can't be cloned`, { cause: e });
827832
}
@@ -8881,7 +8886,7 @@ getJasmineRequireObj().ReportDispatcher = function(j$, private$) {
88818886
return;
88828887
}
88838888

8884-
const thisEvent = structuredClone(event);
8889+
const thisEvent = private$.structuredClone(event);
88858890
if (fn.length <= 1) {
88868891
fns.push({
88878892
fn: function() {
@@ -11025,6 +11030,292 @@ getJasmineRequireObj().StackTrace = function(j$, private$) {
1102511030
return StackTrace;
1102611031
};
1102711032

11033+
(function () {
11034+
'use strict';
11035+
11036+
const VOID = -1;
11037+
const PRIMITIVE = 0;
11038+
const ARRAY = 1;
11039+
const OBJECT = 2;
11040+
const DATE = 3;
11041+
const REGEXP = 4;
11042+
const MAP = 5;
11043+
const SET = 6;
11044+
const ERROR = 7;
11045+
const BIGINT = 8;
11046+
// export const SYMBOL = 9;
11047+
11048+
const env = typeof self === 'object' ? self : globalThis;
11049+
11050+
const guard = (name, init) => {
11051+
switch (name) {
11052+
case 'Function':
11053+
case 'SharedWorker':
11054+
case 'Worker':
11055+
case 'eval':
11056+
case 'setInterval':
11057+
case 'setTimeout':
11058+
throw new TypeError('unable to deserialize ' + name);
11059+
}
11060+
return new env[name](init);
11061+
};
11062+
11063+
const deserializer = ($, _) => {
11064+
const as = (out, index) => {
11065+
$.set(index, out);
11066+
return out;
11067+
};
11068+
11069+
const unpair = index => {
11070+
if ($.has(index))
11071+
return $.get(index);
11072+
11073+
const [type, value] = _[index];
11074+
switch (type) {
11075+
case PRIMITIVE:
11076+
case VOID:
11077+
return as(value, index);
11078+
case ARRAY: {
11079+
const arr = as([], index);
11080+
for (const index of value)
11081+
arr.push(unpair(index));
11082+
return arr;
11083+
}
11084+
case OBJECT: {
11085+
const object = as({}, index);
11086+
for (const [key, index] of value)
11087+
object[unpair(key)] = unpair(index);
11088+
return object;
11089+
}
11090+
case DATE:
11091+
return as(new Date(value), index);
11092+
case REGEXP: {
11093+
const {source, flags} = value;
11094+
return as(new RegExp(source, flags), index);
11095+
}
11096+
case MAP: {
11097+
const map = as(new Map, index);
11098+
for (const [key, index] of value)
11099+
map.set(unpair(key), unpair(index));
11100+
return map;
11101+
}
11102+
case SET: {
11103+
const set = as(new Set, index);
11104+
for (const index of value)
11105+
set.add(unpair(index));
11106+
return set;
11107+
}
11108+
case ERROR: {
11109+
const {name, message} = value;
11110+
return as(guard(name, message), index);
11111+
}
11112+
case BIGINT:
11113+
return as(BigInt(value), index);
11114+
case 'BigInt':
11115+
return as(Object(BigInt(value)), index);
11116+
case 'ArrayBuffer':
11117+
return as(new Uint8Array(value).buffer, value);
11118+
case 'DataView': {
11119+
const { buffer } = new Uint8Array(value);
11120+
return as(new DataView(buffer), value);
11121+
}
11122+
}
11123+
return as(guard(type, value), index);
11124+
};
11125+
11126+
return unpair;
11127+
};
11128+
11129+
/**
11130+
* @typedef {Array<string,any>} Record a type representation
11131+
*/
11132+
11133+
/**
11134+
* Returns a deserialized value from a serialized array of Records.
11135+
* @param {Record[]} serialized a previously serialized value.
11136+
* @returns {any}
11137+
*/
11138+
const deserialize = serialized => deserializer(new Map, serialized)(0);
11139+
11140+
const EMPTY = '';
11141+
11142+
const {toString} = {};
11143+
const {keys} = Object;
11144+
11145+
const typeOf = value => {
11146+
const type = typeof value;
11147+
if (type !== 'object' || !value)
11148+
return [PRIMITIVE, type];
11149+
11150+
const asString = toString.call(value).slice(8, -1);
11151+
switch (asString) {
11152+
case 'Array':
11153+
return [ARRAY, EMPTY];
11154+
case 'Object':
11155+
return [OBJECT, EMPTY];
11156+
case 'Date':
11157+
return [DATE, EMPTY];
11158+
case 'RegExp':
11159+
return [REGEXP, EMPTY];
11160+
case 'Map':
11161+
return [MAP, EMPTY];
11162+
case 'Set':
11163+
return [SET, EMPTY];
11164+
case 'DataView':
11165+
return [ARRAY, asString];
11166+
}
11167+
11168+
if (asString.includes('Array'))
11169+
return [ARRAY, asString];
11170+
11171+
if (asString.includes('Error'))
11172+
return [ERROR, asString];
11173+
11174+
return [OBJECT, asString];
11175+
};
11176+
11177+
const shouldSkip = ([TYPE, type]) => (
11178+
TYPE === PRIMITIVE &&
11179+
(type === 'function' || type === 'symbol')
11180+
);
11181+
11182+
const serializer = (strict, json, $, _) => {
11183+
11184+
const as = (out, value) => {
11185+
const index = _.push(out) - 1;
11186+
$.set(value, index);
11187+
return index;
11188+
};
11189+
11190+
const pair = value => {
11191+
if ($.has(value))
11192+
return $.get(value);
11193+
11194+
let [TYPE, type] = typeOf(value);
11195+
switch (TYPE) {
11196+
case PRIMITIVE: {
11197+
let entry = value;
11198+
switch (type) {
11199+
case 'bigint':
11200+
TYPE = BIGINT;
11201+
entry = value.toString();
11202+
break;
11203+
case 'function':
11204+
case 'symbol':
11205+
if (strict)
11206+
throw new TypeError('unable to serialize ' + type);
11207+
entry = null;
11208+
break;
11209+
case 'undefined':
11210+
return as([VOID], value);
11211+
}
11212+
return as([TYPE, entry], value);
11213+
}
11214+
case ARRAY: {
11215+
if (type) {
11216+
let spread = value;
11217+
if (type === 'DataView') {
11218+
spread = new Uint8Array(value.buffer);
11219+
}
11220+
else if (type === 'ArrayBuffer') {
11221+
spread = new Uint8Array(value);
11222+
}
11223+
return as([type, [...spread]], value);
11224+
}
11225+
11226+
const arr = [];
11227+
const index = as([TYPE, arr], value);
11228+
for (const entry of value)
11229+
arr.push(pair(entry));
11230+
return index;
11231+
}
11232+
case OBJECT: {
11233+
if (type) {
11234+
switch (type) {
11235+
case 'BigInt':
11236+
return as([type, value.toString()], value);
11237+
case 'Boolean':
11238+
case 'Number':
11239+
case 'String':
11240+
return as([type, value.valueOf()], value);
11241+
}
11242+
}
11243+
11244+
if (json && ('toJSON' in value))
11245+
return pair(value.toJSON());
11246+
11247+
const entries = [];
11248+
const index = as([TYPE, entries], value);
11249+
for (const key of keys(value)) {
11250+
if (strict || !shouldSkip(typeOf(value[key])))
11251+
entries.push([pair(key), pair(value[key])]);
11252+
}
11253+
return index;
11254+
}
11255+
case DATE:
11256+
return as([TYPE, value.toISOString()], value);
11257+
case REGEXP: {
11258+
const {source, flags} = value;
11259+
return as([TYPE, {source, flags}], value);
11260+
}
11261+
case MAP: {
11262+
const entries = [];
11263+
const index = as([TYPE, entries], value);
11264+
for (const [key, entry] of value) {
11265+
if (strict || !(shouldSkip(typeOf(key)) || shouldSkip(typeOf(entry))))
11266+
entries.push([pair(key), pair(entry)]);
11267+
}
11268+
return index;
11269+
}
11270+
case SET: {
11271+
const entries = [];
11272+
const index = as([TYPE, entries], value);
11273+
for (const entry of value) {
11274+
if (strict || !shouldSkip(typeOf(entry)))
11275+
entries.push(pair(entry));
11276+
}
11277+
return index;
11278+
}
11279+
}
11280+
11281+
const {message} = value;
11282+
return as([TYPE, {name: type, message}], value);
11283+
};
11284+
11285+
return pair;
11286+
};
11287+
11288+
/**
11289+
* @typedef {Array<string,any>} Record a type representation
11290+
*/
11291+
11292+
/**
11293+
* Returns an array of serialized Records.
11294+
* @param {any} value a serializable value.
11295+
* @param {{json?: boolean, lossy?: boolean}?} options an object with a `lossy` or `json` property that,
11296+
* if `true`, will not throw errors on incompatible types, and behave more
11297+
* like JSON stringify would behave. Symbol and Function will be discarded.
11298+
* @returns {Record[]}
11299+
*/
11300+
const serialize = (value, {json, lossy} = {}) => {
11301+
const _ = [];
11302+
return serializer(!(json || lossy), !!json, new Map, _)(value), _;
11303+
};
11304+
11305+
getJasmineRequireObj().structuredClone = function(j$, private$, jasmineGlobal) {
11306+
const { structuredClone } = jasmineGlobal;
11307+
11308+
if (structuredClone) {
11309+
return structuredClone.bind(jasmineGlobal);
11310+
}
11311+
11312+
return function (any, options) {
11313+
return deserialize(serialize(any, options));
11314+
};
11315+
};
11316+
11317+
})();
11318+
1102811319
getJasmineRequireObj().Suite = function(j$, private$) {
1102911320
'use strict';
1103011321

0 commit comments

Comments
 (0)