Files
ark-stronghold-protocol/public/js/ui/compat.js
T
sganggs 01a44cd288 卫戍协议:盟约 web remake — initial import
Online co-op browser remake of Arknights' Stronghold Protocol: Alliance
(client-simulated combat, official waves/bonds/items/strategies, 3D board,
facing wheel, operator loadout, official emotes, deployment scripts).
2026-09-29 16:21:31 +08:00

94 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Small polyfills for the older browsers the client supports (Safari / iOS 15.0–15.3, Firefox ESR, older Chromium on
// Android tablets). Imported first by main.js (and the dev harnesses), so every later module — including the ones
// loaded on demand (render engine, battle sim) — sees them. Each one is installed only when the browser lacks it
// (feature detection, never UA sniffing) and is a faithful subset of the standard behaviour:
// Object.hasOwn Safari < 15.4, Firefox < 92
// Array/String/TypedArray .at() Safari < 15.4, Firefox < 90
// Array .findLast / .findLastIndex Safari < 15.4, Firefox < 104
// structuredClone Safari < 15.4, Firefox < 94 (JSON-safe values + Map/Set/Date; enough for
// the plain state objects the client clones)
// globalThis.queueMicrotask very old WebViews
// `installCompat(target)` is exported for tests (it patches the given global object instead).
/**
* Install the polyfills on `g` (default globalThis). Returns the names that were missing and got installed.
* @param {any} [g]
* @returns {string[]}
*/
export function installCompat(g = globalThis) {
const added = [];
const def = (obj, name, fn) => {
if (!obj || typeof obj[name] === 'function') return;
try {
Object.defineProperty(obj, name, { value: fn, writable: true, configurable: true, enumerable: false });
added.push(name);
} catch { /* frozen: leave it */ }
};
const O = g.Object || Object;
def(O, 'hasOwn', function hasOwn(obj, key) {
if (obj == null) throw new TypeError('Cannot convert undefined or null to object');
return Object.prototype.hasOwnProperty.call(Object(obj), key);
});
function at(i) {
const len = this.length >>> 0;
let k = Math.trunc(Number(i)) || 0;
if (k < 0) k += len;
if (k < 0 || k >= len) return undefined;
return typeof this === 'string' || this instanceof String ? String(this).charAt(k) : this[k];
}
def((g.Array || Array).prototype, 'at', at);
def((g.String || String).prototype, 'at', at);
const TA = g.Int8Array ? Object.getPrototypeOf(g.Int8Array.prototype) : null;
if (TA) def(TA, 'at', at);
def((g.Array || Array).prototype, 'findLast', function findLast(fn, thisArg) {
for (let i = (this.length >>> 0) - 1; i >= 0; i--) if (fn.call(thisArg, this[i], i, this)) return this[i];
return undefined;
});
def((g.Array || Array).prototype, 'findLastIndex', function findLastIndex(fn, thisArg) {
for (let i = (this.length >>> 0) - 1; i >= 0; i--) if (fn.call(thisArg, this[i], i, this)) return i;
return -1;
});
def(g, 'structuredClone', function structuredClone(value) { return cloneValue(value, new Map()); });
def(g, 'queueMicrotask', function queueMicrotask(fn) { Promise.resolve().then(fn).catch((err) => setTimeout(() => { throw err; })); });
return added;
}
/** Deep clone of plain data (objects, arrays, Map, Set, Date, RegExp, typed arrays), cycles preserved. */
function cloneValue(v, seen) {
if (v === null || typeof v !== 'object') {
if (typeof v === 'function' || typeof v === 'symbol') throw new TypeError('structuredClone: value could not be cloned');
return v;
}
if (seen.has(v)) return seen.get(v);
let out;
if (v instanceof Date) out = new Date(v.getTime());
else if (v instanceof RegExp) out = new RegExp(v.source, v.flags);
else if (ArrayBuffer.isView(v)) out = new v.constructor(v);
else if (v instanceof ArrayBuffer) out = v.slice(0);
else if (v instanceof Map) {
out = new Map();
seen.set(v, out);
for (const [k, x] of v) out.set(cloneValue(k, seen), cloneValue(x, seen));
return out;
} else if (v instanceof Set) {
out = new Set();
seen.set(v, out);
for (const x of v) out.add(cloneValue(x, seen));
return out;
} else if (Array.isArray(v)) {
out = new Array(v.length);
seen.set(v, out);
for (let i = 0; i < v.length; i++) out[i] = cloneValue(v[i], seen);
return out;
} else {
out = {};
seen.set(v, out);
for (const k of Object.keys(v)) out[k] = cloneValue(v[k], seen);
return out;
}
seen.set(v, out);
return out;
}
export const installedPolyfills = installCompat();