1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 // Portions of this file are originally from narwhal.js (http://narwhaljs.org)
6 // Copyright (c) 2009 Thomas Robinson <280north.com>
7 // MIT license: http://opensource.org/licenses/MIT
11 var EXPORTED_SYMBOLS = ["ObjectUtils"];
13 // Used only to cause test failures.
15 var pSlice = Array.prototype.slice;
19 * This tests objects & values for deep equality.
21 * We check using the most exact approximation of equality between two objects
22 * to keep the chance of false positives to a minimum.
23 * `JSON.stringify` is not designed to be used for this purpose; objects may
24 * have ambiguous `toJSON()` implementations that would influence the test.
26 * @param a (mixed) Object or value to be compared.
27 * @param b (mixed) Object or value to be compared.
28 * @return Boolean Whether the objects are deep equal.
31 return _deepEqual(a, b);
35 * A thin wrapper on an object, designed to prevent client code from
36 * accessing non-existent properties because of typos.
39 * let foo = { myProperty: 1 };
40 * foo.MyProperty; // undefined
43 * let strictFoo = ObjectUtils.strict(foo);
44 * strictFoo.myProperty; // 1
45 * strictFoo.MyProperty; // TypeError: No such property "MyProperty"
47 * Note that `strict` has no effect in non-DEBUG mode.
54 * Returns `true` if `obj` is an array without elements, an object without
55 * enumerable properties, or a falsy primitive; `false` otherwise.
61 if (typeof obj != "object") {
64 if (Array.isArray(obj)) {
67 for (let key in obj) {
74 // ... Start of previously MIT-licensed code.
75 // This deepEqual implementation is originally from narwhal.js (http://narwhaljs.org)
76 // Copyright (c) 2009 Thomas Robinson <280north.com>
77 // MIT license: http://opensource.org/licenses/MIT
79 function _deepEqual(a, b) {
80 // The numbering below refers to sections in the CommonJS spec.
82 // 7.1 All identical values are equivalent, as determined by ===.
85 // 7.2 If the b value is a Date object, the a value is
86 // equivalent if it is also a Date object that refers to the same time.
88 let aIsDate = instanceOf(a, "Date");
89 let bIsDate = instanceOf(b, "Date");
90 if (aIsDate || bIsDate) {
91 if (!aIsDate || !bIsDate) {
94 if (isNaN(a.getTime()) && isNaN(b.getTime())) {
97 return a.getTime() === b.getTime();
98 // 7.3 If the b value is a RegExp object, the a value is
99 // equivalent if it is also a RegExp object with the same source and
100 // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
102 let aIsRegExp = instanceOf(a, "RegExp");
103 let bIsRegExp = instanceOf(b, "RegExp");
104 if (aIsRegExp || bIsRegExp) {
108 a.source === b.source &&
109 a.global === b.global &&
110 a.multiline === b.multiline &&
111 a.lastIndex === b.lastIndex &&
112 a.ignoreCase === b.ignoreCase
114 // 7.4 Other pairs that do not both pass typeof value == "object",
115 // equivalence is determined by ==.
117 if (typeof a != "object" || typeof b != "object") {
120 // 7.5 For all other Object pairs, including Array objects, equivalence is
121 // determined by having the same number of owned properties (as verified
122 // with Object.prototype.hasOwnProperty.call), the same set of keys
123 // (although not necessarily the same order), equivalent values for every
124 // corresponding key, and an identical 'prototype' property. Note: this
125 // accounts for both named and indexed properties on Arrays.
126 return objEquiv(a, b);
129 function instanceOf(object, type) {
130 return Object.prototype.toString.call(object) == "[object " + type + "]";
133 function isUndefinedOrNull(value) {
134 return value === null || value === undefined;
137 function isArguments(object) {
138 return instanceOf(object, "Arguments");
141 function objEquiv(a, b) {
142 if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) {
145 // An identical 'prototype' property.
146 if ((a.prototype || undefined) != (b.prototype || undefined)) {
149 // Object.keys may be broken through screwy arguments passing. Converting to
150 // an array solves the problem.
151 if (isArguments(a)) {
152 if (!isArguments(b)) {
157 return _deepEqual(a, b);
164 // Happens when one is a string literal and the other isn't
167 // Having the same number of owned properties (keys incorporates
169 if (ka.length != kb.length) {
172 // The same set of keys (although not necessarily the same order),
175 // Equivalent values for every corresponding key, and possibly expensive deep
177 for (let key of ka) {
178 if (!_deepEqual(a[key], b[key])) {
185 // ... End of previously MIT-licensed code.
187 function _strict(obj) {
188 if (typeof obj != "object") {
189 throw new TypeError("Expected an object");
192 return new Proxy(obj, {
198 let error = new TypeError(`No such property: "${name}"`);
199 Promise.reject(error); // Cause an xpcshell/mochitest failure.