Add monkey patch for structured Clone

This commit is contained in:
Cohee 2023-08-25 16:40:43 +03:00
parent 1dd1607b94
commit e34e51226f
8 changed files with 319 additions and 0 deletions

View File

@ -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>

View File

@ -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.

View File

@ -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);

View File

@ -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};

View File

@ -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));

View File

@ -0,0 +1,6 @@
import structuredClone from './index.js';
if (!("structuredClone" in globalThis)) {
console.debug("Monkey-patching structuredClone");
globalThis.structuredClone = structuredClone;
}

View File

@ -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), _;
};

View File

@ -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;