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 file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 * Module for reading Property Lists (.plist) files
7 * ------------------------------------------------
8 * This module functions as a reader for Apple Property Lists (.plist files).
9 * It supports both binary and xml formatted property lists. It does not
10 * support the legacy ASCII format. Reading of Cocoa's Keyed Archives serialized
11 * to binary property lists isn't supported either.
13 * Property Lists objects are represented by standard JS and Mozilla types,
16 * XML type Cocoa Class Returned type(s)
17 * --------------------------------------------------------------------------
18 * <true/> / <false/> NSNumber TYPE_PRIMITIVE boolean
19 * <integer> / <real> NSNumber TYPE_PRIMITIVE number
20 * TYPE_INT64 String [1]
21 * Not Available NSNull TYPE_PRIMITIVE null [2]
22 * TYPE_PRIMITIVE undefined [3]
23 * <date/> NSDate TYPE_DATE Date
24 * <data/> NSData TYPE_UINT8_ARRAY Uint8Array
25 * <array/> NSArray TYPE_ARRAY Array
26 * Not Available NSSet TYPE_ARRAY Array [2][4]
27 * <dict/> NSDictionary TYPE_DICTIONARY Map
29 * Use PropertyListUtils.getObjectType to detect the type of a Property list
33 * 1) Property lists supports storing U/Int64 numbers, while JS can only handle
34 * numbers that are in this limits of float-64 (±2^53). For numbers that
35 * do not outbound this limits, simple primitive number are always used.
36 * Otherwise, a String object.
37 * 2) About NSNull and NSSet values: While the xml format has no support for
38 * representing null and set values, the documentation for the binary format
39 * states that it supports storing both types. However, the Cocoa APIs for
40 * serializing property lists do not seem to support either types (test with
41 * NSPropertyListSerialization::propertyList:isValidForFormat). Furthermore,
42 * if an array or a dictionary (Map) contains a NSNull or a NSSet value, they cannot
43 * be serialized to a property list.
44 * As for usage within OS X, not surprisingly there's no known usage of
45 * storing either of these types in a property list. It seems that, for now,
46 * Apple is keeping the features of binary and xml formats in sync, probably as
47 * long as the XML format is not officially deprecated.
48 * 3) Not used anywhere.
49 * 4) About NSSet representation: For the time being, we represent those
50 * theoretical NSSet objects the same way NSArray is represented.
51 * While this would most certainly work, it is not the right way to handle
52 * it. A more correct representation for a set is a js generator, which would
53 * read the set lazily and has no indices semantics.
58 var EXPORTED_SYMBOLS = ["PropertyListUtils"];
60 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
62 XPCOMUtils.defineLazyGlobalGetters(this, ["DOMParser", "File", "FileReader"]);
64 ChromeUtils.defineModuleGetter(this, "ctypes",
65 "resource://gre/modules/ctypes.jsm");
66 ChromeUtils.defineModuleGetter(this, "Services",
67 "resource://gre/modules/Services.jsm");
69 var PropertyListUtils = Object.freeze({
71 * Asynchronously reads a file as a property list.
73 * @param aFile (Blob/nsIFile)
74 * the file to be read as a property list.
76 * If the property list is read successfully, aPropertyListRoot is set
77 * to the root object of the property list.
78 * Use getPropertyListObjectType to detect its type.
79 * If it's not read successfully, aPropertyListRoot is set to null.
80 * The reaon for failure is reported to the Error Console.
82 read: function PLU_read(aFile, aCallback) {
83 if (!(aFile instanceof Ci.nsIFile || aFile instanceof File))
84 throw new Error("aFile is not a file object");
85 if (typeof(aCallback) != "function")
86 throw new Error("Invalid value for aCallback");
88 // We guarantee not to throw directly for any other exceptions, and always
90 Services.tm.dispatchToMainThread(() => {
92 function readDOMFile(aFile) {
93 let fileReader = new FileReader();
94 let onLoadEnd = function() {
97 fileReader.removeEventListener("loadend", onLoadEnd);
98 if (fileReader.readyState != fileReader.DONE)
99 throw new Error("Could not read file contents: " + fileReader.error);
101 root = self._readFromArrayBufferSync(fileReader.result);
106 fileReader.addEventListener("loadend", onLoadEnd);
107 fileReader.readAsArrayBuffer(aFile);
111 if (aFile instanceof Ci.nsIFile) {
112 if (!aFile.exists()) {
113 throw new Error("The file pointed by aFile does not exist");
116 File.createFromNsIFile(aFile).then(function(aFile) {
130 * DO NOT USE ME. Once Bug 718189 is fixed, this method won't be public.
132 * Synchronously read an ArrayBuffer contents as a property list.
134 _readFromArrayBufferSync: function PLU__readFromArrayBufferSync(aBuffer) {
135 if (BinaryPropertyListReader.prototype.canProcess(aBuffer))
136 return new BinaryPropertyListReader(aBuffer).root;
138 // Convert the buffer into an XML tree.
139 let domParser = new DOMParser();
140 let bytesView = new Uint8Array(aBuffer);
142 let doc = domParser.parseFromBuffer(bytesView, "application/xml");
143 return new XMLPropertyListReader(doc).root;
145 throw new Error("aBuffer cannot be parsed as a DOM document: " + ex);
157 * Get the type in which the given property list object is represented.
158 * Check the header for the mapping between the TYPE* constants to js types
161 * @return one of the TYPE_* constants listed above.
162 * @note this method is merely for convenience. It has no magic to detect
163 * that aObject is indeed a property list object created by this module.
165 getObjectType: function PLU_getObjectType(aObject) {
166 if (aObject === null || typeof(aObject) != "object")
167 return this.TYPE_PRIMITIVE;
169 // Given current usage, we could assume that aObject was created in the
170 // scope of this module, but in future, this util may be used as part of
171 // serializing js objects to a property list - in which case the object
172 // would most likely be created in the caller's scope.
173 let global = Cu.getGlobalForObject(aObject);
175 if (aObject instanceof global.Map)
176 return this.TYPE_DICTIONARY;
177 if (Array.isArray(aObject))
178 return this.TYPE_ARRAY;
179 if (aObject instanceof global.Date)
180 return this.TYPE_DATE;
181 if (aObject instanceof global.Uint8Array)
182 return this.TYPE_UINT8_ARRAY;
183 if (aObject instanceof global.String && "__INT_64_WRAPPER__" in aObject)
184 return this.TYPE_INT64;
186 throw new Error("aObject is not as a property list object.");
190 * Wraps a 64-bit stored in the form of a string primitive as a String object,
191 * which we can later distiguish from regular string values.
193 * a number in the form of either a primitive string or a primitive number.
194 * @return a String wrapper around aNumberStr that can later be identified
195 * as holding 64-bit number using getObjectType.
197 wrapInt64: function PLU_wrapInt64(aPrimitive) {
198 if (typeof(aPrimitive) != "string" && typeof(aPrimitive) != "number")
199 throw new Error("aPrimitive should be a string primitive");
201 // The function converts string or number to object
202 // So eslint rule is disabled
203 // eslint-disable-next-line no-new-wrappers
204 let wrapped = new String(aPrimitive);
205 Object.defineProperty(wrapped, "__INT_64_WRAPPER__", { value: true });
211 * Here's the base structure of binary-format property lists.
212 * 1) Header - magic number
213 * - 6 bytes - "bplist"
214 * - 2 bytes - version number. This implementation only supports version 00.
216 * Variable-sized objects, see _readObject for how various types of objects
219 * The offset of each object in the objects table. The integer size is
220 * specified in the trailer.
223 * - 1 byte: the size of integers in the offsets table
224 * - 1 byte: the size of object references for arrays, sets and
226 * - 8 bytes: the number of objects in the objects table
227 * - 8 bytes: the index of the root object's offset in the offsets table.
228 * - 8 bytes: the offset of the offsets table.
230 * Note: all integers are stored in big-endian form.
234 * Reader for binary-format property lists.
237 * ArrayBuffer object from which the binary plist should be read.
239 function BinaryPropertyListReader(aBuffer) {
240 this._dataView = new DataView(aBuffer);
242 const JS_MAX_INT = Math.pow(2, 53);
243 this._JS_MAX_INT_SIGNED = ctypes.Int64(JS_MAX_INT);
244 this._JS_MAX_INT_UNSIGNED = ctypes.UInt64(JS_MAX_INT);
245 this._JS_MIN_INT = ctypes.Int64(-JS_MAX_INT);
248 this._readTrailerInfo();
249 this._readObjectsOffsets();
251 throw new Error("Could not read aBuffer as a binary property list");
256 BinaryPropertyListReader.prototype = {
258 * Checks if the given ArrayBuffer can be read as a binary property list.
259 * It can be called on the prototype.
261 canProcess: function BPLR_canProcess(aBuffer) {
262 return Array.from(new Uint8Array(aBuffer, 0, 8)).map(c => String.fromCharCode(c)).
263 join("") == "bplist00";
267 return this._readObject(this._rootObjectIndex);
270 _readTrailerInfo: function BPLR__readTrailer() {
271 // The first 6 bytes of the 32-bytes trailer are unused
272 let trailerOffset = this._dataView.byteLength - 26;
273 [this._offsetTableIntegerSize, this._objectRefSize] =
274 this._readUnsignedInts(trailerOffset, 1, 2);
276 [this._numberOfObjects, this._rootObjectIndex, this._offsetTableOffset] =
277 this._readUnsignedInts(trailerOffset + 2, 8, 3);
280 _readObjectsOffsets: function BPLR__readObjectsOffsets() {
281 this._offsetTable = this._readUnsignedInts(this._offsetTableOffset,
282 this._offsetTableIntegerSize,
283 this._numberOfObjects);
286 _readSignedInt64: function BPLR__readSignedInt64(aByteOffset) {
287 let lo = this._dataView.getUint32(aByteOffset + 4);
288 let hi = this._dataView.getInt32(aByteOffset);
289 let int64 = ctypes.Int64.join(hi, lo);
290 if (ctypes.Int64.compare(int64, this._JS_MAX_INT_SIGNED) == 1 ||
291 ctypes.Int64.compare(int64, this._JS_MIN_INT) == -1)
292 return PropertyListUtils.wrapInt64(int64.toString());
294 return parseInt(int64.toString(), 10);
297 _readReal: function BPLR__readReal(aByteOffset, aRealSize) {
299 return this._dataView.getFloat32(aByteOffset);
301 return this._dataView.getFloat64(aByteOffset);
303 throw new Error("Unsupported real size: " + aRealSize);
307 SIMPLE: parseInt("0000", 2),
308 INTEGER: parseInt("0001", 2),
309 REAL: parseInt("0010", 2),
310 DATE: parseInt("0011", 2),
311 DATA: parseInt("0100", 2),
312 ASCII_STRING: parseInt("0101", 2),
313 UNICODE_STRING: parseInt("0110", 2),
314 UID: parseInt("1000", 2),
315 ARRAY: parseInt("1010", 2),
316 SET: parseInt("1100", 2),
317 DICTIONARY: parseInt("1101", 2),
320 ADDITIONAL_INFO_BITS: {
321 // Applies to OBJECT_TYPE_BITS.SIMPLE
322 NULL: parseInt("0000", 2),
323 FALSE: parseInt("1000", 2),
324 TRUE: parseInt("1001", 2),
325 FILL_BYTE: parseInt("1111", 2),
326 // Applies to OBJECT_TYPE_BITS.DATE
327 DATE: parseInt("0011", 2),
328 // Applies to OBJECT_TYPE_BITS.DATA, ASCII_STRING, UNICODE_STRING, ARRAY,
329 // SET and DICTIONARY.
330 LENGTH_INT_SIZE_FOLLOWS: parseInt("1111", 2),
334 * Returns an object descriptor in the form of two integers: object type and
338 * the descriptor's offset.
339 * @return [objType, additionalInfo] - the object type and additional info.
340 * @see OBJECT_TYPE_BITS and ADDITIONAL_INFO_BITS
342 _readObjectDescriptor: function BPLR__readObjectDescriptor(aByteOffset) {
343 // The first four bits hold the object type. For some types, additional
344 // info is held in the other 4 bits.
345 let byte = this._readUnsignedInts(aByteOffset, 1, 1)[0];
346 return [(byte & 0xF0) >> 4, byte & 0x0F];
349 _readDate: function BPLR__readDate(aByteOffset) {
350 // That's the reference date of NSDate.
351 let date = new Date("1 January 2001, GMT");
353 // NSDate values are float values, but setSeconds takes an integer.
354 date.setMilliseconds(this._readReal(aByteOffset, 8) * 1000);
359 * Reads a portion of the buffer as a string.
362 * The offset in the buffer at which the string starts
363 * @param aNumberOfChars
364 * The length of the string to be read (that is the number of
365 * characters, not bytes).
367 * Whether or not it is a unicode string.
368 * @return the string read.
370 * @note this is tested to work well with unicode surrogate pairs. Because
371 * all unicode characters are read as 2-byte integers, unicode surrogate
372 * pairs are read from the buffer in the form of two integers, as required
373 * by String.fromCharCode.
376 function BPLR__readString(aByteOffset, aNumberOfChars, aUnicode) {
377 let codes = this._readUnsignedInts(aByteOffset, aUnicode ? 2 : 1,
379 return codes.map(c => String.fromCharCode(c)).join("");
383 * Reads an array of unsigned integers from the buffer. Integers larger than
384 * one byte are read in big endian form.
387 * The offset in the buffer at which the array starts.
389 * The size of each int in the array.
391 * The number of ints in the array.
392 * @param [optional] aBigIntAllowed (default: false)
393 * Whether or not to accept integers which outbounds JS limits for
394 * numbers (±2^53) in the form of a String.
395 * @return an array of integers (number primitive and/or Strings for large
396 * numbers (see header)).
397 * @throws if aBigIntAllowed is false and one of the integers in the array
398 * cannot be represented by a primitive js number.
401 function BPLR__readUnsignedInts(aByteOffset, aIntSize, aLength, aBigIntAllowed) {
403 for (let offset = aByteOffset;
404 offset < aByteOffset + aIntSize * aLength;
405 offset += aIntSize) {
407 uints.push(this._dataView.getUint8(offset));
408 } else if (aIntSize == 2) {
409 uints.push(this._dataView.getUint16(offset));
410 } else if (aIntSize == 3) {
411 let int24 = Uint8Array(4);
413 int24[2] = this._dataView.getUint8(offset);
414 int24[1] = this._dataView.getUint8(offset + 1);
415 int24[0] = this._dataView.getUint8(offset + 2);
416 uints.push(Uint32Array(int24.buffer)[0]);
417 } else if (aIntSize == 4) {
418 uints.push(this._dataView.getUint32(offset));
419 } else if (aIntSize == 8) {
420 let lo = this._dataView.getUint32(offset + 4);
421 let hi = this._dataView.getUint32(offset);
422 let uint64 = ctypes.UInt64.join(hi, lo);
423 if (ctypes.UInt64.compare(uint64, this._JS_MAX_INT_UNSIGNED) == 1) {
424 if (aBigIntAllowed === true)
425 uints.push(PropertyListUtils.wrapInt64(uint64.toString()));
427 throw new Error("Integer too big to be read as float 64");
429 uints.push(parseInt(uint64, 10));
432 throw new Error("Unsupported size: " + aIntSize);
440 * Reads from the buffer the data object-count and the offset at which the
441 * first object starts.
443 * @param aObjectOffset
444 * the object's offset.
445 * @return [offset, count] - the offset in the buffer at which the first
446 * object in data starts, and the number of objects.
448 _readDataOffsetAndCount:
449 function BPLR__readDataOffsetAndCount(aObjectOffset) {
450 // The length of some objects in the data can be stored in two ways:
451 // * If it is small enough, it is stored in the second four bits of the
452 // object descriptors.
453 // * Otherwise, those bits are set to 1111, indicating that the next byte
454 // consists of the integer size of the data-length (also stored in the form
455 // of an object descriptor). The length follows this byte.
456 let [, maybeLength] = this._readObjectDescriptor(aObjectOffset);
457 if (maybeLength != this.ADDITIONAL_INFO_BITS.LENGTH_INT_SIZE_FOLLOWS)
458 return [aObjectOffset + 1, maybeLength];
460 let [, intSizeInfo] = this._readObjectDescriptor(aObjectOffset + 1);
462 // The int size is 2^intSizeInfo.
463 let intSize = Math.pow(2, intSizeInfo);
464 let dataLength = this._readUnsignedInts(aObjectOffset + 2, intSize, 1)[0];
465 return [aObjectOffset + 2 + intSize, dataLength];
469 * Read array from the buffer and wrap it as a js array.
470 * @param aObjectOffset
471 * the offset in the buffer at which the array starts.
472 * @param aNumberOfObjects
473 * the number of objects in the array.
474 * @return a js array.
476 _wrapArray: function BPLR__wrapArray(aObjectOffset, aNumberOfObjects) {
477 let refs = this._readUnsignedInts(aObjectOffset,
481 let array = new Array(aNumberOfObjects);
482 let readObjectBound = this._readObject.bind(this);
484 // Each index in the returned array is a lazy getter for its object.
485 Array.prototype.forEach.call(refs, function(ref, objIndex) {
486 Object.defineProperty(array, objIndex, {
488 delete array[objIndex];
489 return array[objIndex] = readObjectBound(ref);
499 * Reads dictionary from the buffer and wraps it as a Map object.
500 * @param aObjectOffset
501 * the offset in the buffer at which the dictionary starts
502 * @param aNumberOfObjects
503 * the number of keys in the dictionary
504 * @return Map-style dictionary.
506 _wrapDictionary(aObjectOffset, aNumberOfObjects) {
507 // A dictionary in the binary format is stored as a list of references to
508 // key-objects, followed by a list of references to the value-objects for
509 // those keys. The size of each list is aNumberOfObjects * this._objectRefSize.
510 let dict = new Proxy(new Map(), LazyMapProxyHandler());
511 if (aNumberOfObjects == 0)
514 let keyObjsRefs = this._readUnsignedInts(aObjectOffset, this._objectRefSize,
517 this._readUnsignedInts(aObjectOffset + aNumberOfObjects * this._objectRefSize,
518 this._objectRefSize, aNumberOfObjects);
519 for (let i = 0; i < aNumberOfObjects; i++) {
520 let key = this._readObject(keyObjsRefs[i]);
521 let readBound = this._readObject.bind(this, valObjsRefs[i]);
523 dict.setAsLazyGetter(key, readBound);
529 * Reads an object at the spcified index in the object table
530 * @param aObjectIndex
531 * index at the object table
532 * @return the property list object at the given index.
534 _readObject: function BPLR__readObject(aObjectIndex) {
535 // If the object was previously read, return the cached object.
536 if (this._objects[aObjectIndex] !== undefined)
537 return this._objects[aObjectIndex];
539 let objOffset = this._offsetTable[aObjectIndex];
540 let [objType, additionalInfo] = this._readObjectDescriptor(objOffset);
543 case this.OBJECT_TYPE_BITS.SIMPLE: {
544 switch (additionalInfo) {
545 case this.ADDITIONAL_INFO_BITS.NULL:
548 case this.ADDITIONAL_INFO_BITS.FILL_BYTE:
551 case this.ADDITIONAL_INFO_BITS.FALSE:
554 case this.ADDITIONAL_INFO_BITS.TRUE:
558 throw new Error("Unexpected value!");
563 case this.OBJECT_TYPE_BITS.INTEGER: {
564 // The integer is sized 2^additionalInfo.
565 let intSize = Math.pow(2, additionalInfo);
567 // For objects, 64-bit integers are always signed. Negative integers
568 // are always represented by a 64-bit integer.
570 value = this._readSignedInt64(objOffset + 1);
572 value = this._readUnsignedInts(objOffset + 1, intSize, 1, true)[0];
576 case this.OBJECT_TYPE_BITS.REAL: {
577 // The real is sized 2^additionalInfo.
578 value = this._readReal(objOffset + 1, Math.pow(2, additionalInfo));
582 case this.OBJECT_TYPE_BITS.DATE: {
583 if (additionalInfo != this.ADDITIONAL_INFO_BITS.DATE)
584 throw new Error("Unexpected value");
586 value = this._readDate(objOffset + 1);
590 case this.OBJECT_TYPE_BITS.DATA: {
591 let [offset, bytesCount] = this._readDataOffsetAndCount(objOffset);
592 value = new Uint8Array(this._readUnsignedInts(offset, 1, bytesCount));
596 case this.OBJECT_TYPE_BITS.ASCII_STRING: {
597 let [offset, charsCount] = this._readDataOffsetAndCount(objOffset);
598 value = this._readString(offset, charsCount, false);
602 case this.OBJECT_TYPE_BITS.UNICODE_STRING: {
603 let [offset, unicharsCount] = this._readDataOffsetAndCount(objOffset);
604 value = this._readString(offset, unicharsCount, true);
608 case this.OBJECT_TYPE_BITS.UID: {
609 // UIDs are only used in Keyed Archives, which are not yet supported.
610 throw new Error("Keyed Archives are not supported");
613 case this.OBJECT_TYPE_BITS.ARRAY:
614 case this.OBJECT_TYPE_BITS.SET: {
615 // Note: For now, we fallback to handle sets the same way we handle
616 // arrays. See comments in the header of this file.
618 // The bytes following the count are references to objects (indices).
619 // Each reference is an unsigned int with size=this._objectRefSize.
620 let [offset, objectsCount] = this._readDataOffsetAndCount(objOffset);
621 value = this._wrapArray(offset, objectsCount);
625 case this.OBJECT_TYPE_BITS.DICTIONARY: {
626 let [offset, objectsCount] = this._readDataOffsetAndCount(objOffset);
627 value = this._wrapDictionary(offset, objectsCount);
632 throw new Error("Unknown object type: " + objType);
636 return this._objects[aObjectIndex] = value;
641 * Reader for XML property lists.
644 * the DOM document to be read as a property list.
646 function XMLPropertyListReader(aDOMDoc) {
647 let docElt = aDOMDoc.documentElement;
648 if (!docElt || docElt.localName != "plist" || !docElt.firstElementChild)
649 throw new Error("aDoc is not a property list document");
651 this._plistRootElement = docElt.firstElementChild;
654 XMLPropertyListReader.prototype = {
656 return this._readObject(this._plistRootElement);
660 * Convert a dom element to a property list object.
662 * a dom element in a xml tree of a property list.
663 * @return a js object representing the property list object.
665 _readObject: function XPLR__readObject(aDOMElt) {
666 switch (aDOMElt.localName) {
673 return aDOMElt.textContent;
675 return this._readInteger(aDOMElt);
677 let number = parseFloat(aDOMElt.textContent.trim());
679 throw "Could not parse float value";
683 return new Date(aDOMElt.textContent);
685 // Strip spaces and new lines.
686 let base64str = aDOMElt.textContent.replace(/\s*/g, "");
687 let decoded = atob(base64str);
688 return new Uint8Array(Array.from(decoded, c => c.charCodeAt(0)));
690 return this._wrapDictionary(aDOMElt);
692 return this._wrapArray(aDOMElt);
694 throw new Error("Unexpected tagname");
698 _readInteger: function XPLR__readInteger(aDOMElt) {
699 // The integer may outbound js's max/min integer value. We recognize this
700 // case by comparing the parsed number to the original string value.
701 // In case of an outbound, we fallback to return the number as a string.
702 let numberAsString = aDOMElt.textContent.toString();
703 let parsedNumber = parseInt(numberAsString, 10);
704 if (isNaN(parsedNumber))
705 throw new Error("Could not parse integer value");
707 if (parsedNumber.toString() == numberAsString)
710 return PropertyListUtils.wrapInt64(numberAsString);
713 _wrapDictionary: function XPLR__wrapDictionary(aDOMElt) {
715 // <key>my true bool</key>
717 // <key>my string key</key>
718 // <string>My String Key</string>
720 if (aDOMElt.children.length % 2 != 0)
721 throw new Error("Invalid dictionary");
722 let dict = new Proxy(new Map(), LazyMapProxyHandler());
723 for (let i = 0; i < aDOMElt.children.length; i += 2) {
724 let keyElem = aDOMElt.children[i];
725 let valElem = aDOMElt.children[i + 1];
727 if (keyElem.localName != "key")
728 throw new Error("Invalid dictionary");
730 let keyName = this._readObject(keyElem);
731 let readBound = this._readObject.bind(this, valElem);
733 dict.setAsLazyGetter(keyName, readBound);
738 _wrapArray: function XPLR__wrapArray(aDOMElt) {
740 // <string>...</string>
741 // <integer></integer>
747 // Each element in the array is a lazy getter for its property list object.
749 let readObjectBound = this._readObject.bind(this);
750 Array.prototype.forEach.call(aDOMElt.children, function(elem, elemIndex) {
751 Object.defineProperty(array, elemIndex, {
753 delete array[elemIndex];
754 return array[elemIndex] = readObjectBound(elem);
765 * Simple handler method to proxy calls to dict/Map objects to implement the
766 * setAsLazyGetter API. With this, a value can be set as a function that will
767 * evaluate its value and only be called when it's first retrieved.
768 * @member _lazyGetters
769 * Set() object to hold keys invoking LazyGetter.
771 * Trap for getting property values. Ensures that if a lazyGetter is present
772 * as value for key, then the function is evaluated, the value is cached,
773 * and its value will be returned.
775 * Target object. (dict/Map)
777 * Name of operation to be invoked on target.
779 * Key to be set, retrieved or deleted. Keys are checked for laziness.
780 * @return Returns value of "name" property of target by default. Otherwise returns
783 function LazyMapProxyHandler() {
785 _lazyGetters: new Set(),
788 case "setAsLazyGetter":
789 return (key, value) => {
790 this._lazyGetters.add(key);
791 target.set(key, value);
795 if (this._lazyGetters.has(key)) {
796 target.set(key, target.get(key)());
797 this._lazyGetters.delete(key);
799 return target.get(key);
803 if (this._lazyGetters.has(key)) {
804 this._lazyGetters.delete(key);
806 return target.delete(key);
809 return key => target.has(key);