diff --git a/lib/util/object_utils.js b/lib/util/object_utils.js index 762af37567..716693b6c5 100644 --- a/lib/util/object_utils.js +++ b/lib/util/object_utils.js @@ -89,4 +89,87 @@ shaka.util.ObjectUtils = class { } 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(',') + ']'; + } }; diff --git a/lib/util/stream_utils.js b/lib/util/stream_utils.js index 8ec728e6b6..61feda48be 100644 --- a/lib/util/stream_utils.js +++ b/lib/util/stream_utils.js @@ -590,40 +590,6 @@ shaka.util.StreamUtils = class { } - /** - * Constructs a string out of an object, similar to the JSON.stringify method. - * Unlike that method, this guarantees that the order of the keys is - * alphabetical, so it can be used as a way to reliably compare two objects. - * - * @param {!Object} obj - * @return {string} - * @private - */ - static alphabeticalKeyOrderStringify_(obj) { - const keys = []; - for (const key in obj) { - keys.push(key); - } - // 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 instanceof Object) { - const stringifiedValue = - shaka.util.StreamUtils.alphabeticalKeyOrderStringify_(value); - terms.push(escapedKey + ':' + stringifiedValue); - } else { - const escapedValue = JSON.stringify(value); - terms.push(escapedKey + ':' + escapedValue); - } - } - return '{' + terms.join(',') + '}'; - } - - /** * Queries mediaCapabilities for the decoding info for that decoding config, * and assigns it to the given variant. @@ -659,7 +625,7 @@ shaka.util.StreamUtils = class { const promises = []; for (const decodingConfig of decodingConfigs) { const cacheKey = - StreamUtils.alphabeticalKeyOrderStringify_(decodingConfig); + shaka.util.ObjectUtils.alphabeticalKeyOrderStringify(decodingConfig); const cache = StreamUtils.decodingConfigCache_; if (cache[cacheKey]) {