715 lines
22 KiB
JavaScript
715 lines
22 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/. */
|
|
|
|
"use strict";
|
|
|
|
const { Actor } = require("resource://devtools/shared/protocol.js");
|
|
const {
|
|
propertyIteratorSpec,
|
|
} = require("resource://devtools/shared/specs/property-iterator.js");
|
|
|
|
const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"ObjectUtils",
|
|
"resource://devtools/server/actors/object/utils.js"
|
|
);
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"propertyDescriptor",
|
|
"resource://devtools/server/actors/object/property-descriptor.js",
|
|
true
|
|
);
|
|
|
|
/**
|
|
* Creates an actor to iterate over an object's property names and values.
|
|
*
|
|
* @param objectActor ObjectActor
|
|
* The object actor.
|
|
* @param options Object
|
|
* A dictionary object with various boolean attributes:
|
|
* - enumEntries Boolean
|
|
* If true, enumerates the entries of a Map or Set object
|
|
* instead of enumerating properties.
|
|
* - ignoreIndexedProperties Boolean
|
|
* If true, filters out Array items.
|
|
* e.g. properties names between `0` and `object.length`.
|
|
* - ignoreNonIndexedProperties Boolean
|
|
* If true, filters out items that aren't array items
|
|
* e.g. properties names that are not a number between `0`
|
|
* and `object.length`.
|
|
* - sort Boolean
|
|
* If true, the iterator will sort the properties by name
|
|
* before dispatching them.
|
|
* - query String
|
|
* If non-empty, will filter the properties by names and values
|
|
* containing this query string. The match is not case-sensitive.
|
|
* Regarding value filtering it just compare to the stringification
|
|
* of the property value.
|
|
*/
|
|
class PropertyIteratorActor extends Actor {
|
|
constructor(objectActor, options, conn) {
|
|
super(conn, propertyIteratorSpec);
|
|
if (!DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
|
|
this.iterator = {
|
|
size: 0,
|
|
propertyName: _index => undefined,
|
|
propertyDescription: _index => undefined,
|
|
};
|
|
} else if (options.enumEntries) {
|
|
const cls = objectActor.className;
|
|
if (cls == "Map") {
|
|
this.iterator = enumMapEntries(objectActor, 0);
|
|
} else if (cls == "WeakMap") {
|
|
this.iterator = enumWeakMapEntries(objectActor, 0);
|
|
} else if (cls == "Set") {
|
|
this.iterator = enumSetEntries(objectActor, 0);
|
|
} else if (cls == "WeakSet") {
|
|
this.iterator = enumWeakSetEntries(objectActor, 0);
|
|
} else if (cls == "Storage") {
|
|
this.iterator = enumStorageEntries(objectActor, 0);
|
|
} else if (cls == "URLSearchParams") {
|
|
this.iterator = enumURLSearchParamsEntries(objectActor, 0);
|
|
} else if (cls == "Headers") {
|
|
this.iterator = enumHeadersEntries(objectActor, 0);
|
|
} else if (cls == "HighlightRegistry") {
|
|
this.iterator = enumHighlightRegistryEntries(objectActor, 0);
|
|
} else if (cls == "FormData") {
|
|
this.iterator = enumFormDataEntries(objectActor, 0);
|
|
} else if (cls == "MIDIInputMap") {
|
|
this.iterator = enumMidiInputMapEntries(objectActor, 0);
|
|
} else if (cls == "MIDIOutputMap") {
|
|
this.iterator = enumMidiOutputMapEntries(objectActor, 0);
|
|
} else if (cls == "CustomStateSet") {
|
|
this.iterator = enumCustomStateSetEntries(objectActor, 0);
|
|
} else {
|
|
throw new Error(
|
|
"Unsupported class to enumerate entries from: " + cls
|
|
);
|
|
}
|
|
} else if (
|
|
ObjectUtils.isArray(objectActor.obj) &&
|
|
options.ignoreNonIndexedProperties &&
|
|
!options.query
|
|
) {
|
|
this.iterator = enumArrayProperties(objectActor, options, 0);
|
|
} else {
|
|
this.iterator = enumObjectProperties(objectActor, options, 0);
|
|
}
|
|
}
|
|
|
|
form() {
|
|
return {
|
|
type: this.typeName,
|
|
actor: this.actorID,
|
|
count: this.iterator.size,
|
|
};
|
|
}
|
|
|
|
names({ indexes }) {
|
|
const list = [];
|
|
for (const idx of indexes) {
|
|
list.push(this.iterator.propertyName(idx));
|
|
}
|
|
return indexes;
|
|
}
|
|
|
|
slice({ start, count }) {
|
|
const ownProperties = Object.create(null);
|
|
for (let i = start, m = start + count; i < m; i++) {
|
|
const name = this.iterator.propertyName(i);
|
|
ownProperties[name] = this.iterator.propertyDescription(i);
|
|
}
|
|
|
|
return {
|
|
ownProperties,
|
|
};
|
|
}
|
|
|
|
all() {
|
|
return this.slice({ start: 0, count: this.iterator.size });
|
|
}
|
|
}
|
|
|
|
function waiveXrays(obj) {
|
|
return isWorker ? obj : Cu.waiveXrays(obj);
|
|
}
|
|
|
|
function unwaiveXrays(obj) {
|
|
return isWorker ? obj : Cu.unwaiveXrays(obj);
|
|
}
|
|
|
|
/**
|
|
* Helper function to create a grip from a Map/Set entry
|
|
*/
|
|
function gripFromEntry(objectActor, entry, depth) {
|
|
entry = unwaiveXrays(entry);
|
|
return objectActor.createValueGrip(
|
|
ObjectUtils.makeDebuggeeValueIfNeeded(objectActor.obj, entry),
|
|
depth
|
|
);
|
|
}
|
|
|
|
function enumArrayProperties(objectActor, options, depth) {
|
|
return {
|
|
size: ObjectUtils.getArrayLength(objectActor.obj),
|
|
propertyName(index) {
|
|
return index;
|
|
},
|
|
propertyDescription(index) {
|
|
return propertyDescriptor(objectActor, index, depth);
|
|
},
|
|
};
|
|
}
|
|
|
|
function enumObjectProperties(objectActor, options, depth) {
|
|
let names = [];
|
|
try {
|
|
names = objectActor.obj.getOwnPropertyNames();
|
|
} catch (ex) {
|
|
// Calling getOwnPropertyNames() on some wrapped native prototypes is not
|
|
// allowed: "cannot modify properties of a WrappedNative". See bug 952093.
|
|
}
|
|
|
|
if (options.ignoreNonIndexedProperties || options.ignoreIndexedProperties) {
|
|
const length = DevToolsUtils.getProperty(objectActor.obj, "length");
|
|
let sliceIndex;
|
|
|
|
const isLengthTrustworthy =
|
|
isUint32(length) &&
|
|
(!length || ObjectUtils.isArrayIndex(names[length - 1])) &&
|
|
!ObjectUtils.isArrayIndex(names[length]);
|
|
|
|
if (!isLengthTrustworthy) {
|
|
// The length property may not reflect what the object looks like, let's find
|
|
// where indexed properties end.
|
|
|
|
if (!ObjectUtils.isArrayIndex(names[0])) {
|
|
// If the first item is not a number, this means there is no indexed properties
|
|
// in this object.
|
|
sliceIndex = 0;
|
|
} else {
|
|
sliceIndex = names.length;
|
|
while (sliceIndex > 0) {
|
|
if (ObjectUtils.isArrayIndex(names[sliceIndex - 1])) {
|
|
break;
|
|
}
|
|
sliceIndex--;
|
|
}
|
|
}
|
|
} else {
|
|
sliceIndex = length;
|
|
}
|
|
|
|
// It appears that getOwnPropertyNames always returns indexed properties
|
|
// first, so we can safely slice `names` for/against indexed properties.
|
|
// We do such clever operation to optimize very large array inspection.
|
|
if (options.ignoreIndexedProperties) {
|
|
// Keep items after `sliceIndex` index
|
|
names = names.slice(sliceIndex);
|
|
} else if (options.ignoreNonIndexedProperties) {
|
|
// Keep `sliceIndex` first items
|
|
names.length = sliceIndex;
|
|
}
|
|
}
|
|
|
|
const safeGetterValues = objectActor._findSafeGetterValues(names, depth);
|
|
const safeGetterNames = Object.keys(safeGetterValues);
|
|
// Merge the safe getter values into the existing properties list.
|
|
for (const name of safeGetterNames) {
|
|
if (!names.includes(name)) {
|
|
names.push(name);
|
|
}
|
|
}
|
|
|
|
if (options.query) {
|
|
let { query } = options;
|
|
query = query.toLowerCase();
|
|
names = names.filter(name => {
|
|
// Filter on attribute names
|
|
if (name.toLowerCase().includes(query)) {
|
|
return true;
|
|
}
|
|
// and then on attribute values
|
|
let desc;
|
|
try {
|
|
desc = objectActor.obj.getOwnPropertyDescriptor(name);
|
|
} catch (e) {
|
|
// Calling getOwnPropertyDescriptor on wrapped native prototypes is not
|
|
// allowed (bug 560072).
|
|
}
|
|
if (desc?.value && String(desc.value).includes(query)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
if (options.sort) {
|
|
names.sort();
|
|
}
|
|
|
|
return {
|
|
size: names.length,
|
|
propertyName(index) {
|
|
return names[index];
|
|
},
|
|
propertyDescription(index) {
|
|
const name = names[index];
|
|
let desc = propertyDescriptor(objectActor, name, depth);
|
|
if (!desc) {
|
|
desc = safeGetterValues[name];
|
|
} else if (name in safeGetterValues) {
|
|
// Merge the safe getter values into the existing properties list.
|
|
const { getterValue, getterPrototypeLevel } = safeGetterValues[name];
|
|
desc.getterValue = getterValue;
|
|
desc.getterPrototypeLevel = getterPrototypeLevel;
|
|
}
|
|
return desc;
|
|
},
|
|
};
|
|
}
|
|
|
|
function getMapEntries(objectActor) {
|
|
const { obj, rawObj } = objectActor;
|
|
// Iterating over a Map via .entries goes through various intermediate
|
|
// objects - an Iterator object, then a 2-element Array object, then the
|
|
// actual values we care about. We don't have Xrays to Iterator objects,
|
|
// so we get Opaque wrappers for them. And even though we have Xrays to
|
|
// Arrays, the semantics often deny access to the entires based on the
|
|
// nature of the values. So we need waive Xrays for the iterator object
|
|
// and the tupes, and then re-apply them on the underlying values until
|
|
// we fix bug 1023984.
|
|
//
|
|
// Even then though, we might want to continue waiving Xrays here for the
|
|
// same reason we do so for Arrays above - this filtering behavior is likely
|
|
// to be more confusing than beneficial in the case of Object previews.
|
|
const iterator = obj.makeDebuggeeValue(
|
|
waiveXrays(Map.prototype.keys.call(rawObj))
|
|
);
|
|
return [...DevToolsUtils.makeDebuggeeIterator(iterator)].map(k => {
|
|
const key = waiveXrays(ObjectUtils.unwrapDebuggeeValue(k));
|
|
const value = Map.prototype.get.call(rawObj, key);
|
|
return [key, value];
|
|
});
|
|
}
|
|
|
|
function enumMapEntries(objectActor, depth) {
|
|
const entries = getMapEntries(objectActor);
|
|
|
|
return {
|
|
*[Symbol.iterator]() {
|
|
for (const [key, value] of entries) {
|
|
yield [key, value].map(val => gripFromEntry(objectActor, val, depth));
|
|
}
|
|
},
|
|
size: entries.length,
|
|
propertyName(index) {
|
|
return index;
|
|
},
|
|
propertyDescription(index) {
|
|
const [key, val] = entries[index];
|
|
return {
|
|
enumerable: true,
|
|
value: {
|
|
type: "mapEntry",
|
|
preview: {
|
|
key: gripFromEntry(objectActor, key, depth),
|
|
value: gripFromEntry(objectActor, val, depth),
|
|
},
|
|
},
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
function enumStorageEntries(objectActor, depth) {
|
|
// Iterating over local / sessionStorage entries goes through various
|
|
// intermediate objects - an Iterator object, then a 2-element Array object,
|
|
// then the actual values we care about. We don't have Xrays to Iterator
|
|
// objects, so we get Opaque wrappers for them.
|
|
const { rawObj } = objectActor;
|
|
const keys = [];
|
|
for (let i = 0; i < rawObj.length; i++) {
|
|
keys.push(rawObj.key(i));
|
|
}
|
|
return {
|
|
*[Symbol.iterator]() {
|
|
for (const key of keys) {
|
|
const value = rawObj.getItem(key);
|
|
yield [key, value].map(val => gripFromEntry(objectActor, val, depth));
|
|
}
|
|
},
|
|
size: keys.length,
|
|
propertyName(index) {
|
|
return index;
|
|
},
|
|
propertyDescription(index) {
|
|
const key = keys[index];
|
|
const val = rawObj.getItem(key);
|
|
return {
|
|
enumerable: true,
|
|
value: {
|
|
type: "storageEntry",
|
|
preview: {
|
|
key: gripFromEntry(objectActor, key, depth),
|
|
value: gripFromEntry(objectActor, val, depth),
|
|
},
|
|
},
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
function enumURLSearchParamsEntries(objectActor, depth) {
|
|
const entries = [...waiveXrays(URLSearchParams.prototype.entries.call(objectActor.rawObj))];
|
|
|
|
return {
|
|
*[Symbol.iterator]() {
|
|
for (const [key, value] of entries) {
|
|
yield [key, value];
|
|
}
|
|
},
|
|
size: entries.length,
|
|
propertyName(index) {
|
|
// UrlSearchParams entries can have the same key multiple times (e.g. `?a=1&a=2`),
|
|
// so let's return the index as a name to be able to display them properly in the client.
|
|
return index;
|
|
},
|
|
propertyDescription(index) {
|
|
const [key, value] = entries[index];
|
|
|
|
return {
|
|
enumerable: true,
|
|
value: {
|
|
type: "urlSearchParamsEntry",
|
|
preview: {
|
|
key: gripFromEntry(objectActor, key, depth),
|
|
value: gripFromEntry(objectActor, value, depth),
|
|
},
|
|
},
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
function enumFormDataEntries(objectActor, depth) {
|
|
const entries = [...waiveXrays(FormData.prototype.entries.call(objectActor.rawObj))];
|
|
|
|
return {
|
|
*[Symbol.iterator]() {
|
|
for (const [key, value] of entries) {
|
|
yield [key, value];
|
|
}
|
|
},
|
|
size: entries.length,
|
|
propertyName(index) {
|
|
return index;
|
|
},
|
|
propertyDescription(index) {
|
|
const [key, value] = entries[index];
|
|
|
|
return {
|
|
enumerable: true,
|
|
value: {
|
|
type: "formDataEntry",
|
|
preview: {
|
|
key: gripFromEntry(objectActor, key, depth),
|
|
value: gripFromEntry(objectActor, value, depth),
|
|
},
|
|
},
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
function enumHeadersEntries(objectActor, depth) {
|
|
const entries = [...waiveXrays(Headers.prototype.entries.call(objectActor.rawObj))];
|
|
|
|
return {
|
|
*[Symbol.iterator]() {
|
|
for (const [key, value] of entries) {
|
|
yield [key, value];
|
|
}
|
|
},
|
|
size: entries.length,
|
|
propertyName(index) {
|
|
return entries[index][0];
|
|
},
|
|
propertyDescription(index) {
|
|
return {
|
|
enumerable: true,
|
|
value: gripFromEntry(objectActor, entries[index][1], depth),
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
function enumHighlightRegistryEntries(objectActor, depth) {
|
|
const entriesFuncDbgObj = objectActor.obj.getProperty("entries").return;
|
|
const entriesDbgObj = entriesFuncDbgObj ? entriesFuncDbgObj.call(objectActor.obj).return : null;
|
|
const entries = entriesDbgObj
|
|
? [...waiveXrays( entriesDbgObj.unsafeDereference())]
|
|
: [];
|
|
|
|
return {
|
|
*[Symbol.iterator]() {
|
|
for (const [key, value] of entries) {
|
|
yield [key, gripFromEntry(objectActor, value, depth)];
|
|
}
|
|
},
|
|
size: entries.length,
|
|
propertyName(index) {
|
|
return index;
|
|
},
|
|
propertyDescription(index) {
|
|
const [key, value] = entries[index];
|
|
return {
|
|
enumerable: true,
|
|
value: {
|
|
type: "highlightRegistryEntry",
|
|
preview: {
|
|
key,
|
|
value: gripFromEntry(objectActor, value, depth),
|
|
},
|
|
},
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
function enumMidiInputMapEntries(objectActor, depth) {
|
|
// We need to waive `rawObj` as we can't get the iterator from the Xray for MapLike (See Bug 1173651).
|
|
// We also need to waive Xrays on the result of the call to `entries` as we don't have
|
|
// Xrays to Iterator objects (see Bug 1023984)
|
|
const entries = Array.from(
|
|
waiveXrays(MIDIInputMap.prototype.entries.call(waiveXrays(objectActor.rawObj)))
|
|
);
|
|
|
|
return {
|
|
*[Symbol.iterator]() {
|
|
for (const [key, value] of entries) {
|
|
yield [key, gripFromEntry(objectActor, value, depth)];
|
|
}
|
|
},
|
|
size: entries.length,
|
|
propertyName(index) {
|
|
return entries[index][0];
|
|
},
|
|
propertyDescription(index) {
|
|
return {
|
|
enumerable: true,
|
|
value: gripFromEntry(objectActor, entries[index][1], depth),
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
function enumMidiOutputMapEntries(objectActor, depth) {
|
|
// We need to waive `rawObj` as we can't get the iterator from the Xray for MapLike (See Bug 1173651).
|
|
// We also need to waive Xrays on the result of the call to `entries` as we don't have
|
|
// Xrays to Iterator objects (see Bug 1023984)
|
|
const entries = Array.from(
|
|
waiveXrays(MIDIOutputMap.prototype.entries.call(waiveXrays(objectActor.rawObj)))
|
|
);
|
|
|
|
return {
|
|
*[Symbol.iterator]() {
|
|
for (const [key, value] of entries) {
|
|
yield [key, gripFromEntry(objectActor, value, depth)];
|
|
}
|
|
},
|
|
size: entries.length,
|
|
propertyName(index) {
|
|
return entries[index][0];
|
|
},
|
|
propertyDescription(index) {
|
|
return {
|
|
enumerable: true,
|
|
value: gripFromEntry(objectActor, entries[index][1], depth),
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
function getWeakMapEntries(rawObj) {
|
|
// We currently lack XrayWrappers for WeakMap, so when we iterate over
|
|
// the values, the temporary iterator objects get created in the target
|
|
// compartment. However, we _do_ have Xrays to Object now, so we end up
|
|
// Xraying those temporary objects, and filtering access to |it.value|
|
|
// based on whether or not it's Xrayable and/or callable, which breaks
|
|
// the for/of iteration.
|
|
//
|
|
// This code is designed to handle untrusted objects, so we can safely
|
|
// waive Xrays on the iterable, and relying on the Debugger machinery to
|
|
// make sure we handle the resulting objects carefully.
|
|
const keys = waiveXrays(ChromeUtils.nondeterministicGetWeakMapKeys(rawObj));
|
|
|
|
return keys.map(k => [k, WeakMap.prototype.get.call(rawObj, k)]);
|
|
}
|
|
|
|
function enumWeakMapEntries(objectActor, depth) {
|
|
const entries = getWeakMapEntries(objectActor.rawObj);
|
|
|
|
return {
|
|
*[Symbol.iterator]() {
|
|
for (let i = 0; i < entries.length; i++) {
|
|
yield entries[i].map(val => gripFromEntry(objectActor, val, depth));
|
|
}
|
|
},
|
|
size: entries.length,
|
|
propertyName(index) {
|
|
return index;
|
|
},
|
|
propertyDescription(index) {
|
|
const [key, val] = entries[index];
|
|
return {
|
|
enumerable: true,
|
|
value: {
|
|
type: "mapEntry",
|
|
preview: {
|
|
key: gripFromEntry(objectActor, key, depth),
|
|
value: gripFromEntry(objectActor, val, depth),
|
|
},
|
|
},
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
function getSetValues(objectActor) {
|
|
// We currently lack XrayWrappers for Set, so when we iterate over
|
|
// the values, the temporary iterator objects get created in the target
|
|
// compartment. However, we _do_ have Xrays to Object now, so we end up
|
|
// Xraying those temporary objects, and filtering access to |it.value|
|
|
// based on whether or not it's Xrayable and/or callable, which breaks
|
|
// the for/of iteration.
|
|
//
|
|
// This code is designed to handle untrusted objects, so we can safely
|
|
// waive Xrays on the iterable, and relying on the Debugger machinery to
|
|
// make sure we handle the resulting objects carefully.
|
|
const iterator = objectActor.obj.makeDebuggeeValue(
|
|
waiveXrays(Set.prototype.values.call(objectActor.rawObj))
|
|
);
|
|
return [...DevToolsUtils.makeDebuggeeIterator(iterator)];
|
|
}
|
|
|
|
function enumSetEntries(objectActor, depth) {
|
|
const values = getSetValues(objectActor).map(v =>
|
|
waiveXrays(ObjectUtils.unwrapDebuggeeValue(v))
|
|
);
|
|
|
|
return {
|
|
*[Symbol.iterator]() {
|
|
for (const item of values) {
|
|
yield gripFromEntry(objectActor, item, depth);
|
|
}
|
|
},
|
|
size: values.length,
|
|
propertyName(index) {
|
|
return index;
|
|
},
|
|
propertyDescription(index) {
|
|
const val = values[index];
|
|
return {
|
|
enumerable: true,
|
|
value: gripFromEntry(objectActor, val, depth),
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
function getWeakSetEntries(rawObj) {
|
|
// We currently lack XrayWrappers for WeakSet, so when we iterate over
|
|
// the values, the temporary iterator objects get created in the target
|
|
// compartment. However, we _do_ have Xrays to Object now, so we end up
|
|
// Xraying those temporary objects, and filtering access to |it.value|
|
|
// based on whether or not it's Xrayable and/or callable, which breaks
|
|
// the for/of iteration.
|
|
//
|
|
// This code is designed to handle untrusted objects, so we can safely
|
|
// waive Xrays on the iterable, and relying on the Debugger machinery to
|
|
// make sure we handle the resulting objects carefully.
|
|
return waiveXrays(ChromeUtils.nondeterministicGetWeakSetKeys(rawObj));
|
|
}
|
|
|
|
function enumWeakSetEntries(objectActor, depth) {
|
|
const keys = getWeakSetEntries(objectActor.rawObj);
|
|
|
|
return {
|
|
*[Symbol.iterator]() {
|
|
for (const item of keys) {
|
|
yield gripFromEntry(objectActor, item, depth);
|
|
}
|
|
},
|
|
size: keys.length,
|
|
propertyName(index) {
|
|
return index;
|
|
},
|
|
propertyDescription(index) {
|
|
const val = keys[index];
|
|
return {
|
|
enumerable: true,
|
|
value: gripFromEntry(objectActor, val, depth),
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
function enumCustomStateSetEntries(objectActor, depth) {
|
|
const { rawObj } = objectActor;
|
|
// We need to waive `rawObj` as we can't get the iterator from the Xray for SetLike (See Bug 1173651).
|
|
// We also need to waive Xrays on the result of the call to `values` as we don't have
|
|
// Xrays to Iterator objects (see Bug 1023984)
|
|
const values = Array.from(
|
|
// eslint-disable-next-line no-undef
|
|
waiveXrays(CustomStateSet.prototype.values.call(waiveXrays(rawObj)))
|
|
);
|
|
|
|
return {
|
|
*[Symbol.iterator]() {
|
|
for (const item of values) {
|
|
yield gripFromEntry(objectActor, item, depth);
|
|
}
|
|
},
|
|
size: values.length,
|
|
propertyName(index) {
|
|
return index;
|
|
},
|
|
propertyDescription(index) {
|
|
const val = values[index];
|
|
return {
|
|
enumerable: true,
|
|
value: gripFromEntry(objectActor, val, depth),
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns true if the parameter can be stored as a 32-bit unsigned integer.
|
|
* If so, it will be suitable for use as the length of an array object.
|
|
*
|
|
* @param num Number
|
|
* The number to test.
|
|
* @return Boolean
|
|
*/
|
|
function isUint32(num) {
|
|
return num >>> 0 === num;
|
|
}
|
|
|
|
module.exports = {
|
|
PropertyIteratorActor,
|
|
enumCustomStateSetEntries,
|
|
enumMapEntries,
|
|
enumMidiInputMapEntries,
|
|
enumMidiOutputMapEntries,
|
|
enumSetEntries,
|
|
enumURLSearchParamsEntries,
|
|
enumFormDataEntries,
|
|
enumHeadersEntries,
|
|
enumHighlightRegistryEntries,
|
|
enumWeakMapEntries,
|
|
enumWeakSetEntries,
|
|
};
|