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/pagination.js"></script>
|
||||||
<script src="lib/toolcool-color-picker.js"></script>
|
<script src="lib/toolcool-color-picker.js"></script>
|
||||||
<script src="lib/svg-inject.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/swiped-events.js"></script>
|
||||||
<script type="module" src="lib/eventemitter.js"></script>
|
<script type="module" src="lib/eventemitter.js"></script>
|
||||||
<script type="module" src="scripts/power-user.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