mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-16 16:16:40 +03:00
90e47eb58a
Add special case for arrays, for compatibility with frameworks or polyfills that add properties to Array or Array instances. Add special case for functions, which always contain circular references and are unexpected in this context. These seem to appear because of the frameworks/polyfills mentioned above. Move everything to ObjectUtils, since this is extremely generic. Closes #7435
176 lines
4.8 KiB
JavaScript
176 lines
4.8 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
goog.provide('shaka.util.ObjectUtils');
|
|
|
|
|
|
shaka.util.ObjectUtils = class {
|
|
/**
|
|
* Performs a deep clone of the given simple object. This does not copy
|
|
* prototypes, custom properties (e.g. read-only), or multiple references to
|
|
* the same object. If the caller needs these fields, it will need to set
|
|
* them after this returns.
|
|
*
|
|
* @template T
|
|
* @param {T} arg
|
|
* @return {T}
|
|
*/
|
|
static cloneObject(arg) {
|
|
const seenObjects = new WeakSet();
|
|
// This recursively clones the value |val|, using the captured variable
|
|
// |seenObjects| to track the objects we have already cloned.
|
|
/** @suppress {strictMissingProperties} */
|
|
const clone = (val) => {
|
|
switch (typeof val) {
|
|
case 'undefined':
|
|
case 'boolean':
|
|
case 'number':
|
|
case 'string':
|
|
case 'symbol':
|
|
case 'function':
|
|
return val;
|
|
case 'object':
|
|
default: {
|
|
// typeof null === 'object'
|
|
if (!val) {
|
|
return val;
|
|
}
|
|
|
|
// This covers Uint8Array and friends, even without a TypedArray
|
|
// base-class constructor.
|
|
const isTypedArray = ArrayBuffer.isView(val);
|
|
if (isTypedArray) {
|
|
return val;
|
|
}
|
|
|
|
if (seenObjects.has(val)) {
|
|
return null;
|
|
}
|
|
|
|
const isArray = val.constructor == Array;
|
|
if (val.constructor != Object && !isArray) {
|
|
return null;
|
|
}
|
|
|
|
seenObjects.add(val);
|
|
const ret = isArray ? [] : {};
|
|
// Note |name| will equal a number for arrays.
|
|
for (const name in val) {
|
|
ret[name] = clone(val[name]);
|
|
}
|
|
|
|
// Length is a non-enumerable property, but we should copy it over in
|
|
// case it is not the default.
|
|
if (isArray) {
|
|
ret.length = val.length;
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
};
|
|
return clone(arg);
|
|
}
|
|
|
|
/**
|
|
* Performs a shallow clone of the given simple object. This does not copy
|
|
* prototypes or custom properties (e.g. read-only).
|
|
*
|
|
* @template T
|
|
* @param {T} original
|
|
* @return {T}
|
|
*/
|
|
static shallowCloneObject(original) {
|
|
const clone = /** @type {?} */({});
|
|
for (const k in original) {
|
|
clone[k] = original[k];
|
|
}
|
|
return clone;
|
|
}
|
|
|
|
|
|
/**
|
|
* Constructs a string out of a value, similar to the JSON.stringify method.
|
|
* Unlike that method, this guarantees that the order of the keys in an
|
|
* object is alphabetical, so it can be used as a way to reliably compare two
|
|
* objects.
|
|
*
|
|
* @param {?} value
|
|
* @return {string}
|
|
*/
|
|
static alphabeticalKeyOrderStringify(value) {
|
|
if (Array.isArray(value)) {
|
|
return shaka.util.ObjectUtils.arrayStringify_(value);
|
|
} else if (typeof value == 'function') {
|
|
// For safety, skip functions. For function x,
|
|
// x.prototype.constructor.prototype === x.prototype, so all functions
|
|
// contain circular references if treated like Objects.
|
|
return '';
|
|
} else if (value instanceof Object) {
|
|
return shaka.util.ObjectUtils.objectStringify_(value);
|
|
} else {
|
|
return JSON.stringify(value);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Helper for alphabeticalKeyOrderStringify for objects.
|
|
*
|
|
* @param {!Object} obj
|
|
* @return {string}
|
|
* @private
|
|
*/
|
|
static objectStringify_(obj) {
|
|
// NOTE: This excludes prototype chain keys. For now, this is intended for
|
|
// anonymous objects only, so we don't care. If that changes, go back to a
|
|
// for-in loop.
|
|
const keys = Object.keys(obj);
|
|
// Alphabetically sort the keys, so they will be in a reliable order.
|
|
keys.sort();
|
|
|
|
const terms = [];
|
|
for (const key of keys) {
|
|
const escapedKey = JSON.stringify(key);
|
|
const value = obj[key];
|
|
if (value !== undefined) {
|
|
const escapedValue =
|
|
shaka.util.ObjectUtils.alphabeticalKeyOrderStringify(value);
|
|
if (escapedValue) {
|
|
terms.push(escapedKey + ':' + escapedValue);
|
|
}
|
|
}
|
|
}
|
|
return '{' + terms.join(',') + '}';
|
|
}
|
|
|
|
|
|
/**
|
|
* Helper for alphabeticalKeyOrderStringify for arrays.
|
|
*
|
|
* This could itself be JSON.stringify, except we want objects within the
|
|
* array to go through our own stringifiers.
|
|
*
|
|
* @param {!Array} arr
|
|
* @return {string}
|
|
* @private
|
|
*/
|
|
static arrayStringify_(arr) {
|
|
const terms = [];
|
|
for (let index = 0; index < arr.length; index++) {
|
|
const escapedKey = index.toString();
|
|
const value = arr[index];
|
|
if (value !== undefined) {
|
|
const escapedValue =
|
|
shaka.util.ObjectUtils.alphabeticalKeyOrderStringify(value);
|
|
if (escapedValue) {
|
|
terms.push(escapedKey + ':' + escapedValue);
|
|
}
|
|
}
|
|
}
|
|
return '[' + terms.join(',') + ']';
|
|
}
|
|
};
|