зеркало из https://github.com/mozilla/gecko-dev.git
185 строки
5.6 KiB
JavaScript
185 строки
5.6 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
// Portions of this file are originally from narwhal.js (http://narwhaljs.org)
|
|
// Copyright (c) 2009 Thomas Robinson <280north.com>
|
|
// MIT license: http://opensource.org/licenses/MIT
|
|
|
|
"use strict";
|
|
|
|
this.EXPORTED_SYMBOLS = [
|
|
"ObjectUtils"
|
|
];
|
|
|
|
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
|
|
|
// Used only to cause test failures.
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
|
"resource://gre/modules/Promise.jsm");
|
|
|
|
this.ObjectUtils = {
|
|
/**
|
|
* This tests objects & values for deep equality.
|
|
*
|
|
* We check using the most exact approximation of equality between two objects
|
|
* to keep the chance of false positives to a minimum.
|
|
* `JSON.stringify` is not designed to be used for this purpose; objects may
|
|
* have ambiguous `toJSON()` implementations that would influence the test.
|
|
*
|
|
* @param a (mixed) Object or value to be compared.
|
|
* @param b (mixed) Object or value to be compared.
|
|
* @return Boolean Whether the objects are deep equal.
|
|
*/
|
|
deepEqual: function(a, b) {
|
|
return _deepEqual(a, b);
|
|
},
|
|
|
|
/**
|
|
* A thin wrapper on an object, designed to prevent client code from
|
|
* accessing non-existent properties because of typos.
|
|
*
|
|
* // Without `strict`
|
|
* let foo = { myProperty: 1 };
|
|
* foo.MyProperty; // undefined
|
|
*
|
|
* // With `strict`
|
|
* let strictFoo = ObjectUtils.strict(foo);
|
|
* strictFoo.myProperty; // 1
|
|
* strictFoo.MyProperty; // TypeError: No such property "MyProperty"
|
|
*
|
|
* Note that `strict` has no effect in non-DEBUG mode.
|
|
*/
|
|
strict: function(obj) {
|
|
return _strict(obj);
|
|
}
|
|
};
|
|
|
|
// ... Start of previously MIT-licensed code.
|
|
// This deepEqual implementation is originally from narwhal.js (http://narwhaljs.org)
|
|
// Copyright (c) 2009 Thomas Robinson <280north.com>
|
|
// MIT license: http://opensource.org/licenses/MIT
|
|
|
|
function _deepEqual(a, b) {
|
|
// The numbering below refers to sections in the CommonJS spec.
|
|
|
|
// 7.1 All identical values are equivalent, as determined by ===.
|
|
if (a === b) {
|
|
return true;
|
|
// 7.2 If the b value is a Date object, the a value is
|
|
// equivalent if it is also a Date object that refers to the same time.
|
|
}
|
|
let aIsDate = instanceOf(a, "Date");
|
|
let bIsDate = instanceOf(b, "Date");
|
|
if (aIsDate || bIsDate) {
|
|
if (!aIsDate || !bIsDate) {
|
|
return false;
|
|
}
|
|
if (isNaN(a.getTime()) && isNaN(b.getTime()))
|
|
return true;
|
|
return a.getTime() === b.getTime();
|
|
// 7.3 If the b value is a RegExp object, the a value is
|
|
// equivalent if it is also a RegExp object with the same source and
|
|
// properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
|
|
}
|
|
let aIsRegExp = instanceOf(a, "RegExp");
|
|
let bIsRegExp = instanceOf(b, "RegExp");
|
|
if (aIsRegExp || bIsRegExp) {
|
|
return aIsRegExp && bIsRegExp &&
|
|
a.source === b.source &&
|
|
a.global === b.global &&
|
|
a.multiline === b.multiline &&
|
|
a.lastIndex === b.lastIndex &&
|
|
a.ignoreCase === b.ignoreCase;
|
|
// 7.4 Other pairs that do not both pass typeof value == "object",
|
|
// equivalence is determined by ==.
|
|
}
|
|
if (typeof a != "object" || typeof b != "object") {
|
|
return a == b;
|
|
}
|
|
// 7.5 For all other Object pairs, including Array objects, equivalence is
|
|
// determined by having the same number of owned properties (as verified
|
|
// with Object.prototype.hasOwnProperty.call), the same set of keys
|
|
// (although not necessarily the same order), equivalent values for every
|
|
// corresponding key, and an identical 'prototype' property. Note: this
|
|
// accounts for both named and indexed properties on Arrays.
|
|
return objEquiv(a, b);
|
|
}
|
|
|
|
function instanceOf(object, type) {
|
|
return Object.prototype.toString.call(object) == "[object " + type + "]";
|
|
}
|
|
|
|
function isUndefinedOrNull(value) {
|
|
return value === null || value === undefined;
|
|
}
|
|
|
|
function isArguments(object) {
|
|
return instanceOf(object, "Arguments");
|
|
}
|
|
|
|
function objEquiv(a, b) {
|
|
if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) {
|
|
return false;
|
|
}
|
|
// An identical 'prototype' property.
|
|
if ((a.prototype || undefined) != (b.prototype || undefined)) {
|
|
return false;
|
|
}
|
|
// Object.keys may be broken through screwy arguments passing. Converting to
|
|
// an array solves the problem.
|
|
if (isArguments(a)) {
|
|
if (!isArguments(b)) {
|
|
return false;
|
|
}
|
|
a = pSlice.call(a);
|
|
b = pSlice.call(b);
|
|
return _deepEqual(a, b);
|
|
}
|
|
let ka, kb;
|
|
try {
|
|
ka = Object.keys(a);
|
|
kb = Object.keys(b);
|
|
} catch (e) {
|
|
// Happens when one is a string literal and the other isn't
|
|
return false;
|
|
}
|
|
// Having the same number of owned properties (keys incorporates
|
|
// hasOwnProperty)
|
|
if (ka.length != kb.length)
|
|
return false;
|
|
// The same set of keys (although not necessarily the same order),
|
|
ka.sort();
|
|
kb.sort();
|
|
// Equivalent values for every corresponding key, and possibly expensive deep
|
|
// test
|
|
for (let key of ka) {
|
|
if (!_deepEqual(a[key], b[key])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ... End of previously MIT-licensed code.
|
|
|
|
function _strict(obj) {
|
|
if (typeof obj != "object") {
|
|
throw new TypeError("Expected an object");
|
|
}
|
|
|
|
return new Proxy(obj, {
|
|
get: function(target, name) {
|
|
if (name in obj) {
|
|
return obj[name];
|
|
}
|
|
|
|
let error = new TypeError(`No such property: "${name}"`);
|
|
Promise.reject(error); // Cause an xpcshell/mochitest failure.
|
|
throw error;
|
|
}
|
|
});
|
|
}
|