Bug 1608150 [wpt PR 21112] - Add missing space in `./wpt lint` command line docs...
[gecko.git] / toolkit / modules / ObjectUtils.jsm
blobb61b925b1090375d66a6ae99c8ae640b27c641ef
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
9 "use strict";
11 var EXPORTED_SYMBOLS = ["ObjectUtils"];
13 // Used only to cause test failures.
15 var pSlice = Array.prototype.slice;
17 var ObjectUtils = {
18   /**
19    * This tests objects & values for deep equality.
20    *
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.
25    *
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.
29    */
30   deepEqual(a, b) {
31     return _deepEqual(a, b);
32   },
34   /**
35    * A thin wrapper on an object, designed to prevent client code from
36    * accessing non-existent properties because of typos.
37    *
38    * // Without `strict`
39    * let foo = { myProperty: 1 };
40    * foo.MyProperty; // undefined
41    *
42    * // With `strict`
43    * let strictFoo = ObjectUtils.strict(foo);
44    * strictFoo.myProperty; // 1
45    * strictFoo.MyProperty; // TypeError: No such property "MyProperty"
46    *
47    * Note that `strict` has no effect in non-DEBUG mode.
48    */
49   strict(obj) {
50     return _strict(obj);
51   },
53   /**
54    * Returns `true` if `obj` is an array without elements, an object without
55    * enumerable properties, or a falsy primitive; `false` otherwise.
56    */
57   isEmpty(obj) {
58     if (!obj) {
59       return true;
60     }
61     if (typeof obj != "object") {
62       return false;
63     }
64     if (Array.isArray(obj)) {
65       return !obj.length;
66     }
67     for (let key in obj) {
68       return false;
69     }
70     return true;
71   },
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 ===.
83   if (a === b) {
84     return true;
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.
87   }
88   let aIsDate = instanceOf(a, "Date");
89   let bIsDate = instanceOf(b, "Date");
90   if (aIsDate || bIsDate) {
91     if (!aIsDate || !bIsDate) {
92       return false;
93     }
94     if (isNaN(a.getTime()) && isNaN(b.getTime())) {
95       return true;
96     }
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`).
101   }
102   let aIsRegExp = instanceOf(a, "RegExp");
103   let bIsRegExp = instanceOf(b, "RegExp");
104   if (aIsRegExp || bIsRegExp) {
105     return (
106       aIsRegExp &&
107       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
113     );
114     // 7.4 Other pairs that do not both pass typeof value == "object",
115     // equivalence is determined by ==.
116   }
117   if (typeof a != "object" || typeof b != "object") {
118     return a == b;
119   }
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)) {
143     return false;
144   }
145   // An identical 'prototype' property.
146   if ((a.prototype || undefined) != (b.prototype || undefined)) {
147     return false;
148   }
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)) {
153       return false;
154     }
155     a = pSlice.call(a);
156     b = pSlice.call(b);
157     return _deepEqual(a, b);
158   }
159   let ka, kb;
160   try {
161     ka = Object.keys(a);
162     kb = Object.keys(b);
163   } catch (e) {
164     // Happens when one is a string literal and the other isn't
165     return false;
166   }
167   // Having the same number of owned properties (keys incorporates
168   // hasOwnProperty)
169   if (ka.length != kb.length) {
170     return false;
171   }
172   // The same set of keys (although not necessarily the same order),
173   ka.sort();
174   kb.sort();
175   // Equivalent values for every corresponding key, and possibly expensive deep
176   // test
177   for (let key of ka) {
178     if (!_deepEqual(a[key], b[key])) {
179       return false;
180     }
181   }
182   return true;
185 // ... End of previously MIT-licensed code.
187 function _strict(obj) {
188   if (typeof obj != "object") {
189     throw new TypeError("Expected an object");
190   }
192   return new Proxy(obj, {
193     get(target, name) {
194       if (name in obj) {
195         return obj[name];
196       }
198       let error = new TypeError(`No such property: "${name}"`);
199       Promise.reject(error); // Cause an xpcshell/mochitest failure.
200       throw error;
201     },
202   });