Add monkey patch for structured Clone
This commit is contained in:
parent
1dd1607b94
commit
e34e51226f
|
@ -45,6 +45,7 @@
|
|||
<script src="lib/pagination.js"></script>
|
||||
<script src="lib/toolcool-color-picker.js"></script>
|
||||
<script src="lib/svg-inject.js"></script>
|
||||
<script type="module" src="lib/structured-clone/monkey-patch.js"></script>
|
||||
<script type="module" src="lib/swiped-events.js"></script>
|
||||
<script type="module" src="lib/eventemitter.js"></script>
|
||||
<script type="module" src="scripts/power-user.js"></script>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
ISC License
|
||||
|
||||
Copyright (c) 2021, Andrea Giammarchi, @WebReflection
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
|
@ -0,0 +1,79 @@
|
|||
import {
|
||||
VOID, PRIMITIVE,
|
||||
ARRAY, OBJECT,
|
||||
DATE, REGEXP, MAP, SET,
|
||||
ERROR, BIGINT
|
||||
} from './types.js';
|
||||
|
||||
const env = typeof self === 'object' ? self : globalThis;
|
||||
|
||||
const deserializer = ($, _) => {
|
||||
const as = (out, index) => {
|
||||
$.set(index, out);
|
||||
return out;
|
||||
};
|
||||
|
||||
const unpair = index => {
|
||||
if ($.has(index))
|
||||
return $.get(index);
|
||||
|
||||
const [type, value] = _[index];
|
||||
switch (type) {
|
||||
case PRIMITIVE:
|
||||
case VOID:
|
||||
return as(value, index);
|
||||
case ARRAY: {
|
||||
const arr = as([], index);
|
||||
for (const index of value)
|
||||
arr.push(unpair(index));
|
||||
return arr;
|
||||
}
|
||||
case OBJECT: {
|
||||
const object = as({}, index);
|
||||
for (const [key, index] of value)
|
||||
object[unpair(key)] = unpair(index);
|
||||
return object;
|
||||
}
|
||||
case DATE:
|
||||
return as(new Date(value), index);
|
||||
case REGEXP: {
|
||||
const {source, flags} = value;
|
||||
return as(new RegExp(source, flags), index);
|
||||
}
|
||||
case MAP: {
|
||||
const map = as(new Map, index);
|
||||
for (const [key, index] of value)
|
||||
map.set(unpair(key), unpair(index));
|
||||
return map;
|
||||
}
|
||||
case SET: {
|
||||
const set = as(new Set, index);
|
||||
for (const index of value)
|
||||
set.add(unpair(index));
|
||||
return set;
|
||||
}
|
||||
case ERROR: {
|
||||
const {name, message} = value;
|
||||
return as(new env[name](message), index);
|
||||
}
|
||||
case BIGINT:
|
||||
return as(BigInt(value), index);
|
||||
case 'BigInt':
|
||||
return as(Object(BigInt(value)), index);
|
||||
}
|
||||
return as(new env[type](value), index);
|
||||
};
|
||||
|
||||
return unpair;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Array<string,any>} Record a type representation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns a deserialized value from a serialized array of Records.
|
||||
* @param {Record[]} serialized a previously serialized value.
|
||||
* @returns {any}
|
||||
*/
|
||||
export const deserialize = serialized => deserializer(new Map, serialized)(0);
|
|
@ -0,0 +1,25 @@
|
|||
import {deserialize} from './deserialize.js';
|
||||
import {serialize} from './serialize.js';
|
||||
|
||||
/**
|
||||
* @typedef {Array<string,any>} Record a type representation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns an array of serialized Records.
|
||||
* @param {any} any a serializable value.
|
||||
* @param {{transfer?: any[], json?: boolean, lossy?: boolean}?} options an object with
|
||||
* a transfer option (ignored when polyfilled) and/or non standard fields that
|
||||
* fallback to the polyfill if present.
|
||||
* @returns {Record[]}
|
||||
*/
|
||||
export default typeof structuredClone === "function" ?
|
||||
/* c8 ignore start */
|
||||
(any, options) => (
|
||||
options && ('json' in options || 'lossy' in options) ?
|
||||
deserialize(serialize(any, options)) : structuredClone(any)
|
||||
) :
|
||||
(any, options) => deserialize(serialize(any, options));
|
||||
/* c8 ignore stop */
|
||||
|
||||
export {deserialize, serialize};
|
|
@ -0,0 +1,21 @@
|
|||
/*! (c) Andrea Giammarchi - ISC */
|
||||
|
||||
import {deserialize} from './deserialize.js';
|
||||
import {serialize} from './serialize.js';
|
||||
|
||||
const {parse: $parse, stringify: $stringify} = JSON;
|
||||
const options = {json: true, lossy: true};
|
||||
|
||||
/**
|
||||
* Revive a previously stringified structured clone.
|
||||
* @param {string} str previously stringified data as string.
|
||||
* @returns {any} whatever was previously stringified as clone.
|
||||
*/
|
||||
export const parse = str => deserialize($parse(str));
|
||||
|
||||
/**
|
||||
* Represent a structured clone value as string.
|
||||
* @param {any} any some clone-able value to stringify.
|
||||
* @returns {string} the value stringified.
|
||||
*/
|
||||
export const stringify = any => $stringify(serialize(any, options));
|
|
@ -0,0 +1,6 @@
|
|||
import structuredClone from './index.js';
|
||||
|
||||
if (!("structuredClone" in globalThis)) {
|
||||
console.debug("Monkey-patching structuredClone");
|
||||
globalThis.structuredClone = structuredClone;
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
import {
|
||||
VOID, PRIMITIVE,
|
||||
ARRAY, OBJECT,
|
||||
DATE, REGEXP, MAP, SET,
|
||||
ERROR, BIGINT
|
||||
} from './types.js';
|
||||
|
||||
const EMPTY = '';
|
||||
|
||||
const {toString} = {};
|
||||
const {keys} = Object;
|
||||
|
||||
const typeOf = value => {
|
||||
const type = typeof value;
|
||||
if (type !== 'object' || !value)
|
||||
return [PRIMITIVE, type];
|
||||
|
||||
const asString = toString.call(value).slice(8, -1);
|
||||
switch (asString) {
|
||||
case 'Array':
|
||||
return [ARRAY, EMPTY];
|
||||
case 'Object':
|
||||
return [OBJECT, EMPTY];
|
||||
case 'Date':
|
||||
return [DATE, EMPTY];
|
||||
case 'RegExp':
|
||||
return [REGEXP, EMPTY];
|
||||
case 'Map':
|
||||
return [MAP, EMPTY];
|
||||
case 'Set':
|
||||
return [SET, EMPTY];
|
||||
}
|
||||
|
||||
if (asString.includes('Array'))
|
||||
return [ARRAY, asString];
|
||||
|
||||
if (asString.includes('Error'))
|
||||
return [ERROR, asString];
|
||||
|
||||
return [OBJECT, asString];
|
||||
};
|
||||
|
||||
const shouldSkip = ([TYPE, type]) => (
|
||||
TYPE === PRIMITIVE &&
|
||||
(type === 'function' || type === 'symbol')
|
||||
);
|
||||
|
||||
const serializer = (strict, json, $, _) => {
|
||||
|
||||
const as = (out, value) => {
|
||||
const index = _.push(out) - 1;
|
||||
$.set(value, index);
|
||||
return index;
|
||||
};
|
||||
|
||||
const pair = value => {
|
||||
if ($.has(value))
|
||||
return $.get(value);
|
||||
|
||||
let [TYPE, type] = typeOf(value);
|
||||
switch (TYPE) {
|
||||
case PRIMITIVE: {
|
||||
let entry = value;
|
||||
switch (type) {
|
||||
case 'bigint':
|
||||
TYPE = BIGINT;
|
||||
entry = value.toString();
|
||||
break;
|
||||
case 'function':
|
||||
case 'symbol':
|
||||
if (strict)
|
||||
throw new TypeError('unable to serialize ' + type);
|
||||
entry = null;
|
||||
break;
|
||||
case 'undefined':
|
||||
return as([VOID], value);
|
||||
}
|
||||
return as([TYPE, entry], value);
|
||||
}
|
||||
case ARRAY: {
|
||||
if (type)
|
||||
return as([type, [...value]], value);
|
||||
|
||||
const arr = [];
|
||||
const index = as([TYPE, arr], value);
|
||||
for (const entry of value)
|
||||
arr.push(pair(entry));
|
||||
return index;
|
||||
}
|
||||
case OBJECT: {
|
||||
if (type) {
|
||||
switch (type) {
|
||||
case 'BigInt':
|
||||
return as([type, value.toString()], value);
|
||||
case 'Boolean':
|
||||
case 'Number':
|
||||
case 'String':
|
||||
return as([type, value.valueOf()], value);
|
||||
}
|
||||
}
|
||||
|
||||
if (json && ('toJSON' in value))
|
||||
return pair(value.toJSON());
|
||||
|
||||
const entries = [];
|
||||
const index = as([TYPE, entries], value);
|
||||
for (const key of keys(value)) {
|
||||
if (strict || !shouldSkip(typeOf(value[key])))
|
||||
entries.push([pair(key), pair(value[key])]);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
case DATE:
|
||||
return as([TYPE, value.toISOString()], value);
|
||||
case REGEXP: {
|
||||
const {source, flags} = value;
|
||||
return as([TYPE, {source, flags}], value);
|
||||
}
|
||||
case MAP: {
|
||||
const entries = [];
|
||||
const index = as([TYPE, entries], value);
|
||||
for (const [key, entry] of value) {
|
||||
if (strict || !(shouldSkip(typeOf(key)) || shouldSkip(typeOf(entry))))
|
||||
entries.push([pair(key), pair(entry)]);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
case SET: {
|
||||
const entries = [];
|
||||
const index = as([TYPE, entries], value);
|
||||
for (const entry of value) {
|
||||
if (strict || !shouldSkip(typeOf(entry)))
|
||||
entries.push(pair(entry));
|
||||
}
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
const {message} = value;
|
||||
return as([TYPE, {name: type, message}], value);
|
||||
};
|
||||
|
||||
return pair;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Array<string,any>} Record a type representation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns an array of serialized Records.
|
||||
* @param {any} value a serializable value.
|
||||
* @param {{json?: boolean, lossy?: boolean}?} options an object with a `lossy` or `json` property that,
|
||||
* if `true`, will not throw errors on incompatible types, and behave more
|
||||
* like JSON stringify would behave. Symbol and Function will be discarded.
|
||||
* @returns {Record[]}
|
||||
*/
|
||||
export const serialize = (value, {json, lossy} = {}) => {
|
||||
const _ = [];
|
||||
return serializer(!(json || lossy), !!json, new Map, _)(value), _;
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
export const VOID = -1;
|
||||
export const PRIMITIVE = 0;
|
||||
export const ARRAY = 1;
|
||||
export const OBJECT = 2;
|
||||
export const DATE = 3;
|
||||
export const REGEXP = 4;
|
||||
export const MAP = 5;
|
||||
export const SET = 6;
|
||||
export const ERROR = 7;
|
||||
export const BIGINT = 8;
|
||||
// export const SYMBOL = 9;
|
Loading…
Reference in New Issue