copy from devtools/server/actors/object.js
copy to devtools/server/actors/array-buffer.js
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/array-buffer.js
@@ -1,2417 +1,16 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 { Cu, Ci } = require("chrome");
-const { GeneratedLocation } = require("devtools/server/actors/common");
-const { DebuggerServer } = require("devtools/server/main");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { assert } = DevToolsUtils;
-
-loader.lazyRequireGetter(this, "ChromeUtils");
-
-// Number of items to preview in objects, arrays, maps, sets, lists,
-// collections, etc.
-const OBJECT_PREVIEW_MAX_ITEMS = 10;
-
-/**
- * Creates an actor for the specified object.
- *
- * @param obj Debugger.Object
- * The debuggee object.
- * @param hooks Object
- * A collection of abstract methods that are implemented by the caller.
- * ObjectActor requires the following functions to be implemented by
- * the caller:
- * - createValueGrip
- * Creates a value grip for the given object
- * - sources
- * TabSources getter that manages the sources of a thread
- * - createEnvironmentActor
- * Creates and return an environment actor
- * - getGripDepth
- * An actor's grip depth getter
- * - incrementGripDepth
- * Increment the actor's grip depth
- * - decrementGripDepth
- * Decrement the actor's grip depth
- * - globalDebugObject
- * The Debuggee Global Object as given by the ThreadActor
- */
-function ObjectActor(obj, {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
-}) {
- assert(!obj.optimizedOut,
- "Should not create object actors for optimized out values!");
- this.obj = obj;
- this.hooks = {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
- };
- this.iterators = new Set();
-}
-
-ObjectActor.prototype = {
- actorPrefix: "obj",
-
- rawValue: function() {
- return this.obj.unsafeDereference();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let g = {
- "type": "object",
- "actor": this.actorID,
- "class": this.obj.class,
- };
-
- let unwrapped = DevToolsUtils.unwrap(this.obj);
-
- // Unsafe objects must be treated carefully.
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- if (DevToolsUtils.isCPOW(this.obj)) {
- // Cross-process object wrappers can't be accessed.
- g.class = "CPOW: " + g.class;
- } else if (unwrapped === undefined) {
- // Objects belonging to an invisible-to-debugger compartment might be proxies,
- // so just in case they shouldn't be accessed.
- g.class = "InvisibleToDebugger: " + g.class;
- } else if (unwrapped.isProxy) {
- // Proxy objects can run traps when accessed, so just create a preview with
- // the target and the handler.
- g.class = "Proxy";
- this.hooks.incrementGripDepth();
- DebuggerServer.ObjectActorPreviewers.Proxy[0](this, g, null);
- this.hooks.decrementGripDepth();
- }
- return g;
- }
-
- // If the debuggee does not subsume the object's compartment, most properties won't
- // be accessible. Cross-orgin Window and Location objects might expose some, though.
- // Change the displayed class, but when creating the preview use the original one.
- if (unwrapped === null) {
- g.class = "Restricted";
- }
-
- this.hooks.incrementGripDepth();
-
- g.extensible = this.obj.isExtensible();
- g.frozen = this.obj.isFrozen();
- g.sealed = this.obj.isSealed();
-
- if (g.class == "Promise") {
- g.promiseState = this._createPromiseState();
- }
-
- // FF40+: Allow to know how many properties an object has to lazily display them
- // when there is a bunch.
- if (isTypedArray(g)) {
- // Bug 1348761: getOwnPropertyNames is unnecessary slow on TypedArrays
- g.ownPropertyLength = getArrayLength(this.obj);
- } else {
- try {
- g.ownPropertyLength = this.obj.getOwnPropertyNames().length;
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let raw = this.obj.unsafeDereference();
-
- // If Cu is not defined, we are running on a worker thread, where xrays
- // don't exist.
- if (Cu) {
- raw = Cu.unwaiveXrays(raw);
- }
-
- if (!DevToolsUtils.isSafeJSObject(raw)) {
- raw = null;
- }
-
- let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
- DebuggerServer.ObjectActorPreviewers.Object;
- for (let fn of previewers) {
- try {
- if (fn(this, g, raw)) {
- break;
- }
- } catch (e) {
- let msg = "ObjectActor.prototype.grip previewer function";
- DevToolsUtils.reportException(msg, e);
- }
- }
-
- this.hooks.decrementGripDepth();
- return g;
- },
-
- /**
- * Returns an object exposing the internal Promise state.
- */
- _createPromiseState: function() {
- const { state, value, reason } = getPromiseState(this.obj);
- let promiseState = { state };
-
- if (state == "fulfilled") {
- promiseState.value = this.hooks.createValueGrip(value);
- } else if (state == "rejected") {
- promiseState.reason = this.hooks.createValueGrip(reason);
- }
-
- promiseState.creationTimestamp = Date.now() - this.obj.promiseLifetime;
-
- // Only add the timeToSettle property if the Promise isn't pending.
- if (state !== "pending") {
- promiseState.timeToSettle = this.obj.promiseTimeToResolution;
- }
-
- return promiseState;
- },
-
- /**
- * Releases this actor from the pool.
- */
- release: function() {
- if (this.registeredPool.objectActors) {
- this.registeredPool.objectActors.delete(this.obj);
- }
- this.iterators.forEach(actor => this.registeredPool.removeActor(actor));
- this.iterators.clear();
- this.registeredPool.removeActor(this);
- },
-
- /**
- * Handle a protocol request to provide the definition site of this function
- * object.
- */
- onDefinitionSite: function() {
- if (this.obj.class != "Function") {
- return {
- from: this.actorID,
- error: "objectNotFunction",
- message: this.actorID + " is not a function."
- };
- }
-
- if (!this.obj.script) {
- return {
- from: this.actorID,
- error: "noScript",
- message: this.actorID + " has no Debugger.Script"
- };
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- this.hooks.sources().createNonSourceMappedActor(this.obj.script.source),
- this.obj.script.startLine,
- 0 // TODO bug 901138: use Debugger.Script.prototype.startColumn
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn
- };
- });
- },
-
- /**
- * Handle a protocol request to provide the names of the properties defined on
- * the object and not its prototype.
- */
- onOwnPropertyNames: function() {
- let props = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- props = this.obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
- return { from: this.actorID, ownPropertyNames: props };
- },
-
- /**
- * Creates an actor to iterate over an object property names and values.
- * See PropertyIteratorActor constructor for more info about options param.
- *
- * @param request object
- * The protocol request object.
- */
- onEnumProperties: function(request) {
- let actor = new PropertyIteratorActor(this, request.options);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over entries of a Map/Set-like object.
- */
- onEnumEntries: function() {
- let actor = new PropertyIteratorActor(this, { enumEntries: true });
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over an object symbols properties.
- */
- onEnumSymbols: function() {
- let actor = new SymbolIteratorActor(this);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Handle a protocol request to provide the prototype and own properties of
- * the object.
- *
- * @returns {Object} An object containing the data of this.obj, of the following form:
- * - {string} from: this.obj's actorID.
- * - {Object} prototype: The descriptor of this.obj's prototype.
- * - {Object} ownProperties: an object where the keys are the names of the
- * this.obj's ownProperties, and the values the descriptors of
- * the properties.
- * - {Array} ownSymbols: An array containing all descriptors of this.obj's
- * ownSymbols. Here we have an array, and not an object like for
- * ownProperties, because we can have multiple symbols with the same
- * name in this.obj, e.g. `{[Symbol()]: "a", [Symbol()]: "b"}`.
- * - {Object} safeGetterValues: an object that maps this.obj's property names
- * with safe getters descriptors.
- */
- onPrototypeAndProperties: function() {
- let proto = null;
- let names = [];
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- proto = this.obj.proto;
- names = this.obj.getOwnPropertyNames();
- symbols = this.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let ownProperties = Object.create(null);
- let ownSymbols = [];
-
- for (let name of names) {
- ownProperties[name] = this._propertyDescriptor(name);
- }
-
- for (let sym of symbols) {
- ownSymbols.push({
- name: sym.toString(),
- descriptor: this._propertyDescriptor(sym)
- });
- }
-
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto),
- ownProperties,
- ownSymbols,
- safeGetterValues: this._findSafeGetterValues(names) };
- },
-
- /**
- * Find the safe getter values for the current Debugger.Object, |this.obj|.
- *
- * @private
- * @param array ownProperties
- * The array that holds the list of known ownProperties names for
- * |this.obj|.
- * @param number [limit=0]
- * Optional limit of getter values to find.
- * @return object
- * An object that maps property names to safe getter descriptors as
- * defined by the remote debugging protocol.
- */
- _findSafeGetterValues: function(ownProperties, limit = 0) {
- let safeGetterValues = Object.create(null);
- let obj = this.obj;
- let level = 0, i = 0;
-
- // Do not search safe getters in unsafe objects.
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- return safeGetterValues;
- }
-
- // Most objects don't have any safe getters but inherit some from their
- // prototype. Avoid calling getOwnPropertyNames on objects that may have
- // many properties like Array, strings or js objects. That to avoid
- // freezing firefox when doing so.
- if (isArray(this.obj) || ["Object", "String"].includes(this.obj.class)) {
- obj = obj.proto;
- level++;
- }
-
- while (obj && DevToolsUtils.isSafeDebuggerObject(obj)) {
- let getters = this._findSafeGetters(obj);
- for (let name of getters) {
- // Avoid overwriting properties from prototypes closer to this.obj. Also
- // avoid providing safeGetterValues from prototypes if property |name|
- // is already defined as an own property.
- if (name in safeGetterValues ||
- (obj != this.obj && ownProperties.includes(name))) {
- continue;
- }
-
- // Ignore __proto__ on Object.prototye.
- if (!obj.proto && name == "__proto__") {
- continue;
- }
-
- let desc = null, getter = null;
- try {
- desc = obj.getOwnPropertyDescriptor(name);
- getter = desc.get;
- } catch (ex) {
- // The above can throw if the cache becomes stale.
- }
- if (!getter) {
- obj._safeGetters = null;
- continue;
- }
-
- let result = getter.call(this.obj);
- if (result && !("throw" in result)) {
- let getterValue = undefined;
- if ("return" in result) {
- getterValue = result.return;
- } else if ("yield" in result) {
- getterValue = result.yield;
- }
- // WebIDL attributes specified with the LenientThis extended attribute
- // return undefined and should be ignored.
- if (getterValue !== undefined) {
- safeGetterValues[name] = {
- getterValue: this.hooks.createValueGrip(getterValue),
- getterPrototypeLevel: level,
- enumerable: desc.enumerable,
- writable: level == 0 ? desc.writable : true,
- };
- if (limit && ++i == limit) {
- break;
- }
- }
- }
- }
- if (limit && i == limit) {
- break;
- }
-
- obj = obj.proto;
- level++;
- }
-
- return safeGetterValues;
- },
-
- /**
- * Find the safe getters for a given Debugger.Object. Safe getters are native
- * getters which are safe to execute.
- *
- * @private
- * @param Debugger.Object object
- * The Debugger.Object where you want to find safe getters.
- * @return Set
- * A Set of names of safe getters. This result is cached for each
- * Debugger.Object.
- */
- _findSafeGetters: function(object) {
- if (object._safeGetters) {
- return object._safeGetters;
- }
-
- let getters = new Set();
-
- if (!DevToolsUtils.isSafeDebuggerObject(object)) {
- object._safeGetters = getters;
- return getters;
- }
-
- let names = [];
- try {
- names = object.getOwnPropertyNames();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
-
- for (let name of names) {
- let desc = null;
- try {
- desc = object.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072).
- }
- if (!desc || desc.value !== undefined || !("get" in desc)) {
- continue;
- }
-
- if (DevToolsUtils.hasSafeGetter(desc)) {
- getters.add(name);
- }
- }
-
- object._safeGetters = getters;
- return getters;
- },
-
- /**
- * Handle a protocol request to provide the prototype of the object.
- */
- onPrototype: function() {
- let proto = null;
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- proto = this.obj.proto;
- }
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto) };
- },
-
- /**
- * Handle a protocol request to provide the property descriptor of the
- * object's specified property.
- *
- * @param request object
- * The protocol request object.
- */
- onProperty: function(request) {
- if (!request.name) {
- return { error: "missingParameter",
- message: "no property name was specified" };
- }
-
- return { from: this.actorID,
- descriptor: this._propertyDescriptor(request.name) };
- },
-
- /**
- * Handle a protocol request to provide the display string for the object.
- */
- onDisplayString: function() {
- const string = stringify(this.obj);
- return { from: this.actorID,
- displayString: this.hooks.createValueGrip(string) };
- },
-
- /**
- * A helper method that creates a property descriptor for the provided object,
- * properly formatted for sending in a protocol response.
- *
- * @private
- * @param string name
- * The property that the descriptor is generated for.
- * @param boolean [onlyEnumerable]
- * Optional: true if you want a descriptor only for an enumerable
- * property, false otherwise.
- * @return object|undefined
- * The property descriptor, or undefined if this is not an enumerable
- * property and onlyEnumerable=true.
- */
- _propertyDescriptor: function(name, onlyEnumerable) {
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- return undefined;
- }
-
- let desc;
- try {
- desc = this.obj.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072). Inform the user with a bogus, but hopefully
- // explanatory, descriptor.
- return {
- configurable: false,
- writable: false,
- enumerable: false,
- value: e.name
- };
- }
-
- if (!desc || onlyEnumerable && !desc.enumerable) {
- return undefined;
- }
-
- let retval = {
- configurable: desc.configurable,
- enumerable: desc.enumerable
- };
-
- if ("value" in desc) {
- retval.writable = desc.writable;
- retval.value = this.hooks.createValueGrip(desc.value);
- } else {
- if ("get" in desc) {
- retval.get = this.hooks.createValueGrip(desc.get);
- }
- if ("set" in desc) {
- retval.set = this.hooks.createValueGrip(desc.set);
- }
- }
- return retval;
- },
-
- /**
- * Handle a protocol request to provide the source code of a function.
- *
- * @param request object
- * The protocol request object.
- */
- onDecompile: function(request) {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "decompile request is only valid for object grips " +
- "with a 'Function' class." };
- }
-
- return { from: this.actorID,
- decompiledCode: this.obj.decompile(!!request.pretty) };
- },
-
- /**
- * Handle a protocol request to provide the parameters of a function.
- */
- onParameterNames: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "'parameterNames' request is only valid for object " +
- "grips with a 'Function' class." };
- }
-
- return { parameterNames: this.obj.parameterNames };
- },
-
- /**
- * Handle a protocol request to release a thread-lifetime grip.
- */
- onRelease: function() {
- this.release();
- return {};
- },
-
- /**
- * Handle a protocol request to provide the lexical scope of a function.
- */
- onScope: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "scope request is only valid for object grips with a" +
- " 'Function' class." };
- }
-
- let envActor = this.hooks.createEnvironmentActor(this.obj.environment,
- this.registeredPool);
- if (!envActor) {
- return { error: "notDebuggee",
- message: "cannot access the environment of this function." };
- }
-
- return { from: this.actorID, scope: envActor.form() };
- },
-
- /**
- * Handle a protocol request to get the list of dependent promises of a
- * promise.
- *
- * @return object
- * Returns an object containing an array of object grips of the
- * dependent promises
- */
- onDependentPromises: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'dependentPromises' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let promises = this.obj.promiseDependentPromises
- .map(p => this.hooks.createValueGrip(p));
-
- return { promises };
- },
-
- /**
- * Handle a protocol request to get the allocation stack of a promise.
- */
- onAllocationStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'allocationStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseAllocationSite;
- let allocationStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- allocationStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(allocationStacks).then(stacks => {
- return { allocationStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the fulfillment stack of a promise.
- */
- onFulfillmentStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'fulfillmentStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let fulfillmentStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- fulfillmentStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(fulfillmentStacks).then(stacks => {
- return { fulfillmentStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the rejection stack of a promise.
- */
- onRejectionStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'rejectionStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let rejectionStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- rejectionStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(rejectionStacks).then(stacks => {
- return { rejectionStack: stacks };
- });
- },
-
- /**
- * Helper function for fetching the source location of a SavedFrame stack.
- *
- * @param SavedFrame stack
- * The promise allocation stack frame
- * @return object
- * Returns an object containing the source location of the SavedFrame
- * stack.
- */
- _getSourceOriginalLocation: function(stack) {
- let source;
-
- // Catch any errors if the source actor cannot be found
- try {
- source = this.hooks.sources().getSourceActorByURL(stack.source);
- } catch (e) {
- // ignored
- }
-
- if (!source) {
- return null;
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- source,
- stack.line,
- stack.column
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn,
- functionDisplayName: stack.functionDisplayName
- };
- });
- }
-};
-
-ObjectActor.prototype.requestTypes = {
- "definitionSite": ObjectActor.prototype.onDefinitionSite,
- "parameterNames": ObjectActor.prototype.onParameterNames,
- "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
- "enumProperties": ObjectActor.prototype.onEnumProperties,
- "prototype": ObjectActor.prototype.onPrototype,
- "property": ObjectActor.prototype.onProperty,
- "displayString": ObjectActor.prototype.onDisplayString,
- "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
- "decompile": ObjectActor.prototype.onDecompile,
- "release": ObjectActor.prototype.onRelease,
- "scope": ObjectActor.prototype.onScope,
- "dependentPromises": ObjectActor.prototype.onDependentPromises,
- "allocationStack": ObjectActor.prototype.onAllocationStack,
- "fulfillmentStack": ObjectActor.prototype.onFulfillmentStack,
- "rejectionStack": ObjectActor.prototype.onRejectionStack,
- "enumEntries": ObjectActor.prototype.onEnumEntries,
- "enumSymbols": ObjectActor.prototype.onEnumSymbols,
-};
-
-/**
- * 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.
- */
-function PropertyIteratorActor(objectActor, options) {
- if (!DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- this.iterator = {
- size: 0,
- propertyName: index => undefined,
- propertyDescription: index => undefined,
- };
- } else if (options.enumEntries) {
- let cls = objectActor.obj.class;
- if (cls == "Map") {
- this.iterator = enumMapEntries(objectActor);
- } else if (cls == "WeakMap") {
- this.iterator = enumWeakMapEntries(objectActor);
- } else if (cls == "Set") {
- this.iterator = enumSetEntries(objectActor);
- } else if (cls == "WeakSet") {
- this.iterator = enumWeakSetEntries(objectActor);
- } else {
- throw new Error("Unsupported class to enumerate entries from: " + cls);
- }
- } else if (
- isArray(objectActor.obj)
- && options.ignoreNonIndexedProperties
- && !options.query
- ) {
- this.iterator = enumArrayProperties(objectActor, options);
- } else {
- this.iterator = enumObjectProperties(objectActor, options);
- }
-}
-
-PropertyIteratorActor.prototype = {
- actorPrefix: "propertyIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- names({ indexes }) {
- let list = [];
- for (let idx of indexes) {
- list.push(this.iterator.propertyName(idx));
- }
- return {
- names: indexes
- };
- },
-
- slice({ start, count }) {
- let ownProperties = Object.create(null);
- for (let i = start, m = start + count; i < m; i++) {
- let name = this.iterator.propertyName(i);
- ownProperties[name] = this.iterator.propertyDescription(i);
- }
- return {
- ownProperties
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-PropertyIteratorActor.prototype.requestTypes = {
- "names": PropertyIteratorActor.prototype.names,
- "slice": PropertyIteratorActor.prototype.slice,
- "all": PropertyIteratorActor.prototype.all,
-};
-
-function enumArrayProperties(objectActor, options) {
- return {
- size: getArrayLength(objectActor.obj),
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- return objectActor._propertyDescriptor(index);
- }
- };
-}
-
-function enumObjectProperties(objectActor, options) {
- 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) {
- let length = DevToolsUtils.getProperty(objectActor.obj, "length");
- let sliceIndex;
-
- const isLengthTrustworthy =
- isUint32(length)
- && (!length || isArrayIndex(names[length - 1]))
- && !isArrayIndex(names[length]);
-
- if (!isLengthTrustworthy) {
- // The length property may not reflect what the object looks like, let's find
- // where indexed properties end.
-
- if (!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 (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,
- // like webaudio buffers.
- if (options.ignoreIndexedProperties) {
- // Keep items after `sliceIndex` index
- names = names.slice(sliceIndex);
- } else if (options.ignoreNonIndexedProperties) {
- // Keep `sliceIndex` first items
- names.length = sliceIndex;
- }
- }
-
- let safeGetterValues = objectActor._findSafeGetterValues(names, 0);
- let safeGetterNames = Object.keys(safeGetterValues);
- // Merge the safe getter values into the existing properties list.
- for (let 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 && 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) {
- let name = names[index];
- let desc = objectActor._propertyDescriptor(name);
- if (!desc) {
- desc = safeGetterValues[name];
- } else if (name in safeGetterValues) {
- // Merge the safe getter values into the existing properties list.
- let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
- desc.getterValue = getterValue;
- desc.getterPrototypeLevel = getterPrototypeLevel;
- }
- return desc;
- }
- };
-}
-
-/**
- * Helper function to create a grip from a Map/Set entry
- */
-function gripFromEntry({ obj, hooks }, entry) {
- return hooks.createValueGrip(
- makeDebuggeeValueIfNeeded(obj, Cu.unwaiveXrays(entry)));
-}
-
-function enumMapEntries(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.
- let raw = objectActor.obj.unsafeDereference();
-
- let keys = [...Cu.waiveXrays(Map.prototype.keys.call(raw))];
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = Map.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = Map.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
-}
-
-function enumWeakMapEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakMapKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = WeakMap.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = WeakMap.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
-}
-
-function enumSetEntries(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.
- let raw = objectActor.obj.unsafeDereference();
- let values = [...Cu.waiveXrays(Set.prototype.values.call(raw))];
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of values) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: values.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = values[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-function enumWeakSetEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakSetKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of keys) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = keys[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-/**
- * Creates an actor to iterate over an object's symbols.
- *
- * @param objectActor ObjectActor
- * The object actor.
- */
-function SymbolIteratorActor(objectActor) {
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- try {
- symbols = objectActor.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- this.iterator = {
- size: symbols.length,
- symbolDescription(index) {
- const symbol = symbols[index];
- return {
- name: symbol.toString(),
- descriptor: objectActor._propertyDescriptor(symbol)
- };
- }
- };
-}
-
-SymbolIteratorActor.prototype = {
- actorPrefix: "symbolIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- slice({ start, count }) {
- let ownSymbols = [];
- for (let i = start, m = start + count; i < m; i++) {
- ownSymbols.push(this.iterator.symbolDescription(i));
- }
- return {
- ownSymbols
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-SymbolIteratorActor.prototype.requestTypes = {
- "slice": SymbolIteratorActor.prototype.slice,
- "all": SymbolIteratorActor.prototype.all,
-};
-
-/**
- * Functions for adding information to ObjectActor grips for the purpose of
- * having customized output. This object holds arrays mapped by
- * Debugger.Object.prototype.class.
- *
- * In each array you can add functions that take three
- * arguments:
- * - the ObjectActor instance and its hooks to make a preview for,
- * - the grip object being prepared for the client,
- * - the raw JS object after calling Debugger.Object.unsafeDereference(). This
- * argument is only provided if the object is safe for reading properties and
- * executing methods. See DevToolsUtils.isSafeJSObject().
- *
- * Functions must return false if they cannot provide preview
- * information for the debugger object, or true otherwise.
- */
-DebuggerServer.ObjectActorPreviewers = {
- String: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("String", String, objectActor, grip, rawObj);
- }],
-
- Boolean: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip, rawObj);
- }],
-
- Number: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Number", Number, objectActor, grip, rawObj);
- }],
-
- Symbol: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Symbol", Symbol, objectActor, grip, rawObj);
- }],
-
- Function: [function({obj, hooks}, grip) {
- if (obj.name) {
- grip.name = obj.name;
- }
-
- if (obj.displayName) {
- grip.displayName = obj.displayName.substr(0, 500);
- }
-
- if (obj.parameterNames) {
- grip.parameterNames = obj.parameterNames;
- }
-
- // Check if the developer has added a de-facto standard displayName
- // property for us to use.
- let userDisplayName;
- try {
- userDisplayName = obj.getOwnPropertyDescriptor("displayName");
- } catch (e) {
- // The above can throw "permission denied" errors when the debuggee
- // does not subsume the function's compartment.
- }
-
- if (userDisplayName && typeof userDisplayName.value == "string" &&
- userDisplayName.value) {
- grip.userDisplayName = hooks.createValueGrip(userDisplayName.value);
- }
-
- let dbgGlobal = hooks.getGlobalDebugObject();
- if (dbgGlobal) {
- let script = dbgGlobal.makeDebuggeeValue(obj.unsafeDereference()).script;
- if (script) {
- grip.location = {
- url: script.url,
- line: script.startLine
- };
- }
- }
-
- return true;
- }],
-
- RegExp: [function({obj, hooks}, grip) {
- let str = DevToolsUtils.callPropertyOnObject(obj, "toString");
- if (typeof str != "string") {
- return false;
- }
-
- grip.displayString = hooks.createValueGrip(str);
- return true;
- }],
-
- Date: [function({obj, hooks}, grip) {
- let time = DevToolsUtils.callPropertyOnObject(obj, "getTime");
- if (typeof time != "number") {
- return false;
- }
-
- grip.preview = {
- timestamp: hooks.createValueGrip(time),
- };
- return true;
- }],
-
- Array: [function({obj, hooks}, grip) {
- let length = getArrayLength(obj);
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let items = grip.preview.items = [];
-
- for (let i = 0; i < length; ++i) {
- // Array Xrays filter out various possibly-unsafe properties (like
- // functions, and claim that the value is undefined instead. This
- // is generally the right thing for privileged code accessing untrusted
- // objects, but quite confusing for Object previews. So we manually
- // override this protection by waiving Xrays on the array, and re-applying
- // Xrays on any indexed value props that we pull off of it.
- let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
- if (desc && !desc.get && !desc.set) {
- let value = Cu.unwaiveXrays(desc.value);
- value = makeDebuggeeValueIfNeeded(obj, value);
- items.push(hooks.createValueGrip(value));
- } else {
- items.push(null);
- }
-
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Set: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: size,
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumSetEntries(objectActor)) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakSet: [function(objectActor, grip) {
- let enumEntries = enumWeakSetEntries(objectActor);
-
- grip.preview = {
- kind: "ArrayLike",
- length: enumEntries.size
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumEntries) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Map: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: size,
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumMapEntries(objectActor)) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakMap: [function(objectActor, grip) {
- let enumEntries = enumWeakMapEntries(objectActor);
-
- grip.preview = {
- kind: "MapLike",
- size: enumEntries.size
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumEntries) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- DOMStringMap: [function({obj, hooks}, grip, rawObj) {
- if (!rawObj) {
- return false;
- }
-
- let keys = obj.getOwnPropertyNames();
- grip.preview = {
- kind: "MapLike",
- size: keys.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let key of keys) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
- entries.push([key, hooks.createValueGrip(value)]);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Proxy: [function({obj, hooks}, grip, rawObj) {
- // The `isProxy` getter of the debuggee object only detects proxies without
- // security wrappers. If false, the target and handler are not available.
- let hasTargetAndHandler = obj.isProxy;
- if (hasTargetAndHandler) {
- grip.proxyTarget = hooks.createValueGrip(obj.proxyTarget);
- grip.proxyHandler = hooks.createValueGrip(obj.proxyHandler);
- }
-
- grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownPropertiesLength: 2 * hasTargetAndHandler
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- if (hasTargetAndHandler) {
- grip.preview.ownProperties["<target>"] = {value: grip.proxyTarget};
- grip.preview.ownProperties["<handler>"] = {value: grip.proxyHandler};
- }
-
- return true;
- }],
-};
-
-/**
- * Generic previewer for classes wrapping primitives, like String,
- * Number and Boolean.
- *
- * @param string className
- * Class name to expect.
- * @param object classObj
- * The class to expect, eg. String. The valueOf() method of the class is
- * invoked on the given object.
- * @param ObjectActor objectActor
- * The object actor
- * @param Object grip
- * The result grip to fill in
- * @return Booolean true if the object was handled, false otherwise
- */
-function wrappedPrimitivePreviewer(className, classObj, objectActor, grip, rawObj) {
- let {obj, hooks} = objectActor;
-
- let v = null;
- try {
- v = classObj.prototype.valueOf.call(rawObj);
- } catch (ex) {
- // valueOf() can throw if the raw JS object is "misbehaved".
- return false;
- }
-
- if (v === null) {
- return false;
- }
-
- let canHandle = GenericObject(objectActor, grip, rawObj, className === "String");
- if (!canHandle) {
- return false;
- }
-
- grip.preview.wrappedValue =
- hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v));
- return true;
-}
-
-function GenericObject(objectActor, grip, rawObj, specialStringBehavior = false) {
- let {obj, hooks} = objectActor;
- if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
- return false;
- }
-
- let i = 0, names = [], symbols = [];
- let preview = grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownSymbols: [],
- };
-
- try {
- names = obj.getOwnPropertyNames();
- symbols = obj.getOwnPropertySymbols();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
- preview.ownPropertiesLength = names.length;
- preview.ownSymbolsLength = symbols.length;
-
- let length;
- if (specialStringBehavior) {
- length = DevToolsUtils.getProperty(obj, "length");
- if (typeof length != "number") {
- specialStringBehavior = false;
- }
- }
-
- for (let name of names) {
- if (specialStringBehavior && /^[0-9]+$/.test(name)) {
- let num = parseInt(name, 10);
- if (num.toString() === name && num >= 0 && num < length) {
- continue;
- }
- }
-
- let desc = objectActor._propertyDescriptor(name, true);
- if (!desc) {
- continue;
- }
-
- preview.ownProperties[name] = desc;
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- for (let symbol of symbols) {
- let descriptor = objectActor._propertyDescriptor(symbol, true);
- if (!descriptor) {
- continue;
- }
-
- preview.ownSymbols.push(Object.assign({
- descriptor
- }, hooks.createValueGrip(symbol)));
-
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- if (i < OBJECT_PREVIEW_MAX_ITEMS) {
- preview.safeGetterValues = objectActor._findSafeGetterValues(
- Object.keys(preview.ownProperties),
- OBJECT_PREVIEW_MAX_ITEMS - i);
- }
-
- return true;
-}
-
-// Preview functions that do not rely on the object class.
-DebuggerServer.ObjectActorPreviewers.Object = [
- function TypedArray({obj, hooks}, grip) {
- if (!isTypedArray(obj)) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: getArrayLength(obj),
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let global = Cu.getGlobalForObject(DebuggerServer);
- let classProto = global[obj.class].prototype;
- // The Xray machinery for TypedArrays denies indexed access on the grounds
- // that it's slow, and advises callers to do a structured clone instead.
- let safeView = Cu.cloneInto(classProto.subarray.call(raw, 0,
- OBJECT_PREVIEW_MAX_ITEMS), global);
- let items = grip.preview.items = [];
- for (let i = 0; i < safeView.length; i++) {
- items.push(safeView[i]);
- }
-
- return true;
- },
-
- function Error({obj, hooks}, grip) {
- switch (obj.class) {
- case "Error":
- case "EvalError":
- case "RangeError":
- case "ReferenceError":
- case "SyntaxError":
- case "TypeError":
- case "URIError":
- let name = DevToolsUtils.getProperty(obj, "name");
- let msg = DevToolsUtils.getProperty(obj, "message");
- let stack = DevToolsUtils.getProperty(obj, "stack");
- let fileName = DevToolsUtils.getProperty(obj, "fileName");
- let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
- let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
- grip.preview = {
- kind: "Error",
- name: hooks.createValueGrip(name),
- message: hooks.createValueGrip(msg),
- stack: hooks.createValueGrip(stack),
- fileName: hooks.createValueGrip(fileName),
- lineNumber: hooks.createValueGrip(lineNumber),
- columnNumber: hooks.createValueGrip(columnNumber),
- };
- return true;
- default:
- return false;
- }
- },
-
- function CSSMediaRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSMediaRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.conditionText),
- };
- return true;
- },
-
- function CSSStyleRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSStyleRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.selectorText),
- };
- return true;
- },
-
- function ObjectWithURL({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(obj.class == "CSSImportRule" ||
- obj.class == "CSSStyleSheet" ||
- obj.class == "Location" ||
- rawObj instanceof Ci.nsIDOMWindow)) {
- return false;
- }
-
- let url;
- if (rawObj instanceof Ci.nsIDOMWindow && rawObj.location) {
- url = rawObj.location.href;
- } else if (rawObj.href) {
- url = rawObj.href;
- } else {
- return false;
- }
-
- grip.preview = {
- kind: "ObjectWithURL",
- url: hooks.createValueGrip(url),
- };
-
- return true;
- },
-
- function ArrayLike({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- obj.class != "DOMStringList" &&
- obj.class != "DOMTokenList" &&
- obj.class != "CSSRuleList" &&
- obj.class != "MediaList" &&
- obj.class != "StyleSheetList" &&
- obj.class != "CSSValueList" &&
- obj.class != "NamedNodeMap" &&
- obj.class != "FileList" &&
- obj.class != "NodeList") {
- return false;
- }
-
- if (typeof rawObj.length != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: rawObj.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
-
- for (let i = 0; i < rawObj.length &&
- items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[i]);
- items.push(hooks.createValueGrip(value));
- }
-
- return true;
- },
-
- function CSSStyleDeclaration({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- (obj.class != "CSSStyleDeclaration" &&
- obj.class != "CSS2Properties")) {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: rawObj.length,
- };
-
- let entries = grip.preview.entries = [];
-
- for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
- i < rawObj.length; i++) {
- let prop = rawObj[i];
- let value = rawObj.getPropertyValue(prop);
- entries.push([prop, hooks.createValueGrip(value)]);
- }
-
- return true;
- },
-
- function DOMNode({obj, hooks}, grip, rawObj) {
- if (isWorker || obj.class == "Object" || !rawObj ||
- !(rawObj instanceof Ci.nsIDOMNode)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMNode",
- nodeType: rawObj.nodeType,
- nodeName: rawObj.nodeName,
- isConnected: rawObj.isConnected === true,
- };
-
- if (rawObj instanceof Ci.nsIDOMDocument && rawObj.location) {
- preview.location = hooks.createValueGrip(rawObj.location.href);
- } else if (rawObj instanceof Ci.nsIDOMDocumentFragment) {
- preview.childNodesLength = rawObj.childNodes.length;
-
- if (hooks.getGripDepth() < 2) {
- preview.childNodes = [];
- for (let node of rawObj.childNodes) {
- let actor = hooks.createValueGrip(obj.makeDebuggeeValue(node));
- preview.childNodes.push(actor);
- if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
- } else if (rawObj instanceof Ci.nsIDOMElement) {
- // For HTML elements (in an HTML document, at least), the nodeName is an
- // uppercased version of the actual element name. Check for HTML
- // elements, that is elements in the HTML namespace, and lowercase the
- // nodeName in that case.
- if (rawObj.namespaceURI == "http://www.w3.org/1999/xhtml") {
- preview.nodeName = preview.nodeName.toLowerCase();
- }
-
- // Add preview for DOM element attributes.
- preview.attributes = {};
- preview.attributesLength = rawObj.attributes.length;
- for (let attr of rawObj.attributes) {
- preview.attributes[attr.nodeName] = hooks.createValueGrip(attr.value);
- }
- } else if (obj.class == "Attr") {
- preview.value = hooks.createValueGrip(rawObj.value);
- } else if (obj.class == "Text" ||
- obj.class == "CDATASection" ||
- obj.class == "Comment") {
- preview.textContent = hooks.createValueGrip(rawObj.textContent);
- }
-
- return true;
- },
-
- function DOMEvent({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMEvent)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMEvent",
- type: rawObj.type,
- properties: Object.create(null),
- };
-
- if (hooks.getGripDepth() < 2) {
- let target = obj.makeDebuggeeValue(rawObj.target);
- preview.target = hooks.createValueGrip(target);
- }
-
- let props = [];
- if (obj.class == "MouseEvent" ||
- obj.class == "DragEvent" ||
- obj.class == "PointerEvent" ||
- obj.class == "SimpleGestureEvent" ||
- obj.class == "WheelEvent") {
- props.push("buttons", "clientX", "clientY", "layerX", "layerY");
- } else if (obj.class == "KeyboardEvent") {
- let modifiers = [];
- if (rawObj.altKey) {
- modifiers.push("Alt");
- }
- if (rawObj.ctrlKey) {
- modifiers.push("Control");
- }
- if (rawObj.metaKey) {
- modifiers.push("Meta");
- }
- if (rawObj.shiftKey) {
- modifiers.push("Shift");
- }
- preview.eventKind = "key";
- preview.modifiers = modifiers;
-
- props.push("key", "charCode", "keyCode");
- } else if (obj.class == "TransitionEvent") {
- props.push("propertyName", "pseudoElement");
- } else if (obj.class == "AnimationEvent") {
- props.push("animationName", "pseudoElement");
- } else if (obj.class == "ClipboardEvent") {
- props.push("clipboardData");
- }
-
- // Add event-specific properties.
- for (let prop of props) {
- let value = rawObj[prop];
- if (value && (typeof value == "object" || typeof value == "function")) {
- // Skip properties pointing to objects.
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- }
-
- // Add any properties we find on the event object.
- if (!props.length) {
- let i = 0;
- for (let prop in rawObj) {
- let value = rawObj[prop];
- if (prop == "target" || prop == "type" || value === null ||
- typeof value == "function") {
- continue;
- }
- if (value && typeof value == "object") {
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
-
- return true;
- },
-
- function DOMException({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMDOMException)) {
- return false;
- }
-
- grip.preview = {
- kind: "DOMException",
- name: hooks.createValueGrip(rawObj.name),
- message: hooks.createValueGrip(rawObj.message),
- code: hooks.createValueGrip(rawObj.code),
- result: hooks.createValueGrip(rawObj.result),
- filename: hooks.createValueGrip(rawObj.filename),
- lineNumber: hooks.createValueGrip(rawObj.lineNumber),
- columnNumber: hooks.createValueGrip(rawObj.columnNumber),
- };
-
- return true;
- },
-
- function PseudoArray({obj, hooks}, grip, rawObj) {
- // An object is considered a pseudo-array if all the following apply:
- // - All its properties are array indices except, optionally, a "length" property.
- // - At least it has the "0" array index.
- // - The array indices are consecutive.
- // - The value of "length", if present, is the number of array indices.
-
- let keys;
- try {
- keys = obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- return false;
- }
- let {length} = keys;
- if (length === 0) {
- return false;
- }
-
- // Array indices should be sorted at the beginning, from smallest to largest.
- // Other properties should be at the end, so check if the last one is "length".
- if (keys[length - 1] === "length") {
- --length;
- if (length === 0 || length !== DevToolsUtils.getProperty(obj, "length")) {
- return false;
- }
- }
-
- // Check that the last key is the array index expected at that position.
- let lastKey = keys[length - 1];
- if (!isArrayIndex(lastKey) || +lastKey !== length - 1) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- // Avoid recursive object grips.
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- let numItems = Math.min(OBJECT_PREVIEW_MAX_ITEMS, length);
-
- for (let i = 0; i < numItems; ++i) {
- let desc = obj.getOwnPropertyDescriptor(i);
- if (desc && "value" in desc) {
- items.push(hooks.createValueGrip(desc.value));
- } else {
- items.push(null);
- }
- }
-
- return true;
- },
-
- function Object(objectActor, grip, rawObj) {
- return GenericObject(objectActor, grip, rawObj, /* specialStringBehavior = */ false);
- },
-];
-
-/**
- * Get thisDebugger.Object referent's `promiseState`.
- *
- * @returns Object
- * An object of one of the following forms:
- * - { state: "pending" }
- * - { state: "fulfilled", value }
- * - { state: "rejected", reason }
- */
-function getPromiseState(obj) {
- if (obj.class != "Promise") {
- throw new Error(
- "Can't call `getPromiseState` on `Debugger.Object`s that don't " +
- "refer to Promise objects.");
- }
-
- let state = { state: obj.promiseState };
- if (state.state === "fulfilled") {
- state.value = obj.promiseValue;
- } else if (state.state === "rejected") {
- state.reason = obj.promiseReason;
- }
- return state;
-}
-
-/**
- * Determine if a given value is non-primitive.
- *
- * @param Any value
- * The value to test.
- * @return Boolean
- * Whether the value is non-primitive.
- */
-function isObject(value) {
- const type = typeof value;
- return type == "object" ? value !== null : type == "function";
-}
-
-/**
- * Create a function that can safely stringify Debugger.Objects of a given
- * builtin type.
- *
- * @param Function ctor
- * The builtin class constructor.
- * @return Function
- * The stringifier for the class.
- */
-function createBuiltinStringifier(ctor) {
- return obj => {
- try {
- return ctor.prototype.toString.call(obj.unsafeDereference());
- } catch (err) {
- // The debuggee will see a "Function" class if the object is callable and
- // its compartment is not subsumed. The above will throw if it's not really
- // a function, e.g. if it's a callable proxy.
- return "[object " + obj.class + "]";
- }
- };
-}
-
-/**
- * Stringify a Debugger.Object-wrapped Error instance.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification of the object.
- */
-function errorStringify(obj) {
- let name = DevToolsUtils.getProperty(obj, "name");
- if (name === "" || name === undefined) {
- name = obj.class;
- } else if (isObject(name)) {
- name = stringify(name);
- }
-
- let message = DevToolsUtils.getProperty(obj, "message");
- if (isObject(message)) {
- message = stringify(message);
- }
-
- if (message === "" || message === undefined) {
- return name;
- }
- return name + ": " + message;
-}
-
-/**
- * Stringify a Debugger.Object based on its class.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification for the object.
- */
-function stringify(obj) {
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- if (DevToolsUtils.isCPOW(obj)) {
- return "<cpow>";
- }
- let unwrapped = DevToolsUtils.unwrap(obj);
- if (unwrapped === undefined) {
- return "<invisibleToDebugger>";
- } else if (unwrapped.isProxy) {
- return "<proxy>";
- }
- // The following line should not be reached. It's there just in case somebody
- // modifies isSafeDebuggerObject to return false for additional kinds of objects.
- return "[object " + obj.class + "]";
- } else if (obj.class == "DeadObject") {
- return "<dead object>";
- }
-
- const stringifier = stringifiers[obj.class] || stringifiers.Object;
-
- try {
- return stringifier(obj);
- } catch (e) {
- DevToolsUtils.reportException("stringify", e);
- return "<failed to stringify object>";
- }
-}
-
-// Used to prevent infinite recursion when an array is found inside itself.
-var seen = null;
-
-var stringifiers = {
- Error: errorStringify,
- EvalError: errorStringify,
- RangeError: errorStringify,
- ReferenceError: errorStringify,
- SyntaxError: errorStringify,
- TypeError: errorStringify,
- URIError: errorStringify,
- Boolean: createBuiltinStringifier(Boolean),
- Function: createBuiltinStringifier(Function),
- Number: createBuiltinStringifier(Number),
- RegExp: createBuiltinStringifier(RegExp),
- String: createBuiltinStringifier(String),
- Object: obj => "[object " + obj.class + "]",
- Array: obj => {
- // If we're at the top level then we need to create the Set for tracking
- // previously stringified arrays.
- const topLevel = !seen;
- if (topLevel) {
- seen = new Set();
- } else if (seen.has(obj)) {
- return "";
- }
-
- seen.add(obj);
-
- const len = getArrayLength(obj);
- let string = "";
-
- // Array.length is always a non-negative safe integer.
- for (let i = 0; i < len; i++) {
- const desc = obj.getOwnPropertyDescriptor(i);
- if (desc) {
- const { value } = desc;
- if (value != null) {
- string += isObject(value) ? stringify(value) : value;
- }
- }
-
- if (i < len - 1) {
- string += ",";
- }
- }
-
- if (topLevel) {
- seen = null;
- }
-
- return string;
- },
- DOMException: obj => {
- const message = DevToolsUtils.getProperty(obj, "message") || "<no message>";
- const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16);
- const code = DevToolsUtils.getProperty(obj, "code");
- const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
-
- return '[Exception... "' + message + '" ' +
- 'code: "' + code + '" ' +
- 'nsresult: "0x' + result + " (" + name + ')"]';
- },
- Promise: obj => {
- const { state, value, reason } = getPromiseState(obj);
- let statePreview = state;
- if (state != "pending") {
- const settledValue = state === "fulfilled" ? value : reason;
- statePreview += ": " + (typeof settledValue === "object" && settledValue !== null
- ? stringify(settledValue)
- : settledValue);
- }
- return "Promise (" + statePreview + ")";
- },
-};
-
-/**
- * Make a debuggee value for the given object, if needed. Primitive values
- * are left the same.
- *
- * Use case: you have a raw JS object (after unsafe dereference) and you want to
- * send it to the client. In that case you need to use an ObjectActor which
- * requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
- * method works only for JS objects and functions.
- *
- * @param Debugger.Object obj
- * @param any value
- * @return object
- */
-function makeDebuggeeValueIfNeeded(obj, value) {
- if (value && (typeof value == "object" || typeof value == "function")) {
- return obj.makeDebuggeeValue(value);
- }
- return value;
-}
-
-/**
- * Creates an actor for the specified "very long" string. "Very long" is specified
- * at the server's discretion.
- *
- * @param string String
- * The string.
- */
-function LongStringActor(string) {
- this.string = string;
- this.stringLength = string.length;
-}
-
-LongStringActor.prototype = {
- actorPrefix: "longString",
-
- rawValue: function() {
- return this.string;
- },
-
- destroy: function() {
- // Because longStringActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- return {
- "type": "longString",
- "initial": this.string.substring(
- 0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
- "length": this.stringLength,
- "actor": this.actorID
- };
- },
-
- /**
- * Handle a request to extract part of this actor's string.
- *
- * @param request object
- * The protocol request object.
- */
- onSubstring: function(request) {
- return {
- "from": this.actorID,
- "substring": this.string.substring(request.start, request.end)
- };
- },
-
- /**
- * Handle a request to release this LongStringActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.longStringActors) {
- delete this.registeredPool.longStringActors[this.string];
- }
- }
-};
-
-LongStringActor.prototype.requestTypes = {
- "substring": LongStringActor.prototype.onSubstring,
- "release": LongStringActor.prototype.onRelease
-};
-
-/**
- * Creates an actor for the specified symbol.
- *
- * @param symbol Symbol
- * The symbol.
- */
-function SymbolActor(symbol) {
- this.symbol = symbol;
-}
-
-SymbolActor.prototype = {
- actorPrefix: "symbol",
-
- rawValue: function() {
- return this.symbol;
- },
-
- destroy: function() {
- // Because symbolActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let form = {
- type: "symbol",
- actor: this.actorID,
- };
- let name = getSymbolName(this.symbol);
- if (name !== undefined) {
- // Create a grip for the name because it might be a longString.
- form.name = createValueGrip(name, this.registeredPool);
- }
- return form;
- },
-
- /**
- * Handle a request to release this SymbolActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.symbolActors) {
- delete this.registeredPool.symbolActors[this.symbol];
- }
- }
-};
-
-SymbolActor.prototype.requestTypes = {
- "release": SymbolActor.prototype.onRelease
-};
-
/**
* Creates an actor for the specified ArrayBuffer.
*
* @param buffer ArrayBuffer
* The buffer.
*/
function ArrayBufferActor(buffer) {
this.buffer = buffer;
@@ -2453,136 +52,16 @@ ArrayBufferActor.prototype = {
}
};
ArrayBufferActor.prototype.requestTypes = {
"slice": ArrayBufferActor.prototype.onSlice,
};
/**
- * Create a grip for the given debuggee value. If the value is an
- * object, will create an actor with the given lifetime.
- */
-function createValueGrip(value, pool, makeObjectGrip) {
- switch (typeof value) {
- case "boolean":
- return value;
-
- case "string":
- if (stringIsLong(value)) {
- return longStringGrip(value, pool);
- }
- return value;
-
- case "number":
- if (value === Infinity) {
- return { type: "Infinity" };
- } else if (value === -Infinity) {
- return { type: "-Infinity" };
- } else if (Number.isNaN(value)) {
- return { type: "NaN" };
- } else if (!value && 1 / value === -Infinity) {
- return { type: "-0" };
- }
- return value;
-
- case "undefined":
- return { type: "undefined" };
-
- case "object":
- if (value === null) {
- return { type: "null" };
- } else if (value.optimizedOut ||
- value.uninitialized ||
- value.missingArguments) {
- // The slot is optimized out, an uninitialized binding, or
- // arguments on a dead scope
- return {
- type: "null",
- optimizedOut: value.optimizedOut,
- uninitialized: value.uninitialized,
- missingArguments: value.missingArguments
- };
- }
- return makeObjectGrip(value, pool);
-
- case "symbol":
- return symbolGrip(value, pool);
-
- default:
- assert(false, "Failed to provide a grip for: " + value);
- return null;
- }
-}
-
-const symbolProtoToString = Symbol.prototype.toString;
-
-function getSymbolName(symbol) {
- const name = symbolProtoToString.call(symbol).slice("Symbol(".length, -1);
- return name || undefined;
-}
-
-/**
- * Returns true if the string is long enough to use a LongStringActor instead
- * of passing the value directly over the protocol.
- *
- * @param str String
- * The string we are checking the length of.
- */
-function stringIsLong(str) {
- return str.length >= DebuggerServer.LONG_STRING_LENGTH;
-}
-
-/**
- * Create a grip for the given string.
- *
- * @param str String
- * The string we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function longStringGrip(str, pool) {
- if (!pool.longStringActors) {
- pool.longStringActors = {};
- }
-
- if (pool.longStringActors.hasOwnProperty(str)) {
- return pool.longStringActors[str].grip();
- }
-
- let actor = new LongStringActor(str);
- pool.addActor(actor);
- pool.longStringActors[str] = actor;
- return actor.grip();
-}
-
-/**
- * Create a grip for the given symbol.
- *
- * @param sym Symbol
- * The symbol we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function symbolGrip(sym, pool) {
- if (!pool.symbolActors) {
- pool.symbolActors = Object.create(null);
- }
-
- if (sym in pool.symbolActors) {
- return pool.symbolActors[sym].grip();
- }
-
- let actor = new SymbolActor(sym);
- pool.addActor(actor);
- pool.symbolActors[sym] = actor;
- return actor.grip();
-}
-
-/**
* Create a grip for the given ArrayBuffer.
*
* @param buffer ArrayBuffer
* The ArrayBuffer we are creating a grip for.
* @param pool ActorPool
* The actor pool where the new actor will be added.
*/
function arrayBufferGrip(buffer, pool) {
@@ -2595,95 +74,12 @@ function arrayBufferGrip(buffer, pool) {
}
let actor = new ArrayBufferActor(buffer);
pool.addActor(actor);
pool.arrayBufferActors.set(buffer, actor);
return actor.grip();
}
-const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
- "Uint32Array", "Int8Array", "Int16Array", "Int32Array",
- "Float32Array", "Float64Array"];
-
-/**
- * Returns true if a debuggee object is a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isTypedArray(object) {
- return TYPED_ARRAY_CLASSES.includes(object.class);
-}
-
-/**
- * Returns true if a debuggee object is an array, including a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isArray(object) {
- return isTypedArray(object) || object.class === "Array";
-}
-
-/**
- * Returns the length of an array (or typed array).
- *
- * @param obj Debugger.Object
- * The debuggee object of the array.
- * @return Number
- * @throws if the object is not an array.
- */
-function getArrayLength(object) {
- if (!isArray(object)) {
- throw new Error("Expected an array, got a " + object.class);
- }
-
- // Real arrays have a reliable `length` own property.
- if (object.class === "Array") {
- return DevToolsUtils.getProperty(object, "length");
- }
-
- // For typed arrays, `DevToolsUtils.getProperty` is not reliable because the `length`
- // getter could be shadowed by an own property, and `getOwnPropertyNames` is
- // unnecessarily slow. Obtain the `length` getter safely and call it manually.
- let typedProto = Object.getPrototypeOf(Uint8Array.prototype);
- let getter = Object.getOwnPropertyDescriptor(typedProto, "length").get;
- return getter.call(object.unsafeDereference());
-}
-
-/**
- * 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;
-}
-
-/**
- * Returns true if the parameter is suitable to be an array index.
- *
- * @param str String
- * @return Boolean
- */
-function isArrayIndex(str) {
- // Transform the parameter to a 32-bit unsigned integer.
- let num = str >>> 0;
- // Check that the parameter is a canonical Uint32 index.
- return num + "" === str &&
- // Array indices cannot attain the maximum Uint32 value.
- num != -1 >>> 0;
-}
-
-exports.ObjectActor = ObjectActor;
-exports.PropertyIteratorActor = PropertyIteratorActor;
-exports.LongStringActor = LongStringActor;
-exports.SymbolActor = SymbolActor;
-exports.createValueGrip = createValueGrip;
-exports.stringIsLong = stringIsLong;
-exports.longStringGrip = longStringGrip;
-exports.arrayBufferGrip = arrayBufferGrip;
+module.exports = {
+ ArrayBufferActor,
+ arrayBufferGrip,
+};
--- a/devtools/server/actors/environment.js
+++ b/devtools/server/actors/environment.js
@@ -3,17 +3,17 @@
/* 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";
/* global Debugger */
const { ActorClassWithSpec } = require("devtools/shared/protocol");
-const { createValueGrip } = require("devtools/server/actors/object");
+const { createValueGrip } = require("devtools/server/actors/object/utils");
const { environmentSpec } = require("devtools/shared/specs/environment");
/**
* Creates an EnvironmentActor. EnvironmentActors are responsible for listing
* the bindings introduced by a lexical environment and assigning new values to
* those identifier bindings.
*
* @param Debugger.Environment aEnvironment
--- a/devtools/server/actors/frame.js
+++ b/devtools/server/actors/frame.js
@@ -2,17 +2,17 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 { ActorPool } = require("devtools/server/actors/common");
-const { createValueGrip } = require("devtools/server/actors/object");
+const { createValueGrip } = require("devtools/server/actors/object/utils");
const { ActorClassWithSpec } = require("devtools/shared/protocol");
const { frameSpec } = require("devtools/shared/specs/frame");
/**
* An actor for a specified stack frame.
*/
let FrameActor = ActorClassWithSpec(frameSpec, {
/**
--- a/devtools/server/actors/moz.build
+++ b/devtools/server/actors/moz.build
@@ -4,28 +4,30 @@
# 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/.
DIRS += [
'canvas',
'emulation',
'highlighters',
'inspector',
+ 'object',
'utils',
'webconsole',
'worker',
]
DevToolsModules(
'accessibility-parent.js',
'accessibility.js',
'actor-registry.js',
'addon.js',
'addons.js',
'animation.js',
+ 'array-buffer.js',
'breakpoint.js',
'call-watcher.js',
'canvas.js',
'child-process.js',
'chrome.js',
'common.js',
'content.js',
'css-properties.js',
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -1,28 +1,32 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 { Cu, Ci } = require("chrome");
+const { Cu } = require("chrome");
const { GeneratedLocation } = require("devtools/server/actors/common");
-const { DebuggerServer } = require("devtools/server/main");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { assert } = DevToolsUtils;
-loader.lazyRequireGetter(this, "ChromeUtils");
+loader.lazyRequireGetter(this, "PropertyIteratorActor", "devtools/server/actors/object/property-iterator", true);
+loader.lazyRequireGetter(this, "SymbolIteratorActor", "devtools/server/actors/object/symbol-iterator", true);
+loader.lazyRequireGetter(this, "previewers", "devtools/server/actors/object/previewers");
+loader.lazyRequireGetter(this, "stringify", "devtools/server/actors/object/stringifiers");
-// Number of items to preview in objects, arrays, maps, sets, lists,
-// collections, etc.
-const OBJECT_PREVIEW_MAX_ITEMS = 10;
-
+const {
+ getArrayLength,
+ getPromiseState,
+ isArray,
+ isTypedArray,
+} = require("devtools/server/actors/object/utils");
/**
* Creates an actor for the specified object.
*
* @param obj Debugger.Object
* The debuggee object.
* @param hooks Object
* A collection of abstract methods that are implemented by the caller.
* ObjectActor requires the following functions to be implemented by
@@ -94,17 +98,17 @@ ObjectActor.prototype = {
// Objects belonging to an invisible-to-debugger compartment might be proxies,
// so just in case they shouldn't be accessed.
g.class = "InvisibleToDebugger: " + g.class;
} else if (unwrapped.isProxy) {
// Proxy objects can run traps when accessed, so just create a preview with
// the target and the handler.
g.class = "Proxy";
this.hooks.incrementGripDepth();
- DebuggerServer.ObjectActorPreviewers.Proxy[0](this, g, null);
+ previewers.Proxy[0](this, g, null);
this.hooks.decrementGripDepth();
}
return g;
}
// If the debuggee does not subsume the object's compartment, most properties won't
// be accessible. Cross-orgin Window and Location objects might expose some, though.
// Change the displayed class, but when creating the preview use the original one.
@@ -143,19 +147,17 @@ ObjectActor.prototype = {
if (Cu) {
raw = Cu.unwaiveXrays(raw);
}
if (!DevToolsUtils.isSafeJSObject(raw)) {
raw = null;
}
- let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
- DebuggerServer.ObjectActorPreviewers.Object;
- for (let fn of previewers) {
+ for (let fn of previewers[this.obj.class] || previewers.Object) {
try {
if (fn(this, g, raw)) {
break;
}
} catch (e) {
let msg = "ObjectActor.prototype.grip previewer function";
DevToolsUtils.reportException(msg, e);
}
@@ -798,1892 +800,9 @@ ObjectActor.prototype.requestTypes = {
"dependentPromises": ObjectActor.prototype.onDependentPromises,
"allocationStack": ObjectActor.prototype.onAllocationStack,
"fulfillmentStack": ObjectActor.prototype.onFulfillmentStack,
"rejectionStack": ObjectActor.prototype.onRejectionStack,
"enumEntries": ObjectActor.prototype.onEnumEntries,
"enumSymbols": ObjectActor.prototype.onEnumSymbols,
};
-/**
- * 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.
- */
-function PropertyIteratorActor(objectActor, options) {
- if (!DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- this.iterator = {
- size: 0,
- propertyName: index => undefined,
- propertyDescription: index => undefined,
- };
- } else if (options.enumEntries) {
- let cls = objectActor.obj.class;
- if (cls == "Map") {
- this.iterator = enumMapEntries(objectActor);
- } else if (cls == "WeakMap") {
- this.iterator = enumWeakMapEntries(objectActor);
- } else if (cls == "Set") {
- this.iterator = enumSetEntries(objectActor);
- } else if (cls == "WeakSet") {
- this.iterator = enumWeakSetEntries(objectActor);
- } else {
- throw new Error("Unsupported class to enumerate entries from: " + cls);
- }
- } else if (
- isArray(objectActor.obj)
- && options.ignoreNonIndexedProperties
- && !options.query
- ) {
- this.iterator = enumArrayProperties(objectActor, options);
- } else {
- this.iterator = enumObjectProperties(objectActor, options);
- }
-}
-
-PropertyIteratorActor.prototype = {
- actorPrefix: "propertyIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- names({ indexes }) {
- let list = [];
- for (let idx of indexes) {
- list.push(this.iterator.propertyName(idx));
- }
- return {
- names: indexes
- };
- },
-
- slice({ start, count }) {
- let ownProperties = Object.create(null);
- for (let i = start, m = start + count; i < m; i++) {
- let name = this.iterator.propertyName(i);
- ownProperties[name] = this.iterator.propertyDescription(i);
- }
- return {
- ownProperties
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-PropertyIteratorActor.prototype.requestTypes = {
- "names": PropertyIteratorActor.prototype.names,
- "slice": PropertyIteratorActor.prototype.slice,
- "all": PropertyIteratorActor.prototype.all,
-};
-
-function enumArrayProperties(objectActor, options) {
- return {
- size: getArrayLength(objectActor.obj),
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- return objectActor._propertyDescriptor(index);
- }
- };
-}
-
-function enumObjectProperties(objectActor, options) {
- 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) {
- let length = DevToolsUtils.getProperty(objectActor.obj, "length");
- let sliceIndex;
-
- const isLengthTrustworthy =
- isUint32(length)
- && (!length || isArrayIndex(names[length - 1]))
- && !isArrayIndex(names[length]);
-
- if (!isLengthTrustworthy) {
- // The length property may not reflect what the object looks like, let's find
- // where indexed properties end.
-
- if (!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 (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,
- // like webaudio buffers.
- if (options.ignoreIndexedProperties) {
- // Keep items after `sliceIndex` index
- names = names.slice(sliceIndex);
- } else if (options.ignoreNonIndexedProperties) {
- // Keep `sliceIndex` first items
- names.length = sliceIndex;
- }
- }
-
- let safeGetterValues = objectActor._findSafeGetterValues(names, 0);
- let safeGetterNames = Object.keys(safeGetterValues);
- // Merge the safe getter values into the existing properties list.
- for (let 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 && 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) {
- let name = names[index];
- let desc = objectActor._propertyDescriptor(name);
- if (!desc) {
- desc = safeGetterValues[name];
- } else if (name in safeGetterValues) {
- // Merge the safe getter values into the existing properties list.
- let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
- desc.getterValue = getterValue;
- desc.getterPrototypeLevel = getterPrototypeLevel;
- }
- return desc;
- }
- };
-}
-
-/**
- * Helper function to create a grip from a Map/Set entry
- */
-function gripFromEntry({ obj, hooks }, entry) {
- return hooks.createValueGrip(
- makeDebuggeeValueIfNeeded(obj, Cu.unwaiveXrays(entry)));
-}
-
-function enumMapEntries(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.
- let raw = objectActor.obj.unsafeDereference();
-
- let keys = [...Cu.waiveXrays(Map.prototype.keys.call(raw))];
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = Map.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = Map.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
-}
-
-function enumWeakMapEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakMapKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = WeakMap.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = WeakMap.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
-}
-
-function enumSetEntries(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.
- let raw = objectActor.obj.unsafeDereference();
- let values = [...Cu.waiveXrays(Set.prototype.values.call(raw))];
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of values) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: values.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = values[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-function enumWeakSetEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakSetKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of keys) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = keys[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-/**
- * Creates an actor to iterate over an object's symbols.
- *
- * @param objectActor ObjectActor
- * The object actor.
- */
-function SymbolIteratorActor(objectActor) {
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- try {
- symbols = objectActor.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- this.iterator = {
- size: symbols.length,
- symbolDescription(index) {
- const symbol = symbols[index];
- return {
- name: symbol.toString(),
- descriptor: objectActor._propertyDescriptor(symbol)
- };
- }
- };
-}
-
-SymbolIteratorActor.prototype = {
- actorPrefix: "symbolIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- slice({ start, count }) {
- let ownSymbols = [];
- for (let i = start, m = start + count; i < m; i++) {
- ownSymbols.push(this.iterator.symbolDescription(i));
- }
- return {
- ownSymbols
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-SymbolIteratorActor.prototype.requestTypes = {
- "slice": SymbolIteratorActor.prototype.slice,
- "all": SymbolIteratorActor.prototype.all,
-};
-
-/**
- * Functions for adding information to ObjectActor grips for the purpose of
- * having customized output. This object holds arrays mapped by
- * Debugger.Object.prototype.class.
- *
- * In each array you can add functions that take three
- * arguments:
- * - the ObjectActor instance and its hooks to make a preview for,
- * - the grip object being prepared for the client,
- * - the raw JS object after calling Debugger.Object.unsafeDereference(). This
- * argument is only provided if the object is safe for reading properties and
- * executing methods. See DevToolsUtils.isSafeJSObject().
- *
- * Functions must return false if they cannot provide preview
- * information for the debugger object, or true otherwise.
- */
-DebuggerServer.ObjectActorPreviewers = {
- String: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("String", String, objectActor, grip, rawObj);
- }],
-
- Boolean: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip, rawObj);
- }],
-
- Number: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Number", Number, objectActor, grip, rawObj);
- }],
-
- Symbol: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Symbol", Symbol, objectActor, grip, rawObj);
- }],
-
- Function: [function({obj, hooks}, grip) {
- if (obj.name) {
- grip.name = obj.name;
- }
-
- if (obj.displayName) {
- grip.displayName = obj.displayName.substr(0, 500);
- }
-
- if (obj.parameterNames) {
- grip.parameterNames = obj.parameterNames;
- }
-
- // Check if the developer has added a de-facto standard displayName
- // property for us to use.
- let userDisplayName;
- try {
- userDisplayName = obj.getOwnPropertyDescriptor("displayName");
- } catch (e) {
- // The above can throw "permission denied" errors when the debuggee
- // does not subsume the function's compartment.
- }
-
- if (userDisplayName && typeof userDisplayName.value == "string" &&
- userDisplayName.value) {
- grip.userDisplayName = hooks.createValueGrip(userDisplayName.value);
- }
-
- let dbgGlobal = hooks.getGlobalDebugObject();
- if (dbgGlobal) {
- let script = dbgGlobal.makeDebuggeeValue(obj.unsafeDereference()).script;
- if (script) {
- grip.location = {
- url: script.url,
- line: script.startLine
- };
- }
- }
-
- return true;
- }],
-
- RegExp: [function({obj, hooks}, grip) {
- let str = DevToolsUtils.callPropertyOnObject(obj, "toString");
- if (typeof str != "string") {
- return false;
- }
-
- grip.displayString = hooks.createValueGrip(str);
- return true;
- }],
-
- Date: [function({obj, hooks}, grip) {
- let time = DevToolsUtils.callPropertyOnObject(obj, "getTime");
- if (typeof time != "number") {
- return false;
- }
-
- grip.preview = {
- timestamp: hooks.createValueGrip(time),
- };
- return true;
- }],
-
- Array: [function({obj, hooks}, grip) {
- let length = getArrayLength(obj);
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let items = grip.preview.items = [];
-
- for (let i = 0; i < length; ++i) {
- // Array Xrays filter out various possibly-unsafe properties (like
- // functions, and claim that the value is undefined instead. This
- // is generally the right thing for privileged code accessing untrusted
- // objects, but quite confusing for Object previews. So we manually
- // override this protection by waiving Xrays on the array, and re-applying
- // Xrays on any indexed value props that we pull off of it.
- let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
- if (desc && !desc.get && !desc.set) {
- let value = Cu.unwaiveXrays(desc.value);
- value = makeDebuggeeValueIfNeeded(obj, value);
- items.push(hooks.createValueGrip(value));
- } else {
- items.push(null);
- }
-
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Set: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: size,
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumSetEntries(objectActor)) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakSet: [function(objectActor, grip) {
- let enumEntries = enumWeakSetEntries(objectActor);
-
- grip.preview = {
- kind: "ArrayLike",
- length: enumEntries.size
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumEntries) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Map: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: size,
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumMapEntries(objectActor)) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakMap: [function(objectActor, grip) {
- let enumEntries = enumWeakMapEntries(objectActor);
-
- grip.preview = {
- kind: "MapLike",
- size: enumEntries.size
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumEntries) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- DOMStringMap: [function({obj, hooks}, grip, rawObj) {
- if (!rawObj) {
- return false;
- }
-
- let keys = obj.getOwnPropertyNames();
- grip.preview = {
- kind: "MapLike",
- size: keys.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let key of keys) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
- entries.push([key, hooks.createValueGrip(value)]);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Proxy: [function({obj, hooks}, grip, rawObj) {
- // The `isProxy` getter of the debuggee object only detects proxies without
- // security wrappers. If false, the target and handler are not available.
- let hasTargetAndHandler = obj.isProxy;
- if (hasTargetAndHandler) {
- grip.proxyTarget = hooks.createValueGrip(obj.proxyTarget);
- grip.proxyHandler = hooks.createValueGrip(obj.proxyHandler);
- }
-
- grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownPropertiesLength: 2 * hasTargetAndHandler
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- if (hasTargetAndHandler) {
- grip.preview.ownProperties["<target>"] = {value: grip.proxyTarget};
- grip.preview.ownProperties["<handler>"] = {value: grip.proxyHandler};
- }
-
- return true;
- }],
-};
-
-/**
- * Generic previewer for classes wrapping primitives, like String,
- * Number and Boolean.
- *
- * @param string className
- * Class name to expect.
- * @param object classObj
- * The class to expect, eg. String. The valueOf() method of the class is
- * invoked on the given object.
- * @param ObjectActor objectActor
- * The object actor
- * @param Object grip
- * The result grip to fill in
- * @return Booolean true if the object was handled, false otherwise
- */
-function wrappedPrimitivePreviewer(className, classObj, objectActor, grip, rawObj) {
- let {obj, hooks} = objectActor;
-
- let v = null;
- try {
- v = classObj.prototype.valueOf.call(rawObj);
- } catch (ex) {
- // valueOf() can throw if the raw JS object is "misbehaved".
- return false;
- }
-
- if (v === null) {
- return false;
- }
-
- let canHandle = GenericObject(objectActor, grip, rawObj, className === "String");
- if (!canHandle) {
- return false;
- }
-
- grip.preview.wrappedValue =
- hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v));
- return true;
-}
-
-function GenericObject(objectActor, grip, rawObj, specialStringBehavior = false) {
- let {obj, hooks} = objectActor;
- if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
- return false;
- }
-
- let i = 0, names = [], symbols = [];
- let preview = grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownSymbols: [],
- };
-
- try {
- names = obj.getOwnPropertyNames();
- symbols = obj.getOwnPropertySymbols();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
- preview.ownPropertiesLength = names.length;
- preview.ownSymbolsLength = symbols.length;
-
- let length;
- if (specialStringBehavior) {
- length = DevToolsUtils.getProperty(obj, "length");
- if (typeof length != "number") {
- specialStringBehavior = false;
- }
- }
-
- for (let name of names) {
- if (specialStringBehavior && /^[0-9]+$/.test(name)) {
- let num = parseInt(name, 10);
- if (num.toString() === name && num >= 0 && num < length) {
- continue;
- }
- }
-
- let desc = objectActor._propertyDescriptor(name, true);
- if (!desc) {
- continue;
- }
-
- preview.ownProperties[name] = desc;
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- for (let symbol of symbols) {
- let descriptor = objectActor._propertyDescriptor(symbol, true);
- if (!descriptor) {
- continue;
- }
-
- preview.ownSymbols.push(Object.assign({
- descriptor
- }, hooks.createValueGrip(symbol)));
-
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- if (i < OBJECT_PREVIEW_MAX_ITEMS) {
- preview.safeGetterValues = objectActor._findSafeGetterValues(
- Object.keys(preview.ownProperties),
- OBJECT_PREVIEW_MAX_ITEMS - i);
- }
-
- return true;
-}
-
-// Preview functions that do not rely on the object class.
-DebuggerServer.ObjectActorPreviewers.Object = [
- function TypedArray({obj, hooks}, grip) {
- if (!isTypedArray(obj)) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: getArrayLength(obj),
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let global = Cu.getGlobalForObject(DebuggerServer);
- let classProto = global[obj.class].prototype;
- // The Xray machinery for TypedArrays denies indexed access on the grounds
- // that it's slow, and advises callers to do a structured clone instead.
- let safeView = Cu.cloneInto(classProto.subarray.call(raw, 0,
- OBJECT_PREVIEW_MAX_ITEMS), global);
- let items = grip.preview.items = [];
- for (let i = 0; i < safeView.length; i++) {
- items.push(safeView[i]);
- }
-
- return true;
- },
-
- function Error({obj, hooks}, grip) {
- switch (obj.class) {
- case "Error":
- case "EvalError":
- case "RangeError":
- case "ReferenceError":
- case "SyntaxError":
- case "TypeError":
- case "URIError":
- let name = DevToolsUtils.getProperty(obj, "name");
- let msg = DevToolsUtils.getProperty(obj, "message");
- let stack = DevToolsUtils.getProperty(obj, "stack");
- let fileName = DevToolsUtils.getProperty(obj, "fileName");
- let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
- let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
- grip.preview = {
- kind: "Error",
- name: hooks.createValueGrip(name),
- message: hooks.createValueGrip(msg),
- stack: hooks.createValueGrip(stack),
- fileName: hooks.createValueGrip(fileName),
- lineNumber: hooks.createValueGrip(lineNumber),
- columnNumber: hooks.createValueGrip(columnNumber),
- };
- return true;
- default:
- return false;
- }
- },
-
- function CSSMediaRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSMediaRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.conditionText),
- };
- return true;
- },
-
- function CSSStyleRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSStyleRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.selectorText),
- };
- return true;
- },
-
- function ObjectWithURL({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(obj.class == "CSSImportRule" ||
- obj.class == "CSSStyleSheet" ||
- obj.class == "Location" ||
- rawObj instanceof Ci.nsIDOMWindow)) {
- return false;
- }
-
- let url;
- if (rawObj instanceof Ci.nsIDOMWindow && rawObj.location) {
- url = rawObj.location.href;
- } else if (rawObj.href) {
- url = rawObj.href;
- } else {
- return false;
- }
-
- grip.preview = {
- kind: "ObjectWithURL",
- url: hooks.createValueGrip(url),
- };
-
- return true;
- },
-
- function ArrayLike({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- obj.class != "DOMStringList" &&
- obj.class != "DOMTokenList" &&
- obj.class != "CSSRuleList" &&
- obj.class != "MediaList" &&
- obj.class != "StyleSheetList" &&
- obj.class != "CSSValueList" &&
- obj.class != "NamedNodeMap" &&
- obj.class != "FileList" &&
- obj.class != "NodeList") {
- return false;
- }
-
- if (typeof rawObj.length != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: rawObj.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
-
- for (let i = 0; i < rawObj.length &&
- items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[i]);
- items.push(hooks.createValueGrip(value));
- }
-
- return true;
- },
-
- function CSSStyleDeclaration({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- (obj.class != "CSSStyleDeclaration" &&
- obj.class != "CSS2Properties")) {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: rawObj.length,
- };
-
- let entries = grip.preview.entries = [];
-
- for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
- i < rawObj.length; i++) {
- let prop = rawObj[i];
- let value = rawObj.getPropertyValue(prop);
- entries.push([prop, hooks.createValueGrip(value)]);
- }
-
- return true;
- },
-
- function DOMNode({obj, hooks}, grip, rawObj) {
- if (isWorker || obj.class == "Object" || !rawObj ||
- !(rawObj instanceof Ci.nsIDOMNode)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMNode",
- nodeType: rawObj.nodeType,
- nodeName: rawObj.nodeName,
- isConnected: rawObj.isConnected === true,
- };
-
- if (rawObj instanceof Ci.nsIDOMDocument && rawObj.location) {
- preview.location = hooks.createValueGrip(rawObj.location.href);
- } else if (rawObj instanceof Ci.nsIDOMDocumentFragment) {
- preview.childNodesLength = rawObj.childNodes.length;
-
- if (hooks.getGripDepth() < 2) {
- preview.childNodes = [];
- for (let node of rawObj.childNodes) {
- let actor = hooks.createValueGrip(obj.makeDebuggeeValue(node));
- preview.childNodes.push(actor);
- if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
- } else if (rawObj instanceof Ci.nsIDOMElement) {
- // For HTML elements (in an HTML document, at least), the nodeName is an
- // uppercased version of the actual element name. Check for HTML
- // elements, that is elements in the HTML namespace, and lowercase the
- // nodeName in that case.
- if (rawObj.namespaceURI == "http://www.w3.org/1999/xhtml") {
- preview.nodeName = preview.nodeName.toLowerCase();
- }
-
- // Add preview for DOM element attributes.
- preview.attributes = {};
- preview.attributesLength = rawObj.attributes.length;
- for (let attr of rawObj.attributes) {
- preview.attributes[attr.nodeName] = hooks.createValueGrip(attr.value);
- }
- } else if (obj.class == "Attr") {
- preview.value = hooks.createValueGrip(rawObj.value);
- } else if (obj.class == "Text" ||
- obj.class == "CDATASection" ||
- obj.class == "Comment") {
- preview.textContent = hooks.createValueGrip(rawObj.textContent);
- }
-
- return true;
- },
-
- function DOMEvent({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMEvent)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMEvent",
- type: rawObj.type,
- properties: Object.create(null),
- };
-
- if (hooks.getGripDepth() < 2) {
- let target = obj.makeDebuggeeValue(rawObj.target);
- preview.target = hooks.createValueGrip(target);
- }
-
- let props = [];
- if (obj.class == "MouseEvent" ||
- obj.class == "DragEvent" ||
- obj.class == "PointerEvent" ||
- obj.class == "SimpleGestureEvent" ||
- obj.class == "WheelEvent") {
- props.push("buttons", "clientX", "clientY", "layerX", "layerY");
- } else if (obj.class == "KeyboardEvent") {
- let modifiers = [];
- if (rawObj.altKey) {
- modifiers.push("Alt");
- }
- if (rawObj.ctrlKey) {
- modifiers.push("Control");
- }
- if (rawObj.metaKey) {
- modifiers.push("Meta");
- }
- if (rawObj.shiftKey) {
- modifiers.push("Shift");
- }
- preview.eventKind = "key";
- preview.modifiers = modifiers;
-
- props.push("key", "charCode", "keyCode");
- } else if (obj.class == "TransitionEvent") {
- props.push("propertyName", "pseudoElement");
- } else if (obj.class == "AnimationEvent") {
- props.push("animationName", "pseudoElement");
- } else if (obj.class == "ClipboardEvent") {
- props.push("clipboardData");
- }
-
- // Add event-specific properties.
- for (let prop of props) {
- let value = rawObj[prop];
- if (value && (typeof value == "object" || typeof value == "function")) {
- // Skip properties pointing to objects.
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- }
-
- // Add any properties we find on the event object.
- if (!props.length) {
- let i = 0;
- for (let prop in rawObj) {
- let value = rawObj[prop];
- if (prop == "target" || prop == "type" || value === null ||
- typeof value == "function") {
- continue;
- }
- if (value && typeof value == "object") {
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
-
- return true;
- },
-
- function DOMException({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMDOMException)) {
- return false;
- }
-
- grip.preview = {
- kind: "DOMException",
- name: hooks.createValueGrip(rawObj.name),
- message: hooks.createValueGrip(rawObj.message),
- code: hooks.createValueGrip(rawObj.code),
- result: hooks.createValueGrip(rawObj.result),
- filename: hooks.createValueGrip(rawObj.filename),
- lineNumber: hooks.createValueGrip(rawObj.lineNumber),
- columnNumber: hooks.createValueGrip(rawObj.columnNumber),
- };
-
- return true;
- },
-
- function PseudoArray({obj, hooks}, grip, rawObj) {
- // An object is considered a pseudo-array if all the following apply:
- // - All its properties are array indices except, optionally, a "length" property.
- // - At least it has the "0" array index.
- // - The array indices are consecutive.
- // - The value of "length", if present, is the number of array indices.
-
- let keys;
- try {
- keys = obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- return false;
- }
- let {length} = keys;
- if (length === 0) {
- return false;
- }
-
- // Array indices should be sorted at the beginning, from smallest to largest.
- // Other properties should be at the end, so check if the last one is "length".
- if (keys[length - 1] === "length") {
- --length;
- if (length === 0 || length !== DevToolsUtils.getProperty(obj, "length")) {
- return false;
- }
- }
-
- // Check that the last key is the array index expected at that position.
- let lastKey = keys[length - 1];
- if (!isArrayIndex(lastKey) || +lastKey !== length - 1) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- // Avoid recursive object grips.
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- let numItems = Math.min(OBJECT_PREVIEW_MAX_ITEMS, length);
-
- for (let i = 0; i < numItems; ++i) {
- let desc = obj.getOwnPropertyDescriptor(i);
- if (desc && "value" in desc) {
- items.push(hooks.createValueGrip(desc.value));
- } else {
- items.push(null);
- }
- }
-
- return true;
- },
-
- function Object(objectActor, grip, rawObj) {
- return GenericObject(objectActor, grip, rawObj, /* specialStringBehavior = */ false);
- },
-];
-
-/**
- * Get thisDebugger.Object referent's `promiseState`.
- *
- * @returns Object
- * An object of one of the following forms:
- * - { state: "pending" }
- * - { state: "fulfilled", value }
- * - { state: "rejected", reason }
- */
-function getPromiseState(obj) {
- if (obj.class != "Promise") {
- throw new Error(
- "Can't call `getPromiseState` on `Debugger.Object`s that don't " +
- "refer to Promise objects.");
- }
-
- let state = { state: obj.promiseState };
- if (state.state === "fulfilled") {
- state.value = obj.promiseValue;
- } else if (state.state === "rejected") {
- state.reason = obj.promiseReason;
- }
- return state;
-}
-
-/**
- * Determine if a given value is non-primitive.
- *
- * @param Any value
- * The value to test.
- * @return Boolean
- * Whether the value is non-primitive.
- */
-function isObject(value) {
- const type = typeof value;
- return type == "object" ? value !== null : type == "function";
-}
-
-/**
- * Create a function that can safely stringify Debugger.Objects of a given
- * builtin type.
- *
- * @param Function ctor
- * The builtin class constructor.
- * @return Function
- * The stringifier for the class.
- */
-function createBuiltinStringifier(ctor) {
- return obj => {
- try {
- return ctor.prototype.toString.call(obj.unsafeDereference());
- } catch (err) {
- // The debuggee will see a "Function" class if the object is callable and
- // its compartment is not subsumed. The above will throw if it's not really
- // a function, e.g. if it's a callable proxy.
- return "[object " + obj.class + "]";
- }
- };
-}
-
-/**
- * Stringify a Debugger.Object-wrapped Error instance.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification of the object.
- */
-function errorStringify(obj) {
- let name = DevToolsUtils.getProperty(obj, "name");
- if (name === "" || name === undefined) {
- name = obj.class;
- } else if (isObject(name)) {
- name = stringify(name);
- }
-
- let message = DevToolsUtils.getProperty(obj, "message");
- if (isObject(message)) {
- message = stringify(message);
- }
-
- if (message === "" || message === undefined) {
- return name;
- }
- return name + ": " + message;
-}
-
-/**
- * Stringify a Debugger.Object based on its class.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification for the object.
- */
-function stringify(obj) {
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- if (DevToolsUtils.isCPOW(obj)) {
- return "<cpow>";
- }
- let unwrapped = DevToolsUtils.unwrap(obj);
- if (unwrapped === undefined) {
- return "<invisibleToDebugger>";
- } else if (unwrapped.isProxy) {
- return "<proxy>";
- }
- // The following line should not be reached. It's there just in case somebody
- // modifies isSafeDebuggerObject to return false for additional kinds of objects.
- return "[object " + obj.class + "]";
- } else if (obj.class == "DeadObject") {
- return "<dead object>";
- }
-
- const stringifier = stringifiers[obj.class] || stringifiers.Object;
-
- try {
- return stringifier(obj);
- } catch (e) {
- DevToolsUtils.reportException("stringify", e);
- return "<failed to stringify object>";
- }
-}
-
-// Used to prevent infinite recursion when an array is found inside itself.
-var seen = null;
-
-var stringifiers = {
- Error: errorStringify,
- EvalError: errorStringify,
- RangeError: errorStringify,
- ReferenceError: errorStringify,
- SyntaxError: errorStringify,
- TypeError: errorStringify,
- URIError: errorStringify,
- Boolean: createBuiltinStringifier(Boolean),
- Function: createBuiltinStringifier(Function),
- Number: createBuiltinStringifier(Number),
- RegExp: createBuiltinStringifier(RegExp),
- String: createBuiltinStringifier(String),
- Object: obj => "[object " + obj.class + "]",
- Array: obj => {
- // If we're at the top level then we need to create the Set for tracking
- // previously stringified arrays.
- const topLevel = !seen;
- if (topLevel) {
- seen = new Set();
- } else if (seen.has(obj)) {
- return "";
- }
-
- seen.add(obj);
-
- const len = getArrayLength(obj);
- let string = "";
-
- // Array.length is always a non-negative safe integer.
- for (let i = 0; i < len; i++) {
- const desc = obj.getOwnPropertyDescriptor(i);
- if (desc) {
- const { value } = desc;
- if (value != null) {
- string += isObject(value) ? stringify(value) : value;
- }
- }
-
- if (i < len - 1) {
- string += ",";
- }
- }
-
- if (topLevel) {
- seen = null;
- }
-
- return string;
- },
- DOMException: obj => {
- const message = DevToolsUtils.getProperty(obj, "message") || "<no message>";
- const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16);
- const code = DevToolsUtils.getProperty(obj, "code");
- const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
-
- return '[Exception... "' + message + '" ' +
- 'code: "' + code + '" ' +
- 'nsresult: "0x' + result + " (" + name + ')"]';
- },
- Promise: obj => {
- const { state, value, reason } = getPromiseState(obj);
- let statePreview = state;
- if (state != "pending") {
- const settledValue = state === "fulfilled" ? value : reason;
- statePreview += ": " + (typeof settledValue === "object" && settledValue !== null
- ? stringify(settledValue)
- : settledValue);
- }
- return "Promise (" + statePreview + ")";
- },
-};
-
-/**
- * Make a debuggee value for the given object, if needed. Primitive values
- * are left the same.
- *
- * Use case: you have a raw JS object (after unsafe dereference) and you want to
- * send it to the client. In that case you need to use an ObjectActor which
- * requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
- * method works only for JS objects and functions.
- *
- * @param Debugger.Object obj
- * @param any value
- * @return object
- */
-function makeDebuggeeValueIfNeeded(obj, value) {
- if (value && (typeof value == "object" || typeof value == "function")) {
- return obj.makeDebuggeeValue(value);
- }
- return value;
-}
-
-/**
- * Creates an actor for the specified "very long" string. "Very long" is specified
- * at the server's discretion.
- *
- * @param string String
- * The string.
- */
-function LongStringActor(string) {
- this.string = string;
- this.stringLength = string.length;
-}
-
-LongStringActor.prototype = {
- actorPrefix: "longString",
-
- rawValue: function() {
- return this.string;
- },
-
- destroy: function() {
- // Because longStringActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- return {
- "type": "longString",
- "initial": this.string.substring(
- 0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
- "length": this.stringLength,
- "actor": this.actorID
- };
- },
-
- /**
- * Handle a request to extract part of this actor's string.
- *
- * @param request object
- * The protocol request object.
- */
- onSubstring: function(request) {
- return {
- "from": this.actorID,
- "substring": this.string.substring(request.start, request.end)
- };
- },
-
- /**
- * Handle a request to release this LongStringActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.longStringActors) {
- delete this.registeredPool.longStringActors[this.string];
- }
- }
-};
-
-LongStringActor.prototype.requestTypes = {
- "substring": LongStringActor.prototype.onSubstring,
- "release": LongStringActor.prototype.onRelease
-};
-
-/**
- * Creates an actor for the specified symbol.
- *
- * @param symbol Symbol
- * The symbol.
- */
-function SymbolActor(symbol) {
- this.symbol = symbol;
-}
-
-SymbolActor.prototype = {
- actorPrefix: "symbol",
-
- rawValue: function() {
- return this.symbol;
- },
-
- destroy: function() {
- // Because symbolActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let form = {
- type: "symbol",
- actor: this.actorID,
- };
- let name = getSymbolName(this.symbol);
- if (name !== undefined) {
- // Create a grip for the name because it might be a longString.
- form.name = createValueGrip(name, this.registeredPool);
- }
- return form;
- },
-
- /**
- * Handle a request to release this SymbolActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.symbolActors) {
- delete this.registeredPool.symbolActors[this.symbol];
- }
- }
-};
-
-SymbolActor.prototype.requestTypes = {
- "release": SymbolActor.prototype.onRelease
-};
-
-/**
- * Creates an actor for the specified ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The buffer.
- */
-function ArrayBufferActor(buffer) {
- this.buffer = buffer;
- this.bufferLength = buffer.byteLength;
-}
-
-ArrayBufferActor.prototype = {
- actorPrefix: "arrayBuffer",
-
- rawValue: function() {
- return this.buffer;
- },
-
- destroy: function() {
- },
-
- grip() {
- return {
- "type": "arrayBuffer",
- "length": this.bufferLength,
- "actor": this.actorID
- };
- },
-
- onSlice({start, count}) {
- let slice = new Uint8Array(this.buffer, start, count);
- let parts = [], offset = 0;
- const PortionSize = 0x6000; // keep it divisible by 3 for btoa() and join()
- while (offset + PortionSize < count) {
- parts.push(btoa(
- String.fromCharCode.apply(null, slice.subarray(offset, offset + PortionSize))));
- offset += PortionSize;
- }
- parts.push(btoa(String.fromCharCode.apply(null, slice.subarray(offset, count))));
- return {
- "from": this.actorID,
- "encoded": parts.join(""),
- };
- }
-};
-
-ArrayBufferActor.prototype.requestTypes = {
- "slice": ArrayBufferActor.prototype.onSlice,
-};
-
-/**
- * Create a grip for the given debuggee value. If the value is an
- * object, will create an actor with the given lifetime.
- */
-function createValueGrip(value, pool, makeObjectGrip) {
- switch (typeof value) {
- case "boolean":
- return value;
-
- case "string":
- if (stringIsLong(value)) {
- return longStringGrip(value, pool);
- }
- return value;
-
- case "number":
- if (value === Infinity) {
- return { type: "Infinity" };
- } else if (value === -Infinity) {
- return { type: "-Infinity" };
- } else if (Number.isNaN(value)) {
- return { type: "NaN" };
- } else if (!value && 1 / value === -Infinity) {
- return { type: "-0" };
- }
- return value;
-
- case "undefined":
- return { type: "undefined" };
-
- case "object":
- if (value === null) {
- return { type: "null" };
- } else if (value.optimizedOut ||
- value.uninitialized ||
- value.missingArguments) {
- // The slot is optimized out, an uninitialized binding, or
- // arguments on a dead scope
- return {
- type: "null",
- optimizedOut: value.optimizedOut,
- uninitialized: value.uninitialized,
- missingArguments: value.missingArguments
- };
- }
- return makeObjectGrip(value, pool);
-
- case "symbol":
- return symbolGrip(value, pool);
-
- default:
- assert(false, "Failed to provide a grip for: " + value);
- return null;
- }
-}
-
-const symbolProtoToString = Symbol.prototype.toString;
-
-function getSymbolName(symbol) {
- const name = symbolProtoToString.call(symbol).slice("Symbol(".length, -1);
- return name || undefined;
-}
-
-/**
- * Returns true if the string is long enough to use a LongStringActor instead
- * of passing the value directly over the protocol.
- *
- * @param str String
- * The string we are checking the length of.
- */
-function stringIsLong(str) {
- return str.length >= DebuggerServer.LONG_STRING_LENGTH;
-}
-
-/**
- * Create a grip for the given string.
- *
- * @param str String
- * The string we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function longStringGrip(str, pool) {
- if (!pool.longStringActors) {
- pool.longStringActors = {};
- }
-
- if (pool.longStringActors.hasOwnProperty(str)) {
- return pool.longStringActors[str].grip();
- }
-
- let actor = new LongStringActor(str);
- pool.addActor(actor);
- pool.longStringActors[str] = actor;
- return actor.grip();
-}
-
-/**
- * Create a grip for the given symbol.
- *
- * @param sym Symbol
- * The symbol we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function symbolGrip(sym, pool) {
- if (!pool.symbolActors) {
- pool.symbolActors = Object.create(null);
- }
-
- if (sym in pool.symbolActors) {
- return pool.symbolActors[sym].grip();
- }
-
- let actor = new SymbolActor(sym);
- pool.addActor(actor);
- pool.symbolActors[sym] = actor;
- return actor.grip();
-}
-
-/**
- * Create a grip for the given ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The ArrayBuffer we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function arrayBufferGrip(buffer, pool) {
- if (!pool.arrayBufferActors) {
- pool.arrayBufferActors = new WeakMap();
- }
-
- if (pool.arrayBufferActors.has(buffer)) {
- return pool.arrayBufferActors.get(buffer).grip();
- }
-
- let actor = new ArrayBufferActor(buffer);
- pool.addActor(actor);
- pool.arrayBufferActors.set(buffer, actor);
- return actor.grip();
-}
-
-const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
- "Uint32Array", "Int8Array", "Int16Array", "Int32Array",
- "Float32Array", "Float64Array"];
-
-/**
- * Returns true if a debuggee object is a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isTypedArray(object) {
- return TYPED_ARRAY_CLASSES.includes(object.class);
-}
-
-/**
- * Returns true if a debuggee object is an array, including a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isArray(object) {
- return isTypedArray(object) || object.class === "Array";
-}
-
-/**
- * Returns the length of an array (or typed array).
- *
- * @param obj Debugger.Object
- * The debuggee object of the array.
- * @return Number
- * @throws if the object is not an array.
- */
-function getArrayLength(object) {
- if (!isArray(object)) {
- throw new Error("Expected an array, got a " + object.class);
- }
-
- // Real arrays have a reliable `length` own property.
- if (object.class === "Array") {
- return DevToolsUtils.getProperty(object, "length");
- }
-
- // For typed arrays, `DevToolsUtils.getProperty` is not reliable because the `length`
- // getter could be shadowed by an own property, and `getOwnPropertyNames` is
- // unnecessarily slow. Obtain the `length` getter safely and call it manually.
- let typedProto = Object.getPrototypeOf(Uint8Array.prototype);
- let getter = Object.getOwnPropertyDescriptor(typedProto, "length").get;
- return getter.call(object.unsafeDereference());
-}
-
-/**
- * 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;
-}
-
-/**
- * Returns true if the parameter is suitable to be an array index.
- *
- * @param str String
- * @return Boolean
- */
-function isArrayIndex(str) {
- // Transform the parameter to a 32-bit unsigned integer.
- let num = str >>> 0;
- // Check that the parameter is a canonical Uint32 index.
- return num + "" === str &&
- // Array indices cannot attain the maximum Uint32 value.
- num != -1 >>> 0;
-}
-
exports.ObjectActor = ObjectActor;
-exports.PropertyIteratorActor = PropertyIteratorActor;
-exports.LongStringActor = LongStringActor;
-exports.SymbolActor = SymbolActor;
-exports.createValueGrip = createValueGrip;
-exports.stringIsLong = stringIsLong;
-exports.longStringGrip = longStringGrip;
-exports.arrayBufferGrip = arrayBufferGrip;
copy from devtools/server/actors/object.js
copy to devtools/server/actors/object/long-string.js
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object/long-string.js
@@ -1,2281 +1,23 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 { Cu, Ci } = require("chrome");
-const { GeneratedLocation } = require("devtools/server/actors/common");
const { DebuggerServer } = require("devtools/server/main");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { assert } = DevToolsUtils;
-
-loader.lazyRequireGetter(this, "ChromeUtils");
-
-// Number of items to preview in objects, arrays, maps, sets, lists,
-// collections, etc.
-const OBJECT_PREVIEW_MAX_ITEMS = 10;
-
-/**
- * Creates an actor for the specified object.
- *
- * @param obj Debugger.Object
- * The debuggee object.
- * @param hooks Object
- * A collection of abstract methods that are implemented by the caller.
- * ObjectActor requires the following functions to be implemented by
- * the caller:
- * - createValueGrip
- * Creates a value grip for the given object
- * - sources
- * TabSources getter that manages the sources of a thread
- * - createEnvironmentActor
- * Creates and return an environment actor
- * - getGripDepth
- * An actor's grip depth getter
- * - incrementGripDepth
- * Increment the actor's grip depth
- * - decrementGripDepth
- * Decrement the actor's grip depth
- * - globalDebugObject
- * The Debuggee Global Object as given by the ThreadActor
- */
-function ObjectActor(obj, {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
-}) {
- assert(!obj.optimizedOut,
- "Should not create object actors for optimized out values!");
- this.obj = obj;
- this.hooks = {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
- };
- this.iterators = new Set();
-}
-
-ObjectActor.prototype = {
- actorPrefix: "obj",
-
- rawValue: function() {
- return this.obj.unsafeDereference();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let g = {
- "type": "object",
- "actor": this.actorID,
- "class": this.obj.class,
- };
-
- let unwrapped = DevToolsUtils.unwrap(this.obj);
-
- // Unsafe objects must be treated carefully.
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- if (DevToolsUtils.isCPOW(this.obj)) {
- // Cross-process object wrappers can't be accessed.
- g.class = "CPOW: " + g.class;
- } else if (unwrapped === undefined) {
- // Objects belonging to an invisible-to-debugger compartment might be proxies,
- // so just in case they shouldn't be accessed.
- g.class = "InvisibleToDebugger: " + g.class;
- } else if (unwrapped.isProxy) {
- // Proxy objects can run traps when accessed, so just create a preview with
- // the target and the handler.
- g.class = "Proxy";
- this.hooks.incrementGripDepth();
- DebuggerServer.ObjectActorPreviewers.Proxy[0](this, g, null);
- this.hooks.decrementGripDepth();
- }
- return g;
- }
-
- // If the debuggee does not subsume the object's compartment, most properties won't
- // be accessible. Cross-orgin Window and Location objects might expose some, though.
- // Change the displayed class, but when creating the preview use the original one.
- if (unwrapped === null) {
- g.class = "Restricted";
- }
-
- this.hooks.incrementGripDepth();
-
- g.extensible = this.obj.isExtensible();
- g.frozen = this.obj.isFrozen();
- g.sealed = this.obj.isSealed();
-
- if (g.class == "Promise") {
- g.promiseState = this._createPromiseState();
- }
-
- // FF40+: Allow to know how many properties an object has to lazily display them
- // when there is a bunch.
- if (isTypedArray(g)) {
- // Bug 1348761: getOwnPropertyNames is unnecessary slow on TypedArrays
- g.ownPropertyLength = getArrayLength(this.obj);
- } else {
- try {
- g.ownPropertyLength = this.obj.getOwnPropertyNames().length;
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let raw = this.obj.unsafeDereference();
-
- // If Cu is not defined, we are running on a worker thread, where xrays
- // don't exist.
- if (Cu) {
- raw = Cu.unwaiveXrays(raw);
- }
-
- if (!DevToolsUtils.isSafeJSObject(raw)) {
- raw = null;
- }
-
- let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
- DebuggerServer.ObjectActorPreviewers.Object;
- for (let fn of previewers) {
- try {
- if (fn(this, g, raw)) {
- break;
- }
- } catch (e) {
- let msg = "ObjectActor.prototype.grip previewer function";
- DevToolsUtils.reportException(msg, e);
- }
- }
-
- this.hooks.decrementGripDepth();
- return g;
- },
-
- /**
- * Returns an object exposing the internal Promise state.
- */
- _createPromiseState: function() {
- const { state, value, reason } = getPromiseState(this.obj);
- let promiseState = { state };
-
- if (state == "fulfilled") {
- promiseState.value = this.hooks.createValueGrip(value);
- } else if (state == "rejected") {
- promiseState.reason = this.hooks.createValueGrip(reason);
- }
-
- promiseState.creationTimestamp = Date.now() - this.obj.promiseLifetime;
-
- // Only add the timeToSettle property if the Promise isn't pending.
- if (state !== "pending") {
- promiseState.timeToSettle = this.obj.promiseTimeToResolution;
- }
-
- return promiseState;
- },
-
- /**
- * Releases this actor from the pool.
- */
- release: function() {
- if (this.registeredPool.objectActors) {
- this.registeredPool.objectActors.delete(this.obj);
- }
- this.iterators.forEach(actor => this.registeredPool.removeActor(actor));
- this.iterators.clear();
- this.registeredPool.removeActor(this);
- },
-
- /**
- * Handle a protocol request to provide the definition site of this function
- * object.
- */
- onDefinitionSite: function() {
- if (this.obj.class != "Function") {
- return {
- from: this.actorID,
- error: "objectNotFunction",
- message: this.actorID + " is not a function."
- };
- }
-
- if (!this.obj.script) {
- return {
- from: this.actorID,
- error: "noScript",
- message: this.actorID + " has no Debugger.Script"
- };
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- this.hooks.sources().createNonSourceMappedActor(this.obj.script.source),
- this.obj.script.startLine,
- 0 // TODO bug 901138: use Debugger.Script.prototype.startColumn
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn
- };
- });
- },
-
- /**
- * Handle a protocol request to provide the names of the properties defined on
- * the object and not its prototype.
- */
- onOwnPropertyNames: function() {
- let props = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- props = this.obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
- return { from: this.actorID, ownPropertyNames: props };
- },
-
- /**
- * Creates an actor to iterate over an object property names and values.
- * See PropertyIteratorActor constructor for more info about options param.
- *
- * @param request object
- * The protocol request object.
- */
- onEnumProperties: function(request) {
- let actor = new PropertyIteratorActor(this, request.options);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over entries of a Map/Set-like object.
- */
- onEnumEntries: function() {
- let actor = new PropertyIteratorActor(this, { enumEntries: true });
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over an object symbols properties.
- */
- onEnumSymbols: function() {
- let actor = new SymbolIteratorActor(this);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Handle a protocol request to provide the prototype and own properties of
- * the object.
- *
- * @returns {Object} An object containing the data of this.obj, of the following form:
- * - {string} from: this.obj's actorID.
- * - {Object} prototype: The descriptor of this.obj's prototype.
- * - {Object} ownProperties: an object where the keys are the names of the
- * this.obj's ownProperties, and the values the descriptors of
- * the properties.
- * - {Array} ownSymbols: An array containing all descriptors of this.obj's
- * ownSymbols. Here we have an array, and not an object like for
- * ownProperties, because we can have multiple symbols with the same
- * name in this.obj, e.g. `{[Symbol()]: "a", [Symbol()]: "b"}`.
- * - {Object} safeGetterValues: an object that maps this.obj's property names
- * with safe getters descriptors.
- */
- onPrototypeAndProperties: function() {
- let proto = null;
- let names = [];
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- proto = this.obj.proto;
- names = this.obj.getOwnPropertyNames();
- symbols = this.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let ownProperties = Object.create(null);
- let ownSymbols = [];
-
- for (let name of names) {
- ownProperties[name] = this._propertyDescriptor(name);
- }
-
- for (let sym of symbols) {
- ownSymbols.push({
- name: sym.toString(),
- descriptor: this._propertyDescriptor(sym)
- });
- }
-
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto),
- ownProperties,
- ownSymbols,
- safeGetterValues: this._findSafeGetterValues(names) };
- },
-
- /**
- * Find the safe getter values for the current Debugger.Object, |this.obj|.
- *
- * @private
- * @param array ownProperties
- * The array that holds the list of known ownProperties names for
- * |this.obj|.
- * @param number [limit=0]
- * Optional limit of getter values to find.
- * @return object
- * An object that maps property names to safe getter descriptors as
- * defined by the remote debugging protocol.
- */
- _findSafeGetterValues: function(ownProperties, limit = 0) {
- let safeGetterValues = Object.create(null);
- let obj = this.obj;
- let level = 0, i = 0;
-
- // Do not search safe getters in unsafe objects.
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- return safeGetterValues;
- }
-
- // Most objects don't have any safe getters but inherit some from their
- // prototype. Avoid calling getOwnPropertyNames on objects that may have
- // many properties like Array, strings or js objects. That to avoid
- // freezing firefox when doing so.
- if (isArray(this.obj) || ["Object", "String"].includes(this.obj.class)) {
- obj = obj.proto;
- level++;
- }
-
- while (obj && DevToolsUtils.isSafeDebuggerObject(obj)) {
- let getters = this._findSafeGetters(obj);
- for (let name of getters) {
- // Avoid overwriting properties from prototypes closer to this.obj. Also
- // avoid providing safeGetterValues from prototypes if property |name|
- // is already defined as an own property.
- if (name in safeGetterValues ||
- (obj != this.obj && ownProperties.includes(name))) {
- continue;
- }
-
- // Ignore __proto__ on Object.prototye.
- if (!obj.proto && name == "__proto__") {
- continue;
- }
-
- let desc = null, getter = null;
- try {
- desc = obj.getOwnPropertyDescriptor(name);
- getter = desc.get;
- } catch (ex) {
- // The above can throw if the cache becomes stale.
- }
- if (!getter) {
- obj._safeGetters = null;
- continue;
- }
-
- let result = getter.call(this.obj);
- if (result && !("throw" in result)) {
- let getterValue = undefined;
- if ("return" in result) {
- getterValue = result.return;
- } else if ("yield" in result) {
- getterValue = result.yield;
- }
- // WebIDL attributes specified with the LenientThis extended attribute
- // return undefined and should be ignored.
- if (getterValue !== undefined) {
- safeGetterValues[name] = {
- getterValue: this.hooks.createValueGrip(getterValue),
- getterPrototypeLevel: level,
- enumerable: desc.enumerable,
- writable: level == 0 ? desc.writable : true,
- };
- if (limit && ++i == limit) {
- break;
- }
- }
- }
- }
- if (limit && i == limit) {
- break;
- }
-
- obj = obj.proto;
- level++;
- }
-
- return safeGetterValues;
- },
-
- /**
- * Find the safe getters for a given Debugger.Object. Safe getters are native
- * getters which are safe to execute.
- *
- * @private
- * @param Debugger.Object object
- * The Debugger.Object where you want to find safe getters.
- * @return Set
- * A Set of names of safe getters. This result is cached for each
- * Debugger.Object.
- */
- _findSafeGetters: function(object) {
- if (object._safeGetters) {
- return object._safeGetters;
- }
-
- let getters = new Set();
-
- if (!DevToolsUtils.isSafeDebuggerObject(object)) {
- object._safeGetters = getters;
- return getters;
- }
-
- let names = [];
- try {
- names = object.getOwnPropertyNames();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
-
- for (let name of names) {
- let desc = null;
- try {
- desc = object.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072).
- }
- if (!desc || desc.value !== undefined || !("get" in desc)) {
- continue;
- }
-
- if (DevToolsUtils.hasSafeGetter(desc)) {
- getters.add(name);
- }
- }
-
- object._safeGetters = getters;
- return getters;
- },
-
- /**
- * Handle a protocol request to provide the prototype of the object.
- */
- onPrototype: function() {
- let proto = null;
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- proto = this.obj.proto;
- }
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto) };
- },
-
- /**
- * Handle a protocol request to provide the property descriptor of the
- * object's specified property.
- *
- * @param request object
- * The protocol request object.
- */
- onProperty: function(request) {
- if (!request.name) {
- return { error: "missingParameter",
- message: "no property name was specified" };
- }
-
- return { from: this.actorID,
- descriptor: this._propertyDescriptor(request.name) };
- },
-
- /**
- * Handle a protocol request to provide the display string for the object.
- */
- onDisplayString: function() {
- const string = stringify(this.obj);
- return { from: this.actorID,
- displayString: this.hooks.createValueGrip(string) };
- },
-
- /**
- * A helper method that creates a property descriptor for the provided object,
- * properly formatted for sending in a protocol response.
- *
- * @private
- * @param string name
- * The property that the descriptor is generated for.
- * @param boolean [onlyEnumerable]
- * Optional: true if you want a descriptor only for an enumerable
- * property, false otherwise.
- * @return object|undefined
- * The property descriptor, or undefined if this is not an enumerable
- * property and onlyEnumerable=true.
- */
- _propertyDescriptor: function(name, onlyEnumerable) {
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- return undefined;
- }
-
- let desc;
- try {
- desc = this.obj.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072). Inform the user with a bogus, but hopefully
- // explanatory, descriptor.
- return {
- configurable: false,
- writable: false,
- enumerable: false,
- value: e.name
- };
- }
-
- if (!desc || onlyEnumerable && !desc.enumerable) {
- return undefined;
- }
-
- let retval = {
- configurable: desc.configurable,
- enumerable: desc.enumerable
- };
-
- if ("value" in desc) {
- retval.writable = desc.writable;
- retval.value = this.hooks.createValueGrip(desc.value);
- } else {
- if ("get" in desc) {
- retval.get = this.hooks.createValueGrip(desc.get);
- }
- if ("set" in desc) {
- retval.set = this.hooks.createValueGrip(desc.set);
- }
- }
- return retval;
- },
-
- /**
- * Handle a protocol request to provide the source code of a function.
- *
- * @param request object
- * The protocol request object.
- */
- onDecompile: function(request) {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "decompile request is only valid for object grips " +
- "with a 'Function' class." };
- }
-
- return { from: this.actorID,
- decompiledCode: this.obj.decompile(!!request.pretty) };
- },
-
- /**
- * Handle a protocol request to provide the parameters of a function.
- */
- onParameterNames: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "'parameterNames' request is only valid for object " +
- "grips with a 'Function' class." };
- }
-
- return { parameterNames: this.obj.parameterNames };
- },
-
- /**
- * Handle a protocol request to release a thread-lifetime grip.
- */
- onRelease: function() {
- this.release();
- return {};
- },
-
- /**
- * Handle a protocol request to provide the lexical scope of a function.
- */
- onScope: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "scope request is only valid for object grips with a" +
- " 'Function' class." };
- }
-
- let envActor = this.hooks.createEnvironmentActor(this.obj.environment,
- this.registeredPool);
- if (!envActor) {
- return { error: "notDebuggee",
- message: "cannot access the environment of this function." };
- }
-
- return { from: this.actorID, scope: envActor.form() };
- },
-
- /**
- * Handle a protocol request to get the list of dependent promises of a
- * promise.
- *
- * @return object
- * Returns an object containing an array of object grips of the
- * dependent promises
- */
- onDependentPromises: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'dependentPromises' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let promises = this.obj.promiseDependentPromises
- .map(p => this.hooks.createValueGrip(p));
-
- return { promises };
- },
-
- /**
- * Handle a protocol request to get the allocation stack of a promise.
- */
- onAllocationStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'allocationStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseAllocationSite;
- let allocationStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- allocationStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(allocationStacks).then(stacks => {
- return { allocationStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the fulfillment stack of a promise.
- */
- onFulfillmentStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'fulfillmentStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let fulfillmentStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- fulfillmentStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(fulfillmentStacks).then(stacks => {
- return { fulfillmentStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the rejection stack of a promise.
- */
- onRejectionStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'rejectionStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let rejectionStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- rejectionStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(rejectionStacks).then(stacks => {
- return { rejectionStack: stacks };
- });
- },
-
- /**
- * Helper function for fetching the source location of a SavedFrame stack.
- *
- * @param SavedFrame stack
- * The promise allocation stack frame
- * @return object
- * Returns an object containing the source location of the SavedFrame
- * stack.
- */
- _getSourceOriginalLocation: function(stack) {
- let source;
-
- // Catch any errors if the source actor cannot be found
- try {
- source = this.hooks.sources().getSourceActorByURL(stack.source);
- } catch (e) {
- // ignored
- }
-
- if (!source) {
- return null;
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- source,
- stack.line,
- stack.column
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn,
- functionDisplayName: stack.functionDisplayName
- };
- });
- }
-};
-
-ObjectActor.prototype.requestTypes = {
- "definitionSite": ObjectActor.prototype.onDefinitionSite,
- "parameterNames": ObjectActor.prototype.onParameterNames,
- "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
- "enumProperties": ObjectActor.prototype.onEnumProperties,
- "prototype": ObjectActor.prototype.onPrototype,
- "property": ObjectActor.prototype.onProperty,
- "displayString": ObjectActor.prototype.onDisplayString,
- "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
- "decompile": ObjectActor.prototype.onDecompile,
- "release": ObjectActor.prototype.onRelease,
- "scope": ObjectActor.prototype.onScope,
- "dependentPromises": ObjectActor.prototype.onDependentPromises,
- "allocationStack": ObjectActor.prototype.onAllocationStack,
- "fulfillmentStack": ObjectActor.prototype.onFulfillmentStack,
- "rejectionStack": ObjectActor.prototype.onRejectionStack,
- "enumEntries": ObjectActor.prototype.onEnumEntries,
- "enumSymbols": ObjectActor.prototype.onEnumSymbols,
-};
-
-/**
- * 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.
- */
-function PropertyIteratorActor(objectActor, options) {
- if (!DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- this.iterator = {
- size: 0,
- propertyName: index => undefined,
- propertyDescription: index => undefined,
- };
- } else if (options.enumEntries) {
- let cls = objectActor.obj.class;
- if (cls == "Map") {
- this.iterator = enumMapEntries(objectActor);
- } else if (cls == "WeakMap") {
- this.iterator = enumWeakMapEntries(objectActor);
- } else if (cls == "Set") {
- this.iterator = enumSetEntries(objectActor);
- } else if (cls == "WeakSet") {
- this.iterator = enumWeakSetEntries(objectActor);
- } else {
- throw new Error("Unsupported class to enumerate entries from: " + cls);
- }
- } else if (
- isArray(objectActor.obj)
- && options.ignoreNonIndexedProperties
- && !options.query
- ) {
- this.iterator = enumArrayProperties(objectActor, options);
- } else {
- this.iterator = enumObjectProperties(objectActor, options);
- }
-}
-
-PropertyIteratorActor.prototype = {
- actorPrefix: "propertyIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- names({ indexes }) {
- let list = [];
- for (let idx of indexes) {
- list.push(this.iterator.propertyName(idx));
- }
- return {
- names: indexes
- };
- },
-
- slice({ start, count }) {
- let ownProperties = Object.create(null);
- for (let i = start, m = start + count; i < m; i++) {
- let name = this.iterator.propertyName(i);
- ownProperties[name] = this.iterator.propertyDescription(i);
- }
- return {
- ownProperties
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-PropertyIteratorActor.prototype.requestTypes = {
- "names": PropertyIteratorActor.prototype.names,
- "slice": PropertyIteratorActor.prototype.slice,
- "all": PropertyIteratorActor.prototype.all,
-};
-
-function enumArrayProperties(objectActor, options) {
- return {
- size: getArrayLength(objectActor.obj),
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- return objectActor._propertyDescriptor(index);
- }
- };
-}
-
-function enumObjectProperties(objectActor, options) {
- 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) {
- let length = DevToolsUtils.getProperty(objectActor.obj, "length");
- let sliceIndex;
-
- const isLengthTrustworthy =
- isUint32(length)
- && (!length || isArrayIndex(names[length - 1]))
- && !isArrayIndex(names[length]);
-
- if (!isLengthTrustworthy) {
- // The length property may not reflect what the object looks like, let's find
- // where indexed properties end.
-
- if (!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 (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,
- // like webaudio buffers.
- if (options.ignoreIndexedProperties) {
- // Keep items after `sliceIndex` index
- names = names.slice(sliceIndex);
- } else if (options.ignoreNonIndexedProperties) {
- // Keep `sliceIndex` first items
- names.length = sliceIndex;
- }
- }
-
- let safeGetterValues = objectActor._findSafeGetterValues(names, 0);
- let safeGetterNames = Object.keys(safeGetterValues);
- // Merge the safe getter values into the existing properties list.
- for (let 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 && 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) {
- let name = names[index];
- let desc = objectActor._propertyDescriptor(name);
- if (!desc) {
- desc = safeGetterValues[name];
- } else if (name in safeGetterValues) {
- // Merge the safe getter values into the existing properties list.
- let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
- desc.getterValue = getterValue;
- desc.getterPrototypeLevel = getterPrototypeLevel;
- }
- return desc;
- }
- };
-}
-
-/**
- * Helper function to create a grip from a Map/Set entry
- */
-function gripFromEntry({ obj, hooks }, entry) {
- return hooks.createValueGrip(
- makeDebuggeeValueIfNeeded(obj, Cu.unwaiveXrays(entry)));
-}
-
-function enumMapEntries(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.
- let raw = objectActor.obj.unsafeDereference();
-
- let keys = [...Cu.waiveXrays(Map.prototype.keys.call(raw))];
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = Map.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = Map.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
-}
-
-function enumWeakMapEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakMapKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = WeakMap.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = WeakMap.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
-}
-
-function enumSetEntries(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.
- let raw = objectActor.obj.unsafeDereference();
- let values = [...Cu.waiveXrays(Set.prototype.values.call(raw))];
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of values) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: values.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = values[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-function enumWeakSetEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakSetKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of keys) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = keys[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-/**
- * Creates an actor to iterate over an object's symbols.
- *
- * @param objectActor ObjectActor
- * The object actor.
- */
-function SymbolIteratorActor(objectActor) {
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- try {
- symbols = objectActor.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- this.iterator = {
- size: symbols.length,
- symbolDescription(index) {
- const symbol = symbols[index];
- return {
- name: symbol.toString(),
- descriptor: objectActor._propertyDescriptor(symbol)
- };
- }
- };
-}
-
-SymbolIteratorActor.prototype = {
- actorPrefix: "symbolIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- slice({ start, count }) {
- let ownSymbols = [];
- for (let i = start, m = start + count; i < m; i++) {
- ownSymbols.push(this.iterator.symbolDescription(i));
- }
- return {
- ownSymbols
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-SymbolIteratorActor.prototype.requestTypes = {
- "slice": SymbolIteratorActor.prototype.slice,
- "all": SymbolIteratorActor.prototype.all,
-};
-
-/**
- * Functions for adding information to ObjectActor grips for the purpose of
- * having customized output. This object holds arrays mapped by
- * Debugger.Object.prototype.class.
- *
- * In each array you can add functions that take three
- * arguments:
- * - the ObjectActor instance and its hooks to make a preview for,
- * - the grip object being prepared for the client,
- * - the raw JS object after calling Debugger.Object.unsafeDereference(). This
- * argument is only provided if the object is safe for reading properties and
- * executing methods. See DevToolsUtils.isSafeJSObject().
- *
- * Functions must return false if they cannot provide preview
- * information for the debugger object, or true otherwise.
- */
-DebuggerServer.ObjectActorPreviewers = {
- String: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("String", String, objectActor, grip, rawObj);
- }],
-
- Boolean: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip, rawObj);
- }],
-
- Number: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Number", Number, objectActor, grip, rawObj);
- }],
-
- Symbol: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Symbol", Symbol, objectActor, grip, rawObj);
- }],
-
- Function: [function({obj, hooks}, grip) {
- if (obj.name) {
- grip.name = obj.name;
- }
-
- if (obj.displayName) {
- grip.displayName = obj.displayName.substr(0, 500);
- }
-
- if (obj.parameterNames) {
- grip.parameterNames = obj.parameterNames;
- }
-
- // Check if the developer has added a de-facto standard displayName
- // property for us to use.
- let userDisplayName;
- try {
- userDisplayName = obj.getOwnPropertyDescriptor("displayName");
- } catch (e) {
- // The above can throw "permission denied" errors when the debuggee
- // does not subsume the function's compartment.
- }
-
- if (userDisplayName && typeof userDisplayName.value == "string" &&
- userDisplayName.value) {
- grip.userDisplayName = hooks.createValueGrip(userDisplayName.value);
- }
-
- let dbgGlobal = hooks.getGlobalDebugObject();
- if (dbgGlobal) {
- let script = dbgGlobal.makeDebuggeeValue(obj.unsafeDereference()).script;
- if (script) {
- grip.location = {
- url: script.url,
- line: script.startLine
- };
- }
- }
-
- return true;
- }],
-
- RegExp: [function({obj, hooks}, grip) {
- let str = DevToolsUtils.callPropertyOnObject(obj, "toString");
- if (typeof str != "string") {
- return false;
- }
-
- grip.displayString = hooks.createValueGrip(str);
- return true;
- }],
-
- Date: [function({obj, hooks}, grip) {
- let time = DevToolsUtils.callPropertyOnObject(obj, "getTime");
- if (typeof time != "number") {
- return false;
- }
-
- grip.preview = {
- timestamp: hooks.createValueGrip(time),
- };
- return true;
- }],
-
- Array: [function({obj, hooks}, grip) {
- let length = getArrayLength(obj);
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let items = grip.preview.items = [];
-
- for (let i = 0; i < length; ++i) {
- // Array Xrays filter out various possibly-unsafe properties (like
- // functions, and claim that the value is undefined instead. This
- // is generally the right thing for privileged code accessing untrusted
- // objects, but quite confusing for Object previews. So we manually
- // override this protection by waiving Xrays on the array, and re-applying
- // Xrays on any indexed value props that we pull off of it.
- let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
- if (desc && !desc.get && !desc.set) {
- let value = Cu.unwaiveXrays(desc.value);
- value = makeDebuggeeValueIfNeeded(obj, value);
- items.push(hooks.createValueGrip(value));
- } else {
- items.push(null);
- }
-
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Set: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: size,
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumSetEntries(objectActor)) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakSet: [function(objectActor, grip) {
- let enumEntries = enumWeakSetEntries(objectActor);
-
- grip.preview = {
- kind: "ArrayLike",
- length: enumEntries.size
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumEntries) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Map: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: size,
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumMapEntries(objectActor)) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakMap: [function(objectActor, grip) {
- let enumEntries = enumWeakMapEntries(objectActor);
-
- grip.preview = {
- kind: "MapLike",
- size: enumEntries.size
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumEntries) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- DOMStringMap: [function({obj, hooks}, grip, rawObj) {
- if (!rawObj) {
- return false;
- }
-
- let keys = obj.getOwnPropertyNames();
- grip.preview = {
- kind: "MapLike",
- size: keys.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let key of keys) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
- entries.push([key, hooks.createValueGrip(value)]);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Proxy: [function({obj, hooks}, grip, rawObj) {
- // The `isProxy` getter of the debuggee object only detects proxies without
- // security wrappers. If false, the target and handler are not available.
- let hasTargetAndHandler = obj.isProxy;
- if (hasTargetAndHandler) {
- grip.proxyTarget = hooks.createValueGrip(obj.proxyTarget);
- grip.proxyHandler = hooks.createValueGrip(obj.proxyHandler);
- }
-
- grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownPropertiesLength: 2 * hasTargetAndHandler
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- if (hasTargetAndHandler) {
- grip.preview.ownProperties["<target>"] = {value: grip.proxyTarget};
- grip.preview.ownProperties["<handler>"] = {value: grip.proxyHandler};
- }
-
- return true;
- }],
-};
-
-/**
- * Generic previewer for classes wrapping primitives, like String,
- * Number and Boolean.
- *
- * @param string className
- * Class name to expect.
- * @param object classObj
- * The class to expect, eg. String. The valueOf() method of the class is
- * invoked on the given object.
- * @param ObjectActor objectActor
- * The object actor
- * @param Object grip
- * The result grip to fill in
- * @return Booolean true if the object was handled, false otherwise
- */
-function wrappedPrimitivePreviewer(className, classObj, objectActor, grip, rawObj) {
- let {obj, hooks} = objectActor;
-
- let v = null;
- try {
- v = classObj.prototype.valueOf.call(rawObj);
- } catch (ex) {
- // valueOf() can throw if the raw JS object is "misbehaved".
- return false;
- }
-
- if (v === null) {
- return false;
- }
-
- let canHandle = GenericObject(objectActor, grip, rawObj, className === "String");
- if (!canHandle) {
- return false;
- }
-
- grip.preview.wrappedValue =
- hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v));
- return true;
-}
-
-function GenericObject(objectActor, grip, rawObj, specialStringBehavior = false) {
- let {obj, hooks} = objectActor;
- if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
- return false;
- }
-
- let i = 0, names = [], symbols = [];
- let preview = grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownSymbols: [],
- };
-
- try {
- names = obj.getOwnPropertyNames();
- symbols = obj.getOwnPropertySymbols();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
- preview.ownPropertiesLength = names.length;
- preview.ownSymbolsLength = symbols.length;
-
- let length;
- if (specialStringBehavior) {
- length = DevToolsUtils.getProperty(obj, "length");
- if (typeof length != "number") {
- specialStringBehavior = false;
- }
- }
-
- for (let name of names) {
- if (specialStringBehavior && /^[0-9]+$/.test(name)) {
- let num = parseInt(name, 10);
- if (num.toString() === name && num >= 0 && num < length) {
- continue;
- }
- }
-
- let desc = objectActor._propertyDescriptor(name, true);
- if (!desc) {
- continue;
- }
-
- preview.ownProperties[name] = desc;
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- for (let symbol of symbols) {
- let descriptor = objectActor._propertyDescriptor(symbol, true);
- if (!descriptor) {
- continue;
- }
-
- preview.ownSymbols.push(Object.assign({
- descriptor
- }, hooks.createValueGrip(symbol)));
-
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- if (i < OBJECT_PREVIEW_MAX_ITEMS) {
- preview.safeGetterValues = objectActor._findSafeGetterValues(
- Object.keys(preview.ownProperties),
- OBJECT_PREVIEW_MAX_ITEMS - i);
- }
-
- return true;
-}
-
-// Preview functions that do not rely on the object class.
-DebuggerServer.ObjectActorPreviewers.Object = [
- function TypedArray({obj, hooks}, grip) {
- if (!isTypedArray(obj)) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: getArrayLength(obj),
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let global = Cu.getGlobalForObject(DebuggerServer);
- let classProto = global[obj.class].prototype;
- // The Xray machinery for TypedArrays denies indexed access on the grounds
- // that it's slow, and advises callers to do a structured clone instead.
- let safeView = Cu.cloneInto(classProto.subarray.call(raw, 0,
- OBJECT_PREVIEW_MAX_ITEMS), global);
- let items = grip.preview.items = [];
- for (let i = 0; i < safeView.length; i++) {
- items.push(safeView[i]);
- }
-
- return true;
- },
-
- function Error({obj, hooks}, grip) {
- switch (obj.class) {
- case "Error":
- case "EvalError":
- case "RangeError":
- case "ReferenceError":
- case "SyntaxError":
- case "TypeError":
- case "URIError":
- let name = DevToolsUtils.getProperty(obj, "name");
- let msg = DevToolsUtils.getProperty(obj, "message");
- let stack = DevToolsUtils.getProperty(obj, "stack");
- let fileName = DevToolsUtils.getProperty(obj, "fileName");
- let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
- let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
- grip.preview = {
- kind: "Error",
- name: hooks.createValueGrip(name),
- message: hooks.createValueGrip(msg),
- stack: hooks.createValueGrip(stack),
- fileName: hooks.createValueGrip(fileName),
- lineNumber: hooks.createValueGrip(lineNumber),
- columnNumber: hooks.createValueGrip(columnNumber),
- };
- return true;
- default:
- return false;
- }
- },
-
- function CSSMediaRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSMediaRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.conditionText),
- };
- return true;
- },
-
- function CSSStyleRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSStyleRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.selectorText),
- };
- return true;
- },
-
- function ObjectWithURL({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(obj.class == "CSSImportRule" ||
- obj.class == "CSSStyleSheet" ||
- obj.class == "Location" ||
- rawObj instanceof Ci.nsIDOMWindow)) {
- return false;
- }
-
- let url;
- if (rawObj instanceof Ci.nsIDOMWindow && rawObj.location) {
- url = rawObj.location.href;
- } else if (rawObj.href) {
- url = rawObj.href;
- } else {
- return false;
- }
-
- grip.preview = {
- kind: "ObjectWithURL",
- url: hooks.createValueGrip(url),
- };
-
- return true;
- },
-
- function ArrayLike({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- obj.class != "DOMStringList" &&
- obj.class != "DOMTokenList" &&
- obj.class != "CSSRuleList" &&
- obj.class != "MediaList" &&
- obj.class != "StyleSheetList" &&
- obj.class != "CSSValueList" &&
- obj.class != "NamedNodeMap" &&
- obj.class != "FileList" &&
- obj.class != "NodeList") {
- return false;
- }
-
- if (typeof rawObj.length != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: rawObj.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
-
- for (let i = 0; i < rawObj.length &&
- items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[i]);
- items.push(hooks.createValueGrip(value));
- }
-
- return true;
- },
-
- function CSSStyleDeclaration({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- (obj.class != "CSSStyleDeclaration" &&
- obj.class != "CSS2Properties")) {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: rawObj.length,
- };
-
- let entries = grip.preview.entries = [];
-
- for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
- i < rawObj.length; i++) {
- let prop = rawObj[i];
- let value = rawObj.getPropertyValue(prop);
- entries.push([prop, hooks.createValueGrip(value)]);
- }
-
- return true;
- },
-
- function DOMNode({obj, hooks}, grip, rawObj) {
- if (isWorker || obj.class == "Object" || !rawObj ||
- !(rawObj instanceof Ci.nsIDOMNode)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMNode",
- nodeType: rawObj.nodeType,
- nodeName: rawObj.nodeName,
- isConnected: rawObj.isConnected === true,
- };
-
- if (rawObj instanceof Ci.nsIDOMDocument && rawObj.location) {
- preview.location = hooks.createValueGrip(rawObj.location.href);
- } else if (rawObj instanceof Ci.nsIDOMDocumentFragment) {
- preview.childNodesLength = rawObj.childNodes.length;
-
- if (hooks.getGripDepth() < 2) {
- preview.childNodes = [];
- for (let node of rawObj.childNodes) {
- let actor = hooks.createValueGrip(obj.makeDebuggeeValue(node));
- preview.childNodes.push(actor);
- if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
- } else if (rawObj instanceof Ci.nsIDOMElement) {
- // For HTML elements (in an HTML document, at least), the nodeName is an
- // uppercased version of the actual element name. Check for HTML
- // elements, that is elements in the HTML namespace, and lowercase the
- // nodeName in that case.
- if (rawObj.namespaceURI == "http://www.w3.org/1999/xhtml") {
- preview.nodeName = preview.nodeName.toLowerCase();
- }
-
- // Add preview for DOM element attributes.
- preview.attributes = {};
- preview.attributesLength = rawObj.attributes.length;
- for (let attr of rawObj.attributes) {
- preview.attributes[attr.nodeName] = hooks.createValueGrip(attr.value);
- }
- } else if (obj.class == "Attr") {
- preview.value = hooks.createValueGrip(rawObj.value);
- } else if (obj.class == "Text" ||
- obj.class == "CDATASection" ||
- obj.class == "Comment") {
- preview.textContent = hooks.createValueGrip(rawObj.textContent);
- }
-
- return true;
- },
-
- function DOMEvent({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMEvent)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMEvent",
- type: rawObj.type,
- properties: Object.create(null),
- };
-
- if (hooks.getGripDepth() < 2) {
- let target = obj.makeDebuggeeValue(rawObj.target);
- preview.target = hooks.createValueGrip(target);
- }
-
- let props = [];
- if (obj.class == "MouseEvent" ||
- obj.class == "DragEvent" ||
- obj.class == "PointerEvent" ||
- obj.class == "SimpleGestureEvent" ||
- obj.class == "WheelEvent") {
- props.push("buttons", "clientX", "clientY", "layerX", "layerY");
- } else if (obj.class == "KeyboardEvent") {
- let modifiers = [];
- if (rawObj.altKey) {
- modifiers.push("Alt");
- }
- if (rawObj.ctrlKey) {
- modifiers.push("Control");
- }
- if (rawObj.metaKey) {
- modifiers.push("Meta");
- }
- if (rawObj.shiftKey) {
- modifiers.push("Shift");
- }
- preview.eventKind = "key";
- preview.modifiers = modifiers;
-
- props.push("key", "charCode", "keyCode");
- } else if (obj.class == "TransitionEvent") {
- props.push("propertyName", "pseudoElement");
- } else if (obj.class == "AnimationEvent") {
- props.push("animationName", "pseudoElement");
- } else if (obj.class == "ClipboardEvent") {
- props.push("clipboardData");
- }
-
- // Add event-specific properties.
- for (let prop of props) {
- let value = rawObj[prop];
- if (value && (typeof value == "object" || typeof value == "function")) {
- // Skip properties pointing to objects.
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- }
-
- // Add any properties we find on the event object.
- if (!props.length) {
- let i = 0;
- for (let prop in rawObj) {
- let value = rawObj[prop];
- if (prop == "target" || prop == "type" || value === null ||
- typeof value == "function") {
- continue;
- }
- if (value && typeof value == "object") {
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
-
- return true;
- },
-
- function DOMException({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMDOMException)) {
- return false;
- }
-
- grip.preview = {
- kind: "DOMException",
- name: hooks.createValueGrip(rawObj.name),
- message: hooks.createValueGrip(rawObj.message),
- code: hooks.createValueGrip(rawObj.code),
- result: hooks.createValueGrip(rawObj.result),
- filename: hooks.createValueGrip(rawObj.filename),
- lineNumber: hooks.createValueGrip(rawObj.lineNumber),
- columnNumber: hooks.createValueGrip(rawObj.columnNumber),
- };
-
- return true;
- },
-
- function PseudoArray({obj, hooks}, grip, rawObj) {
- // An object is considered a pseudo-array if all the following apply:
- // - All its properties are array indices except, optionally, a "length" property.
- // - At least it has the "0" array index.
- // - The array indices are consecutive.
- // - The value of "length", if present, is the number of array indices.
-
- let keys;
- try {
- keys = obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- return false;
- }
- let {length} = keys;
- if (length === 0) {
- return false;
- }
-
- // Array indices should be sorted at the beginning, from smallest to largest.
- // Other properties should be at the end, so check if the last one is "length".
- if (keys[length - 1] === "length") {
- --length;
- if (length === 0 || length !== DevToolsUtils.getProperty(obj, "length")) {
- return false;
- }
- }
-
- // Check that the last key is the array index expected at that position.
- let lastKey = keys[length - 1];
- if (!isArrayIndex(lastKey) || +lastKey !== length - 1) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- // Avoid recursive object grips.
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- let numItems = Math.min(OBJECT_PREVIEW_MAX_ITEMS, length);
-
- for (let i = 0; i < numItems; ++i) {
- let desc = obj.getOwnPropertyDescriptor(i);
- if (desc && "value" in desc) {
- items.push(hooks.createValueGrip(desc.value));
- } else {
- items.push(null);
- }
- }
-
- return true;
- },
-
- function Object(objectActor, grip, rawObj) {
- return GenericObject(objectActor, grip, rawObj, /* specialStringBehavior = */ false);
- },
-];
-
-/**
- * Get thisDebugger.Object referent's `promiseState`.
- *
- * @returns Object
- * An object of one of the following forms:
- * - { state: "pending" }
- * - { state: "fulfilled", value }
- * - { state: "rejected", reason }
- */
-function getPromiseState(obj) {
- if (obj.class != "Promise") {
- throw new Error(
- "Can't call `getPromiseState` on `Debugger.Object`s that don't " +
- "refer to Promise objects.");
- }
-
- let state = { state: obj.promiseState };
- if (state.state === "fulfilled") {
- state.value = obj.promiseValue;
- } else if (state.state === "rejected") {
- state.reason = obj.promiseReason;
- }
- return state;
-}
-
-/**
- * Determine if a given value is non-primitive.
- *
- * @param Any value
- * The value to test.
- * @return Boolean
- * Whether the value is non-primitive.
- */
-function isObject(value) {
- const type = typeof value;
- return type == "object" ? value !== null : type == "function";
-}
-
-/**
- * Create a function that can safely stringify Debugger.Objects of a given
- * builtin type.
- *
- * @param Function ctor
- * The builtin class constructor.
- * @return Function
- * The stringifier for the class.
- */
-function createBuiltinStringifier(ctor) {
- return obj => {
- try {
- return ctor.prototype.toString.call(obj.unsafeDereference());
- } catch (err) {
- // The debuggee will see a "Function" class if the object is callable and
- // its compartment is not subsumed. The above will throw if it's not really
- // a function, e.g. if it's a callable proxy.
- return "[object " + obj.class + "]";
- }
- };
-}
-
-/**
- * Stringify a Debugger.Object-wrapped Error instance.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification of the object.
- */
-function errorStringify(obj) {
- let name = DevToolsUtils.getProperty(obj, "name");
- if (name === "" || name === undefined) {
- name = obj.class;
- } else if (isObject(name)) {
- name = stringify(name);
- }
-
- let message = DevToolsUtils.getProperty(obj, "message");
- if (isObject(message)) {
- message = stringify(message);
- }
-
- if (message === "" || message === undefined) {
- return name;
- }
- return name + ": " + message;
-}
-
-/**
- * Stringify a Debugger.Object based on its class.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification for the object.
- */
-function stringify(obj) {
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- if (DevToolsUtils.isCPOW(obj)) {
- return "<cpow>";
- }
- let unwrapped = DevToolsUtils.unwrap(obj);
- if (unwrapped === undefined) {
- return "<invisibleToDebugger>";
- } else if (unwrapped.isProxy) {
- return "<proxy>";
- }
- // The following line should not be reached. It's there just in case somebody
- // modifies isSafeDebuggerObject to return false for additional kinds of objects.
- return "[object " + obj.class + "]";
- } else if (obj.class == "DeadObject") {
- return "<dead object>";
- }
-
- const stringifier = stringifiers[obj.class] || stringifiers.Object;
-
- try {
- return stringifier(obj);
- } catch (e) {
- DevToolsUtils.reportException("stringify", e);
- return "<failed to stringify object>";
- }
-}
-
-// Used to prevent infinite recursion when an array is found inside itself.
-var seen = null;
-
-var stringifiers = {
- Error: errorStringify,
- EvalError: errorStringify,
- RangeError: errorStringify,
- ReferenceError: errorStringify,
- SyntaxError: errorStringify,
- TypeError: errorStringify,
- URIError: errorStringify,
- Boolean: createBuiltinStringifier(Boolean),
- Function: createBuiltinStringifier(Function),
- Number: createBuiltinStringifier(Number),
- RegExp: createBuiltinStringifier(RegExp),
- String: createBuiltinStringifier(String),
- Object: obj => "[object " + obj.class + "]",
- Array: obj => {
- // If we're at the top level then we need to create the Set for tracking
- // previously stringified arrays.
- const topLevel = !seen;
- if (topLevel) {
- seen = new Set();
- } else if (seen.has(obj)) {
- return "";
- }
-
- seen.add(obj);
-
- const len = getArrayLength(obj);
- let string = "";
-
- // Array.length is always a non-negative safe integer.
- for (let i = 0; i < len; i++) {
- const desc = obj.getOwnPropertyDescriptor(i);
- if (desc) {
- const { value } = desc;
- if (value != null) {
- string += isObject(value) ? stringify(value) : value;
- }
- }
-
- if (i < len - 1) {
- string += ",";
- }
- }
-
- if (topLevel) {
- seen = null;
- }
-
- return string;
- },
- DOMException: obj => {
- const message = DevToolsUtils.getProperty(obj, "message") || "<no message>";
- const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16);
- const code = DevToolsUtils.getProperty(obj, "code");
- const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
-
- return '[Exception... "' + message + '" ' +
- 'code: "' + code + '" ' +
- 'nsresult: "0x' + result + " (" + name + ')"]';
- },
- Promise: obj => {
- const { state, value, reason } = getPromiseState(obj);
- let statePreview = state;
- if (state != "pending") {
- const settledValue = state === "fulfilled" ? value : reason;
- statePreview += ": " + (typeof settledValue === "object" && settledValue !== null
- ? stringify(settledValue)
- : settledValue);
- }
- return "Promise (" + statePreview + ")";
- },
-};
-
-/**
- * Make a debuggee value for the given object, if needed. Primitive values
- * are left the same.
- *
- * Use case: you have a raw JS object (after unsafe dereference) and you want to
- * send it to the client. In that case you need to use an ObjectActor which
- * requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
- * method works only for JS objects and functions.
- *
- * @param Debugger.Object obj
- * @param any value
- * @return object
- */
-function makeDebuggeeValueIfNeeded(obj, value) {
- if (value && (typeof value == "object" || typeof value == "function")) {
- return obj.makeDebuggeeValue(value);
- }
- return value;
-}
/**
* Creates an actor for the specified "very long" string. "Very long" is specified
* at the server's discretion.
+ * There is a newer, protocol.js based LongString actor in
+ * devtools/server/actors/string.js and if you can you should use this one.
*
* @param string String
* The string.
*/
function LongStringActor(string) {
this.string = string;
this.stringLength = string.length;
}
@@ -2340,203 +82,16 @@ LongStringActor.prototype = {
};
LongStringActor.prototype.requestTypes = {
"substring": LongStringActor.prototype.onSubstring,
"release": LongStringActor.prototype.onRelease
};
/**
- * Creates an actor for the specified symbol.
- *
- * @param symbol Symbol
- * The symbol.
- */
-function SymbolActor(symbol) {
- this.symbol = symbol;
-}
-
-SymbolActor.prototype = {
- actorPrefix: "symbol",
-
- rawValue: function() {
- return this.symbol;
- },
-
- destroy: function() {
- // Because symbolActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let form = {
- type: "symbol",
- actor: this.actorID,
- };
- let name = getSymbolName(this.symbol);
- if (name !== undefined) {
- // Create a grip for the name because it might be a longString.
- form.name = createValueGrip(name, this.registeredPool);
- }
- return form;
- },
-
- /**
- * Handle a request to release this SymbolActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.symbolActors) {
- delete this.registeredPool.symbolActors[this.symbol];
- }
- }
-};
-
-SymbolActor.prototype.requestTypes = {
- "release": SymbolActor.prototype.onRelease
-};
-
-/**
- * Creates an actor for the specified ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The buffer.
- */
-function ArrayBufferActor(buffer) {
- this.buffer = buffer;
- this.bufferLength = buffer.byteLength;
-}
-
-ArrayBufferActor.prototype = {
- actorPrefix: "arrayBuffer",
-
- rawValue: function() {
- return this.buffer;
- },
-
- destroy: function() {
- },
-
- grip() {
- return {
- "type": "arrayBuffer",
- "length": this.bufferLength,
- "actor": this.actorID
- };
- },
-
- onSlice({start, count}) {
- let slice = new Uint8Array(this.buffer, start, count);
- let parts = [], offset = 0;
- const PortionSize = 0x6000; // keep it divisible by 3 for btoa() and join()
- while (offset + PortionSize < count) {
- parts.push(btoa(
- String.fromCharCode.apply(null, slice.subarray(offset, offset + PortionSize))));
- offset += PortionSize;
- }
- parts.push(btoa(String.fromCharCode.apply(null, slice.subarray(offset, count))));
- return {
- "from": this.actorID,
- "encoded": parts.join(""),
- };
- }
-};
-
-ArrayBufferActor.prototype.requestTypes = {
- "slice": ArrayBufferActor.prototype.onSlice,
-};
-
-/**
- * Create a grip for the given debuggee value. If the value is an
- * object, will create an actor with the given lifetime.
- */
-function createValueGrip(value, pool, makeObjectGrip) {
- switch (typeof value) {
- case "boolean":
- return value;
-
- case "string":
- if (stringIsLong(value)) {
- return longStringGrip(value, pool);
- }
- return value;
-
- case "number":
- if (value === Infinity) {
- return { type: "Infinity" };
- } else if (value === -Infinity) {
- return { type: "-Infinity" };
- } else if (Number.isNaN(value)) {
- return { type: "NaN" };
- } else if (!value && 1 / value === -Infinity) {
- return { type: "-0" };
- }
- return value;
-
- case "undefined":
- return { type: "undefined" };
-
- case "object":
- if (value === null) {
- return { type: "null" };
- } else if (value.optimizedOut ||
- value.uninitialized ||
- value.missingArguments) {
- // The slot is optimized out, an uninitialized binding, or
- // arguments on a dead scope
- return {
- type: "null",
- optimizedOut: value.optimizedOut,
- uninitialized: value.uninitialized,
- missingArguments: value.missingArguments
- };
- }
- return makeObjectGrip(value, pool);
-
- case "symbol":
- return symbolGrip(value, pool);
-
- default:
- assert(false, "Failed to provide a grip for: " + value);
- return null;
- }
-}
-
-const symbolProtoToString = Symbol.prototype.toString;
-
-function getSymbolName(symbol) {
- const name = symbolProtoToString.call(symbol).slice("Symbol(".length, -1);
- return name || undefined;
-}
-
-/**
- * Returns true if the string is long enough to use a LongStringActor instead
- * of passing the value directly over the protocol.
- *
- * @param str String
- * The string we are checking the length of.
- */
-function stringIsLong(str) {
- return str.length >= DebuggerServer.LONG_STRING_LENGTH;
-}
-
-/**
* Create a grip for the given string.
*
* @param str String
* The string we are creating a grip for.
* @param pool ActorPool
* The actor pool where the new actor will be added.
*/
function longStringGrip(str, pool) {
@@ -2549,141 +104,12 @@ function longStringGrip(str, pool) {
}
let actor = new LongStringActor(str);
pool.addActor(actor);
pool.longStringActors[str] = actor;
return actor.grip();
}
-/**
- * Create a grip for the given symbol.
- *
- * @param sym Symbol
- * The symbol we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function symbolGrip(sym, pool) {
- if (!pool.symbolActors) {
- pool.symbolActors = Object.create(null);
- }
-
- if (sym in pool.symbolActors) {
- return pool.symbolActors[sym].grip();
- }
-
- let actor = new SymbolActor(sym);
- pool.addActor(actor);
- pool.symbolActors[sym] = actor;
- return actor.grip();
-}
-
-/**
- * Create a grip for the given ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The ArrayBuffer we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function arrayBufferGrip(buffer, pool) {
- if (!pool.arrayBufferActors) {
- pool.arrayBufferActors = new WeakMap();
- }
-
- if (pool.arrayBufferActors.has(buffer)) {
- return pool.arrayBufferActors.get(buffer).grip();
- }
-
- let actor = new ArrayBufferActor(buffer);
- pool.addActor(actor);
- pool.arrayBufferActors.set(buffer, actor);
- return actor.grip();
-}
-
-const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
- "Uint32Array", "Int8Array", "Int16Array", "Int32Array",
- "Float32Array", "Float64Array"];
-
-/**
- * Returns true if a debuggee object is a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isTypedArray(object) {
- return TYPED_ARRAY_CLASSES.includes(object.class);
-}
-
-/**
- * Returns true if a debuggee object is an array, including a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isArray(object) {
- return isTypedArray(object) || object.class === "Array";
-}
-
-/**
- * Returns the length of an array (or typed array).
- *
- * @param obj Debugger.Object
- * The debuggee object of the array.
- * @return Number
- * @throws if the object is not an array.
- */
-function getArrayLength(object) {
- if (!isArray(object)) {
- throw new Error("Expected an array, got a " + object.class);
- }
-
- // Real arrays have a reliable `length` own property.
- if (object.class === "Array") {
- return DevToolsUtils.getProperty(object, "length");
- }
-
- // For typed arrays, `DevToolsUtils.getProperty` is not reliable because the `length`
- // getter could be shadowed by an own property, and `getOwnPropertyNames` is
- // unnecessarily slow. Obtain the `length` getter safely and call it manually.
- let typedProto = Object.getPrototypeOf(Uint8Array.prototype);
- let getter = Object.getOwnPropertyDescriptor(typedProto, "length").get;
- return getter.call(object.unsafeDereference());
-}
-
-/**
- * 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;
-}
-
-/**
- * Returns true if the parameter is suitable to be an array index.
- *
- * @param str String
- * @return Boolean
- */
-function isArrayIndex(str) {
- // Transform the parameter to a 32-bit unsigned integer.
- let num = str >>> 0;
- // Check that the parameter is a canonical Uint32 index.
- return num + "" === str &&
- // Array indices cannot attain the maximum Uint32 value.
- num != -1 >>> 0;
-}
-
-exports.ObjectActor = ObjectActor;
-exports.PropertyIteratorActor = PropertyIteratorActor;
-exports.LongStringActor = LongStringActor;
-exports.SymbolActor = SymbolActor;
-exports.createValueGrip = createValueGrip;
-exports.stringIsLong = stringIsLong;
-exports.longStringGrip = longStringGrip;
-exports.arrayBufferGrip = arrayBufferGrip;
+module.exports = {
+ LongStringActor,
+ longStringGrip,
+};
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/object/moz.build
@@ -0,0 +1,13 @@
+# 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/.
+
+DevToolsModules(
+ 'long-string.js',
+ 'previewers.js',
+ 'property-iterator.js',
+ 'stringifiers.js',
+ 'symbol-iterator.js',
+ 'symbol.js',
+ 'utils.js',
+)
copy from devtools/server/actors/object.js
copy to devtools/server/actors/object/previewers.js
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object/previewers.js
@@ -2,1275 +2,42 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 { Cu, Ci } = require("chrome");
-const { GeneratedLocation } = require("devtools/server/actors/common");
const { DebuggerServer } = require("devtools/server/main");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { assert } = DevToolsUtils;
-
-loader.lazyRequireGetter(this, "ChromeUtils");
+loader.lazyRequireGetter(this, "ObjectUtils", "devtools/server/actors/object/utils");
+loader.lazyRequireGetter(this, "PropertyIterators", "devtools/server/actors/object/property-iterator");
// Number of items to preview in objects, arrays, maps, sets, lists,
// collections, etc.
const OBJECT_PREVIEW_MAX_ITEMS = 10;
/**
- * Creates an actor for the specified object.
- *
- * @param obj Debugger.Object
- * The debuggee object.
- * @param hooks Object
- * A collection of abstract methods that are implemented by the caller.
- * ObjectActor requires the following functions to be implemented by
- * the caller:
- * - createValueGrip
- * Creates a value grip for the given object
- * - sources
- * TabSources getter that manages the sources of a thread
- * - createEnvironmentActor
- * Creates and return an environment actor
- * - getGripDepth
- * An actor's grip depth getter
- * - incrementGripDepth
- * Increment the actor's grip depth
- * - decrementGripDepth
- * Decrement the actor's grip depth
- * - globalDebugObject
- * The Debuggee Global Object as given by the ThreadActor
- */
-function ObjectActor(obj, {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
-}) {
- assert(!obj.optimizedOut,
- "Should not create object actors for optimized out values!");
- this.obj = obj;
- this.hooks = {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
- };
- this.iterators = new Set();
-}
-
-ObjectActor.prototype = {
- actorPrefix: "obj",
-
- rawValue: function() {
- return this.obj.unsafeDereference();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let g = {
- "type": "object",
- "actor": this.actorID,
- "class": this.obj.class,
- };
-
- let unwrapped = DevToolsUtils.unwrap(this.obj);
-
- // Unsafe objects must be treated carefully.
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- if (DevToolsUtils.isCPOW(this.obj)) {
- // Cross-process object wrappers can't be accessed.
- g.class = "CPOW: " + g.class;
- } else if (unwrapped === undefined) {
- // Objects belonging to an invisible-to-debugger compartment might be proxies,
- // so just in case they shouldn't be accessed.
- g.class = "InvisibleToDebugger: " + g.class;
- } else if (unwrapped.isProxy) {
- // Proxy objects can run traps when accessed, so just create a preview with
- // the target and the handler.
- g.class = "Proxy";
- this.hooks.incrementGripDepth();
- DebuggerServer.ObjectActorPreviewers.Proxy[0](this, g, null);
- this.hooks.decrementGripDepth();
- }
- return g;
- }
-
- // If the debuggee does not subsume the object's compartment, most properties won't
- // be accessible. Cross-orgin Window and Location objects might expose some, though.
- // Change the displayed class, but when creating the preview use the original one.
- if (unwrapped === null) {
- g.class = "Restricted";
- }
-
- this.hooks.incrementGripDepth();
-
- g.extensible = this.obj.isExtensible();
- g.frozen = this.obj.isFrozen();
- g.sealed = this.obj.isSealed();
-
- if (g.class == "Promise") {
- g.promiseState = this._createPromiseState();
- }
-
- // FF40+: Allow to know how many properties an object has to lazily display them
- // when there is a bunch.
- if (isTypedArray(g)) {
- // Bug 1348761: getOwnPropertyNames is unnecessary slow on TypedArrays
- g.ownPropertyLength = getArrayLength(this.obj);
- } else {
- try {
- g.ownPropertyLength = this.obj.getOwnPropertyNames().length;
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let raw = this.obj.unsafeDereference();
-
- // If Cu is not defined, we are running on a worker thread, where xrays
- // don't exist.
- if (Cu) {
- raw = Cu.unwaiveXrays(raw);
- }
-
- if (!DevToolsUtils.isSafeJSObject(raw)) {
- raw = null;
- }
-
- let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
- DebuggerServer.ObjectActorPreviewers.Object;
- for (let fn of previewers) {
- try {
- if (fn(this, g, raw)) {
- break;
- }
- } catch (e) {
- let msg = "ObjectActor.prototype.grip previewer function";
- DevToolsUtils.reportException(msg, e);
- }
- }
-
- this.hooks.decrementGripDepth();
- return g;
- },
-
- /**
- * Returns an object exposing the internal Promise state.
- */
- _createPromiseState: function() {
- const { state, value, reason } = getPromiseState(this.obj);
- let promiseState = { state };
-
- if (state == "fulfilled") {
- promiseState.value = this.hooks.createValueGrip(value);
- } else if (state == "rejected") {
- promiseState.reason = this.hooks.createValueGrip(reason);
- }
-
- promiseState.creationTimestamp = Date.now() - this.obj.promiseLifetime;
-
- // Only add the timeToSettle property if the Promise isn't pending.
- if (state !== "pending") {
- promiseState.timeToSettle = this.obj.promiseTimeToResolution;
- }
-
- return promiseState;
- },
-
- /**
- * Releases this actor from the pool.
- */
- release: function() {
- if (this.registeredPool.objectActors) {
- this.registeredPool.objectActors.delete(this.obj);
- }
- this.iterators.forEach(actor => this.registeredPool.removeActor(actor));
- this.iterators.clear();
- this.registeredPool.removeActor(this);
- },
-
- /**
- * Handle a protocol request to provide the definition site of this function
- * object.
- */
- onDefinitionSite: function() {
- if (this.obj.class != "Function") {
- return {
- from: this.actorID,
- error: "objectNotFunction",
- message: this.actorID + " is not a function."
- };
- }
-
- if (!this.obj.script) {
- return {
- from: this.actorID,
- error: "noScript",
- message: this.actorID + " has no Debugger.Script"
- };
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- this.hooks.sources().createNonSourceMappedActor(this.obj.script.source),
- this.obj.script.startLine,
- 0 // TODO bug 901138: use Debugger.Script.prototype.startColumn
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn
- };
- });
- },
-
- /**
- * Handle a protocol request to provide the names of the properties defined on
- * the object and not its prototype.
- */
- onOwnPropertyNames: function() {
- let props = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- props = this.obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
- return { from: this.actorID, ownPropertyNames: props };
- },
-
- /**
- * Creates an actor to iterate over an object property names and values.
- * See PropertyIteratorActor constructor for more info about options param.
- *
- * @param request object
- * The protocol request object.
- */
- onEnumProperties: function(request) {
- let actor = new PropertyIteratorActor(this, request.options);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over entries of a Map/Set-like object.
- */
- onEnumEntries: function() {
- let actor = new PropertyIteratorActor(this, { enumEntries: true });
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over an object symbols properties.
- */
- onEnumSymbols: function() {
- let actor = new SymbolIteratorActor(this);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Handle a protocol request to provide the prototype and own properties of
- * the object.
- *
- * @returns {Object} An object containing the data of this.obj, of the following form:
- * - {string} from: this.obj's actorID.
- * - {Object} prototype: The descriptor of this.obj's prototype.
- * - {Object} ownProperties: an object where the keys are the names of the
- * this.obj's ownProperties, and the values the descriptors of
- * the properties.
- * - {Array} ownSymbols: An array containing all descriptors of this.obj's
- * ownSymbols. Here we have an array, and not an object like for
- * ownProperties, because we can have multiple symbols with the same
- * name in this.obj, e.g. `{[Symbol()]: "a", [Symbol()]: "b"}`.
- * - {Object} safeGetterValues: an object that maps this.obj's property names
- * with safe getters descriptors.
- */
- onPrototypeAndProperties: function() {
- let proto = null;
- let names = [];
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- proto = this.obj.proto;
- names = this.obj.getOwnPropertyNames();
- symbols = this.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let ownProperties = Object.create(null);
- let ownSymbols = [];
-
- for (let name of names) {
- ownProperties[name] = this._propertyDescriptor(name);
- }
-
- for (let sym of symbols) {
- ownSymbols.push({
- name: sym.toString(),
- descriptor: this._propertyDescriptor(sym)
- });
- }
-
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto),
- ownProperties,
- ownSymbols,
- safeGetterValues: this._findSafeGetterValues(names) };
- },
-
- /**
- * Find the safe getter values for the current Debugger.Object, |this.obj|.
- *
- * @private
- * @param array ownProperties
- * The array that holds the list of known ownProperties names for
- * |this.obj|.
- * @param number [limit=0]
- * Optional limit of getter values to find.
- * @return object
- * An object that maps property names to safe getter descriptors as
- * defined by the remote debugging protocol.
- */
- _findSafeGetterValues: function(ownProperties, limit = 0) {
- let safeGetterValues = Object.create(null);
- let obj = this.obj;
- let level = 0, i = 0;
-
- // Do not search safe getters in unsafe objects.
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- return safeGetterValues;
- }
-
- // Most objects don't have any safe getters but inherit some from their
- // prototype. Avoid calling getOwnPropertyNames on objects that may have
- // many properties like Array, strings or js objects. That to avoid
- // freezing firefox when doing so.
- if (isArray(this.obj) || ["Object", "String"].includes(this.obj.class)) {
- obj = obj.proto;
- level++;
- }
-
- while (obj && DevToolsUtils.isSafeDebuggerObject(obj)) {
- let getters = this._findSafeGetters(obj);
- for (let name of getters) {
- // Avoid overwriting properties from prototypes closer to this.obj. Also
- // avoid providing safeGetterValues from prototypes if property |name|
- // is already defined as an own property.
- if (name in safeGetterValues ||
- (obj != this.obj && ownProperties.includes(name))) {
- continue;
- }
-
- // Ignore __proto__ on Object.prototye.
- if (!obj.proto && name == "__proto__") {
- continue;
- }
-
- let desc = null, getter = null;
- try {
- desc = obj.getOwnPropertyDescriptor(name);
- getter = desc.get;
- } catch (ex) {
- // The above can throw if the cache becomes stale.
- }
- if (!getter) {
- obj._safeGetters = null;
- continue;
- }
-
- let result = getter.call(this.obj);
- if (result && !("throw" in result)) {
- let getterValue = undefined;
- if ("return" in result) {
- getterValue = result.return;
- } else if ("yield" in result) {
- getterValue = result.yield;
- }
- // WebIDL attributes specified with the LenientThis extended attribute
- // return undefined and should be ignored.
- if (getterValue !== undefined) {
- safeGetterValues[name] = {
- getterValue: this.hooks.createValueGrip(getterValue),
- getterPrototypeLevel: level,
- enumerable: desc.enumerable,
- writable: level == 0 ? desc.writable : true,
- };
- if (limit && ++i == limit) {
- break;
- }
- }
- }
- }
- if (limit && i == limit) {
- break;
- }
-
- obj = obj.proto;
- level++;
- }
-
- return safeGetterValues;
- },
-
- /**
- * Find the safe getters for a given Debugger.Object. Safe getters are native
- * getters which are safe to execute.
- *
- * @private
- * @param Debugger.Object object
- * The Debugger.Object where you want to find safe getters.
- * @return Set
- * A Set of names of safe getters. This result is cached for each
- * Debugger.Object.
- */
- _findSafeGetters: function(object) {
- if (object._safeGetters) {
- return object._safeGetters;
- }
-
- let getters = new Set();
-
- if (!DevToolsUtils.isSafeDebuggerObject(object)) {
- object._safeGetters = getters;
- return getters;
- }
-
- let names = [];
- try {
- names = object.getOwnPropertyNames();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
-
- for (let name of names) {
- let desc = null;
- try {
- desc = object.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072).
- }
- if (!desc || desc.value !== undefined || !("get" in desc)) {
- continue;
- }
-
- if (DevToolsUtils.hasSafeGetter(desc)) {
- getters.add(name);
- }
- }
-
- object._safeGetters = getters;
- return getters;
- },
-
- /**
- * Handle a protocol request to provide the prototype of the object.
- */
- onPrototype: function() {
- let proto = null;
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- proto = this.obj.proto;
- }
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto) };
- },
-
- /**
- * Handle a protocol request to provide the property descriptor of the
- * object's specified property.
- *
- * @param request object
- * The protocol request object.
- */
- onProperty: function(request) {
- if (!request.name) {
- return { error: "missingParameter",
- message: "no property name was specified" };
- }
-
- return { from: this.actorID,
- descriptor: this._propertyDescriptor(request.name) };
- },
-
- /**
- * Handle a protocol request to provide the display string for the object.
- */
- onDisplayString: function() {
- const string = stringify(this.obj);
- return { from: this.actorID,
- displayString: this.hooks.createValueGrip(string) };
- },
-
- /**
- * A helper method that creates a property descriptor for the provided object,
- * properly formatted for sending in a protocol response.
- *
- * @private
- * @param string name
- * The property that the descriptor is generated for.
- * @param boolean [onlyEnumerable]
- * Optional: true if you want a descriptor only for an enumerable
- * property, false otherwise.
- * @return object|undefined
- * The property descriptor, or undefined if this is not an enumerable
- * property and onlyEnumerable=true.
- */
- _propertyDescriptor: function(name, onlyEnumerable) {
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- return undefined;
- }
-
- let desc;
- try {
- desc = this.obj.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072). Inform the user with a bogus, but hopefully
- // explanatory, descriptor.
- return {
- configurable: false,
- writable: false,
- enumerable: false,
- value: e.name
- };
- }
-
- if (!desc || onlyEnumerable && !desc.enumerable) {
- return undefined;
- }
-
- let retval = {
- configurable: desc.configurable,
- enumerable: desc.enumerable
- };
-
- if ("value" in desc) {
- retval.writable = desc.writable;
- retval.value = this.hooks.createValueGrip(desc.value);
- } else {
- if ("get" in desc) {
- retval.get = this.hooks.createValueGrip(desc.get);
- }
- if ("set" in desc) {
- retval.set = this.hooks.createValueGrip(desc.set);
- }
- }
- return retval;
- },
-
- /**
- * Handle a protocol request to provide the source code of a function.
- *
- * @param request object
- * The protocol request object.
- */
- onDecompile: function(request) {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "decompile request is only valid for object grips " +
- "with a 'Function' class." };
- }
-
- return { from: this.actorID,
- decompiledCode: this.obj.decompile(!!request.pretty) };
- },
-
- /**
- * Handle a protocol request to provide the parameters of a function.
- */
- onParameterNames: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "'parameterNames' request is only valid for object " +
- "grips with a 'Function' class." };
- }
-
- return { parameterNames: this.obj.parameterNames };
- },
-
- /**
- * Handle a protocol request to release a thread-lifetime grip.
- */
- onRelease: function() {
- this.release();
- return {};
- },
-
- /**
- * Handle a protocol request to provide the lexical scope of a function.
- */
- onScope: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "scope request is only valid for object grips with a" +
- " 'Function' class." };
- }
-
- let envActor = this.hooks.createEnvironmentActor(this.obj.environment,
- this.registeredPool);
- if (!envActor) {
- return { error: "notDebuggee",
- message: "cannot access the environment of this function." };
- }
-
- return { from: this.actorID, scope: envActor.form() };
- },
-
- /**
- * Handle a protocol request to get the list of dependent promises of a
- * promise.
- *
- * @return object
- * Returns an object containing an array of object grips of the
- * dependent promises
- */
- onDependentPromises: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'dependentPromises' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let promises = this.obj.promiseDependentPromises
- .map(p => this.hooks.createValueGrip(p));
-
- return { promises };
- },
-
- /**
- * Handle a protocol request to get the allocation stack of a promise.
- */
- onAllocationStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'allocationStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseAllocationSite;
- let allocationStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- allocationStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(allocationStacks).then(stacks => {
- return { allocationStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the fulfillment stack of a promise.
- */
- onFulfillmentStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'fulfillmentStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let fulfillmentStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- fulfillmentStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(fulfillmentStacks).then(stacks => {
- return { fulfillmentStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the rejection stack of a promise.
- */
- onRejectionStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'rejectionStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let rejectionStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- rejectionStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(rejectionStacks).then(stacks => {
- return { rejectionStack: stacks };
- });
- },
-
- /**
- * Helper function for fetching the source location of a SavedFrame stack.
- *
- * @param SavedFrame stack
- * The promise allocation stack frame
- * @return object
- * Returns an object containing the source location of the SavedFrame
- * stack.
- */
- _getSourceOriginalLocation: function(stack) {
- let source;
-
- // Catch any errors if the source actor cannot be found
- try {
- source = this.hooks.sources().getSourceActorByURL(stack.source);
- } catch (e) {
- // ignored
- }
-
- if (!source) {
- return null;
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- source,
- stack.line,
- stack.column
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn,
- functionDisplayName: stack.functionDisplayName
- };
- });
- }
-};
-
-ObjectActor.prototype.requestTypes = {
- "definitionSite": ObjectActor.prototype.onDefinitionSite,
- "parameterNames": ObjectActor.prototype.onParameterNames,
- "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
- "enumProperties": ObjectActor.prototype.onEnumProperties,
- "prototype": ObjectActor.prototype.onPrototype,
- "property": ObjectActor.prototype.onProperty,
- "displayString": ObjectActor.prototype.onDisplayString,
- "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
- "decompile": ObjectActor.prototype.onDecompile,
- "release": ObjectActor.prototype.onRelease,
- "scope": ObjectActor.prototype.onScope,
- "dependentPromises": ObjectActor.prototype.onDependentPromises,
- "allocationStack": ObjectActor.prototype.onAllocationStack,
- "fulfillmentStack": ObjectActor.prototype.onFulfillmentStack,
- "rejectionStack": ObjectActor.prototype.onRejectionStack,
- "enumEntries": ObjectActor.prototype.onEnumEntries,
- "enumSymbols": ObjectActor.prototype.onEnumSymbols,
-};
-
-/**
- * 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.
- */
-function PropertyIteratorActor(objectActor, options) {
- if (!DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- this.iterator = {
- size: 0,
- propertyName: index => undefined,
- propertyDescription: index => undefined,
- };
- } else if (options.enumEntries) {
- let cls = objectActor.obj.class;
- if (cls == "Map") {
- this.iterator = enumMapEntries(objectActor);
- } else if (cls == "WeakMap") {
- this.iterator = enumWeakMapEntries(objectActor);
- } else if (cls == "Set") {
- this.iterator = enumSetEntries(objectActor);
- } else if (cls == "WeakSet") {
- this.iterator = enumWeakSetEntries(objectActor);
- } else {
- throw new Error("Unsupported class to enumerate entries from: " + cls);
- }
- } else if (
- isArray(objectActor.obj)
- && options.ignoreNonIndexedProperties
- && !options.query
- ) {
- this.iterator = enumArrayProperties(objectActor, options);
- } else {
- this.iterator = enumObjectProperties(objectActor, options);
- }
-}
-
-PropertyIteratorActor.prototype = {
- actorPrefix: "propertyIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- names({ indexes }) {
- let list = [];
- for (let idx of indexes) {
- list.push(this.iterator.propertyName(idx));
- }
- return {
- names: indexes
- };
- },
-
- slice({ start, count }) {
- let ownProperties = Object.create(null);
- for (let i = start, m = start + count; i < m; i++) {
- let name = this.iterator.propertyName(i);
- ownProperties[name] = this.iterator.propertyDescription(i);
- }
- return {
- ownProperties
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-PropertyIteratorActor.prototype.requestTypes = {
- "names": PropertyIteratorActor.prototype.names,
- "slice": PropertyIteratorActor.prototype.slice,
- "all": PropertyIteratorActor.prototype.all,
-};
-
-function enumArrayProperties(objectActor, options) {
- return {
- size: getArrayLength(objectActor.obj),
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- return objectActor._propertyDescriptor(index);
- }
- };
-}
-
-function enumObjectProperties(objectActor, options) {
- 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) {
- let length = DevToolsUtils.getProperty(objectActor.obj, "length");
- let sliceIndex;
-
- const isLengthTrustworthy =
- isUint32(length)
- && (!length || isArrayIndex(names[length - 1]))
- && !isArrayIndex(names[length]);
-
- if (!isLengthTrustworthy) {
- // The length property may not reflect what the object looks like, let's find
- // where indexed properties end.
-
- if (!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 (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,
- // like webaudio buffers.
- if (options.ignoreIndexedProperties) {
- // Keep items after `sliceIndex` index
- names = names.slice(sliceIndex);
- } else if (options.ignoreNonIndexedProperties) {
- // Keep `sliceIndex` first items
- names.length = sliceIndex;
- }
- }
-
- let safeGetterValues = objectActor._findSafeGetterValues(names, 0);
- let safeGetterNames = Object.keys(safeGetterValues);
- // Merge the safe getter values into the existing properties list.
- for (let 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 && 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) {
- let name = names[index];
- let desc = objectActor._propertyDescriptor(name);
- if (!desc) {
- desc = safeGetterValues[name];
- } else if (name in safeGetterValues) {
- // Merge the safe getter values into the existing properties list.
- let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
- desc.getterValue = getterValue;
- desc.getterPrototypeLevel = getterPrototypeLevel;
- }
- return desc;
- }
- };
-}
-
-/**
- * Helper function to create a grip from a Map/Set entry
- */
-function gripFromEntry({ obj, hooks }, entry) {
- return hooks.createValueGrip(
- makeDebuggeeValueIfNeeded(obj, Cu.unwaiveXrays(entry)));
-}
-
-function enumMapEntries(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.
- let raw = objectActor.obj.unsafeDereference();
-
- let keys = [...Cu.waiveXrays(Map.prototype.keys.call(raw))];
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = Map.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = Map.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
-}
-
-function enumWeakMapEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakMapKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = WeakMap.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = WeakMap.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
-}
-
-function enumSetEntries(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.
- let raw = objectActor.obj.unsafeDereference();
- let values = [...Cu.waiveXrays(Set.prototype.values.call(raw))];
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of values) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: values.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = values[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-function enumWeakSetEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakSetKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of keys) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = keys[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-/**
- * Creates an actor to iterate over an object's symbols.
- *
- * @param objectActor ObjectActor
- * The object actor.
- */
-function SymbolIteratorActor(objectActor) {
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- try {
- symbols = objectActor.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- this.iterator = {
- size: symbols.length,
- symbolDescription(index) {
- const symbol = symbols[index];
- return {
- name: symbol.toString(),
- descriptor: objectActor._propertyDescriptor(symbol)
- };
- }
- };
-}
-
-SymbolIteratorActor.prototype = {
- actorPrefix: "symbolIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- slice({ start, count }) {
- let ownSymbols = [];
- for (let i = start, m = start + count; i < m; i++) {
- ownSymbols.push(this.iterator.symbolDescription(i));
- }
- return {
- ownSymbols
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-SymbolIteratorActor.prototype.requestTypes = {
- "slice": SymbolIteratorActor.prototype.slice,
- "all": SymbolIteratorActor.prototype.all,
-};
-
-/**
* Functions for adding information to ObjectActor grips for the purpose of
* having customized output. This object holds arrays mapped by
* Debugger.Object.prototype.class.
*
* In each array you can add functions that take three
* arguments:
* - the ObjectActor instance and its hooks to make a preview for,
* - the grip object being prepared for the client,
* - the raw JS object after calling Debugger.Object.unsafeDereference(). This
* argument is only provided if the object is safe for reading properties and
* executing methods. See DevToolsUtils.isSafeJSObject().
*
* Functions must return false if they cannot provide preview
* information for the debugger object, or true otherwise.
*/
-DebuggerServer.ObjectActorPreviewers = {
+const previewers = {
String: [function(objectActor, grip, rawObj) {
return wrappedPrimitivePreviewer("String", String, objectActor, grip, rawObj);
}],
Boolean: [function(objectActor, grip, rawObj) {
return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip, rawObj);
}],
@@ -1342,17 +109,17 @@ DebuggerServer.ObjectActorPreviewers = {
grip.preview = {
timestamp: hooks.createValueGrip(time),
};
return true;
}],
Array: [function({obj, hooks}, grip) {
- let length = getArrayLength(obj);
+ let length = ObjectUtils.getArrayLength(obj);
grip.preview = {
kind: "ArrayLike",
length: length,
};
if (hooks.getGripDepth() > 1) {
return true;
@@ -1366,17 +133,17 @@ DebuggerServer.ObjectActorPreviewers = {
// functions, and claim that the value is undefined instead. This
// is generally the right thing for privileged code accessing untrusted
// objects, but quite confusing for Object previews. So we manually
// override this protection by waiving Xrays on the array, and re-applying
// Xrays on any indexed value props that we pull off of it.
let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
if (desc && !desc.get && !desc.set) {
let value = Cu.unwaiveXrays(desc.value);
- value = makeDebuggeeValueIfNeeded(obj, value);
+ value = ObjectUtils.makeDebuggeeValueIfNeeded(obj, value);
items.push(hooks.createValueGrip(value));
} else {
items.push(null);
}
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
@@ -1397,28 +164,28 @@ DebuggerServer.ObjectActorPreviewers = {
};
// Avoid recursive object grips.
if (objectActor.hooks.getGripDepth() > 1) {
return true;
}
let items = grip.preview.items = [];
- for (let item of enumSetEntries(objectActor)) {
+ for (let item of PropertyIterators.enumSetEntries(objectActor)) {
items.push(item);
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
return true;
}],
WeakSet: [function(objectActor, grip) {
- let enumEntries = enumWeakSetEntries(objectActor);
+ let enumEntries = PropertyIterators.enumWeakSetEntries(objectActor);
grip.preview = {
kind: "ArrayLike",
length: enumEntries.size
};
// Avoid recursive object grips.
if (objectActor.hooks.getGripDepth() > 1) {
@@ -1447,28 +214,28 @@ DebuggerServer.ObjectActorPreviewers = {
size: size,
};
if (objectActor.hooks.getGripDepth() > 1) {
return true;
}
let entries = grip.preview.entries = [];
- for (let entry of enumMapEntries(objectActor)) {
+ for (let entry of PropertyIterators.enumMapEntries(objectActor)) {
entries.push(entry);
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
return true;
}],
WeakMap: [function(objectActor, grip) {
- let enumEntries = enumWeakMapEntries(objectActor);
+ let enumEntries = PropertyIterators.enumWeakMapEntries(objectActor);
grip.preview = {
kind: "MapLike",
size: enumEntries.size
};
if (objectActor.hooks.getGripDepth() > 1) {
return true;
@@ -1497,17 +264,17 @@ DebuggerServer.ObjectActorPreviewers = {
};
if (hooks.getGripDepth() > 1) {
return true;
}
let entries = grip.preview.entries = [];
for (let key of keys) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
+ let value = ObjectUtils.makeDebuggeeValueIfNeeded(obj, rawObj[key]);
entries.push([key, hooks.createValueGrip(value)]);
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
return true;
}],
@@ -1571,17 +338,17 @@ function wrappedPrimitivePreviewer(class
}
let canHandle = GenericObject(objectActor, grip, rawObj, className === "String");
if (!canHandle) {
return false;
}
grip.preview.wrappedValue =
- hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v));
+ hooks.createValueGrip(ObjectUtils.makeDebuggeeValueIfNeeded(obj, v));
return true;
}
function GenericObject(objectActor, grip, rawObj, specialStringBehavior = false) {
let {obj, hooks} = objectActor;
if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
return false;
}
@@ -1650,25 +417,25 @@ function GenericObject(objectActor, grip
Object.keys(preview.ownProperties),
OBJECT_PREVIEW_MAX_ITEMS - i);
}
return true;
}
// Preview functions that do not rely on the object class.
-DebuggerServer.ObjectActorPreviewers.Object = [
+previewers.Object = [
function TypedArray({obj, hooks}, grip) {
- if (!isTypedArray(obj)) {
+ if (!ObjectUtils.isTypedArray(obj)) {
return false;
}
grip.preview = {
kind: "ArrayLike",
- length: getArrayLength(obj),
+ length: ObjectUtils.getArrayLength(obj),
};
if (hooks.getGripDepth() > 1) {
return true;
}
let raw = obj.unsafeDereference();
let global = Cu.getGlobalForObject(DebuggerServer);
@@ -1788,17 +555,17 @@ DebuggerServer.ObjectActorPreviewers.Obj
if (hooks.getGripDepth() > 1) {
return true;
}
let items = grip.preview.items = [];
for (let i = 0; i < rawObj.length &&
items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[i]);
+ let value = ObjectUtils.makeDebuggeeValueIfNeeded(obj, rawObj[i]);
items.push(hooks.createValueGrip(value));
}
return true;
},
function CSSStyleDeclaration({obj, hooks}, grip, rawObj) {
if (isWorker || !rawObj ||
@@ -2010,17 +777,17 @@ DebuggerServer.ObjectActorPreviewers.Obj
--length;
if (length === 0 || length !== DevToolsUtils.getProperty(obj, "length")) {
return false;
}
}
// Check that the last key is the array index expected at that position.
let lastKey = keys[length - 1];
- if (!isArrayIndex(lastKey) || +lastKey !== length - 1) {
+ if (!ObjectUtils.isArrayIndex(lastKey) || +lastKey !== length - 1) {
return false;
}
grip.preview = {
kind: "ArrayLike",
length: length,
};
@@ -2044,646 +811,9 @@ DebuggerServer.ObjectActorPreviewers.Obj
return true;
},
function Object(objectActor, grip, rawObj) {
return GenericObject(objectActor, grip, rawObj, /* specialStringBehavior = */ false);
},
];
-/**
- * Get thisDebugger.Object referent's `promiseState`.
- *
- * @returns Object
- * An object of one of the following forms:
- * - { state: "pending" }
- * - { state: "fulfilled", value }
- * - { state: "rejected", reason }
- */
-function getPromiseState(obj) {
- if (obj.class != "Promise") {
- throw new Error(
- "Can't call `getPromiseState` on `Debugger.Object`s that don't " +
- "refer to Promise objects.");
- }
-
- let state = { state: obj.promiseState };
- if (state.state === "fulfilled") {
- state.value = obj.promiseValue;
- } else if (state.state === "rejected") {
- state.reason = obj.promiseReason;
- }
- return state;
-}
-
-/**
- * Determine if a given value is non-primitive.
- *
- * @param Any value
- * The value to test.
- * @return Boolean
- * Whether the value is non-primitive.
- */
-function isObject(value) {
- const type = typeof value;
- return type == "object" ? value !== null : type == "function";
-}
-
-/**
- * Create a function that can safely stringify Debugger.Objects of a given
- * builtin type.
- *
- * @param Function ctor
- * The builtin class constructor.
- * @return Function
- * The stringifier for the class.
- */
-function createBuiltinStringifier(ctor) {
- return obj => {
- try {
- return ctor.prototype.toString.call(obj.unsafeDereference());
- } catch (err) {
- // The debuggee will see a "Function" class if the object is callable and
- // its compartment is not subsumed. The above will throw if it's not really
- // a function, e.g. if it's a callable proxy.
- return "[object " + obj.class + "]";
- }
- };
-}
-
-/**
- * Stringify a Debugger.Object-wrapped Error instance.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification of the object.
- */
-function errorStringify(obj) {
- let name = DevToolsUtils.getProperty(obj, "name");
- if (name === "" || name === undefined) {
- name = obj.class;
- } else if (isObject(name)) {
- name = stringify(name);
- }
-
- let message = DevToolsUtils.getProperty(obj, "message");
- if (isObject(message)) {
- message = stringify(message);
- }
-
- if (message === "" || message === undefined) {
- return name;
- }
- return name + ": " + message;
-}
-
-/**
- * Stringify a Debugger.Object based on its class.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification for the object.
- */
-function stringify(obj) {
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- if (DevToolsUtils.isCPOW(obj)) {
- return "<cpow>";
- }
- let unwrapped = DevToolsUtils.unwrap(obj);
- if (unwrapped === undefined) {
- return "<invisibleToDebugger>";
- } else if (unwrapped.isProxy) {
- return "<proxy>";
- }
- // The following line should not be reached. It's there just in case somebody
- // modifies isSafeDebuggerObject to return false for additional kinds of objects.
- return "[object " + obj.class + "]";
- } else if (obj.class == "DeadObject") {
- return "<dead object>";
- }
-
- const stringifier = stringifiers[obj.class] || stringifiers.Object;
-
- try {
- return stringifier(obj);
- } catch (e) {
- DevToolsUtils.reportException("stringify", e);
- return "<failed to stringify object>";
- }
-}
-
-// Used to prevent infinite recursion when an array is found inside itself.
-var seen = null;
-
-var stringifiers = {
- Error: errorStringify,
- EvalError: errorStringify,
- RangeError: errorStringify,
- ReferenceError: errorStringify,
- SyntaxError: errorStringify,
- TypeError: errorStringify,
- URIError: errorStringify,
- Boolean: createBuiltinStringifier(Boolean),
- Function: createBuiltinStringifier(Function),
- Number: createBuiltinStringifier(Number),
- RegExp: createBuiltinStringifier(RegExp),
- String: createBuiltinStringifier(String),
- Object: obj => "[object " + obj.class + "]",
- Array: obj => {
- // If we're at the top level then we need to create the Set for tracking
- // previously stringified arrays.
- const topLevel = !seen;
- if (topLevel) {
- seen = new Set();
- } else if (seen.has(obj)) {
- return "";
- }
-
- seen.add(obj);
-
- const len = getArrayLength(obj);
- let string = "";
-
- // Array.length is always a non-negative safe integer.
- for (let i = 0; i < len; i++) {
- const desc = obj.getOwnPropertyDescriptor(i);
- if (desc) {
- const { value } = desc;
- if (value != null) {
- string += isObject(value) ? stringify(value) : value;
- }
- }
-
- if (i < len - 1) {
- string += ",";
- }
- }
-
- if (topLevel) {
- seen = null;
- }
-
- return string;
- },
- DOMException: obj => {
- const message = DevToolsUtils.getProperty(obj, "message") || "<no message>";
- const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16);
- const code = DevToolsUtils.getProperty(obj, "code");
- const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
-
- return '[Exception... "' + message + '" ' +
- 'code: "' + code + '" ' +
- 'nsresult: "0x' + result + " (" + name + ')"]';
- },
- Promise: obj => {
- const { state, value, reason } = getPromiseState(obj);
- let statePreview = state;
- if (state != "pending") {
- const settledValue = state === "fulfilled" ? value : reason;
- statePreview += ": " + (typeof settledValue === "object" && settledValue !== null
- ? stringify(settledValue)
- : settledValue);
- }
- return "Promise (" + statePreview + ")";
- },
-};
-
-/**
- * Make a debuggee value for the given object, if needed. Primitive values
- * are left the same.
- *
- * Use case: you have a raw JS object (after unsafe dereference) and you want to
- * send it to the client. In that case you need to use an ObjectActor which
- * requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
- * method works only for JS objects and functions.
- *
- * @param Debugger.Object obj
- * @param any value
- * @return object
- */
-function makeDebuggeeValueIfNeeded(obj, value) {
- if (value && (typeof value == "object" || typeof value == "function")) {
- return obj.makeDebuggeeValue(value);
- }
- return value;
-}
-
-/**
- * Creates an actor for the specified "very long" string. "Very long" is specified
- * at the server's discretion.
- *
- * @param string String
- * The string.
- */
-function LongStringActor(string) {
- this.string = string;
- this.stringLength = string.length;
-}
-
-LongStringActor.prototype = {
- actorPrefix: "longString",
-
- rawValue: function() {
- return this.string;
- },
-
- destroy: function() {
- // Because longStringActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- return {
- "type": "longString",
- "initial": this.string.substring(
- 0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
- "length": this.stringLength,
- "actor": this.actorID
- };
- },
-
- /**
- * Handle a request to extract part of this actor's string.
- *
- * @param request object
- * The protocol request object.
- */
- onSubstring: function(request) {
- return {
- "from": this.actorID,
- "substring": this.string.substring(request.start, request.end)
- };
- },
-
- /**
- * Handle a request to release this LongStringActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.longStringActors) {
- delete this.registeredPool.longStringActors[this.string];
- }
- }
-};
-
-LongStringActor.prototype.requestTypes = {
- "substring": LongStringActor.prototype.onSubstring,
- "release": LongStringActor.prototype.onRelease
-};
-
-/**
- * Creates an actor for the specified symbol.
- *
- * @param symbol Symbol
- * The symbol.
- */
-function SymbolActor(symbol) {
- this.symbol = symbol;
-}
-
-SymbolActor.prototype = {
- actorPrefix: "symbol",
-
- rawValue: function() {
- return this.symbol;
- },
-
- destroy: function() {
- // Because symbolActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let form = {
- type: "symbol",
- actor: this.actorID,
- };
- let name = getSymbolName(this.symbol);
- if (name !== undefined) {
- // Create a grip for the name because it might be a longString.
- form.name = createValueGrip(name, this.registeredPool);
- }
- return form;
- },
-
- /**
- * Handle a request to release this SymbolActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.symbolActors) {
- delete this.registeredPool.symbolActors[this.symbol];
- }
- }
-};
-
-SymbolActor.prototype.requestTypes = {
- "release": SymbolActor.prototype.onRelease
-};
-
-/**
- * Creates an actor for the specified ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The buffer.
- */
-function ArrayBufferActor(buffer) {
- this.buffer = buffer;
- this.bufferLength = buffer.byteLength;
-}
-
-ArrayBufferActor.prototype = {
- actorPrefix: "arrayBuffer",
-
- rawValue: function() {
- return this.buffer;
- },
-
- destroy: function() {
- },
-
- grip() {
- return {
- "type": "arrayBuffer",
- "length": this.bufferLength,
- "actor": this.actorID
- };
- },
-
- onSlice({start, count}) {
- let slice = new Uint8Array(this.buffer, start, count);
- let parts = [], offset = 0;
- const PortionSize = 0x6000; // keep it divisible by 3 for btoa() and join()
- while (offset + PortionSize < count) {
- parts.push(btoa(
- String.fromCharCode.apply(null, slice.subarray(offset, offset + PortionSize))));
- offset += PortionSize;
- }
- parts.push(btoa(String.fromCharCode.apply(null, slice.subarray(offset, count))));
- return {
- "from": this.actorID,
- "encoded": parts.join(""),
- };
- }
-};
-
-ArrayBufferActor.prototype.requestTypes = {
- "slice": ArrayBufferActor.prototype.onSlice,
-};
-
-/**
- * Create a grip for the given debuggee value. If the value is an
- * object, will create an actor with the given lifetime.
- */
-function createValueGrip(value, pool, makeObjectGrip) {
- switch (typeof value) {
- case "boolean":
- return value;
-
- case "string":
- if (stringIsLong(value)) {
- return longStringGrip(value, pool);
- }
- return value;
-
- case "number":
- if (value === Infinity) {
- return { type: "Infinity" };
- } else if (value === -Infinity) {
- return { type: "-Infinity" };
- } else if (Number.isNaN(value)) {
- return { type: "NaN" };
- } else if (!value && 1 / value === -Infinity) {
- return { type: "-0" };
- }
- return value;
-
- case "undefined":
- return { type: "undefined" };
-
- case "object":
- if (value === null) {
- return { type: "null" };
- } else if (value.optimizedOut ||
- value.uninitialized ||
- value.missingArguments) {
- // The slot is optimized out, an uninitialized binding, or
- // arguments on a dead scope
- return {
- type: "null",
- optimizedOut: value.optimizedOut,
- uninitialized: value.uninitialized,
- missingArguments: value.missingArguments
- };
- }
- return makeObjectGrip(value, pool);
-
- case "symbol":
- return symbolGrip(value, pool);
-
- default:
- assert(false, "Failed to provide a grip for: " + value);
- return null;
- }
-}
-
-const symbolProtoToString = Symbol.prototype.toString;
-
-function getSymbolName(symbol) {
- const name = symbolProtoToString.call(symbol).slice("Symbol(".length, -1);
- return name || undefined;
-}
-
-/**
- * Returns true if the string is long enough to use a LongStringActor instead
- * of passing the value directly over the protocol.
- *
- * @param str String
- * The string we are checking the length of.
- */
-function stringIsLong(str) {
- return str.length >= DebuggerServer.LONG_STRING_LENGTH;
-}
-
-/**
- * Create a grip for the given string.
- *
- * @param str String
- * The string we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function longStringGrip(str, pool) {
- if (!pool.longStringActors) {
- pool.longStringActors = {};
- }
-
- if (pool.longStringActors.hasOwnProperty(str)) {
- return pool.longStringActors[str].grip();
- }
-
- let actor = new LongStringActor(str);
- pool.addActor(actor);
- pool.longStringActors[str] = actor;
- return actor.grip();
-}
-
-/**
- * Create a grip for the given symbol.
- *
- * @param sym Symbol
- * The symbol we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function symbolGrip(sym, pool) {
- if (!pool.symbolActors) {
- pool.symbolActors = Object.create(null);
- }
-
- if (sym in pool.symbolActors) {
- return pool.symbolActors[sym].grip();
- }
-
- let actor = new SymbolActor(sym);
- pool.addActor(actor);
- pool.symbolActors[sym] = actor;
- return actor.grip();
-}
-
-/**
- * Create a grip for the given ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The ArrayBuffer we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function arrayBufferGrip(buffer, pool) {
- if (!pool.arrayBufferActors) {
- pool.arrayBufferActors = new WeakMap();
- }
-
- if (pool.arrayBufferActors.has(buffer)) {
- return pool.arrayBufferActors.get(buffer).grip();
- }
-
- let actor = new ArrayBufferActor(buffer);
- pool.addActor(actor);
- pool.arrayBufferActors.set(buffer, actor);
- return actor.grip();
-}
-
-const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
- "Uint32Array", "Int8Array", "Int16Array", "Int32Array",
- "Float32Array", "Float64Array"];
-
-/**
- * Returns true if a debuggee object is a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isTypedArray(object) {
- return TYPED_ARRAY_CLASSES.includes(object.class);
-}
-
-/**
- * Returns true if a debuggee object is an array, including a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isArray(object) {
- return isTypedArray(object) || object.class === "Array";
-}
-
-/**
- * Returns the length of an array (or typed array).
- *
- * @param obj Debugger.Object
- * The debuggee object of the array.
- * @return Number
- * @throws if the object is not an array.
- */
-function getArrayLength(object) {
- if (!isArray(object)) {
- throw new Error("Expected an array, got a " + object.class);
- }
-
- // Real arrays have a reliable `length` own property.
- if (object.class === "Array") {
- return DevToolsUtils.getProperty(object, "length");
- }
-
- // For typed arrays, `DevToolsUtils.getProperty` is not reliable because the `length`
- // getter could be shadowed by an own property, and `getOwnPropertyNames` is
- // unnecessarily slow. Obtain the `length` getter safely and call it manually.
- let typedProto = Object.getPrototypeOf(Uint8Array.prototype);
- let getter = Object.getOwnPropertyDescriptor(typedProto, "length").get;
- return getter.call(object.unsafeDereference());
-}
-
-/**
- * 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;
-}
-
-/**
- * Returns true if the parameter is suitable to be an array index.
- *
- * @param str String
- * @return Boolean
- */
-function isArrayIndex(str) {
- // Transform the parameter to a 32-bit unsigned integer.
- let num = str >>> 0;
- // Check that the parameter is a canonical Uint32 index.
- return num + "" === str &&
- // Array indices cannot attain the maximum Uint32 value.
- num != -1 >>> 0;
-}
-
-exports.ObjectActor = ObjectActor;
-exports.PropertyIteratorActor = PropertyIteratorActor;
-exports.LongStringActor = LongStringActor;
-exports.SymbolActor = SymbolActor;
-exports.createValueGrip = createValueGrip;
-exports.stringIsLong = stringIsLong;
-exports.longStringGrip = longStringGrip;
-exports.arrayBufferGrip = arrayBufferGrip;
+module.exports = previewers;
copy from devtools/server/actors/object.js
copy to devtools/server/actors/object/property-iterator.js
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object/property-iterator.js
@@ -1,812 +1,20 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 { Cu, Ci } = require("chrome");
-const { GeneratedLocation } = require("devtools/server/actors/common");
-const { DebuggerServer } = require("devtools/server/main");
+const { Cu } = require("chrome");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { assert } = DevToolsUtils;
-
loader.lazyRequireGetter(this, "ChromeUtils");
-
-// Number of items to preview in objects, arrays, maps, sets, lists,
-// collections, etc.
-const OBJECT_PREVIEW_MAX_ITEMS = 10;
-
-/**
- * Creates an actor for the specified object.
- *
- * @param obj Debugger.Object
- * The debuggee object.
- * @param hooks Object
- * A collection of abstract methods that are implemented by the caller.
- * ObjectActor requires the following functions to be implemented by
- * the caller:
- * - createValueGrip
- * Creates a value grip for the given object
- * - sources
- * TabSources getter that manages the sources of a thread
- * - createEnvironmentActor
- * Creates and return an environment actor
- * - getGripDepth
- * An actor's grip depth getter
- * - incrementGripDepth
- * Increment the actor's grip depth
- * - decrementGripDepth
- * Decrement the actor's grip depth
- * - globalDebugObject
- * The Debuggee Global Object as given by the ThreadActor
- */
-function ObjectActor(obj, {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
-}) {
- assert(!obj.optimizedOut,
- "Should not create object actors for optimized out values!");
- this.obj = obj;
- this.hooks = {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
- };
- this.iterators = new Set();
-}
-
-ObjectActor.prototype = {
- actorPrefix: "obj",
-
- rawValue: function() {
- return this.obj.unsafeDereference();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let g = {
- "type": "object",
- "actor": this.actorID,
- "class": this.obj.class,
- };
-
- let unwrapped = DevToolsUtils.unwrap(this.obj);
-
- // Unsafe objects must be treated carefully.
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- if (DevToolsUtils.isCPOW(this.obj)) {
- // Cross-process object wrappers can't be accessed.
- g.class = "CPOW: " + g.class;
- } else if (unwrapped === undefined) {
- // Objects belonging to an invisible-to-debugger compartment might be proxies,
- // so just in case they shouldn't be accessed.
- g.class = "InvisibleToDebugger: " + g.class;
- } else if (unwrapped.isProxy) {
- // Proxy objects can run traps when accessed, so just create a preview with
- // the target and the handler.
- g.class = "Proxy";
- this.hooks.incrementGripDepth();
- DebuggerServer.ObjectActorPreviewers.Proxy[0](this, g, null);
- this.hooks.decrementGripDepth();
- }
- return g;
- }
-
- // If the debuggee does not subsume the object's compartment, most properties won't
- // be accessible. Cross-orgin Window and Location objects might expose some, though.
- // Change the displayed class, but when creating the preview use the original one.
- if (unwrapped === null) {
- g.class = "Restricted";
- }
-
- this.hooks.incrementGripDepth();
-
- g.extensible = this.obj.isExtensible();
- g.frozen = this.obj.isFrozen();
- g.sealed = this.obj.isSealed();
-
- if (g.class == "Promise") {
- g.promiseState = this._createPromiseState();
- }
-
- // FF40+: Allow to know how many properties an object has to lazily display them
- // when there is a bunch.
- if (isTypedArray(g)) {
- // Bug 1348761: getOwnPropertyNames is unnecessary slow on TypedArrays
- g.ownPropertyLength = getArrayLength(this.obj);
- } else {
- try {
- g.ownPropertyLength = this.obj.getOwnPropertyNames().length;
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let raw = this.obj.unsafeDereference();
-
- // If Cu is not defined, we are running on a worker thread, where xrays
- // don't exist.
- if (Cu) {
- raw = Cu.unwaiveXrays(raw);
- }
-
- if (!DevToolsUtils.isSafeJSObject(raw)) {
- raw = null;
- }
-
- let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
- DebuggerServer.ObjectActorPreviewers.Object;
- for (let fn of previewers) {
- try {
- if (fn(this, g, raw)) {
- break;
- }
- } catch (e) {
- let msg = "ObjectActor.prototype.grip previewer function";
- DevToolsUtils.reportException(msg, e);
- }
- }
-
- this.hooks.decrementGripDepth();
- return g;
- },
-
- /**
- * Returns an object exposing the internal Promise state.
- */
- _createPromiseState: function() {
- const { state, value, reason } = getPromiseState(this.obj);
- let promiseState = { state };
-
- if (state == "fulfilled") {
- promiseState.value = this.hooks.createValueGrip(value);
- } else if (state == "rejected") {
- promiseState.reason = this.hooks.createValueGrip(reason);
- }
-
- promiseState.creationTimestamp = Date.now() - this.obj.promiseLifetime;
-
- // Only add the timeToSettle property if the Promise isn't pending.
- if (state !== "pending") {
- promiseState.timeToSettle = this.obj.promiseTimeToResolution;
- }
-
- return promiseState;
- },
-
- /**
- * Releases this actor from the pool.
- */
- release: function() {
- if (this.registeredPool.objectActors) {
- this.registeredPool.objectActors.delete(this.obj);
- }
- this.iterators.forEach(actor => this.registeredPool.removeActor(actor));
- this.iterators.clear();
- this.registeredPool.removeActor(this);
- },
-
- /**
- * Handle a protocol request to provide the definition site of this function
- * object.
- */
- onDefinitionSite: function() {
- if (this.obj.class != "Function") {
- return {
- from: this.actorID,
- error: "objectNotFunction",
- message: this.actorID + " is not a function."
- };
- }
-
- if (!this.obj.script) {
- return {
- from: this.actorID,
- error: "noScript",
- message: this.actorID + " has no Debugger.Script"
- };
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- this.hooks.sources().createNonSourceMappedActor(this.obj.script.source),
- this.obj.script.startLine,
- 0 // TODO bug 901138: use Debugger.Script.prototype.startColumn
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn
- };
- });
- },
-
- /**
- * Handle a protocol request to provide the names of the properties defined on
- * the object and not its prototype.
- */
- onOwnPropertyNames: function() {
- let props = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- props = this.obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
- return { from: this.actorID, ownPropertyNames: props };
- },
-
- /**
- * Creates an actor to iterate over an object property names and values.
- * See PropertyIteratorActor constructor for more info about options param.
- *
- * @param request object
- * The protocol request object.
- */
- onEnumProperties: function(request) {
- let actor = new PropertyIteratorActor(this, request.options);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over entries of a Map/Set-like object.
- */
- onEnumEntries: function() {
- let actor = new PropertyIteratorActor(this, { enumEntries: true });
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over an object symbols properties.
- */
- onEnumSymbols: function() {
- let actor = new SymbolIteratorActor(this);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Handle a protocol request to provide the prototype and own properties of
- * the object.
- *
- * @returns {Object} An object containing the data of this.obj, of the following form:
- * - {string} from: this.obj's actorID.
- * - {Object} prototype: The descriptor of this.obj's prototype.
- * - {Object} ownProperties: an object where the keys are the names of the
- * this.obj's ownProperties, and the values the descriptors of
- * the properties.
- * - {Array} ownSymbols: An array containing all descriptors of this.obj's
- * ownSymbols. Here we have an array, and not an object like for
- * ownProperties, because we can have multiple symbols with the same
- * name in this.obj, e.g. `{[Symbol()]: "a", [Symbol()]: "b"}`.
- * - {Object} safeGetterValues: an object that maps this.obj's property names
- * with safe getters descriptors.
- */
- onPrototypeAndProperties: function() {
- let proto = null;
- let names = [];
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- proto = this.obj.proto;
- names = this.obj.getOwnPropertyNames();
- symbols = this.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let ownProperties = Object.create(null);
- let ownSymbols = [];
-
- for (let name of names) {
- ownProperties[name] = this._propertyDescriptor(name);
- }
-
- for (let sym of symbols) {
- ownSymbols.push({
- name: sym.toString(),
- descriptor: this._propertyDescriptor(sym)
- });
- }
-
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto),
- ownProperties,
- ownSymbols,
- safeGetterValues: this._findSafeGetterValues(names) };
- },
-
- /**
- * Find the safe getter values for the current Debugger.Object, |this.obj|.
- *
- * @private
- * @param array ownProperties
- * The array that holds the list of known ownProperties names for
- * |this.obj|.
- * @param number [limit=0]
- * Optional limit of getter values to find.
- * @return object
- * An object that maps property names to safe getter descriptors as
- * defined by the remote debugging protocol.
- */
- _findSafeGetterValues: function(ownProperties, limit = 0) {
- let safeGetterValues = Object.create(null);
- let obj = this.obj;
- let level = 0, i = 0;
-
- // Do not search safe getters in unsafe objects.
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- return safeGetterValues;
- }
-
- // Most objects don't have any safe getters but inherit some from their
- // prototype. Avoid calling getOwnPropertyNames on objects that may have
- // many properties like Array, strings or js objects. That to avoid
- // freezing firefox when doing so.
- if (isArray(this.obj) || ["Object", "String"].includes(this.obj.class)) {
- obj = obj.proto;
- level++;
- }
-
- while (obj && DevToolsUtils.isSafeDebuggerObject(obj)) {
- let getters = this._findSafeGetters(obj);
- for (let name of getters) {
- // Avoid overwriting properties from prototypes closer to this.obj. Also
- // avoid providing safeGetterValues from prototypes if property |name|
- // is already defined as an own property.
- if (name in safeGetterValues ||
- (obj != this.obj && ownProperties.includes(name))) {
- continue;
- }
-
- // Ignore __proto__ on Object.prototye.
- if (!obj.proto && name == "__proto__") {
- continue;
- }
-
- let desc = null, getter = null;
- try {
- desc = obj.getOwnPropertyDescriptor(name);
- getter = desc.get;
- } catch (ex) {
- // The above can throw if the cache becomes stale.
- }
- if (!getter) {
- obj._safeGetters = null;
- continue;
- }
-
- let result = getter.call(this.obj);
- if (result && !("throw" in result)) {
- let getterValue = undefined;
- if ("return" in result) {
- getterValue = result.return;
- } else if ("yield" in result) {
- getterValue = result.yield;
- }
- // WebIDL attributes specified with the LenientThis extended attribute
- // return undefined and should be ignored.
- if (getterValue !== undefined) {
- safeGetterValues[name] = {
- getterValue: this.hooks.createValueGrip(getterValue),
- getterPrototypeLevel: level,
- enumerable: desc.enumerable,
- writable: level == 0 ? desc.writable : true,
- };
- if (limit && ++i == limit) {
- break;
- }
- }
- }
- }
- if (limit && i == limit) {
- break;
- }
-
- obj = obj.proto;
- level++;
- }
-
- return safeGetterValues;
- },
-
- /**
- * Find the safe getters for a given Debugger.Object. Safe getters are native
- * getters which are safe to execute.
- *
- * @private
- * @param Debugger.Object object
- * The Debugger.Object where you want to find safe getters.
- * @return Set
- * A Set of names of safe getters. This result is cached for each
- * Debugger.Object.
- */
- _findSafeGetters: function(object) {
- if (object._safeGetters) {
- return object._safeGetters;
- }
-
- let getters = new Set();
-
- if (!DevToolsUtils.isSafeDebuggerObject(object)) {
- object._safeGetters = getters;
- return getters;
- }
-
- let names = [];
- try {
- names = object.getOwnPropertyNames();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
-
- for (let name of names) {
- let desc = null;
- try {
- desc = object.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072).
- }
- if (!desc || desc.value !== undefined || !("get" in desc)) {
- continue;
- }
-
- if (DevToolsUtils.hasSafeGetter(desc)) {
- getters.add(name);
- }
- }
-
- object._safeGetters = getters;
- return getters;
- },
-
- /**
- * Handle a protocol request to provide the prototype of the object.
- */
- onPrototype: function() {
- let proto = null;
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- proto = this.obj.proto;
- }
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto) };
- },
-
- /**
- * Handle a protocol request to provide the property descriptor of the
- * object's specified property.
- *
- * @param request object
- * The protocol request object.
- */
- onProperty: function(request) {
- if (!request.name) {
- return { error: "missingParameter",
- message: "no property name was specified" };
- }
-
- return { from: this.actorID,
- descriptor: this._propertyDescriptor(request.name) };
- },
-
- /**
- * Handle a protocol request to provide the display string for the object.
- */
- onDisplayString: function() {
- const string = stringify(this.obj);
- return { from: this.actorID,
- displayString: this.hooks.createValueGrip(string) };
- },
-
- /**
- * A helper method that creates a property descriptor for the provided object,
- * properly formatted for sending in a protocol response.
- *
- * @private
- * @param string name
- * The property that the descriptor is generated for.
- * @param boolean [onlyEnumerable]
- * Optional: true if you want a descriptor only for an enumerable
- * property, false otherwise.
- * @return object|undefined
- * The property descriptor, or undefined if this is not an enumerable
- * property and onlyEnumerable=true.
- */
- _propertyDescriptor: function(name, onlyEnumerable) {
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- return undefined;
- }
-
- let desc;
- try {
- desc = this.obj.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072). Inform the user with a bogus, but hopefully
- // explanatory, descriptor.
- return {
- configurable: false,
- writable: false,
- enumerable: false,
- value: e.name
- };
- }
-
- if (!desc || onlyEnumerable && !desc.enumerable) {
- return undefined;
- }
-
- let retval = {
- configurable: desc.configurable,
- enumerable: desc.enumerable
- };
-
- if ("value" in desc) {
- retval.writable = desc.writable;
- retval.value = this.hooks.createValueGrip(desc.value);
- } else {
- if ("get" in desc) {
- retval.get = this.hooks.createValueGrip(desc.get);
- }
- if ("set" in desc) {
- retval.set = this.hooks.createValueGrip(desc.set);
- }
- }
- return retval;
- },
-
- /**
- * Handle a protocol request to provide the source code of a function.
- *
- * @param request object
- * The protocol request object.
- */
- onDecompile: function(request) {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "decompile request is only valid for object grips " +
- "with a 'Function' class." };
- }
-
- return { from: this.actorID,
- decompiledCode: this.obj.decompile(!!request.pretty) };
- },
-
- /**
- * Handle a protocol request to provide the parameters of a function.
- */
- onParameterNames: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "'parameterNames' request is only valid for object " +
- "grips with a 'Function' class." };
- }
-
- return { parameterNames: this.obj.parameterNames };
- },
-
- /**
- * Handle a protocol request to release a thread-lifetime grip.
- */
- onRelease: function() {
- this.release();
- return {};
- },
-
- /**
- * Handle a protocol request to provide the lexical scope of a function.
- */
- onScope: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "scope request is only valid for object grips with a" +
- " 'Function' class." };
- }
-
- let envActor = this.hooks.createEnvironmentActor(this.obj.environment,
- this.registeredPool);
- if (!envActor) {
- return { error: "notDebuggee",
- message: "cannot access the environment of this function." };
- }
-
- return { from: this.actorID, scope: envActor.form() };
- },
-
- /**
- * Handle a protocol request to get the list of dependent promises of a
- * promise.
- *
- * @return object
- * Returns an object containing an array of object grips of the
- * dependent promises
- */
- onDependentPromises: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'dependentPromises' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let promises = this.obj.promiseDependentPromises
- .map(p => this.hooks.createValueGrip(p));
-
- return { promises };
- },
-
- /**
- * Handle a protocol request to get the allocation stack of a promise.
- */
- onAllocationStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'allocationStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseAllocationSite;
- let allocationStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- allocationStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(allocationStacks).then(stacks => {
- return { allocationStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the fulfillment stack of a promise.
- */
- onFulfillmentStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'fulfillmentStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let fulfillmentStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- fulfillmentStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(fulfillmentStacks).then(stacks => {
- return { fulfillmentStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the rejection stack of a promise.
- */
- onRejectionStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'rejectionStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let rejectionStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- rejectionStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(rejectionStacks).then(stacks => {
- return { rejectionStack: stacks };
- });
- },
-
- /**
- * Helper function for fetching the source location of a SavedFrame stack.
- *
- * @param SavedFrame stack
- * The promise allocation stack frame
- * @return object
- * Returns an object containing the source location of the SavedFrame
- * stack.
- */
- _getSourceOriginalLocation: function(stack) {
- let source;
-
- // Catch any errors if the source actor cannot be found
- try {
- source = this.hooks.sources().getSourceActorByURL(stack.source);
- } catch (e) {
- // ignored
- }
-
- if (!source) {
- return null;
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- source,
- stack.line,
- stack.column
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn,
- functionDisplayName: stack.functionDisplayName
- };
- });
- }
-};
-
-ObjectActor.prototype.requestTypes = {
- "definitionSite": ObjectActor.prototype.onDefinitionSite,
- "parameterNames": ObjectActor.prototype.onParameterNames,
- "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
- "enumProperties": ObjectActor.prototype.onEnumProperties,
- "prototype": ObjectActor.prototype.onPrototype,
- "property": ObjectActor.prototype.onProperty,
- "displayString": ObjectActor.prototype.onDisplayString,
- "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
- "decompile": ObjectActor.prototype.onDecompile,
- "release": ObjectActor.prototype.onRelease,
- "scope": ObjectActor.prototype.onScope,
- "dependentPromises": ObjectActor.prototype.onDependentPromises,
- "allocationStack": ObjectActor.prototype.onAllocationStack,
- "fulfillmentStack": ObjectActor.prototype.onFulfillmentStack,
- "rejectionStack": ObjectActor.prototype.onRejectionStack,
- "enumEntries": ObjectActor.prototype.onEnumEntries,
- "enumSymbols": ObjectActor.prototype.onEnumSymbols,
-};
+loader.lazyRequireGetter(this, "ObjectUtils", "devtools/server/actors/object/utils");
/**
* 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:
@@ -845,17 +53,17 @@ function PropertyIteratorActor(objectAct
} else if (cls == "Set") {
this.iterator = enumSetEntries(objectActor);
} else if (cls == "WeakSet") {
this.iterator = enumWeakSetEntries(objectActor);
} else {
throw new Error("Unsupported class to enumerate entries from: " + cls);
}
} else if (
- isArray(objectActor.obj)
+ ObjectUtils.isArray(objectActor.obj)
&& options.ignoreNonIndexedProperties
&& !options.query
) {
this.iterator = enumArrayProperties(objectActor, options);
} else {
this.iterator = enumObjectProperties(objectActor, options);
}
}
@@ -898,19 +106,27 @@ PropertyIteratorActor.prototype = {
};
PropertyIteratorActor.prototype.requestTypes = {
"names": PropertyIteratorActor.prototype.names,
"slice": PropertyIteratorActor.prototype.slice,
"all": PropertyIteratorActor.prototype.all,
};
+/**
+ * Helper function to create a grip from a Map/Set entry
+ */
+function gripFromEntry({ obj, hooks }, entry) {
+ return hooks.createValueGrip(
+ ObjectUtils.makeDebuggeeValueIfNeeded(obj, Cu.unwaiveXrays(entry)));
+}
+
function enumArrayProperties(objectActor, options) {
return {
- size: getArrayLength(objectActor.obj),
+ size: ObjectUtils.getArrayLength(objectActor.obj),
propertyName(index) {
return index;
},
propertyDescription(index) {
return objectActor._propertyDescriptor(index);
}
};
}
@@ -923,33 +139,32 @@ function enumObjectProperties(objectActo
// Calling getOwnPropertyNames() on some wrapped native prototypes is not
// allowed: "cannot modify properties of a WrappedNative". See bug 952093.
}
if (options.ignoreNonIndexedProperties || options.ignoreIndexedProperties) {
let length = DevToolsUtils.getProperty(objectActor.obj, "length");
let sliceIndex;
- const isLengthTrustworthy =
- isUint32(length)
- && (!length || isArrayIndex(names[length - 1]))
- && !isArrayIndex(names[length]);
+ 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 (!isArrayIndex(names[0])) {
+ 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 (isArrayIndex(names[sliceIndex - 1])) {
+ if (ObjectUtils.isArrayIndex(names[sliceIndex - 1])) {
break;
}
sliceIndex--;
}
}
} else {
sliceIndex = length;
}
@@ -1020,24 +235,16 @@ function enumObjectProperties(objectActo
desc.getterValue = getterValue;
desc.getterPrototypeLevel = getterPrototypeLevel;
}
return desc;
}
};
}
-/**
- * Helper function to create a grip from a Map/Set entry
- */
-function gripFromEntry({ obj, hooks }, entry) {
- return hooks.createValueGrip(
- makeDebuggeeValueIfNeeded(obj, Cu.unwaiveXrays(entry)));
-}
-
function enumMapEntries(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
@@ -1185,1505 +392,26 @@ function enumWeakSetEntries(objectActor)
enumerable: true,
value: gripFromEntry(objectActor, val)
};
}
};
}
/**
- * Creates an actor to iterate over an object's symbols.
- *
- * @param objectActor ObjectActor
- * The object actor.
- */
-function SymbolIteratorActor(objectActor) {
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- try {
- symbols = objectActor.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- this.iterator = {
- size: symbols.length,
- symbolDescription(index) {
- const symbol = symbols[index];
- return {
- name: symbol.toString(),
- descriptor: objectActor._propertyDescriptor(symbol)
- };
- }
- };
-}
-
-SymbolIteratorActor.prototype = {
- actorPrefix: "symbolIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- slice({ start, count }) {
- let ownSymbols = [];
- for (let i = start, m = start + count; i < m; i++) {
- ownSymbols.push(this.iterator.symbolDescription(i));
- }
- return {
- ownSymbols
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-SymbolIteratorActor.prototype.requestTypes = {
- "slice": SymbolIteratorActor.prototype.slice,
- "all": SymbolIteratorActor.prototype.all,
-};
-
-/**
- * Functions for adding information to ObjectActor grips for the purpose of
- * having customized output. This object holds arrays mapped by
- * Debugger.Object.prototype.class.
- *
- * In each array you can add functions that take three
- * arguments:
- * - the ObjectActor instance and its hooks to make a preview for,
- * - the grip object being prepared for the client,
- * - the raw JS object after calling Debugger.Object.unsafeDereference(). This
- * argument is only provided if the object is safe for reading properties and
- * executing methods. See DevToolsUtils.isSafeJSObject().
- *
- * Functions must return false if they cannot provide preview
- * information for the debugger object, or true otherwise.
- */
-DebuggerServer.ObjectActorPreviewers = {
- String: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("String", String, objectActor, grip, rawObj);
- }],
-
- Boolean: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip, rawObj);
- }],
-
- Number: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Number", Number, objectActor, grip, rawObj);
- }],
-
- Symbol: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Symbol", Symbol, objectActor, grip, rawObj);
- }],
-
- Function: [function({obj, hooks}, grip) {
- if (obj.name) {
- grip.name = obj.name;
- }
-
- if (obj.displayName) {
- grip.displayName = obj.displayName.substr(0, 500);
- }
-
- if (obj.parameterNames) {
- grip.parameterNames = obj.parameterNames;
- }
-
- // Check if the developer has added a de-facto standard displayName
- // property for us to use.
- let userDisplayName;
- try {
- userDisplayName = obj.getOwnPropertyDescriptor("displayName");
- } catch (e) {
- // The above can throw "permission denied" errors when the debuggee
- // does not subsume the function's compartment.
- }
-
- if (userDisplayName && typeof userDisplayName.value == "string" &&
- userDisplayName.value) {
- grip.userDisplayName = hooks.createValueGrip(userDisplayName.value);
- }
-
- let dbgGlobal = hooks.getGlobalDebugObject();
- if (dbgGlobal) {
- let script = dbgGlobal.makeDebuggeeValue(obj.unsafeDereference()).script;
- if (script) {
- grip.location = {
- url: script.url,
- line: script.startLine
- };
- }
- }
-
- return true;
- }],
-
- RegExp: [function({obj, hooks}, grip) {
- let str = DevToolsUtils.callPropertyOnObject(obj, "toString");
- if (typeof str != "string") {
- return false;
- }
-
- grip.displayString = hooks.createValueGrip(str);
- return true;
- }],
-
- Date: [function({obj, hooks}, grip) {
- let time = DevToolsUtils.callPropertyOnObject(obj, "getTime");
- if (typeof time != "number") {
- return false;
- }
-
- grip.preview = {
- timestamp: hooks.createValueGrip(time),
- };
- return true;
- }],
-
- Array: [function({obj, hooks}, grip) {
- let length = getArrayLength(obj);
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let items = grip.preview.items = [];
-
- for (let i = 0; i < length; ++i) {
- // Array Xrays filter out various possibly-unsafe properties (like
- // functions, and claim that the value is undefined instead. This
- // is generally the right thing for privileged code accessing untrusted
- // objects, but quite confusing for Object previews. So we manually
- // override this protection by waiving Xrays on the array, and re-applying
- // Xrays on any indexed value props that we pull off of it.
- let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
- if (desc && !desc.get && !desc.set) {
- let value = Cu.unwaiveXrays(desc.value);
- value = makeDebuggeeValueIfNeeded(obj, value);
- items.push(hooks.createValueGrip(value));
- } else {
- items.push(null);
- }
-
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Set: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: size,
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumSetEntries(objectActor)) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakSet: [function(objectActor, grip) {
- let enumEntries = enumWeakSetEntries(objectActor);
-
- grip.preview = {
- kind: "ArrayLike",
- length: enumEntries.size
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumEntries) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Map: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: size,
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumMapEntries(objectActor)) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakMap: [function(objectActor, grip) {
- let enumEntries = enumWeakMapEntries(objectActor);
-
- grip.preview = {
- kind: "MapLike",
- size: enumEntries.size
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumEntries) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- DOMStringMap: [function({obj, hooks}, grip, rawObj) {
- if (!rawObj) {
- return false;
- }
-
- let keys = obj.getOwnPropertyNames();
- grip.preview = {
- kind: "MapLike",
- size: keys.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let key of keys) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
- entries.push([key, hooks.createValueGrip(value)]);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Proxy: [function({obj, hooks}, grip, rawObj) {
- // The `isProxy` getter of the debuggee object only detects proxies without
- // security wrappers. If false, the target and handler are not available.
- let hasTargetAndHandler = obj.isProxy;
- if (hasTargetAndHandler) {
- grip.proxyTarget = hooks.createValueGrip(obj.proxyTarget);
- grip.proxyHandler = hooks.createValueGrip(obj.proxyHandler);
- }
-
- grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownPropertiesLength: 2 * hasTargetAndHandler
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- if (hasTargetAndHandler) {
- grip.preview.ownProperties["<target>"] = {value: grip.proxyTarget};
- grip.preview.ownProperties["<handler>"] = {value: grip.proxyHandler};
- }
-
- return true;
- }],
-};
-
-/**
- * Generic previewer for classes wrapping primitives, like String,
- * Number and Boolean.
- *
- * @param string className
- * Class name to expect.
- * @param object classObj
- * The class to expect, eg. String. The valueOf() method of the class is
- * invoked on the given object.
- * @param ObjectActor objectActor
- * The object actor
- * @param Object grip
- * The result grip to fill in
- * @return Booolean true if the object was handled, false otherwise
- */
-function wrappedPrimitivePreviewer(className, classObj, objectActor, grip, rawObj) {
- let {obj, hooks} = objectActor;
-
- let v = null;
- try {
- v = classObj.prototype.valueOf.call(rawObj);
- } catch (ex) {
- // valueOf() can throw if the raw JS object is "misbehaved".
- return false;
- }
-
- if (v === null) {
- return false;
- }
-
- let canHandle = GenericObject(objectActor, grip, rawObj, className === "String");
- if (!canHandle) {
- return false;
- }
-
- grip.preview.wrappedValue =
- hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v));
- return true;
-}
-
-function GenericObject(objectActor, grip, rawObj, specialStringBehavior = false) {
- let {obj, hooks} = objectActor;
- if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
- return false;
- }
-
- let i = 0, names = [], symbols = [];
- let preview = grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownSymbols: [],
- };
-
- try {
- names = obj.getOwnPropertyNames();
- symbols = obj.getOwnPropertySymbols();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
- preview.ownPropertiesLength = names.length;
- preview.ownSymbolsLength = symbols.length;
-
- let length;
- if (specialStringBehavior) {
- length = DevToolsUtils.getProperty(obj, "length");
- if (typeof length != "number") {
- specialStringBehavior = false;
- }
- }
-
- for (let name of names) {
- if (specialStringBehavior && /^[0-9]+$/.test(name)) {
- let num = parseInt(name, 10);
- if (num.toString() === name && num >= 0 && num < length) {
- continue;
- }
- }
-
- let desc = objectActor._propertyDescriptor(name, true);
- if (!desc) {
- continue;
- }
-
- preview.ownProperties[name] = desc;
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- for (let symbol of symbols) {
- let descriptor = objectActor._propertyDescriptor(symbol, true);
- if (!descriptor) {
- continue;
- }
-
- preview.ownSymbols.push(Object.assign({
- descriptor
- }, hooks.createValueGrip(symbol)));
-
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- if (i < OBJECT_PREVIEW_MAX_ITEMS) {
- preview.safeGetterValues = objectActor._findSafeGetterValues(
- Object.keys(preview.ownProperties),
- OBJECT_PREVIEW_MAX_ITEMS - i);
- }
-
- return true;
-}
-
-// Preview functions that do not rely on the object class.
-DebuggerServer.ObjectActorPreviewers.Object = [
- function TypedArray({obj, hooks}, grip) {
- if (!isTypedArray(obj)) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: getArrayLength(obj),
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let global = Cu.getGlobalForObject(DebuggerServer);
- let classProto = global[obj.class].prototype;
- // The Xray machinery for TypedArrays denies indexed access on the grounds
- // that it's slow, and advises callers to do a structured clone instead.
- let safeView = Cu.cloneInto(classProto.subarray.call(raw, 0,
- OBJECT_PREVIEW_MAX_ITEMS), global);
- let items = grip.preview.items = [];
- for (let i = 0; i < safeView.length; i++) {
- items.push(safeView[i]);
- }
-
- return true;
- },
-
- function Error({obj, hooks}, grip) {
- switch (obj.class) {
- case "Error":
- case "EvalError":
- case "RangeError":
- case "ReferenceError":
- case "SyntaxError":
- case "TypeError":
- case "URIError":
- let name = DevToolsUtils.getProperty(obj, "name");
- let msg = DevToolsUtils.getProperty(obj, "message");
- let stack = DevToolsUtils.getProperty(obj, "stack");
- let fileName = DevToolsUtils.getProperty(obj, "fileName");
- let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
- let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
- grip.preview = {
- kind: "Error",
- name: hooks.createValueGrip(name),
- message: hooks.createValueGrip(msg),
- stack: hooks.createValueGrip(stack),
- fileName: hooks.createValueGrip(fileName),
- lineNumber: hooks.createValueGrip(lineNumber),
- columnNumber: hooks.createValueGrip(columnNumber),
- };
- return true;
- default:
- return false;
- }
- },
-
- function CSSMediaRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSMediaRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.conditionText),
- };
- return true;
- },
-
- function CSSStyleRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSStyleRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.selectorText),
- };
- return true;
- },
-
- function ObjectWithURL({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(obj.class == "CSSImportRule" ||
- obj.class == "CSSStyleSheet" ||
- obj.class == "Location" ||
- rawObj instanceof Ci.nsIDOMWindow)) {
- return false;
- }
-
- let url;
- if (rawObj instanceof Ci.nsIDOMWindow && rawObj.location) {
- url = rawObj.location.href;
- } else if (rawObj.href) {
- url = rawObj.href;
- } else {
- return false;
- }
-
- grip.preview = {
- kind: "ObjectWithURL",
- url: hooks.createValueGrip(url),
- };
-
- return true;
- },
-
- function ArrayLike({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- obj.class != "DOMStringList" &&
- obj.class != "DOMTokenList" &&
- obj.class != "CSSRuleList" &&
- obj.class != "MediaList" &&
- obj.class != "StyleSheetList" &&
- obj.class != "CSSValueList" &&
- obj.class != "NamedNodeMap" &&
- obj.class != "FileList" &&
- obj.class != "NodeList") {
- return false;
- }
-
- if (typeof rawObj.length != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: rawObj.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
-
- for (let i = 0; i < rawObj.length &&
- items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[i]);
- items.push(hooks.createValueGrip(value));
- }
-
- return true;
- },
-
- function CSSStyleDeclaration({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- (obj.class != "CSSStyleDeclaration" &&
- obj.class != "CSS2Properties")) {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: rawObj.length,
- };
-
- let entries = grip.preview.entries = [];
-
- for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
- i < rawObj.length; i++) {
- let prop = rawObj[i];
- let value = rawObj.getPropertyValue(prop);
- entries.push([prop, hooks.createValueGrip(value)]);
- }
-
- return true;
- },
-
- function DOMNode({obj, hooks}, grip, rawObj) {
- if (isWorker || obj.class == "Object" || !rawObj ||
- !(rawObj instanceof Ci.nsIDOMNode)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMNode",
- nodeType: rawObj.nodeType,
- nodeName: rawObj.nodeName,
- isConnected: rawObj.isConnected === true,
- };
-
- if (rawObj instanceof Ci.nsIDOMDocument && rawObj.location) {
- preview.location = hooks.createValueGrip(rawObj.location.href);
- } else if (rawObj instanceof Ci.nsIDOMDocumentFragment) {
- preview.childNodesLength = rawObj.childNodes.length;
-
- if (hooks.getGripDepth() < 2) {
- preview.childNodes = [];
- for (let node of rawObj.childNodes) {
- let actor = hooks.createValueGrip(obj.makeDebuggeeValue(node));
- preview.childNodes.push(actor);
- if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
- } else if (rawObj instanceof Ci.nsIDOMElement) {
- // For HTML elements (in an HTML document, at least), the nodeName is an
- // uppercased version of the actual element name. Check for HTML
- // elements, that is elements in the HTML namespace, and lowercase the
- // nodeName in that case.
- if (rawObj.namespaceURI == "http://www.w3.org/1999/xhtml") {
- preview.nodeName = preview.nodeName.toLowerCase();
- }
-
- // Add preview for DOM element attributes.
- preview.attributes = {};
- preview.attributesLength = rawObj.attributes.length;
- for (let attr of rawObj.attributes) {
- preview.attributes[attr.nodeName] = hooks.createValueGrip(attr.value);
- }
- } else if (obj.class == "Attr") {
- preview.value = hooks.createValueGrip(rawObj.value);
- } else if (obj.class == "Text" ||
- obj.class == "CDATASection" ||
- obj.class == "Comment") {
- preview.textContent = hooks.createValueGrip(rawObj.textContent);
- }
-
- return true;
- },
-
- function DOMEvent({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMEvent)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMEvent",
- type: rawObj.type,
- properties: Object.create(null),
- };
-
- if (hooks.getGripDepth() < 2) {
- let target = obj.makeDebuggeeValue(rawObj.target);
- preview.target = hooks.createValueGrip(target);
- }
-
- let props = [];
- if (obj.class == "MouseEvent" ||
- obj.class == "DragEvent" ||
- obj.class == "PointerEvent" ||
- obj.class == "SimpleGestureEvent" ||
- obj.class == "WheelEvent") {
- props.push("buttons", "clientX", "clientY", "layerX", "layerY");
- } else if (obj.class == "KeyboardEvent") {
- let modifiers = [];
- if (rawObj.altKey) {
- modifiers.push("Alt");
- }
- if (rawObj.ctrlKey) {
- modifiers.push("Control");
- }
- if (rawObj.metaKey) {
- modifiers.push("Meta");
- }
- if (rawObj.shiftKey) {
- modifiers.push("Shift");
- }
- preview.eventKind = "key";
- preview.modifiers = modifiers;
-
- props.push("key", "charCode", "keyCode");
- } else if (obj.class == "TransitionEvent") {
- props.push("propertyName", "pseudoElement");
- } else if (obj.class == "AnimationEvent") {
- props.push("animationName", "pseudoElement");
- } else if (obj.class == "ClipboardEvent") {
- props.push("clipboardData");
- }
-
- // Add event-specific properties.
- for (let prop of props) {
- let value = rawObj[prop];
- if (value && (typeof value == "object" || typeof value == "function")) {
- // Skip properties pointing to objects.
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- }
-
- // Add any properties we find on the event object.
- if (!props.length) {
- let i = 0;
- for (let prop in rawObj) {
- let value = rawObj[prop];
- if (prop == "target" || prop == "type" || value === null ||
- typeof value == "function") {
- continue;
- }
- if (value && typeof value == "object") {
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
-
- return true;
- },
-
- function DOMException({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMDOMException)) {
- return false;
- }
-
- grip.preview = {
- kind: "DOMException",
- name: hooks.createValueGrip(rawObj.name),
- message: hooks.createValueGrip(rawObj.message),
- code: hooks.createValueGrip(rawObj.code),
- result: hooks.createValueGrip(rawObj.result),
- filename: hooks.createValueGrip(rawObj.filename),
- lineNumber: hooks.createValueGrip(rawObj.lineNumber),
- columnNumber: hooks.createValueGrip(rawObj.columnNumber),
- };
-
- return true;
- },
-
- function PseudoArray({obj, hooks}, grip, rawObj) {
- // An object is considered a pseudo-array if all the following apply:
- // - All its properties are array indices except, optionally, a "length" property.
- // - At least it has the "0" array index.
- // - The array indices are consecutive.
- // - The value of "length", if present, is the number of array indices.
-
- let keys;
- try {
- keys = obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- return false;
- }
- let {length} = keys;
- if (length === 0) {
- return false;
- }
-
- // Array indices should be sorted at the beginning, from smallest to largest.
- // Other properties should be at the end, so check if the last one is "length".
- if (keys[length - 1] === "length") {
- --length;
- if (length === 0 || length !== DevToolsUtils.getProperty(obj, "length")) {
- return false;
- }
- }
-
- // Check that the last key is the array index expected at that position.
- let lastKey = keys[length - 1];
- if (!isArrayIndex(lastKey) || +lastKey !== length - 1) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- // Avoid recursive object grips.
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- let numItems = Math.min(OBJECT_PREVIEW_MAX_ITEMS, length);
-
- for (let i = 0; i < numItems; ++i) {
- let desc = obj.getOwnPropertyDescriptor(i);
- if (desc && "value" in desc) {
- items.push(hooks.createValueGrip(desc.value));
- } else {
- items.push(null);
- }
- }
-
- return true;
- },
-
- function Object(objectActor, grip, rawObj) {
- return GenericObject(objectActor, grip, rawObj, /* specialStringBehavior = */ false);
- },
-];
-
-/**
- * Get thisDebugger.Object referent's `promiseState`.
- *
- * @returns Object
- * An object of one of the following forms:
- * - { state: "pending" }
- * - { state: "fulfilled", value }
- * - { state: "rejected", reason }
- */
-function getPromiseState(obj) {
- if (obj.class != "Promise") {
- throw new Error(
- "Can't call `getPromiseState` on `Debugger.Object`s that don't " +
- "refer to Promise objects.");
- }
-
- let state = { state: obj.promiseState };
- if (state.state === "fulfilled") {
- state.value = obj.promiseValue;
- } else if (state.state === "rejected") {
- state.reason = obj.promiseReason;
- }
- return state;
-}
-
-/**
- * Determine if a given value is non-primitive.
- *
- * @param Any value
- * The value to test.
- * @return Boolean
- * Whether the value is non-primitive.
- */
-function isObject(value) {
- const type = typeof value;
- return type == "object" ? value !== null : type == "function";
-}
-
-/**
- * Create a function that can safely stringify Debugger.Objects of a given
- * builtin type.
- *
- * @param Function ctor
- * The builtin class constructor.
- * @return Function
- * The stringifier for the class.
- */
-function createBuiltinStringifier(ctor) {
- return obj => {
- try {
- return ctor.prototype.toString.call(obj.unsafeDereference());
- } catch (err) {
- // The debuggee will see a "Function" class if the object is callable and
- // its compartment is not subsumed. The above will throw if it's not really
- // a function, e.g. if it's a callable proxy.
- return "[object " + obj.class + "]";
- }
- };
-}
-
-/**
- * Stringify a Debugger.Object-wrapped Error instance.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification of the object.
- */
-function errorStringify(obj) {
- let name = DevToolsUtils.getProperty(obj, "name");
- if (name === "" || name === undefined) {
- name = obj.class;
- } else if (isObject(name)) {
- name = stringify(name);
- }
-
- let message = DevToolsUtils.getProperty(obj, "message");
- if (isObject(message)) {
- message = stringify(message);
- }
-
- if (message === "" || message === undefined) {
- return name;
- }
- return name + ": " + message;
-}
-
-/**
- * Stringify a Debugger.Object based on its class.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification for the object.
- */
-function stringify(obj) {
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- if (DevToolsUtils.isCPOW(obj)) {
- return "<cpow>";
- }
- let unwrapped = DevToolsUtils.unwrap(obj);
- if (unwrapped === undefined) {
- return "<invisibleToDebugger>";
- } else if (unwrapped.isProxy) {
- return "<proxy>";
- }
- // The following line should not be reached. It's there just in case somebody
- // modifies isSafeDebuggerObject to return false for additional kinds of objects.
- return "[object " + obj.class + "]";
- } else if (obj.class == "DeadObject") {
- return "<dead object>";
- }
-
- const stringifier = stringifiers[obj.class] || stringifiers.Object;
-
- try {
- return stringifier(obj);
- } catch (e) {
- DevToolsUtils.reportException("stringify", e);
- return "<failed to stringify object>";
- }
-}
-
-// Used to prevent infinite recursion when an array is found inside itself.
-var seen = null;
-
-var stringifiers = {
- Error: errorStringify,
- EvalError: errorStringify,
- RangeError: errorStringify,
- ReferenceError: errorStringify,
- SyntaxError: errorStringify,
- TypeError: errorStringify,
- URIError: errorStringify,
- Boolean: createBuiltinStringifier(Boolean),
- Function: createBuiltinStringifier(Function),
- Number: createBuiltinStringifier(Number),
- RegExp: createBuiltinStringifier(RegExp),
- String: createBuiltinStringifier(String),
- Object: obj => "[object " + obj.class + "]",
- Array: obj => {
- // If we're at the top level then we need to create the Set for tracking
- // previously stringified arrays.
- const topLevel = !seen;
- if (topLevel) {
- seen = new Set();
- } else if (seen.has(obj)) {
- return "";
- }
-
- seen.add(obj);
-
- const len = getArrayLength(obj);
- let string = "";
-
- // Array.length is always a non-negative safe integer.
- for (let i = 0; i < len; i++) {
- const desc = obj.getOwnPropertyDescriptor(i);
- if (desc) {
- const { value } = desc;
- if (value != null) {
- string += isObject(value) ? stringify(value) : value;
- }
- }
-
- if (i < len - 1) {
- string += ",";
- }
- }
-
- if (topLevel) {
- seen = null;
- }
-
- return string;
- },
- DOMException: obj => {
- const message = DevToolsUtils.getProperty(obj, "message") || "<no message>";
- const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16);
- const code = DevToolsUtils.getProperty(obj, "code");
- const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
-
- return '[Exception... "' + message + '" ' +
- 'code: "' + code + '" ' +
- 'nsresult: "0x' + result + " (" + name + ')"]';
- },
- Promise: obj => {
- const { state, value, reason } = getPromiseState(obj);
- let statePreview = state;
- if (state != "pending") {
- const settledValue = state === "fulfilled" ? value : reason;
- statePreview += ": " + (typeof settledValue === "object" && settledValue !== null
- ? stringify(settledValue)
- : settledValue);
- }
- return "Promise (" + statePreview + ")";
- },
-};
-
-/**
- * Make a debuggee value for the given object, if needed. Primitive values
- * are left the same.
- *
- * Use case: you have a raw JS object (after unsafe dereference) and you want to
- * send it to the client. In that case you need to use an ObjectActor which
- * requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
- * method works only for JS objects and functions.
- *
- * @param Debugger.Object obj
- * @param any value
- * @return object
- */
-function makeDebuggeeValueIfNeeded(obj, value) {
- if (value && (typeof value == "object" || typeof value == "function")) {
- return obj.makeDebuggeeValue(value);
- }
- return value;
-}
-
-/**
- * Creates an actor for the specified "very long" string. "Very long" is specified
- * at the server's discretion.
- *
- * @param string String
- * The string.
- */
-function LongStringActor(string) {
- this.string = string;
- this.stringLength = string.length;
-}
-
-LongStringActor.prototype = {
- actorPrefix: "longString",
-
- rawValue: function() {
- return this.string;
- },
-
- destroy: function() {
- // Because longStringActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- return {
- "type": "longString",
- "initial": this.string.substring(
- 0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
- "length": this.stringLength,
- "actor": this.actorID
- };
- },
-
- /**
- * Handle a request to extract part of this actor's string.
- *
- * @param request object
- * The protocol request object.
- */
- onSubstring: function(request) {
- return {
- "from": this.actorID,
- "substring": this.string.substring(request.start, request.end)
- };
- },
-
- /**
- * Handle a request to release this LongStringActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.longStringActors) {
- delete this.registeredPool.longStringActors[this.string];
- }
- }
-};
-
-LongStringActor.prototype.requestTypes = {
- "substring": LongStringActor.prototype.onSubstring,
- "release": LongStringActor.prototype.onRelease
-};
-
-/**
- * Creates an actor for the specified symbol.
- *
- * @param symbol Symbol
- * The symbol.
- */
-function SymbolActor(symbol) {
- this.symbol = symbol;
-}
-
-SymbolActor.prototype = {
- actorPrefix: "symbol",
-
- rawValue: function() {
- return this.symbol;
- },
-
- destroy: function() {
- // Because symbolActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let form = {
- type: "symbol",
- actor: this.actorID,
- };
- let name = getSymbolName(this.symbol);
- if (name !== undefined) {
- // Create a grip for the name because it might be a longString.
- form.name = createValueGrip(name, this.registeredPool);
- }
- return form;
- },
-
- /**
- * Handle a request to release this SymbolActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.symbolActors) {
- delete this.registeredPool.symbolActors[this.symbol];
- }
- }
-};
-
-SymbolActor.prototype.requestTypes = {
- "release": SymbolActor.prototype.onRelease
-};
-
-/**
- * Creates an actor for the specified ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The buffer.
- */
-function ArrayBufferActor(buffer) {
- this.buffer = buffer;
- this.bufferLength = buffer.byteLength;
-}
-
-ArrayBufferActor.prototype = {
- actorPrefix: "arrayBuffer",
-
- rawValue: function() {
- return this.buffer;
- },
-
- destroy: function() {
- },
-
- grip() {
- return {
- "type": "arrayBuffer",
- "length": this.bufferLength,
- "actor": this.actorID
- };
- },
-
- onSlice({start, count}) {
- let slice = new Uint8Array(this.buffer, start, count);
- let parts = [], offset = 0;
- const PortionSize = 0x6000; // keep it divisible by 3 for btoa() and join()
- while (offset + PortionSize < count) {
- parts.push(btoa(
- String.fromCharCode.apply(null, slice.subarray(offset, offset + PortionSize))));
- offset += PortionSize;
- }
- parts.push(btoa(String.fromCharCode.apply(null, slice.subarray(offset, count))));
- return {
- "from": this.actorID,
- "encoded": parts.join(""),
- };
- }
-};
-
-ArrayBufferActor.prototype.requestTypes = {
- "slice": ArrayBufferActor.prototype.onSlice,
-};
-
-/**
- * Create a grip for the given debuggee value. If the value is an
- * object, will create an actor with the given lifetime.
- */
-function createValueGrip(value, pool, makeObjectGrip) {
- switch (typeof value) {
- case "boolean":
- return value;
-
- case "string":
- if (stringIsLong(value)) {
- return longStringGrip(value, pool);
- }
- return value;
-
- case "number":
- if (value === Infinity) {
- return { type: "Infinity" };
- } else if (value === -Infinity) {
- return { type: "-Infinity" };
- } else if (Number.isNaN(value)) {
- return { type: "NaN" };
- } else if (!value && 1 / value === -Infinity) {
- return { type: "-0" };
- }
- return value;
-
- case "undefined":
- return { type: "undefined" };
-
- case "object":
- if (value === null) {
- return { type: "null" };
- } else if (value.optimizedOut ||
- value.uninitialized ||
- value.missingArguments) {
- // The slot is optimized out, an uninitialized binding, or
- // arguments on a dead scope
- return {
- type: "null",
- optimizedOut: value.optimizedOut,
- uninitialized: value.uninitialized,
- missingArguments: value.missingArguments
- };
- }
- return makeObjectGrip(value, pool);
-
- case "symbol":
- return symbolGrip(value, pool);
-
- default:
- assert(false, "Failed to provide a grip for: " + value);
- return null;
- }
-}
-
-const symbolProtoToString = Symbol.prototype.toString;
-
-function getSymbolName(symbol) {
- const name = symbolProtoToString.call(symbol).slice("Symbol(".length, -1);
- return name || undefined;
-}
-
-/**
- * Returns true if the string is long enough to use a LongStringActor instead
- * of passing the value directly over the protocol.
- *
- * @param str String
- * The string we are checking the length of.
- */
-function stringIsLong(str) {
- return str.length >= DebuggerServer.LONG_STRING_LENGTH;
-}
-
-/**
- * Create a grip for the given string.
- *
- * @param str String
- * The string we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function longStringGrip(str, pool) {
- if (!pool.longStringActors) {
- pool.longStringActors = {};
- }
-
- if (pool.longStringActors.hasOwnProperty(str)) {
- return pool.longStringActors[str].grip();
- }
-
- let actor = new LongStringActor(str);
- pool.addActor(actor);
- pool.longStringActors[str] = actor;
- return actor.grip();
-}
-
-/**
- * Create a grip for the given symbol.
- *
- * @param sym Symbol
- * The symbol we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function symbolGrip(sym, pool) {
- if (!pool.symbolActors) {
- pool.symbolActors = Object.create(null);
- }
-
- if (sym in pool.symbolActors) {
- return pool.symbolActors[sym].grip();
- }
-
- let actor = new SymbolActor(sym);
- pool.addActor(actor);
- pool.symbolActors[sym] = actor;
- return actor.grip();
-}
-
-/**
- * Create a grip for the given ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The ArrayBuffer we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function arrayBufferGrip(buffer, pool) {
- if (!pool.arrayBufferActors) {
- pool.arrayBufferActors = new WeakMap();
- }
-
- if (pool.arrayBufferActors.has(buffer)) {
- return pool.arrayBufferActors.get(buffer).grip();
- }
-
- let actor = new ArrayBufferActor(buffer);
- pool.addActor(actor);
- pool.arrayBufferActors.set(buffer, actor);
- return actor.grip();
-}
-
-const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
- "Uint32Array", "Int8Array", "Int16Array", "Int32Array",
- "Float32Array", "Float64Array"];
-
-/**
- * Returns true if a debuggee object is a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isTypedArray(object) {
- return TYPED_ARRAY_CLASSES.includes(object.class);
-}
-
-/**
- * Returns true if a debuggee object is an array, including a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isArray(object) {
- return isTypedArray(object) || object.class === "Array";
-}
-
-/**
- * Returns the length of an array (or typed array).
- *
- * @param obj Debugger.Object
- * The debuggee object of the array.
- * @return Number
- * @throws if the object is not an array.
- */
-function getArrayLength(object) {
- if (!isArray(object)) {
- throw new Error("Expected an array, got a " + object.class);
- }
-
- // Real arrays have a reliable `length` own property.
- if (object.class === "Array") {
- return DevToolsUtils.getProperty(object, "length");
- }
-
- // For typed arrays, `DevToolsUtils.getProperty` is not reliable because the `length`
- // getter could be shadowed by an own property, and `getOwnPropertyNames` is
- // unnecessarily slow. Obtain the `length` getter safely and call it manually.
- let typedProto = Object.getPrototypeOf(Uint8Array.prototype);
- let getter = Object.getOwnPropertyDescriptor(typedProto, "length").get;
- return getter.call(object.unsafeDereference());
-}
-
-/**
* 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;
}
-/**
- * Returns true if the parameter is suitable to be an array index.
- *
- * @param str String
- * @return Boolean
- */
-function isArrayIndex(str) {
- // Transform the parameter to a 32-bit unsigned integer.
- let num = str >>> 0;
- // Check that the parameter is a canonical Uint32 index.
- return num + "" === str &&
- // Array indices cannot attain the maximum Uint32 value.
- num != -1 >>> 0;
-}
-
-exports.ObjectActor = ObjectActor;
-exports.PropertyIteratorActor = PropertyIteratorActor;
-exports.LongStringActor = LongStringActor;
-exports.SymbolActor = SymbolActor;
-exports.createValueGrip = createValueGrip;
-exports.stringIsLong = stringIsLong;
-exports.longStringGrip = longStringGrip;
-exports.arrayBufferGrip = arrayBufferGrip;
+module.exports = {
+ PropertyIteratorActor,
+ enumMapEntries,
+ enumSetEntries,
+ enumWeakMapEntries,
+ enumWeakSetEntries,
+};
copy from devtools/server/actors/object.js
copy to devtools/server/actors/object/stringifiers.js
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object/stringifiers.js
@@ -1,2095 +1,54 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 { Cu, Ci } = require("chrome");
-const { GeneratedLocation } = require("devtools/server/actors/common");
-const { DebuggerServer } = require("devtools/server/main");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { assert } = DevToolsUtils;
-
-loader.lazyRequireGetter(this, "ChromeUtils");
-
-// Number of items to preview in objects, arrays, maps, sets, lists,
-// collections, etc.
-const OBJECT_PREVIEW_MAX_ITEMS = 10;
-
-/**
- * Creates an actor for the specified object.
- *
- * @param obj Debugger.Object
- * The debuggee object.
- * @param hooks Object
- * A collection of abstract methods that are implemented by the caller.
- * ObjectActor requires the following functions to be implemented by
- * the caller:
- * - createValueGrip
- * Creates a value grip for the given object
- * - sources
- * TabSources getter that manages the sources of a thread
- * - createEnvironmentActor
- * Creates and return an environment actor
- * - getGripDepth
- * An actor's grip depth getter
- * - incrementGripDepth
- * Increment the actor's grip depth
- * - decrementGripDepth
- * Decrement the actor's grip depth
- * - globalDebugObject
- * The Debuggee Global Object as given by the ThreadActor
- */
-function ObjectActor(obj, {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
-}) {
- assert(!obj.optimizedOut,
- "Should not create object actors for optimized out values!");
- this.obj = obj;
- this.hooks = {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
- };
- this.iterators = new Set();
-}
-
-ObjectActor.prototype = {
- actorPrefix: "obj",
-
- rawValue: function() {
- return this.obj.unsafeDereference();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let g = {
- "type": "object",
- "actor": this.actorID,
- "class": this.obj.class,
- };
-
- let unwrapped = DevToolsUtils.unwrap(this.obj);
-
- // Unsafe objects must be treated carefully.
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- if (DevToolsUtils.isCPOW(this.obj)) {
- // Cross-process object wrappers can't be accessed.
- g.class = "CPOW: " + g.class;
- } else if (unwrapped === undefined) {
- // Objects belonging to an invisible-to-debugger compartment might be proxies,
- // so just in case they shouldn't be accessed.
- g.class = "InvisibleToDebugger: " + g.class;
- } else if (unwrapped.isProxy) {
- // Proxy objects can run traps when accessed, so just create a preview with
- // the target and the handler.
- g.class = "Proxy";
- this.hooks.incrementGripDepth();
- DebuggerServer.ObjectActorPreviewers.Proxy[0](this, g, null);
- this.hooks.decrementGripDepth();
- }
- return g;
- }
-
- // If the debuggee does not subsume the object's compartment, most properties won't
- // be accessible. Cross-orgin Window and Location objects might expose some, though.
- // Change the displayed class, but when creating the preview use the original one.
- if (unwrapped === null) {
- g.class = "Restricted";
- }
-
- this.hooks.incrementGripDepth();
-
- g.extensible = this.obj.isExtensible();
- g.frozen = this.obj.isFrozen();
- g.sealed = this.obj.isSealed();
-
- if (g.class == "Promise") {
- g.promiseState = this._createPromiseState();
- }
-
- // FF40+: Allow to know how many properties an object has to lazily display them
- // when there is a bunch.
- if (isTypedArray(g)) {
- // Bug 1348761: getOwnPropertyNames is unnecessary slow on TypedArrays
- g.ownPropertyLength = getArrayLength(this.obj);
- } else {
- try {
- g.ownPropertyLength = this.obj.getOwnPropertyNames().length;
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let raw = this.obj.unsafeDereference();
-
- // If Cu is not defined, we are running on a worker thread, where xrays
- // don't exist.
- if (Cu) {
- raw = Cu.unwaiveXrays(raw);
- }
-
- if (!DevToolsUtils.isSafeJSObject(raw)) {
- raw = null;
- }
-
- let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
- DebuggerServer.ObjectActorPreviewers.Object;
- for (let fn of previewers) {
- try {
- if (fn(this, g, raw)) {
- break;
- }
- } catch (e) {
- let msg = "ObjectActor.prototype.grip previewer function";
- DevToolsUtils.reportException(msg, e);
- }
- }
-
- this.hooks.decrementGripDepth();
- return g;
- },
-
- /**
- * Returns an object exposing the internal Promise state.
- */
- _createPromiseState: function() {
- const { state, value, reason } = getPromiseState(this.obj);
- let promiseState = { state };
-
- if (state == "fulfilled") {
- promiseState.value = this.hooks.createValueGrip(value);
- } else if (state == "rejected") {
- promiseState.reason = this.hooks.createValueGrip(reason);
- }
-
- promiseState.creationTimestamp = Date.now() - this.obj.promiseLifetime;
-
- // Only add the timeToSettle property if the Promise isn't pending.
- if (state !== "pending") {
- promiseState.timeToSettle = this.obj.promiseTimeToResolution;
- }
-
- return promiseState;
- },
-
- /**
- * Releases this actor from the pool.
- */
- release: function() {
- if (this.registeredPool.objectActors) {
- this.registeredPool.objectActors.delete(this.obj);
- }
- this.iterators.forEach(actor => this.registeredPool.removeActor(actor));
- this.iterators.clear();
- this.registeredPool.removeActor(this);
- },
-
- /**
- * Handle a protocol request to provide the definition site of this function
- * object.
- */
- onDefinitionSite: function() {
- if (this.obj.class != "Function") {
- return {
- from: this.actorID,
- error: "objectNotFunction",
- message: this.actorID + " is not a function."
- };
- }
-
- if (!this.obj.script) {
- return {
- from: this.actorID,
- error: "noScript",
- message: this.actorID + " has no Debugger.Script"
- };
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- this.hooks.sources().createNonSourceMappedActor(this.obj.script.source),
- this.obj.script.startLine,
- 0 // TODO bug 901138: use Debugger.Script.prototype.startColumn
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn
- };
- });
- },
-
- /**
- * Handle a protocol request to provide the names of the properties defined on
- * the object and not its prototype.
- */
- onOwnPropertyNames: function() {
- let props = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- props = this.obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
- return { from: this.actorID, ownPropertyNames: props };
- },
-
- /**
- * Creates an actor to iterate over an object property names and values.
- * See PropertyIteratorActor constructor for more info about options param.
- *
- * @param request object
- * The protocol request object.
- */
- onEnumProperties: function(request) {
- let actor = new PropertyIteratorActor(this, request.options);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over entries of a Map/Set-like object.
- */
- onEnumEntries: function() {
- let actor = new PropertyIteratorActor(this, { enumEntries: true });
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over an object symbols properties.
- */
- onEnumSymbols: function() {
- let actor = new SymbolIteratorActor(this);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Handle a protocol request to provide the prototype and own properties of
- * the object.
- *
- * @returns {Object} An object containing the data of this.obj, of the following form:
- * - {string} from: this.obj's actorID.
- * - {Object} prototype: The descriptor of this.obj's prototype.
- * - {Object} ownProperties: an object where the keys are the names of the
- * this.obj's ownProperties, and the values the descriptors of
- * the properties.
- * - {Array} ownSymbols: An array containing all descriptors of this.obj's
- * ownSymbols. Here we have an array, and not an object like for
- * ownProperties, because we can have multiple symbols with the same
- * name in this.obj, e.g. `{[Symbol()]: "a", [Symbol()]: "b"}`.
- * - {Object} safeGetterValues: an object that maps this.obj's property names
- * with safe getters descriptors.
- */
- onPrototypeAndProperties: function() {
- let proto = null;
- let names = [];
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- proto = this.obj.proto;
- names = this.obj.getOwnPropertyNames();
- symbols = this.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let ownProperties = Object.create(null);
- let ownSymbols = [];
-
- for (let name of names) {
- ownProperties[name] = this._propertyDescriptor(name);
- }
-
- for (let sym of symbols) {
- ownSymbols.push({
- name: sym.toString(),
- descriptor: this._propertyDescriptor(sym)
- });
- }
-
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto),
- ownProperties,
- ownSymbols,
- safeGetterValues: this._findSafeGetterValues(names) };
- },
-
- /**
- * Find the safe getter values for the current Debugger.Object, |this.obj|.
- *
- * @private
- * @param array ownProperties
- * The array that holds the list of known ownProperties names for
- * |this.obj|.
- * @param number [limit=0]
- * Optional limit of getter values to find.
- * @return object
- * An object that maps property names to safe getter descriptors as
- * defined by the remote debugging protocol.
- */
- _findSafeGetterValues: function(ownProperties, limit = 0) {
- let safeGetterValues = Object.create(null);
- let obj = this.obj;
- let level = 0, i = 0;
-
- // Do not search safe getters in unsafe objects.
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- return safeGetterValues;
- }
-
- // Most objects don't have any safe getters but inherit some from their
- // prototype. Avoid calling getOwnPropertyNames on objects that may have
- // many properties like Array, strings or js objects. That to avoid
- // freezing firefox when doing so.
- if (isArray(this.obj) || ["Object", "String"].includes(this.obj.class)) {
- obj = obj.proto;
- level++;
- }
-
- while (obj && DevToolsUtils.isSafeDebuggerObject(obj)) {
- let getters = this._findSafeGetters(obj);
- for (let name of getters) {
- // Avoid overwriting properties from prototypes closer to this.obj. Also
- // avoid providing safeGetterValues from prototypes if property |name|
- // is already defined as an own property.
- if (name in safeGetterValues ||
- (obj != this.obj && ownProperties.includes(name))) {
- continue;
- }
-
- // Ignore __proto__ on Object.prototye.
- if (!obj.proto && name == "__proto__") {
- continue;
- }
-
- let desc = null, getter = null;
- try {
- desc = obj.getOwnPropertyDescriptor(name);
- getter = desc.get;
- } catch (ex) {
- // The above can throw if the cache becomes stale.
- }
- if (!getter) {
- obj._safeGetters = null;
- continue;
- }
-
- let result = getter.call(this.obj);
- if (result && !("throw" in result)) {
- let getterValue = undefined;
- if ("return" in result) {
- getterValue = result.return;
- } else if ("yield" in result) {
- getterValue = result.yield;
- }
- // WebIDL attributes specified with the LenientThis extended attribute
- // return undefined and should be ignored.
- if (getterValue !== undefined) {
- safeGetterValues[name] = {
- getterValue: this.hooks.createValueGrip(getterValue),
- getterPrototypeLevel: level,
- enumerable: desc.enumerable,
- writable: level == 0 ? desc.writable : true,
- };
- if (limit && ++i == limit) {
- break;
- }
- }
- }
- }
- if (limit && i == limit) {
- break;
- }
-
- obj = obj.proto;
- level++;
- }
-
- return safeGetterValues;
- },
-
- /**
- * Find the safe getters for a given Debugger.Object. Safe getters are native
- * getters which are safe to execute.
- *
- * @private
- * @param Debugger.Object object
- * The Debugger.Object where you want to find safe getters.
- * @return Set
- * A Set of names of safe getters. This result is cached for each
- * Debugger.Object.
- */
- _findSafeGetters: function(object) {
- if (object._safeGetters) {
- return object._safeGetters;
- }
-
- let getters = new Set();
-
- if (!DevToolsUtils.isSafeDebuggerObject(object)) {
- object._safeGetters = getters;
- return getters;
- }
-
- let names = [];
- try {
- names = object.getOwnPropertyNames();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
-
- for (let name of names) {
- let desc = null;
- try {
- desc = object.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072).
- }
- if (!desc || desc.value !== undefined || !("get" in desc)) {
- continue;
- }
-
- if (DevToolsUtils.hasSafeGetter(desc)) {
- getters.add(name);
- }
- }
-
- object._safeGetters = getters;
- return getters;
- },
-
- /**
- * Handle a protocol request to provide the prototype of the object.
- */
- onPrototype: function() {
- let proto = null;
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- proto = this.obj.proto;
- }
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto) };
- },
-
- /**
- * Handle a protocol request to provide the property descriptor of the
- * object's specified property.
- *
- * @param request object
- * The protocol request object.
- */
- onProperty: function(request) {
- if (!request.name) {
- return { error: "missingParameter",
- message: "no property name was specified" };
- }
-
- return { from: this.actorID,
- descriptor: this._propertyDescriptor(request.name) };
- },
- /**
- * Handle a protocol request to provide the display string for the object.
- */
- onDisplayString: function() {
- const string = stringify(this.obj);
- return { from: this.actorID,
- displayString: this.hooks.createValueGrip(string) };
- },
-
- /**
- * A helper method that creates a property descriptor for the provided object,
- * properly formatted for sending in a protocol response.
- *
- * @private
- * @param string name
- * The property that the descriptor is generated for.
- * @param boolean [onlyEnumerable]
- * Optional: true if you want a descriptor only for an enumerable
- * property, false otherwise.
- * @return object|undefined
- * The property descriptor, or undefined if this is not an enumerable
- * property and onlyEnumerable=true.
- */
- _propertyDescriptor: function(name, onlyEnumerable) {
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- return undefined;
- }
-
- let desc;
- try {
- desc = this.obj.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072). Inform the user with a bogus, but hopefully
- // explanatory, descriptor.
- return {
- configurable: false,
- writable: false,
- enumerable: false,
- value: e.name
- };
- }
-
- if (!desc || onlyEnumerable && !desc.enumerable) {
- return undefined;
- }
-
- let retval = {
- configurable: desc.configurable,
- enumerable: desc.enumerable
- };
-
- if ("value" in desc) {
- retval.writable = desc.writable;
- retval.value = this.hooks.createValueGrip(desc.value);
- } else {
- if ("get" in desc) {
- retval.get = this.hooks.createValueGrip(desc.get);
- }
- if ("set" in desc) {
- retval.set = this.hooks.createValueGrip(desc.set);
- }
- }
- return retval;
- },
-
- /**
- * Handle a protocol request to provide the source code of a function.
- *
- * @param request object
- * The protocol request object.
- */
- onDecompile: function(request) {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "decompile request is only valid for object grips " +
- "with a 'Function' class." };
- }
-
- return { from: this.actorID,
- decompiledCode: this.obj.decompile(!!request.pretty) };
- },
-
- /**
- * Handle a protocol request to provide the parameters of a function.
- */
- onParameterNames: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "'parameterNames' request is only valid for object " +
- "grips with a 'Function' class." };
- }
-
- return { parameterNames: this.obj.parameterNames };
- },
-
- /**
- * Handle a protocol request to release a thread-lifetime grip.
- */
- onRelease: function() {
- this.release();
- return {};
- },
-
- /**
- * Handle a protocol request to provide the lexical scope of a function.
- */
- onScope: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "scope request is only valid for object grips with a" +
- " 'Function' class." };
- }
-
- let envActor = this.hooks.createEnvironmentActor(this.obj.environment,
- this.registeredPool);
- if (!envActor) {
- return { error: "notDebuggee",
- message: "cannot access the environment of this function." };
- }
-
- return { from: this.actorID, scope: envActor.form() };
- },
-
- /**
- * Handle a protocol request to get the list of dependent promises of a
- * promise.
- *
- * @return object
- * Returns an object containing an array of object grips of the
- * dependent promises
- */
- onDependentPromises: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'dependentPromises' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let promises = this.obj.promiseDependentPromises
- .map(p => this.hooks.createValueGrip(p));
-
- return { promises };
- },
-
- /**
- * Handle a protocol request to get the allocation stack of a promise.
- */
- onAllocationStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'allocationStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseAllocationSite;
- let allocationStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- allocationStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(allocationStacks).then(stacks => {
- return { allocationStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the fulfillment stack of a promise.
- */
- onFulfillmentStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'fulfillmentStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let fulfillmentStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- fulfillmentStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(fulfillmentStacks).then(stacks => {
- return { fulfillmentStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the rejection stack of a promise.
- */
- onRejectionStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'rejectionStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let rejectionStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- rejectionStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(rejectionStacks).then(stacks => {
- return { rejectionStack: stacks };
- });
- },
-
- /**
- * Helper function for fetching the source location of a SavedFrame stack.
- *
- * @param SavedFrame stack
- * The promise allocation stack frame
- * @return object
- * Returns an object containing the source location of the SavedFrame
- * stack.
- */
- _getSourceOriginalLocation: function(stack) {
- let source;
-
- // Catch any errors if the source actor cannot be found
- try {
- source = this.hooks.sources().getSourceActorByURL(stack.source);
- } catch (e) {
- // ignored
- }
-
- if (!source) {
- return null;
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- source,
- stack.line,
- stack.column
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn,
- functionDisplayName: stack.functionDisplayName
- };
- });
- }
-};
-
-ObjectActor.prototype.requestTypes = {
- "definitionSite": ObjectActor.prototype.onDefinitionSite,
- "parameterNames": ObjectActor.prototype.onParameterNames,
- "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
- "enumProperties": ObjectActor.prototype.onEnumProperties,
- "prototype": ObjectActor.prototype.onPrototype,
- "property": ObjectActor.prototype.onProperty,
- "displayString": ObjectActor.prototype.onDisplayString,
- "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
- "decompile": ObjectActor.prototype.onDecompile,
- "release": ObjectActor.prototype.onRelease,
- "scope": ObjectActor.prototype.onScope,
- "dependentPromises": ObjectActor.prototype.onDependentPromises,
- "allocationStack": ObjectActor.prototype.onAllocationStack,
- "fulfillmentStack": ObjectActor.prototype.onFulfillmentStack,
- "rejectionStack": ObjectActor.prototype.onRejectionStack,
- "enumEntries": ObjectActor.prototype.onEnumEntries,
- "enumSymbols": ObjectActor.prototype.onEnumSymbols,
-};
-
-/**
- * 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.
- */
-function PropertyIteratorActor(objectActor, options) {
- if (!DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- this.iterator = {
- size: 0,
- propertyName: index => undefined,
- propertyDescription: index => undefined,
- };
- } else if (options.enumEntries) {
- let cls = objectActor.obj.class;
- if (cls == "Map") {
- this.iterator = enumMapEntries(objectActor);
- } else if (cls == "WeakMap") {
- this.iterator = enumWeakMapEntries(objectActor);
- } else if (cls == "Set") {
- this.iterator = enumSetEntries(objectActor);
- } else if (cls == "WeakSet") {
- this.iterator = enumWeakSetEntries(objectActor);
- } else {
- throw new Error("Unsupported class to enumerate entries from: " + cls);
- }
- } else if (
- isArray(objectActor.obj)
- && options.ignoreNonIndexedProperties
- && !options.query
- ) {
- this.iterator = enumArrayProperties(objectActor, options);
- } else {
- this.iterator = enumObjectProperties(objectActor, options);
- }
-}
-
-PropertyIteratorActor.prototype = {
- actorPrefix: "propertyIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- names({ indexes }) {
- let list = [];
- for (let idx of indexes) {
- list.push(this.iterator.propertyName(idx));
- }
- return {
- names: indexes
- };
- },
-
- slice({ start, count }) {
- let ownProperties = Object.create(null);
- for (let i = start, m = start + count; i < m; i++) {
- let name = this.iterator.propertyName(i);
- ownProperties[name] = this.iterator.propertyDescription(i);
- }
- return {
- ownProperties
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-PropertyIteratorActor.prototype.requestTypes = {
- "names": PropertyIteratorActor.prototype.names,
- "slice": PropertyIteratorActor.prototype.slice,
- "all": PropertyIteratorActor.prototype.all,
-};
-
-function enumArrayProperties(objectActor, options) {
- return {
- size: getArrayLength(objectActor.obj),
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- return objectActor._propertyDescriptor(index);
- }
- };
-}
-
-function enumObjectProperties(objectActor, options) {
- 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) {
- let length = DevToolsUtils.getProperty(objectActor.obj, "length");
- let sliceIndex;
-
- const isLengthTrustworthy =
- isUint32(length)
- && (!length || isArrayIndex(names[length - 1]))
- && !isArrayIndex(names[length]);
-
- if (!isLengthTrustworthy) {
- // The length property may not reflect what the object looks like, let's find
- // where indexed properties end.
-
- if (!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 (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,
- // like webaudio buffers.
- if (options.ignoreIndexedProperties) {
- // Keep items after `sliceIndex` index
- names = names.slice(sliceIndex);
- } else if (options.ignoreNonIndexedProperties) {
- // Keep `sliceIndex` first items
- names.length = sliceIndex;
- }
- }
-
- let safeGetterValues = objectActor._findSafeGetterValues(names, 0);
- let safeGetterNames = Object.keys(safeGetterValues);
- // Merge the safe getter values into the existing properties list.
- for (let 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 && 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) {
- let name = names[index];
- let desc = objectActor._propertyDescriptor(name);
- if (!desc) {
- desc = safeGetterValues[name];
- } else if (name in safeGetterValues) {
- // Merge the safe getter values into the existing properties list.
- let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
- desc.getterValue = getterValue;
- desc.getterPrototypeLevel = getterPrototypeLevel;
- }
- return desc;
- }
- };
-}
+loader.lazyRequireGetter(this, "ObjectUtils", "devtools/server/actors/object/utils");
/**
- * Helper function to create a grip from a Map/Set entry
+ * Stringify a Debugger.Object based on its class.
+ *
+ * @param Debugger.Object obj
+ * The object to stringify.
+ * @return String
+ * The stringification for the object.
*/
-function gripFromEntry({ obj, hooks }, entry) {
- return hooks.createValueGrip(
- makeDebuggeeValueIfNeeded(obj, Cu.unwaiveXrays(entry)));
-}
-
-function enumMapEntries(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.
- let raw = objectActor.obj.unsafeDereference();
-
- let keys = [...Cu.waiveXrays(Map.prototype.keys.call(raw))];
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = Map.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = Map.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
+function stringify(obj) {
+ if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
+ if (DevToolsUtils.isCPOW(obj)) {
+ return "<cpow>";
}
- };
-}
-
-function enumWeakMapEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakMapKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = WeakMap.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = WeakMap.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
+ let unwrapped = DevToolsUtils.unwrap(obj);
+ if (unwrapped === undefined) {
+ return "<invisibleToDebugger>";
+ } else if (unwrapped.isProxy) {
+ return "<proxy>";
}
- };
-}
-
-function enumSetEntries(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.
- let raw = objectActor.obj.unsafeDereference();
- let values = [...Cu.waiveXrays(Set.prototype.values.call(raw))];
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of values) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: values.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = values[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-function enumWeakSetEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakSetKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of keys) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = keys[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-/**
- * Creates an actor to iterate over an object's symbols.
- *
- * @param objectActor ObjectActor
- * The object actor.
- */
-function SymbolIteratorActor(objectActor) {
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- try {
- symbols = objectActor.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
+ // The following line should not be reached. It's there just in case somebody
+ // modifies isSafeDebuggerObject to return false for additional kinds of objects.
+ return "[object " + obj.class + "]";
+ } else if (obj.class == "DeadObject") {
+ return "<dead object>";
}
- this.iterator = {
- size: symbols.length,
- symbolDescription(index) {
- const symbol = symbols[index];
- return {
- name: symbol.toString(),
- descriptor: objectActor._propertyDescriptor(symbol)
- };
- }
- };
-}
-
-SymbolIteratorActor.prototype = {
- actorPrefix: "symbolIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- slice({ start, count }) {
- let ownSymbols = [];
- for (let i = start, m = start + count; i < m; i++) {
- ownSymbols.push(this.iterator.symbolDescription(i));
- }
- return {
- ownSymbols
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-SymbolIteratorActor.prototype.requestTypes = {
- "slice": SymbolIteratorActor.prototype.slice,
- "all": SymbolIteratorActor.prototype.all,
-};
-
-/**
- * Functions for adding information to ObjectActor grips for the purpose of
- * having customized output. This object holds arrays mapped by
- * Debugger.Object.prototype.class.
- *
- * In each array you can add functions that take three
- * arguments:
- * - the ObjectActor instance and its hooks to make a preview for,
- * - the grip object being prepared for the client,
- * - the raw JS object after calling Debugger.Object.unsafeDereference(). This
- * argument is only provided if the object is safe for reading properties and
- * executing methods. See DevToolsUtils.isSafeJSObject().
- *
- * Functions must return false if they cannot provide preview
- * information for the debugger object, or true otherwise.
- */
-DebuggerServer.ObjectActorPreviewers = {
- String: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("String", String, objectActor, grip, rawObj);
- }],
-
- Boolean: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip, rawObj);
- }],
-
- Number: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Number", Number, objectActor, grip, rawObj);
- }],
-
- Symbol: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Symbol", Symbol, objectActor, grip, rawObj);
- }],
-
- Function: [function({obj, hooks}, grip) {
- if (obj.name) {
- grip.name = obj.name;
- }
-
- if (obj.displayName) {
- grip.displayName = obj.displayName.substr(0, 500);
- }
-
- if (obj.parameterNames) {
- grip.parameterNames = obj.parameterNames;
- }
-
- // Check if the developer has added a de-facto standard displayName
- // property for us to use.
- let userDisplayName;
- try {
- userDisplayName = obj.getOwnPropertyDescriptor("displayName");
- } catch (e) {
- // The above can throw "permission denied" errors when the debuggee
- // does not subsume the function's compartment.
- }
-
- if (userDisplayName && typeof userDisplayName.value == "string" &&
- userDisplayName.value) {
- grip.userDisplayName = hooks.createValueGrip(userDisplayName.value);
- }
-
- let dbgGlobal = hooks.getGlobalDebugObject();
- if (dbgGlobal) {
- let script = dbgGlobal.makeDebuggeeValue(obj.unsafeDereference()).script;
- if (script) {
- grip.location = {
- url: script.url,
- line: script.startLine
- };
- }
- }
-
- return true;
- }],
-
- RegExp: [function({obj, hooks}, grip) {
- let str = DevToolsUtils.callPropertyOnObject(obj, "toString");
- if (typeof str != "string") {
- return false;
- }
-
- grip.displayString = hooks.createValueGrip(str);
- return true;
- }],
-
- Date: [function({obj, hooks}, grip) {
- let time = DevToolsUtils.callPropertyOnObject(obj, "getTime");
- if (typeof time != "number") {
- return false;
- }
-
- grip.preview = {
- timestamp: hooks.createValueGrip(time),
- };
- return true;
- }],
-
- Array: [function({obj, hooks}, grip) {
- let length = getArrayLength(obj);
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let items = grip.preview.items = [];
-
- for (let i = 0; i < length; ++i) {
- // Array Xrays filter out various possibly-unsafe properties (like
- // functions, and claim that the value is undefined instead. This
- // is generally the right thing for privileged code accessing untrusted
- // objects, but quite confusing for Object previews. So we manually
- // override this protection by waiving Xrays on the array, and re-applying
- // Xrays on any indexed value props that we pull off of it.
- let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
- if (desc && !desc.get && !desc.set) {
- let value = Cu.unwaiveXrays(desc.value);
- value = makeDebuggeeValueIfNeeded(obj, value);
- items.push(hooks.createValueGrip(value));
- } else {
- items.push(null);
- }
-
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Set: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: size,
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumSetEntries(objectActor)) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakSet: [function(objectActor, grip) {
- let enumEntries = enumWeakSetEntries(objectActor);
-
- grip.preview = {
- kind: "ArrayLike",
- length: enumEntries.size
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumEntries) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Map: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: size,
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumMapEntries(objectActor)) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakMap: [function(objectActor, grip) {
- let enumEntries = enumWeakMapEntries(objectActor);
-
- grip.preview = {
- kind: "MapLike",
- size: enumEntries.size
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumEntries) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- DOMStringMap: [function({obj, hooks}, grip, rawObj) {
- if (!rawObj) {
- return false;
- }
-
- let keys = obj.getOwnPropertyNames();
- grip.preview = {
- kind: "MapLike",
- size: keys.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let key of keys) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
- entries.push([key, hooks.createValueGrip(value)]);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Proxy: [function({obj, hooks}, grip, rawObj) {
- // The `isProxy` getter of the debuggee object only detects proxies without
- // security wrappers. If false, the target and handler are not available.
- let hasTargetAndHandler = obj.isProxy;
- if (hasTargetAndHandler) {
- grip.proxyTarget = hooks.createValueGrip(obj.proxyTarget);
- grip.proxyHandler = hooks.createValueGrip(obj.proxyHandler);
- }
-
- grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownPropertiesLength: 2 * hasTargetAndHandler
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- if (hasTargetAndHandler) {
- grip.preview.ownProperties["<target>"] = {value: grip.proxyTarget};
- grip.preview.ownProperties["<handler>"] = {value: grip.proxyHandler};
- }
-
- return true;
- }],
-};
-
-/**
- * Generic previewer for classes wrapping primitives, like String,
- * Number and Boolean.
- *
- * @param string className
- * Class name to expect.
- * @param object classObj
- * The class to expect, eg. String. The valueOf() method of the class is
- * invoked on the given object.
- * @param ObjectActor objectActor
- * The object actor
- * @param Object grip
- * The result grip to fill in
- * @return Booolean true if the object was handled, false otherwise
- */
-function wrappedPrimitivePreviewer(className, classObj, objectActor, grip, rawObj) {
- let {obj, hooks} = objectActor;
-
- let v = null;
- try {
- v = classObj.prototype.valueOf.call(rawObj);
- } catch (ex) {
- // valueOf() can throw if the raw JS object is "misbehaved".
- return false;
- }
-
- if (v === null) {
- return false;
- }
-
- let canHandle = GenericObject(objectActor, grip, rawObj, className === "String");
- if (!canHandle) {
- return false;
- }
-
- grip.preview.wrappedValue =
- hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v));
- return true;
-}
-
-function GenericObject(objectActor, grip, rawObj, specialStringBehavior = false) {
- let {obj, hooks} = objectActor;
- if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
- return false;
- }
-
- let i = 0, names = [], symbols = [];
- let preview = grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownSymbols: [],
- };
+ const stringifier = stringifiers[obj.class] || stringifiers.Object;
try {
- names = obj.getOwnPropertyNames();
- symbols = obj.getOwnPropertySymbols();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
- preview.ownPropertiesLength = names.length;
- preview.ownSymbolsLength = symbols.length;
-
- let length;
- if (specialStringBehavior) {
- length = DevToolsUtils.getProperty(obj, "length");
- if (typeof length != "number") {
- specialStringBehavior = false;
- }
- }
-
- for (let name of names) {
- if (specialStringBehavior && /^[0-9]+$/.test(name)) {
- let num = parseInt(name, 10);
- if (num.toString() === name && num >= 0 && num < length) {
- continue;
- }
- }
-
- let desc = objectActor._propertyDescriptor(name, true);
- if (!desc) {
- continue;
- }
-
- preview.ownProperties[name] = desc;
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- for (let symbol of symbols) {
- let descriptor = objectActor._propertyDescriptor(symbol, true);
- if (!descriptor) {
- continue;
- }
-
- preview.ownSymbols.push(Object.assign({
- descriptor
- }, hooks.createValueGrip(symbol)));
-
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- if (i < OBJECT_PREVIEW_MAX_ITEMS) {
- preview.safeGetterValues = objectActor._findSafeGetterValues(
- Object.keys(preview.ownProperties),
- OBJECT_PREVIEW_MAX_ITEMS - i);
+ return stringifier(obj);
+ } catch (e) {
+ DevToolsUtils.reportException("stringify", e);
+ return "<failed to stringify object>";
}
-
- return true;
-}
-
-// Preview functions that do not rely on the object class.
-DebuggerServer.ObjectActorPreviewers.Object = [
- function TypedArray({obj, hooks}, grip) {
- if (!isTypedArray(obj)) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: getArrayLength(obj),
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let global = Cu.getGlobalForObject(DebuggerServer);
- let classProto = global[obj.class].prototype;
- // The Xray machinery for TypedArrays denies indexed access on the grounds
- // that it's slow, and advises callers to do a structured clone instead.
- let safeView = Cu.cloneInto(classProto.subarray.call(raw, 0,
- OBJECT_PREVIEW_MAX_ITEMS), global);
- let items = grip.preview.items = [];
- for (let i = 0; i < safeView.length; i++) {
- items.push(safeView[i]);
- }
-
- return true;
- },
-
- function Error({obj, hooks}, grip) {
- switch (obj.class) {
- case "Error":
- case "EvalError":
- case "RangeError":
- case "ReferenceError":
- case "SyntaxError":
- case "TypeError":
- case "URIError":
- let name = DevToolsUtils.getProperty(obj, "name");
- let msg = DevToolsUtils.getProperty(obj, "message");
- let stack = DevToolsUtils.getProperty(obj, "stack");
- let fileName = DevToolsUtils.getProperty(obj, "fileName");
- let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
- let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
- grip.preview = {
- kind: "Error",
- name: hooks.createValueGrip(name),
- message: hooks.createValueGrip(msg),
- stack: hooks.createValueGrip(stack),
- fileName: hooks.createValueGrip(fileName),
- lineNumber: hooks.createValueGrip(lineNumber),
- columnNumber: hooks.createValueGrip(columnNumber),
- };
- return true;
- default:
- return false;
- }
- },
-
- function CSSMediaRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSMediaRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.conditionText),
- };
- return true;
- },
-
- function CSSStyleRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSStyleRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.selectorText),
- };
- return true;
- },
-
- function ObjectWithURL({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(obj.class == "CSSImportRule" ||
- obj.class == "CSSStyleSheet" ||
- obj.class == "Location" ||
- rawObj instanceof Ci.nsIDOMWindow)) {
- return false;
- }
-
- let url;
- if (rawObj instanceof Ci.nsIDOMWindow && rawObj.location) {
- url = rawObj.location.href;
- } else if (rawObj.href) {
- url = rawObj.href;
- } else {
- return false;
- }
-
- grip.preview = {
- kind: "ObjectWithURL",
- url: hooks.createValueGrip(url),
- };
-
- return true;
- },
-
- function ArrayLike({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- obj.class != "DOMStringList" &&
- obj.class != "DOMTokenList" &&
- obj.class != "CSSRuleList" &&
- obj.class != "MediaList" &&
- obj.class != "StyleSheetList" &&
- obj.class != "CSSValueList" &&
- obj.class != "NamedNodeMap" &&
- obj.class != "FileList" &&
- obj.class != "NodeList") {
- return false;
- }
-
- if (typeof rawObj.length != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: rawObj.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
-
- for (let i = 0; i < rawObj.length &&
- items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[i]);
- items.push(hooks.createValueGrip(value));
- }
-
- return true;
- },
-
- function CSSStyleDeclaration({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- (obj.class != "CSSStyleDeclaration" &&
- obj.class != "CSS2Properties")) {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: rawObj.length,
- };
-
- let entries = grip.preview.entries = [];
-
- for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
- i < rawObj.length; i++) {
- let prop = rawObj[i];
- let value = rawObj.getPropertyValue(prop);
- entries.push([prop, hooks.createValueGrip(value)]);
- }
-
- return true;
- },
-
- function DOMNode({obj, hooks}, grip, rawObj) {
- if (isWorker || obj.class == "Object" || !rawObj ||
- !(rawObj instanceof Ci.nsIDOMNode)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMNode",
- nodeType: rawObj.nodeType,
- nodeName: rawObj.nodeName,
- isConnected: rawObj.isConnected === true,
- };
-
- if (rawObj instanceof Ci.nsIDOMDocument && rawObj.location) {
- preview.location = hooks.createValueGrip(rawObj.location.href);
- } else if (rawObj instanceof Ci.nsIDOMDocumentFragment) {
- preview.childNodesLength = rawObj.childNodes.length;
-
- if (hooks.getGripDepth() < 2) {
- preview.childNodes = [];
- for (let node of rawObj.childNodes) {
- let actor = hooks.createValueGrip(obj.makeDebuggeeValue(node));
- preview.childNodes.push(actor);
- if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
- } else if (rawObj instanceof Ci.nsIDOMElement) {
- // For HTML elements (in an HTML document, at least), the nodeName is an
- // uppercased version of the actual element name. Check for HTML
- // elements, that is elements in the HTML namespace, and lowercase the
- // nodeName in that case.
- if (rawObj.namespaceURI == "http://www.w3.org/1999/xhtml") {
- preview.nodeName = preview.nodeName.toLowerCase();
- }
-
- // Add preview for DOM element attributes.
- preview.attributes = {};
- preview.attributesLength = rawObj.attributes.length;
- for (let attr of rawObj.attributes) {
- preview.attributes[attr.nodeName] = hooks.createValueGrip(attr.value);
- }
- } else if (obj.class == "Attr") {
- preview.value = hooks.createValueGrip(rawObj.value);
- } else if (obj.class == "Text" ||
- obj.class == "CDATASection" ||
- obj.class == "Comment") {
- preview.textContent = hooks.createValueGrip(rawObj.textContent);
- }
-
- return true;
- },
-
- function DOMEvent({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMEvent)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMEvent",
- type: rawObj.type,
- properties: Object.create(null),
- };
-
- if (hooks.getGripDepth() < 2) {
- let target = obj.makeDebuggeeValue(rawObj.target);
- preview.target = hooks.createValueGrip(target);
- }
-
- let props = [];
- if (obj.class == "MouseEvent" ||
- obj.class == "DragEvent" ||
- obj.class == "PointerEvent" ||
- obj.class == "SimpleGestureEvent" ||
- obj.class == "WheelEvent") {
- props.push("buttons", "clientX", "clientY", "layerX", "layerY");
- } else if (obj.class == "KeyboardEvent") {
- let modifiers = [];
- if (rawObj.altKey) {
- modifiers.push("Alt");
- }
- if (rawObj.ctrlKey) {
- modifiers.push("Control");
- }
- if (rawObj.metaKey) {
- modifiers.push("Meta");
- }
- if (rawObj.shiftKey) {
- modifiers.push("Shift");
- }
- preview.eventKind = "key";
- preview.modifiers = modifiers;
-
- props.push("key", "charCode", "keyCode");
- } else if (obj.class == "TransitionEvent") {
- props.push("propertyName", "pseudoElement");
- } else if (obj.class == "AnimationEvent") {
- props.push("animationName", "pseudoElement");
- } else if (obj.class == "ClipboardEvent") {
- props.push("clipboardData");
- }
-
- // Add event-specific properties.
- for (let prop of props) {
- let value = rawObj[prop];
- if (value && (typeof value == "object" || typeof value == "function")) {
- // Skip properties pointing to objects.
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- }
-
- // Add any properties we find on the event object.
- if (!props.length) {
- let i = 0;
- for (let prop in rawObj) {
- let value = rawObj[prop];
- if (prop == "target" || prop == "type" || value === null ||
- typeof value == "function") {
- continue;
- }
- if (value && typeof value == "object") {
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
-
- return true;
- },
-
- function DOMException({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMDOMException)) {
- return false;
- }
-
- grip.preview = {
- kind: "DOMException",
- name: hooks.createValueGrip(rawObj.name),
- message: hooks.createValueGrip(rawObj.message),
- code: hooks.createValueGrip(rawObj.code),
- result: hooks.createValueGrip(rawObj.result),
- filename: hooks.createValueGrip(rawObj.filename),
- lineNumber: hooks.createValueGrip(rawObj.lineNumber),
- columnNumber: hooks.createValueGrip(rawObj.columnNumber),
- };
-
- return true;
- },
-
- function PseudoArray({obj, hooks}, grip, rawObj) {
- // An object is considered a pseudo-array if all the following apply:
- // - All its properties are array indices except, optionally, a "length" property.
- // - At least it has the "0" array index.
- // - The array indices are consecutive.
- // - The value of "length", if present, is the number of array indices.
-
- let keys;
- try {
- keys = obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- return false;
- }
- let {length} = keys;
- if (length === 0) {
- return false;
- }
-
- // Array indices should be sorted at the beginning, from smallest to largest.
- // Other properties should be at the end, so check if the last one is "length".
- if (keys[length - 1] === "length") {
- --length;
- if (length === 0 || length !== DevToolsUtils.getProperty(obj, "length")) {
- return false;
- }
- }
-
- // Check that the last key is the array index expected at that position.
- let lastKey = keys[length - 1];
- if (!isArrayIndex(lastKey) || +lastKey !== length - 1) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- // Avoid recursive object grips.
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- let numItems = Math.min(OBJECT_PREVIEW_MAX_ITEMS, length);
-
- for (let i = 0; i < numItems; ++i) {
- let desc = obj.getOwnPropertyDescriptor(i);
- if (desc && "value" in desc) {
- items.push(hooks.createValueGrip(desc.value));
- } else {
- items.push(null);
- }
- }
-
- return true;
- },
-
- function Object(objectActor, grip, rawObj) {
- return GenericObject(objectActor, grip, rawObj, /* specialStringBehavior = */ false);
- },
-];
-
-/**
- * Get thisDebugger.Object referent's `promiseState`.
- *
- * @returns Object
- * An object of one of the following forms:
- * - { state: "pending" }
- * - { state: "fulfilled", value }
- * - { state: "rejected", reason }
- */
-function getPromiseState(obj) {
- if (obj.class != "Promise") {
- throw new Error(
- "Can't call `getPromiseState` on `Debugger.Object`s that don't " +
- "refer to Promise objects.");
- }
-
- let state = { state: obj.promiseState };
- if (state.state === "fulfilled") {
- state.value = obj.promiseValue;
- } else if (state.state === "rejected") {
- state.reason = obj.promiseReason;
- }
- return state;
-}
-
-/**
- * Determine if a given value is non-primitive.
- *
- * @param Any value
- * The value to test.
- * @return Boolean
- * Whether the value is non-primitive.
- */
-function isObject(value) {
- const type = typeof value;
- return type == "object" ? value !== null : type == "function";
}
/**
* Create a function that can safely stringify Debugger.Objects of a given
* builtin type.
*
* @param Function ctor
* The builtin class constructor.
@@ -2131,52 +90,16 @@ function errorStringify(obj) {
}
if (message === "" || message === undefined) {
return name;
}
return name + ": " + message;
}
-/**
- * Stringify a Debugger.Object based on its class.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification for the object.
- */
-function stringify(obj) {
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- if (DevToolsUtils.isCPOW(obj)) {
- return "<cpow>";
- }
- let unwrapped = DevToolsUtils.unwrap(obj);
- if (unwrapped === undefined) {
- return "<invisibleToDebugger>";
- } else if (unwrapped.isProxy) {
- return "<proxy>";
- }
- // The following line should not be reached. It's there just in case somebody
- // modifies isSafeDebuggerObject to return false for additional kinds of objects.
- return "[object " + obj.class + "]";
- } else if (obj.class == "DeadObject") {
- return "<dead object>";
- }
-
- const stringifier = stringifiers[obj.class] || stringifiers.Object;
-
- try {
- return stringifier(obj);
- } catch (e) {
- DevToolsUtils.reportException("stringify", e);
- return "<failed to stringify object>";
- }
-}
-
// Used to prevent infinite recursion when an array is found inside itself.
var seen = null;
var stringifiers = {
Error: errorStringify,
EvalError: errorStringify,
RangeError: errorStringify,
ReferenceError: errorStringify,
@@ -2196,17 +119,17 @@ var stringifiers = {
if (topLevel) {
seen = new Set();
} else if (seen.has(obj)) {
return "";
}
seen.add(obj);
- const len = getArrayLength(obj);
+ const len = ObjectUtils.getArrayLength(obj);
let string = "";
// Array.length is always a non-negative safe integer.
for (let i = 0; i < len; i++) {
const desc = obj.getOwnPropertyDescriptor(i);
if (desc) {
const { value } = desc;
if (value != null) {
@@ -2231,459 +154,34 @@ var stringifiers = {
const code = DevToolsUtils.getProperty(obj, "code");
const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
return '[Exception... "' + message + '" ' +
'code: "' + code + '" ' +
'nsresult: "0x' + result + " (" + name + ')"]';
},
Promise: obj => {
- const { state, value, reason } = getPromiseState(obj);
+ const { state, value, reason } = ObjectUtils.getPromiseState(obj);
let statePreview = state;
if (state != "pending") {
const settledValue = state === "fulfilled" ? value : reason;
statePreview += ": " + (typeof settledValue === "object" && settledValue !== null
? stringify(settledValue)
: settledValue);
}
return "Promise (" + statePreview + ")";
},
};
/**
- * Make a debuggee value for the given object, if needed. Primitive values
- * are left the same.
- *
- * Use case: you have a raw JS object (after unsafe dereference) and you want to
- * send it to the client. In that case you need to use an ObjectActor which
- * requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
- * method works only for JS objects and functions.
+ * Determine if a given value is non-primitive.
*
- * @param Debugger.Object obj
- * @param any value
- * @return object
- */
-function makeDebuggeeValueIfNeeded(obj, value) {
- if (value && (typeof value == "object" || typeof value == "function")) {
- return obj.makeDebuggeeValue(value);
- }
- return value;
-}
-
-/**
- * Creates an actor for the specified "very long" string. "Very long" is specified
- * at the server's discretion.
- *
- * @param string String
- * The string.
+ * @param Any value
+ * The value to test.
+ * @return Boolean
+ * Whether the value is non-primitive.
*/
-function LongStringActor(string) {
- this.string = string;
- this.stringLength = string.length;
-}
-
-LongStringActor.prototype = {
- actorPrefix: "longString",
-
- rawValue: function() {
- return this.string;
- },
-
- destroy: function() {
- // Because longStringActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- return {
- "type": "longString",
- "initial": this.string.substring(
- 0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
- "length": this.stringLength,
- "actor": this.actorID
- };
- },
-
- /**
- * Handle a request to extract part of this actor's string.
- *
- * @param request object
- * The protocol request object.
- */
- onSubstring: function(request) {
- return {
- "from": this.actorID,
- "substring": this.string.substring(request.start, request.end)
- };
- },
-
- /**
- * Handle a request to release this LongStringActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.longStringActors) {
- delete this.registeredPool.longStringActors[this.string];
- }
- }
-};
-
-LongStringActor.prototype.requestTypes = {
- "substring": LongStringActor.prototype.onSubstring,
- "release": LongStringActor.prototype.onRelease
-};
-
-/**
- * Creates an actor for the specified symbol.
- *
- * @param symbol Symbol
- * The symbol.
- */
-function SymbolActor(symbol) {
- this.symbol = symbol;
-}
-
-SymbolActor.prototype = {
- actorPrefix: "symbol",
-
- rawValue: function() {
- return this.symbol;
- },
-
- destroy: function() {
- // Because symbolActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let form = {
- type: "symbol",
- actor: this.actorID,
- };
- let name = getSymbolName(this.symbol);
- if (name !== undefined) {
- // Create a grip for the name because it might be a longString.
- form.name = createValueGrip(name, this.registeredPool);
- }
- return form;
- },
-
- /**
- * Handle a request to release this SymbolActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.symbolActors) {
- delete this.registeredPool.symbolActors[this.symbol];
- }
- }
-};
-
-SymbolActor.prototype.requestTypes = {
- "release": SymbolActor.prototype.onRelease
-};
-
-/**
- * Creates an actor for the specified ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The buffer.
- */
-function ArrayBufferActor(buffer) {
- this.buffer = buffer;
- this.bufferLength = buffer.byteLength;
+function isObject(value) {
+ const type = typeof value;
+ return type == "object" ? value !== null : type == "function";
}
-ArrayBufferActor.prototype = {
- actorPrefix: "arrayBuffer",
-
- rawValue: function() {
- return this.buffer;
- },
-
- destroy: function() {
- },
-
- grip() {
- return {
- "type": "arrayBuffer",
- "length": this.bufferLength,
- "actor": this.actorID
- };
- },
-
- onSlice({start, count}) {
- let slice = new Uint8Array(this.buffer, start, count);
- let parts = [], offset = 0;
- const PortionSize = 0x6000; // keep it divisible by 3 for btoa() and join()
- while (offset + PortionSize < count) {
- parts.push(btoa(
- String.fromCharCode.apply(null, slice.subarray(offset, offset + PortionSize))));
- offset += PortionSize;
- }
- parts.push(btoa(String.fromCharCode.apply(null, slice.subarray(offset, count))));
- return {
- "from": this.actorID,
- "encoded": parts.join(""),
- };
- }
-};
-
-ArrayBufferActor.prototype.requestTypes = {
- "slice": ArrayBufferActor.prototype.onSlice,
-};
-
-/**
- * Create a grip for the given debuggee value. If the value is an
- * object, will create an actor with the given lifetime.
- */
-function createValueGrip(value, pool, makeObjectGrip) {
- switch (typeof value) {
- case "boolean":
- return value;
-
- case "string":
- if (stringIsLong(value)) {
- return longStringGrip(value, pool);
- }
- return value;
-
- case "number":
- if (value === Infinity) {
- return { type: "Infinity" };
- } else if (value === -Infinity) {
- return { type: "-Infinity" };
- } else if (Number.isNaN(value)) {
- return { type: "NaN" };
- } else if (!value && 1 / value === -Infinity) {
- return { type: "-0" };
- }
- return value;
-
- case "undefined":
- return { type: "undefined" };
-
- case "object":
- if (value === null) {
- return { type: "null" };
- } else if (value.optimizedOut ||
- value.uninitialized ||
- value.missingArguments) {
- // The slot is optimized out, an uninitialized binding, or
- // arguments on a dead scope
- return {
- type: "null",
- optimizedOut: value.optimizedOut,
- uninitialized: value.uninitialized,
- missingArguments: value.missingArguments
- };
- }
- return makeObjectGrip(value, pool);
-
- case "symbol":
- return symbolGrip(value, pool);
-
- default:
- assert(false, "Failed to provide a grip for: " + value);
- return null;
- }
-}
-
-const symbolProtoToString = Symbol.prototype.toString;
-
-function getSymbolName(symbol) {
- const name = symbolProtoToString.call(symbol).slice("Symbol(".length, -1);
- return name || undefined;
-}
-
-/**
- * Returns true if the string is long enough to use a LongStringActor instead
- * of passing the value directly over the protocol.
- *
- * @param str String
- * The string we are checking the length of.
- */
-function stringIsLong(str) {
- return str.length >= DebuggerServer.LONG_STRING_LENGTH;
-}
-
-/**
- * Create a grip for the given string.
- *
- * @param str String
- * The string we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function longStringGrip(str, pool) {
- if (!pool.longStringActors) {
- pool.longStringActors = {};
- }
-
- if (pool.longStringActors.hasOwnProperty(str)) {
- return pool.longStringActors[str].grip();
- }
-
- let actor = new LongStringActor(str);
- pool.addActor(actor);
- pool.longStringActors[str] = actor;
- return actor.grip();
-}
-
-/**
- * Create a grip for the given symbol.
- *
- * @param sym Symbol
- * The symbol we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function symbolGrip(sym, pool) {
- if (!pool.symbolActors) {
- pool.symbolActors = Object.create(null);
- }
-
- if (sym in pool.symbolActors) {
- return pool.symbolActors[sym].grip();
- }
-
- let actor = new SymbolActor(sym);
- pool.addActor(actor);
- pool.symbolActors[sym] = actor;
- return actor.grip();
-}
-
-/**
- * Create a grip for the given ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The ArrayBuffer we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function arrayBufferGrip(buffer, pool) {
- if (!pool.arrayBufferActors) {
- pool.arrayBufferActors = new WeakMap();
- }
-
- if (pool.arrayBufferActors.has(buffer)) {
- return pool.arrayBufferActors.get(buffer).grip();
- }
-
- let actor = new ArrayBufferActor(buffer);
- pool.addActor(actor);
- pool.arrayBufferActors.set(buffer, actor);
- return actor.grip();
-}
-
-const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
- "Uint32Array", "Int8Array", "Int16Array", "Int32Array",
- "Float32Array", "Float64Array"];
-
-/**
- * Returns true if a debuggee object is a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isTypedArray(object) {
- return TYPED_ARRAY_CLASSES.includes(object.class);
-}
-
-/**
- * Returns true if a debuggee object is an array, including a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isArray(object) {
- return isTypedArray(object) || object.class === "Array";
-}
-
-/**
- * Returns the length of an array (or typed array).
- *
- * @param obj Debugger.Object
- * The debuggee object of the array.
- * @return Number
- * @throws if the object is not an array.
- */
-function getArrayLength(object) {
- if (!isArray(object)) {
- throw new Error("Expected an array, got a " + object.class);
- }
-
- // Real arrays have a reliable `length` own property.
- if (object.class === "Array") {
- return DevToolsUtils.getProperty(object, "length");
- }
-
- // For typed arrays, `DevToolsUtils.getProperty` is not reliable because the `length`
- // getter could be shadowed by an own property, and `getOwnPropertyNames` is
- // unnecessarily slow. Obtain the `length` getter safely and call it manually.
- let typedProto = Object.getPrototypeOf(Uint8Array.prototype);
- let getter = Object.getOwnPropertyDescriptor(typedProto, "length").get;
- return getter.call(object.unsafeDereference());
-}
-
-/**
- * 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;
-}
-
-/**
- * Returns true if the parameter is suitable to be an array index.
- *
- * @param str String
- * @return Boolean
- */
-function isArrayIndex(str) {
- // Transform the parameter to a 32-bit unsigned integer.
- let num = str >>> 0;
- // Check that the parameter is a canonical Uint32 index.
- return num + "" === str &&
- // Array indices cannot attain the maximum Uint32 value.
- num != -1 >>> 0;
-}
-
-exports.ObjectActor = ObjectActor;
-exports.PropertyIteratorActor = PropertyIteratorActor;
-exports.LongStringActor = LongStringActor;
-exports.SymbolActor = SymbolActor;
-exports.createValueGrip = createValueGrip;
-exports.stringIsLong = stringIsLong;
-exports.longStringGrip = longStringGrip;
-exports.arrayBufferGrip = arrayBufferGrip;
+module.exports = stringify;
copy from devtools/server/actors/object.js
copy to devtools/server/actors/object/symbol-iterator.js
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object/symbol-iterator.js
@@ -1,1198 +1,16 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 { Cu, Ci } = require("chrome");
-const { GeneratedLocation } = require("devtools/server/actors/common");
-const { DebuggerServer } = require("devtools/server/main");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { assert } = DevToolsUtils;
-
-loader.lazyRequireGetter(this, "ChromeUtils");
-
-// Number of items to preview in objects, arrays, maps, sets, lists,
-// collections, etc.
-const OBJECT_PREVIEW_MAX_ITEMS = 10;
-
-/**
- * Creates an actor for the specified object.
- *
- * @param obj Debugger.Object
- * The debuggee object.
- * @param hooks Object
- * A collection of abstract methods that are implemented by the caller.
- * ObjectActor requires the following functions to be implemented by
- * the caller:
- * - createValueGrip
- * Creates a value grip for the given object
- * - sources
- * TabSources getter that manages the sources of a thread
- * - createEnvironmentActor
- * Creates and return an environment actor
- * - getGripDepth
- * An actor's grip depth getter
- * - incrementGripDepth
- * Increment the actor's grip depth
- * - decrementGripDepth
- * Decrement the actor's grip depth
- * - globalDebugObject
- * The Debuggee Global Object as given by the ThreadActor
- */
-function ObjectActor(obj, {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
-}) {
- assert(!obj.optimizedOut,
- "Should not create object actors for optimized out values!");
- this.obj = obj;
- this.hooks = {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
- };
- this.iterators = new Set();
-}
-
-ObjectActor.prototype = {
- actorPrefix: "obj",
-
- rawValue: function() {
- return this.obj.unsafeDereference();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let g = {
- "type": "object",
- "actor": this.actorID,
- "class": this.obj.class,
- };
-
- let unwrapped = DevToolsUtils.unwrap(this.obj);
-
- // Unsafe objects must be treated carefully.
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- if (DevToolsUtils.isCPOW(this.obj)) {
- // Cross-process object wrappers can't be accessed.
- g.class = "CPOW: " + g.class;
- } else if (unwrapped === undefined) {
- // Objects belonging to an invisible-to-debugger compartment might be proxies,
- // so just in case they shouldn't be accessed.
- g.class = "InvisibleToDebugger: " + g.class;
- } else if (unwrapped.isProxy) {
- // Proxy objects can run traps when accessed, so just create a preview with
- // the target and the handler.
- g.class = "Proxy";
- this.hooks.incrementGripDepth();
- DebuggerServer.ObjectActorPreviewers.Proxy[0](this, g, null);
- this.hooks.decrementGripDepth();
- }
- return g;
- }
-
- // If the debuggee does not subsume the object's compartment, most properties won't
- // be accessible. Cross-orgin Window and Location objects might expose some, though.
- // Change the displayed class, but when creating the preview use the original one.
- if (unwrapped === null) {
- g.class = "Restricted";
- }
-
- this.hooks.incrementGripDepth();
-
- g.extensible = this.obj.isExtensible();
- g.frozen = this.obj.isFrozen();
- g.sealed = this.obj.isSealed();
-
- if (g.class == "Promise") {
- g.promiseState = this._createPromiseState();
- }
-
- // FF40+: Allow to know how many properties an object has to lazily display them
- // when there is a bunch.
- if (isTypedArray(g)) {
- // Bug 1348761: getOwnPropertyNames is unnecessary slow on TypedArrays
- g.ownPropertyLength = getArrayLength(this.obj);
- } else {
- try {
- g.ownPropertyLength = this.obj.getOwnPropertyNames().length;
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let raw = this.obj.unsafeDereference();
-
- // If Cu is not defined, we are running on a worker thread, where xrays
- // don't exist.
- if (Cu) {
- raw = Cu.unwaiveXrays(raw);
- }
-
- if (!DevToolsUtils.isSafeJSObject(raw)) {
- raw = null;
- }
-
- let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
- DebuggerServer.ObjectActorPreviewers.Object;
- for (let fn of previewers) {
- try {
- if (fn(this, g, raw)) {
- break;
- }
- } catch (e) {
- let msg = "ObjectActor.prototype.grip previewer function";
- DevToolsUtils.reportException(msg, e);
- }
- }
-
- this.hooks.decrementGripDepth();
- return g;
- },
-
- /**
- * Returns an object exposing the internal Promise state.
- */
- _createPromiseState: function() {
- const { state, value, reason } = getPromiseState(this.obj);
- let promiseState = { state };
-
- if (state == "fulfilled") {
- promiseState.value = this.hooks.createValueGrip(value);
- } else if (state == "rejected") {
- promiseState.reason = this.hooks.createValueGrip(reason);
- }
-
- promiseState.creationTimestamp = Date.now() - this.obj.promiseLifetime;
-
- // Only add the timeToSettle property if the Promise isn't pending.
- if (state !== "pending") {
- promiseState.timeToSettle = this.obj.promiseTimeToResolution;
- }
-
- return promiseState;
- },
-
- /**
- * Releases this actor from the pool.
- */
- release: function() {
- if (this.registeredPool.objectActors) {
- this.registeredPool.objectActors.delete(this.obj);
- }
- this.iterators.forEach(actor => this.registeredPool.removeActor(actor));
- this.iterators.clear();
- this.registeredPool.removeActor(this);
- },
-
- /**
- * Handle a protocol request to provide the definition site of this function
- * object.
- */
- onDefinitionSite: function() {
- if (this.obj.class != "Function") {
- return {
- from: this.actorID,
- error: "objectNotFunction",
- message: this.actorID + " is not a function."
- };
- }
-
- if (!this.obj.script) {
- return {
- from: this.actorID,
- error: "noScript",
- message: this.actorID + " has no Debugger.Script"
- };
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- this.hooks.sources().createNonSourceMappedActor(this.obj.script.source),
- this.obj.script.startLine,
- 0 // TODO bug 901138: use Debugger.Script.prototype.startColumn
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn
- };
- });
- },
-
- /**
- * Handle a protocol request to provide the names of the properties defined on
- * the object and not its prototype.
- */
- onOwnPropertyNames: function() {
- let props = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- props = this.obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
- return { from: this.actorID, ownPropertyNames: props };
- },
-
- /**
- * Creates an actor to iterate over an object property names and values.
- * See PropertyIteratorActor constructor for more info about options param.
- *
- * @param request object
- * The protocol request object.
- */
- onEnumProperties: function(request) {
- let actor = new PropertyIteratorActor(this, request.options);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over entries of a Map/Set-like object.
- */
- onEnumEntries: function() {
- let actor = new PropertyIteratorActor(this, { enumEntries: true });
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over an object symbols properties.
- */
- onEnumSymbols: function() {
- let actor = new SymbolIteratorActor(this);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Handle a protocol request to provide the prototype and own properties of
- * the object.
- *
- * @returns {Object} An object containing the data of this.obj, of the following form:
- * - {string} from: this.obj's actorID.
- * - {Object} prototype: The descriptor of this.obj's prototype.
- * - {Object} ownProperties: an object where the keys are the names of the
- * this.obj's ownProperties, and the values the descriptors of
- * the properties.
- * - {Array} ownSymbols: An array containing all descriptors of this.obj's
- * ownSymbols. Here we have an array, and not an object like for
- * ownProperties, because we can have multiple symbols with the same
- * name in this.obj, e.g. `{[Symbol()]: "a", [Symbol()]: "b"}`.
- * - {Object} safeGetterValues: an object that maps this.obj's property names
- * with safe getters descriptors.
- */
- onPrototypeAndProperties: function() {
- let proto = null;
- let names = [];
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- proto = this.obj.proto;
- names = this.obj.getOwnPropertyNames();
- symbols = this.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let ownProperties = Object.create(null);
- let ownSymbols = [];
-
- for (let name of names) {
- ownProperties[name] = this._propertyDescriptor(name);
- }
-
- for (let sym of symbols) {
- ownSymbols.push({
- name: sym.toString(),
- descriptor: this._propertyDescriptor(sym)
- });
- }
-
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto),
- ownProperties,
- ownSymbols,
- safeGetterValues: this._findSafeGetterValues(names) };
- },
-
- /**
- * Find the safe getter values for the current Debugger.Object, |this.obj|.
- *
- * @private
- * @param array ownProperties
- * The array that holds the list of known ownProperties names for
- * |this.obj|.
- * @param number [limit=0]
- * Optional limit of getter values to find.
- * @return object
- * An object that maps property names to safe getter descriptors as
- * defined by the remote debugging protocol.
- */
- _findSafeGetterValues: function(ownProperties, limit = 0) {
- let safeGetterValues = Object.create(null);
- let obj = this.obj;
- let level = 0, i = 0;
-
- // Do not search safe getters in unsafe objects.
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- return safeGetterValues;
- }
-
- // Most objects don't have any safe getters but inherit some from their
- // prototype. Avoid calling getOwnPropertyNames on objects that may have
- // many properties like Array, strings or js objects. That to avoid
- // freezing firefox when doing so.
- if (isArray(this.obj) || ["Object", "String"].includes(this.obj.class)) {
- obj = obj.proto;
- level++;
- }
-
- while (obj && DevToolsUtils.isSafeDebuggerObject(obj)) {
- let getters = this._findSafeGetters(obj);
- for (let name of getters) {
- // Avoid overwriting properties from prototypes closer to this.obj. Also
- // avoid providing safeGetterValues from prototypes if property |name|
- // is already defined as an own property.
- if (name in safeGetterValues ||
- (obj != this.obj && ownProperties.includes(name))) {
- continue;
- }
-
- // Ignore __proto__ on Object.prototye.
- if (!obj.proto && name == "__proto__") {
- continue;
- }
-
- let desc = null, getter = null;
- try {
- desc = obj.getOwnPropertyDescriptor(name);
- getter = desc.get;
- } catch (ex) {
- // The above can throw if the cache becomes stale.
- }
- if (!getter) {
- obj._safeGetters = null;
- continue;
- }
-
- let result = getter.call(this.obj);
- if (result && !("throw" in result)) {
- let getterValue = undefined;
- if ("return" in result) {
- getterValue = result.return;
- } else if ("yield" in result) {
- getterValue = result.yield;
- }
- // WebIDL attributes specified with the LenientThis extended attribute
- // return undefined and should be ignored.
- if (getterValue !== undefined) {
- safeGetterValues[name] = {
- getterValue: this.hooks.createValueGrip(getterValue),
- getterPrototypeLevel: level,
- enumerable: desc.enumerable,
- writable: level == 0 ? desc.writable : true,
- };
- if (limit && ++i == limit) {
- break;
- }
- }
- }
- }
- if (limit && i == limit) {
- break;
- }
-
- obj = obj.proto;
- level++;
- }
-
- return safeGetterValues;
- },
-
- /**
- * Find the safe getters for a given Debugger.Object. Safe getters are native
- * getters which are safe to execute.
- *
- * @private
- * @param Debugger.Object object
- * The Debugger.Object where you want to find safe getters.
- * @return Set
- * A Set of names of safe getters. This result is cached for each
- * Debugger.Object.
- */
- _findSafeGetters: function(object) {
- if (object._safeGetters) {
- return object._safeGetters;
- }
-
- let getters = new Set();
-
- if (!DevToolsUtils.isSafeDebuggerObject(object)) {
- object._safeGetters = getters;
- return getters;
- }
-
- let names = [];
- try {
- names = object.getOwnPropertyNames();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
-
- for (let name of names) {
- let desc = null;
- try {
- desc = object.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072).
- }
- if (!desc || desc.value !== undefined || !("get" in desc)) {
- continue;
- }
-
- if (DevToolsUtils.hasSafeGetter(desc)) {
- getters.add(name);
- }
- }
-
- object._safeGetters = getters;
- return getters;
- },
-
- /**
- * Handle a protocol request to provide the prototype of the object.
- */
- onPrototype: function() {
- let proto = null;
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- proto = this.obj.proto;
- }
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto) };
- },
-
- /**
- * Handle a protocol request to provide the property descriptor of the
- * object's specified property.
- *
- * @param request object
- * The protocol request object.
- */
- onProperty: function(request) {
- if (!request.name) {
- return { error: "missingParameter",
- message: "no property name was specified" };
- }
-
- return { from: this.actorID,
- descriptor: this._propertyDescriptor(request.name) };
- },
-
- /**
- * Handle a protocol request to provide the display string for the object.
- */
- onDisplayString: function() {
- const string = stringify(this.obj);
- return { from: this.actorID,
- displayString: this.hooks.createValueGrip(string) };
- },
-
- /**
- * A helper method that creates a property descriptor for the provided object,
- * properly formatted for sending in a protocol response.
- *
- * @private
- * @param string name
- * The property that the descriptor is generated for.
- * @param boolean [onlyEnumerable]
- * Optional: true if you want a descriptor only for an enumerable
- * property, false otherwise.
- * @return object|undefined
- * The property descriptor, or undefined if this is not an enumerable
- * property and onlyEnumerable=true.
- */
- _propertyDescriptor: function(name, onlyEnumerable) {
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- return undefined;
- }
-
- let desc;
- try {
- desc = this.obj.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072). Inform the user with a bogus, but hopefully
- // explanatory, descriptor.
- return {
- configurable: false,
- writable: false,
- enumerable: false,
- value: e.name
- };
- }
-
- if (!desc || onlyEnumerable && !desc.enumerable) {
- return undefined;
- }
-
- let retval = {
- configurable: desc.configurable,
- enumerable: desc.enumerable
- };
-
- if ("value" in desc) {
- retval.writable = desc.writable;
- retval.value = this.hooks.createValueGrip(desc.value);
- } else {
- if ("get" in desc) {
- retval.get = this.hooks.createValueGrip(desc.get);
- }
- if ("set" in desc) {
- retval.set = this.hooks.createValueGrip(desc.set);
- }
- }
- return retval;
- },
-
- /**
- * Handle a protocol request to provide the source code of a function.
- *
- * @param request object
- * The protocol request object.
- */
- onDecompile: function(request) {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "decompile request is only valid for object grips " +
- "with a 'Function' class." };
- }
-
- return { from: this.actorID,
- decompiledCode: this.obj.decompile(!!request.pretty) };
- },
-
- /**
- * Handle a protocol request to provide the parameters of a function.
- */
- onParameterNames: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "'parameterNames' request is only valid for object " +
- "grips with a 'Function' class." };
- }
-
- return { parameterNames: this.obj.parameterNames };
- },
-
- /**
- * Handle a protocol request to release a thread-lifetime grip.
- */
- onRelease: function() {
- this.release();
- return {};
- },
-
- /**
- * Handle a protocol request to provide the lexical scope of a function.
- */
- onScope: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "scope request is only valid for object grips with a" +
- " 'Function' class." };
- }
-
- let envActor = this.hooks.createEnvironmentActor(this.obj.environment,
- this.registeredPool);
- if (!envActor) {
- return { error: "notDebuggee",
- message: "cannot access the environment of this function." };
- }
-
- return { from: this.actorID, scope: envActor.form() };
- },
-
- /**
- * Handle a protocol request to get the list of dependent promises of a
- * promise.
- *
- * @return object
- * Returns an object containing an array of object grips of the
- * dependent promises
- */
- onDependentPromises: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'dependentPromises' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let promises = this.obj.promiseDependentPromises
- .map(p => this.hooks.createValueGrip(p));
-
- return { promises };
- },
-
- /**
- * Handle a protocol request to get the allocation stack of a promise.
- */
- onAllocationStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'allocationStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseAllocationSite;
- let allocationStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- allocationStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(allocationStacks).then(stacks => {
- return { allocationStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the fulfillment stack of a promise.
- */
- onFulfillmentStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'fulfillmentStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let fulfillmentStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- fulfillmentStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(fulfillmentStacks).then(stacks => {
- return { fulfillmentStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the rejection stack of a promise.
- */
- onRejectionStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'rejectionStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let rejectionStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- rejectionStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(rejectionStacks).then(stacks => {
- return { rejectionStack: stacks };
- });
- },
-
- /**
- * Helper function for fetching the source location of a SavedFrame stack.
- *
- * @param SavedFrame stack
- * The promise allocation stack frame
- * @return object
- * Returns an object containing the source location of the SavedFrame
- * stack.
- */
- _getSourceOriginalLocation: function(stack) {
- let source;
-
- // Catch any errors if the source actor cannot be found
- try {
- source = this.hooks.sources().getSourceActorByURL(stack.source);
- } catch (e) {
- // ignored
- }
-
- if (!source) {
- return null;
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- source,
- stack.line,
- stack.column
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn,
- functionDisplayName: stack.functionDisplayName
- };
- });
- }
-};
-
-ObjectActor.prototype.requestTypes = {
- "definitionSite": ObjectActor.prototype.onDefinitionSite,
- "parameterNames": ObjectActor.prototype.onParameterNames,
- "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
- "enumProperties": ObjectActor.prototype.onEnumProperties,
- "prototype": ObjectActor.prototype.onPrototype,
- "property": ObjectActor.prototype.onProperty,
- "displayString": ObjectActor.prototype.onDisplayString,
- "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
- "decompile": ObjectActor.prototype.onDecompile,
- "release": ObjectActor.prototype.onRelease,
- "scope": ObjectActor.prototype.onScope,
- "dependentPromises": ObjectActor.prototype.onDependentPromises,
- "allocationStack": ObjectActor.prototype.onAllocationStack,
- "fulfillmentStack": ObjectActor.prototype.onFulfillmentStack,
- "rejectionStack": ObjectActor.prototype.onRejectionStack,
- "enumEntries": ObjectActor.prototype.onEnumEntries,
- "enumSymbols": ObjectActor.prototype.onEnumSymbols,
-};
-
-/**
- * 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.
- */
-function PropertyIteratorActor(objectActor, options) {
- if (!DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- this.iterator = {
- size: 0,
- propertyName: index => undefined,
- propertyDescription: index => undefined,
- };
- } else if (options.enumEntries) {
- let cls = objectActor.obj.class;
- if (cls == "Map") {
- this.iterator = enumMapEntries(objectActor);
- } else if (cls == "WeakMap") {
- this.iterator = enumWeakMapEntries(objectActor);
- } else if (cls == "Set") {
- this.iterator = enumSetEntries(objectActor);
- } else if (cls == "WeakSet") {
- this.iterator = enumWeakSetEntries(objectActor);
- } else {
- throw new Error("Unsupported class to enumerate entries from: " + cls);
- }
- } else if (
- isArray(objectActor.obj)
- && options.ignoreNonIndexedProperties
- && !options.query
- ) {
- this.iterator = enumArrayProperties(objectActor, options);
- } else {
- this.iterator = enumObjectProperties(objectActor, options);
- }
-}
-
-PropertyIteratorActor.prototype = {
- actorPrefix: "propertyIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- names({ indexes }) {
- let list = [];
- for (let idx of indexes) {
- list.push(this.iterator.propertyName(idx));
- }
- return {
- names: indexes
- };
- },
-
- slice({ start, count }) {
- let ownProperties = Object.create(null);
- for (let i = start, m = start + count; i < m; i++) {
- let name = this.iterator.propertyName(i);
- ownProperties[name] = this.iterator.propertyDescription(i);
- }
- return {
- ownProperties
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-PropertyIteratorActor.prototype.requestTypes = {
- "names": PropertyIteratorActor.prototype.names,
- "slice": PropertyIteratorActor.prototype.slice,
- "all": PropertyIteratorActor.prototype.all,
-};
-
-function enumArrayProperties(objectActor, options) {
- return {
- size: getArrayLength(objectActor.obj),
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- return objectActor._propertyDescriptor(index);
- }
- };
-}
-
-function enumObjectProperties(objectActor, options) {
- 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) {
- let length = DevToolsUtils.getProperty(objectActor.obj, "length");
- let sliceIndex;
-
- const isLengthTrustworthy =
- isUint32(length)
- && (!length || isArrayIndex(names[length - 1]))
- && !isArrayIndex(names[length]);
-
- if (!isLengthTrustworthy) {
- // The length property may not reflect what the object looks like, let's find
- // where indexed properties end.
-
- if (!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 (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,
- // like webaudio buffers.
- if (options.ignoreIndexedProperties) {
- // Keep items after `sliceIndex` index
- names = names.slice(sliceIndex);
- } else if (options.ignoreNonIndexedProperties) {
- // Keep `sliceIndex` first items
- names.length = sliceIndex;
- }
- }
-
- let safeGetterValues = objectActor._findSafeGetterValues(names, 0);
- let safeGetterNames = Object.keys(safeGetterValues);
- // Merge the safe getter values into the existing properties list.
- for (let 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 && 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) {
- let name = names[index];
- let desc = objectActor._propertyDescriptor(name);
- if (!desc) {
- desc = safeGetterValues[name];
- } else if (name in safeGetterValues) {
- // Merge the safe getter values into the existing properties list.
- let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
- desc.getterValue = getterValue;
- desc.getterPrototypeLevel = getterPrototypeLevel;
- }
- return desc;
- }
- };
-}
-
-/**
- * Helper function to create a grip from a Map/Set entry
- */
-function gripFromEntry({ obj, hooks }, entry) {
- return hooks.createValueGrip(
- makeDebuggeeValueIfNeeded(obj, Cu.unwaiveXrays(entry)));
-}
-
-function enumMapEntries(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.
- let raw = objectActor.obj.unsafeDereference();
-
- let keys = [...Cu.waiveXrays(Map.prototype.keys.call(raw))];
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = Map.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = Map.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
-}
-
-function enumWeakMapEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakMapKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = WeakMap.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = WeakMap.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
-}
-
-function enumSetEntries(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.
- let raw = objectActor.obj.unsafeDereference();
- let values = [...Cu.waiveXrays(Set.prototype.values.call(raw))];
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of values) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: values.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = values[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-function enumWeakSetEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakSetKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of keys) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = keys[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
/**
* Creates an actor to iterate over an object's symbols.
*
* @param objectActor ObjectActor
* The object actor.
*/
function SymbolIteratorActor(objectActor) {
@@ -1244,1446 +62,9 @@ SymbolIteratorActor.prototype = {
}
};
SymbolIteratorActor.prototype.requestTypes = {
"slice": SymbolIteratorActor.prototype.slice,
"all": SymbolIteratorActor.prototype.all,
};
-/**
- * Functions for adding information to ObjectActor grips for the purpose of
- * having customized output. This object holds arrays mapped by
- * Debugger.Object.prototype.class.
- *
- * In each array you can add functions that take three
- * arguments:
- * - the ObjectActor instance and its hooks to make a preview for,
- * - the grip object being prepared for the client,
- * - the raw JS object after calling Debugger.Object.unsafeDereference(). This
- * argument is only provided if the object is safe for reading properties and
- * executing methods. See DevToolsUtils.isSafeJSObject().
- *
- * Functions must return false if they cannot provide preview
- * information for the debugger object, or true otherwise.
- */
-DebuggerServer.ObjectActorPreviewers = {
- String: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("String", String, objectActor, grip, rawObj);
- }],
-
- Boolean: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip, rawObj);
- }],
-
- Number: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Number", Number, objectActor, grip, rawObj);
- }],
-
- Symbol: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Symbol", Symbol, objectActor, grip, rawObj);
- }],
-
- Function: [function({obj, hooks}, grip) {
- if (obj.name) {
- grip.name = obj.name;
- }
-
- if (obj.displayName) {
- grip.displayName = obj.displayName.substr(0, 500);
- }
-
- if (obj.parameterNames) {
- grip.parameterNames = obj.parameterNames;
- }
-
- // Check if the developer has added a de-facto standard displayName
- // property for us to use.
- let userDisplayName;
- try {
- userDisplayName = obj.getOwnPropertyDescriptor("displayName");
- } catch (e) {
- // The above can throw "permission denied" errors when the debuggee
- // does not subsume the function's compartment.
- }
-
- if (userDisplayName && typeof userDisplayName.value == "string" &&
- userDisplayName.value) {
- grip.userDisplayName = hooks.createValueGrip(userDisplayName.value);
- }
-
- let dbgGlobal = hooks.getGlobalDebugObject();
- if (dbgGlobal) {
- let script = dbgGlobal.makeDebuggeeValue(obj.unsafeDereference()).script;
- if (script) {
- grip.location = {
- url: script.url,
- line: script.startLine
- };
- }
- }
-
- return true;
- }],
-
- RegExp: [function({obj, hooks}, grip) {
- let str = DevToolsUtils.callPropertyOnObject(obj, "toString");
- if (typeof str != "string") {
- return false;
- }
-
- grip.displayString = hooks.createValueGrip(str);
- return true;
- }],
-
- Date: [function({obj, hooks}, grip) {
- let time = DevToolsUtils.callPropertyOnObject(obj, "getTime");
- if (typeof time != "number") {
- return false;
- }
-
- grip.preview = {
- timestamp: hooks.createValueGrip(time),
- };
- return true;
- }],
-
- Array: [function({obj, hooks}, grip) {
- let length = getArrayLength(obj);
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let items = grip.preview.items = [];
-
- for (let i = 0; i < length; ++i) {
- // Array Xrays filter out various possibly-unsafe properties (like
- // functions, and claim that the value is undefined instead. This
- // is generally the right thing for privileged code accessing untrusted
- // objects, but quite confusing for Object previews. So we manually
- // override this protection by waiving Xrays on the array, and re-applying
- // Xrays on any indexed value props that we pull off of it.
- let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
- if (desc && !desc.get && !desc.set) {
- let value = Cu.unwaiveXrays(desc.value);
- value = makeDebuggeeValueIfNeeded(obj, value);
- items.push(hooks.createValueGrip(value));
- } else {
- items.push(null);
- }
-
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Set: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: size,
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumSetEntries(objectActor)) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakSet: [function(objectActor, grip) {
- let enumEntries = enumWeakSetEntries(objectActor);
-
- grip.preview = {
- kind: "ArrayLike",
- length: enumEntries.size
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumEntries) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Map: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: size,
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumMapEntries(objectActor)) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakMap: [function(objectActor, grip) {
- let enumEntries = enumWeakMapEntries(objectActor);
-
- grip.preview = {
- kind: "MapLike",
- size: enumEntries.size
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumEntries) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- DOMStringMap: [function({obj, hooks}, grip, rawObj) {
- if (!rawObj) {
- return false;
- }
-
- let keys = obj.getOwnPropertyNames();
- grip.preview = {
- kind: "MapLike",
- size: keys.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let key of keys) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
- entries.push([key, hooks.createValueGrip(value)]);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Proxy: [function({obj, hooks}, grip, rawObj) {
- // The `isProxy` getter of the debuggee object only detects proxies without
- // security wrappers. If false, the target and handler are not available.
- let hasTargetAndHandler = obj.isProxy;
- if (hasTargetAndHandler) {
- grip.proxyTarget = hooks.createValueGrip(obj.proxyTarget);
- grip.proxyHandler = hooks.createValueGrip(obj.proxyHandler);
- }
-
- grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownPropertiesLength: 2 * hasTargetAndHandler
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- if (hasTargetAndHandler) {
- grip.preview.ownProperties["<target>"] = {value: grip.proxyTarget};
- grip.preview.ownProperties["<handler>"] = {value: grip.proxyHandler};
- }
-
- return true;
- }],
-};
-
-/**
- * Generic previewer for classes wrapping primitives, like String,
- * Number and Boolean.
- *
- * @param string className
- * Class name to expect.
- * @param object classObj
- * The class to expect, eg. String. The valueOf() method of the class is
- * invoked on the given object.
- * @param ObjectActor objectActor
- * The object actor
- * @param Object grip
- * The result grip to fill in
- * @return Booolean true if the object was handled, false otherwise
- */
-function wrappedPrimitivePreviewer(className, classObj, objectActor, grip, rawObj) {
- let {obj, hooks} = objectActor;
-
- let v = null;
- try {
- v = classObj.prototype.valueOf.call(rawObj);
- } catch (ex) {
- // valueOf() can throw if the raw JS object is "misbehaved".
- return false;
- }
-
- if (v === null) {
- return false;
- }
-
- let canHandle = GenericObject(objectActor, grip, rawObj, className === "String");
- if (!canHandle) {
- return false;
- }
-
- grip.preview.wrappedValue =
- hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v));
- return true;
-}
-
-function GenericObject(objectActor, grip, rawObj, specialStringBehavior = false) {
- let {obj, hooks} = objectActor;
- if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
- return false;
- }
-
- let i = 0, names = [], symbols = [];
- let preview = grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownSymbols: [],
- };
-
- try {
- names = obj.getOwnPropertyNames();
- symbols = obj.getOwnPropertySymbols();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
- preview.ownPropertiesLength = names.length;
- preview.ownSymbolsLength = symbols.length;
-
- let length;
- if (specialStringBehavior) {
- length = DevToolsUtils.getProperty(obj, "length");
- if (typeof length != "number") {
- specialStringBehavior = false;
- }
- }
-
- for (let name of names) {
- if (specialStringBehavior && /^[0-9]+$/.test(name)) {
- let num = parseInt(name, 10);
- if (num.toString() === name && num >= 0 && num < length) {
- continue;
- }
- }
-
- let desc = objectActor._propertyDescriptor(name, true);
- if (!desc) {
- continue;
- }
-
- preview.ownProperties[name] = desc;
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- for (let symbol of symbols) {
- let descriptor = objectActor._propertyDescriptor(symbol, true);
- if (!descriptor) {
- continue;
- }
-
- preview.ownSymbols.push(Object.assign({
- descriptor
- }, hooks.createValueGrip(symbol)));
-
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- if (i < OBJECT_PREVIEW_MAX_ITEMS) {
- preview.safeGetterValues = objectActor._findSafeGetterValues(
- Object.keys(preview.ownProperties),
- OBJECT_PREVIEW_MAX_ITEMS - i);
- }
-
- return true;
-}
-
-// Preview functions that do not rely on the object class.
-DebuggerServer.ObjectActorPreviewers.Object = [
- function TypedArray({obj, hooks}, grip) {
- if (!isTypedArray(obj)) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: getArrayLength(obj),
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let global = Cu.getGlobalForObject(DebuggerServer);
- let classProto = global[obj.class].prototype;
- // The Xray machinery for TypedArrays denies indexed access on the grounds
- // that it's slow, and advises callers to do a structured clone instead.
- let safeView = Cu.cloneInto(classProto.subarray.call(raw, 0,
- OBJECT_PREVIEW_MAX_ITEMS), global);
- let items = grip.preview.items = [];
- for (let i = 0; i < safeView.length; i++) {
- items.push(safeView[i]);
- }
-
- return true;
- },
-
- function Error({obj, hooks}, grip) {
- switch (obj.class) {
- case "Error":
- case "EvalError":
- case "RangeError":
- case "ReferenceError":
- case "SyntaxError":
- case "TypeError":
- case "URIError":
- let name = DevToolsUtils.getProperty(obj, "name");
- let msg = DevToolsUtils.getProperty(obj, "message");
- let stack = DevToolsUtils.getProperty(obj, "stack");
- let fileName = DevToolsUtils.getProperty(obj, "fileName");
- let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
- let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
- grip.preview = {
- kind: "Error",
- name: hooks.createValueGrip(name),
- message: hooks.createValueGrip(msg),
- stack: hooks.createValueGrip(stack),
- fileName: hooks.createValueGrip(fileName),
- lineNumber: hooks.createValueGrip(lineNumber),
- columnNumber: hooks.createValueGrip(columnNumber),
- };
- return true;
- default:
- return false;
- }
- },
-
- function CSSMediaRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSMediaRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.conditionText),
- };
- return true;
- },
-
- function CSSStyleRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSStyleRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.selectorText),
- };
- return true;
- },
-
- function ObjectWithURL({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(obj.class == "CSSImportRule" ||
- obj.class == "CSSStyleSheet" ||
- obj.class == "Location" ||
- rawObj instanceof Ci.nsIDOMWindow)) {
- return false;
- }
-
- let url;
- if (rawObj instanceof Ci.nsIDOMWindow && rawObj.location) {
- url = rawObj.location.href;
- } else if (rawObj.href) {
- url = rawObj.href;
- } else {
- return false;
- }
-
- grip.preview = {
- kind: "ObjectWithURL",
- url: hooks.createValueGrip(url),
- };
-
- return true;
- },
-
- function ArrayLike({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- obj.class != "DOMStringList" &&
- obj.class != "DOMTokenList" &&
- obj.class != "CSSRuleList" &&
- obj.class != "MediaList" &&
- obj.class != "StyleSheetList" &&
- obj.class != "CSSValueList" &&
- obj.class != "NamedNodeMap" &&
- obj.class != "FileList" &&
- obj.class != "NodeList") {
- return false;
- }
-
- if (typeof rawObj.length != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: rawObj.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
-
- for (let i = 0; i < rawObj.length &&
- items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[i]);
- items.push(hooks.createValueGrip(value));
- }
-
- return true;
- },
-
- function CSSStyleDeclaration({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- (obj.class != "CSSStyleDeclaration" &&
- obj.class != "CSS2Properties")) {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: rawObj.length,
- };
-
- let entries = grip.preview.entries = [];
-
- for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
- i < rawObj.length; i++) {
- let prop = rawObj[i];
- let value = rawObj.getPropertyValue(prop);
- entries.push([prop, hooks.createValueGrip(value)]);
- }
-
- return true;
- },
-
- function DOMNode({obj, hooks}, grip, rawObj) {
- if (isWorker || obj.class == "Object" || !rawObj ||
- !(rawObj instanceof Ci.nsIDOMNode)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMNode",
- nodeType: rawObj.nodeType,
- nodeName: rawObj.nodeName,
- isConnected: rawObj.isConnected === true,
- };
-
- if (rawObj instanceof Ci.nsIDOMDocument && rawObj.location) {
- preview.location = hooks.createValueGrip(rawObj.location.href);
- } else if (rawObj instanceof Ci.nsIDOMDocumentFragment) {
- preview.childNodesLength = rawObj.childNodes.length;
-
- if (hooks.getGripDepth() < 2) {
- preview.childNodes = [];
- for (let node of rawObj.childNodes) {
- let actor = hooks.createValueGrip(obj.makeDebuggeeValue(node));
- preview.childNodes.push(actor);
- if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
- } else if (rawObj instanceof Ci.nsIDOMElement) {
- // For HTML elements (in an HTML document, at least), the nodeName is an
- // uppercased version of the actual element name. Check for HTML
- // elements, that is elements in the HTML namespace, and lowercase the
- // nodeName in that case.
- if (rawObj.namespaceURI == "http://www.w3.org/1999/xhtml") {
- preview.nodeName = preview.nodeName.toLowerCase();
- }
-
- // Add preview for DOM element attributes.
- preview.attributes = {};
- preview.attributesLength = rawObj.attributes.length;
- for (let attr of rawObj.attributes) {
- preview.attributes[attr.nodeName] = hooks.createValueGrip(attr.value);
- }
- } else if (obj.class == "Attr") {
- preview.value = hooks.createValueGrip(rawObj.value);
- } else if (obj.class == "Text" ||
- obj.class == "CDATASection" ||
- obj.class == "Comment") {
- preview.textContent = hooks.createValueGrip(rawObj.textContent);
- }
-
- return true;
- },
-
- function DOMEvent({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMEvent)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMEvent",
- type: rawObj.type,
- properties: Object.create(null),
- };
-
- if (hooks.getGripDepth() < 2) {
- let target = obj.makeDebuggeeValue(rawObj.target);
- preview.target = hooks.createValueGrip(target);
- }
-
- let props = [];
- if (obj.class == "MouseEvent" ||
- obj.class == "DragEvent" ||
- obj.class == "PointerEvent" ||
- obj.class == "SimpleGestureEvent" ||
- obj.class == "WheelEvent") {
- props.push("buttons", "clientX", "clientY", "layerX", "layerY");
- } else if (obj.class == "KeyboardEvent") {
- let modifiers = [];
- if (rawObj.altKey) {
- modifiers.push("Alt");
- }
- if (rawObj.ctrlKey) {
- modifiers.push("Control");
- }
- if (rawObj.metaKey) {
- modifiers.push("Meta");
- }
- if (rawObj.shiftKey) {
- modifiers.push("Shift");
- }
- preview.eventKind = "key";
- preview.modifiers = modifiers;
-
- props.push("key", "charCode", "keyCode");
- } else if (obj.class == "TransitionEvent") {
- props.push("propertyName", "pseudoElement");
- } else if (obj.class == "AnimationEvent") {
- props.push("animationName", "pseudoElement");
- } else if (obj.class == "ClipboardEvent") {
- props.push("clipboardData");
- }
-
- // Add event-specific properties.
- for (let prop of props) {
- let value = rawObj[prop];
- if (value && (typeof value == "object" || typeof value == "function")) {
- // Skip properties pointing to objects.
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- }
-
- // Add any properties we find on the event object.
- if (!props.length) {
- let i = 0;
- for (let prop in rawObj) {
- let value = rawObj[prop];
- if (prop == "target" || prop == "type" || value === null ||
- typeof value == "function") {
- continue;
- }
- if (value && typeof value == "object") {
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
-
- return true;
- },
-
- function DOMException({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMDOMException)) {
- return false;
- }
-
- grip.preview = {
- kind: "DOMException",
- name: hooks.createValueGrip(rawObj.name),
- message: hooks.createValueGrip(rawObj.message),
- code: hooks.createValueGrip(rawObj.code),
- result: hooks.createValueGrip(rawObj.result),
- filename: hooks.createValueGrip(rawObj.filename),
- lineNumber: hooks.createValueGrip(rawObj.lineNumber),
- columnNumber: hooks.createValueGrip(rawObj.columnNumber),
- };
-
- return true;
- },
-
- function PseudoArray({obj, hooks}, grip, rawObj) {
- // An object is considered a pseudo-array if all the following apply:
- // - All its properties are array indices except, optionally, a "length" property.
- // - At least it has the "0" array index.
- // - The array indices are consecutive.
- // - The value of "length", if present, is the number of array indices.
-
- let keys;
- try {
- keys = obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- return false;
- }
- let {length} = keys;
- if (length === 0) {
- return false;
- }
-
- // Array indices should be sorted at the beginning, from smallest to largest.
- // Other properties should be at the end, so check if the last one is "length".
- if (keys[length - 1] === "length") {
- --length;
- if (length === 0 || length !== DevToolsUtils.getProperty(obj, "length")) {
- return false;
- }
- }
-
- // Check that the last key is the array index expected at that position.
- let lastKey = keys[length - 1];
- if (!isArrayIndex(lastKey) || +lastKey !== length - 1) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- // Avoid recursive object grips.
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- let numItems = Math.min(OBJECT_PREVIEW_MAX_ITEMS, length);
-
- for (let i = 0; i < numItems; ++i) {
- let desc = obj.getOwnPropertyDescriptor(i);
- if (desc && "value" in desc) {
- items.push(hooks.createValueGrip(desc.value));
- } else {
- items.push(null);
- }
- }
-
- return true;
- },
-
- function Object(objectActor, grip, rawObj) {
- return GenericObject(objectActor, grip, rawObj, /* specialStringBehavior = */ false);
- },
-];
-
-/**
- * Get thisDebugger.Object referent's `promiseState`.
- *
- * @returns Object
- * An object of one of the following forms:
- * - { state: "pending" }
- * - { state: "fulfilled", value }
- * - { state: "rejected", reason }
- */
-function getPromiseState(obj) {
- if (obj.class != "Promise") {
- throw new Error(
- "Can't call `getPromiseState` on `Debugger.Object`s that don't " +
- "refer to Promise objects.");
- }
-
- let state = { state: obj.promiseState };
- if (state.state === "fulfilled") {
- state.value = obj.promiseValue;
- } else if (state.state === "rejected") {
- state.reason = obj.promiseReason;
- }
- return state;
-}
-
-/**
- * Determine if a given value is non-primitive.
- *
- * @param Any value
- * The value to test.
- * @return Boolean
- * Whether the value is non-primitive.
- */
-function isObject(value) {
- const type = typeof value;
- return type == "object" ? value !== null : type == "function";
-}
-
-/**
- * Create a function that can safely stringify Debugger.Objects of a given
- * builtin type.
- *
- * @param Function ctor
- * The builtin class constructor.
- * @return Function
- * The stringifier for the class.
- */
-function createBuiltinStringifier(ctor) {
- return obj => {
- try {
- return ctor.prototype.toString.call(obj.unsafeDereference());
- } catch (err) {
- // The debuggee will see a "Function" class if the object is callable and
- // its compartment is not subsumed. The above will throw if it's not really
- // a function, e.g. if it's a callable proxy.
- return "[object " + obj.class + "]";
- }
- };
-}
-
-/**
- * Stringify a Debugger.Object-wrapped Error instance.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification of the object.
- */
-function errorStringify(obj) {
- let name = DevToolsUtils.getProperty(obj, "name");
- if (name === "" || name === undefined) {
- name = obj.class;
- } else if (isObject(name)) {
- name = stringify(name);
- }
-
- let message = DevToolsUtils.getProperty(obj, "message");
- if (isObject(message)) {
- message = stringify(message);
- }
-
- if (message === "" || message === undefined) {
- return name;
- }
- return name + ": " + message;
-}
-
-/**
- * Stringify a Debugger.Object based on its class.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification for the object.
- */
-function stringify(obj) {
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- if (DevToolsUtils.isCPOW(obj)) {
- return "<cpow>";
- }
- let unwrapped = DevToolsUtils.unwrap(obj);
- if (unwrapped === undefined) {
- return "<invisibleToDebugger>";
- } else if (unwrapped.isProxy) {
- return "<proxy>";
- }
- // The following line should not be reached. It's there just in case somebody
- // modifies isSafeDebuggerObject to return false for additional kinds of objects.
- return "[object " + obj.class + "]";
- } else if (obj.class == "DeadObject") {
- return "<dead object>";
- }
-
- const stringifier = stringifiers[obj.class] || stringifiers.Object;
-
- try {
- return stringifier(obj);
- } catch (e) {
- DevToolsUtils.reportException("stringify", e);
- return "<failed to stringify object>";
- }
-}
-
-// Used to prevent infinite recursion when an array is found inside itself.
-var seen = null;
-
-var stringifiers = {
- Error: errorStringify,
- EvalError: errorStringify,
- RangeError: errorStringify,
- ReferenceError: errorStringify,
- SyntaxError: errorStringify,
- TypeError: errorStringify,
- URIError: errorStringify,
- Boolean: createBuiltinStringifier(Boolean),
- Function: createBuiltinStringifier(Function),
- Number: createBuiltinStringifier(Number),
- RegExp: createBuiltinStringifier(RegExp),
- String: createBuiltinStringifier(String),
- Object: obj => "[object " + obj.class + "]",
- Array: obj => {
- // If we're at the top level then we need to create the Set for tracking
- // previously stringified arrays.
- const topLevel = !seen;
- if (topLevel) {
- seen = new Set();
- } else if (seen.has(obj)) {
- return "";
- }
-
- seen.add(obj);
-
- const len = getArrayLength(obj);
- let string = "";
-
- // Array.length is always a non-negative safe integer.
- for (let i = 0; i < len; i++) {
- const desc = obj.getOwnPropertyDescriptor(i);
- if (desc) {
- const { value } = desc;
- if (value != null) {
- string += isObject(value) ? stringify(value) : value;
- }
- }
-
- if (i < len - 1) {
- string += ",";
- }
- }
-
- if (topLevel) {
- seen = null;
- }
-
- return string;
- },
- DOMException: obj => {
- const message = DevToolsUtils.getProperty(obj, "message") || "<no message>";
- const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16);
- const code = DevToolsUtils.getProperty(obj, "code");
- const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
-
- return '[Exception... "' + message + '" ' +
- 'code: "' + code + '" ' +
- 'nsresult: "0x' + result + " (" + name + ')"]';
- },
- Promise: obj => {
- const { state, value, reason } = getPromiseState(obj);
- let statePreview = state;
- if (state != "pending") {
- const settledValue = state === "fulfilled" ? value : reason;
- statePreview += ": " + (typeof settledValue === "object" && settledValue !== null
- ? stringify(settledValue)
- : settledValue);
- }
- return "Promise (" + statePreview + ")";
- },
-};
-
-/**
- * Make a debuggee value for the given object, if needed. Primitive values
- * are left the same.
- *
- * Use case: you have a raw JS object (after unsafe dereference) and you want to
- * send it to the client. In that case you need to use an ObjectActor which
- * requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
- * method works only for JS objects and functions.
- *
- * @param Debugger.Object obj
- * @param any value
- * @return object
- */
-function makeDebuggeeValueIfNeeded(obj, value) {
- if (value && (typeof value == "object" || typeof value == "function")) {
- return obj.makeDebuggeeValue(value);
- }
- return value;
-}
-
-/**
- * Creates an actor for the specified "very long" string. "Very long" is specified
- * at the server's discretion.
- *
- * @param string String
- * The string.
- */
-function LongStringActor(string) {
- this.string = string;
- this.stringLength = string.length;
-}
-
-LongStringActor.prototype = {
- actorPrefix: "longString",
-
- rawValue: function() {
- return this.string;
- },
-
- destroy: function() {
- // Because longStringActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- return {
- "type": "longString",
- "initial": this.string.substring(
- 0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
- "length": this.stringLength,
- "actor": this.actorID
- };
- },
-
- /**
- * Handle a request to extract part of this actor's string.
- *
- * @param request object
- * The protocol request object.
- */
- onSubstring: function(request) {
- return {
- "from": this.actorID,
- "substring": this.string.substring(request.start, request.end)
- };
- },
-
- /**
- * Handle a request to release this LongStringActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.longStringActors) {
- delete this.registeredPool.longStringActors[this.string];
- }
- }
-};
-
-LongStringActor.prototype.requestTypes = {
- "substring": LongStringActor.prototype.onSubstring,
- "release": LongStringActor.prototype.onRelease
-};
-
-/**
- * Creates an actor for the specified symbol.
- *
- * @param symbol Symbol
- * The symbol.
- */
-function SymbolActor(symbol) {
- this.symbol = symbol;
-}
-
-SymbolActor.prototype = {
- actorPrefix: "symbol",
-
- rawValue: function() {
- return this.symbol;
- },
-
- destroy: function() {
- // Because symbolActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let form = {
- type: "symbol",
- actor: this.actorID,
- };
- let name = getSymbolName(this.symbol);
- if (name !== undefined) {
- // Create a grip for the name because it might be a longString.
- form.name = createValueGrip(name, this.registeredPool);
- }
- return form;
- },
-
- /**
- * Handle a request to release this SymbolActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.symbolActors) {
- delete this.registeredPool.symbolActors[this.symbol];
- }
- }
-};
-
-SymbolActor.prototype.requestTypes = {
- "release": SymbolActor.prototype.onRelease
-};
-
-/**
- * Creates an actor for the specified ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The buffer.
- */
-function ArrayBufferActor(buffer) {
- this.buffer = buffer;
- this.bufferLength = buffer.byteLength;
-}
-
-ArrayBufferActor.prototype = {
- actorPrefix: "arrayBuffer",
-
- rawValue: function() {
- return this.buffer;
- },
-
- destroy: function() {
- },
-
- grip() {
- return {
- "type": "arrayBuffer",
- "length": this.bufferLength,
- "actor": this.actorID
- };
- },
-
- onSlice({start, count}) {
- let slice = new Uint8Array(this.buffer, start, count);
- let parts = [], offset = 0;
- const PortionSize = 0x6000; // keep it divisible by 3 for btoa() and join()
- while (offset + PortionSize < count) {
- parts.push(btoa(
- String.fromCharCode.apply(null, slice.subarray(offset, offset + PortionSize))));
- offset += PortionSize;
- }
- parts.push(btoa(String.fromCharCode.apply(null, slice.subarray(offset, count))));
- return {
- "from": this.actorID,
- "encoded": parts.join(""),
- };
- }
-};
-
-ArrayBufferActor.prototype.requestTypes = {
- "slice": ArrayBufferActor.prototype.onSlice,
-};
-
-/**
- * Create a grip for the given debuggee value. If the value is an
- * object, will create an actor with the given lifetime.
- */
-function createValueGrip(value, pool, makeObjectGrip) {
- switch (typeof value) {
- case "boolean":
- return value;
-
- case "string":
- if (stringIsLong(value)) {
- return longStringGrip(value, pool);
- }
- return value;
-
- case "number":
- if (value === Infinity) {
- return { type: "Infinity" };
- } else if (value === -Infinity) {
- return { type: "-Infinity" };
- } else if (Number.isNaN(value)) {
- return { type: "NaN" };
- } else if (!value && 1 / value === -Infinity) {
- return { type: "-0" };
- }
- return value;
-
- case "undefined":
- return { type: "undefined" };
-
- case "object":
- if (value === null) {
- return { type: "null" };
- } else if (value.optimizedOut ||
- value.uninitialized ||
- value.missingArguments) {
- // The slot is optimized out, an uninitialized binding, or
- // arguments on a dead scope
- return {
- type: "null",
- optimizedOut: value.optimizedOut,
- uninitialized: value.uninitialized,
- missingArguments: value.missingArguments
- };
- }
- return makeObjectGrip(value, pool);
-
- case "symbol":
- return symbolGrip(value, pool);
-
- default:
- assert(false, "Failed to provide a grip for: " + value);
- return null;
- }
-}
-
-const symbolProtoToString = Symbol.prototype.toString;
-
-function getSymbolName(symbol) {
- const name = symbolProtoToString.call(symbol).slice("Symbol(".length, -1);
- return name || undefined;
-}
-
-/**
- * Returns true if the string is long enough to use a LongStringActor instead
- * of passing the value directly over the protocol.
- *
- * @param str String
- * The string we are checking the length of.
- */
-function stringIsLong(str) {
- return str.length >= DebuggerServer.LONG_STRING_LENGTH;
-}
-
-/**
- * Create a grip for the given string.
- *
- * @param str String
- * The string we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function longStringGrip(str, pool) {
- if (!pool.longStringActors) {
- pool.longStringActors = {};
- }
-
- if (pool.longStringActors.hasOwnProperty(str)) {
- return pool.longStringActors[str].grip();
- }
-
- let actor = new LongStringActor(str);
- pool.addActor(actor);
- pool.longStringActors[str] = actor;
- return actor.grip();
-}
-
-/**
- * Create a grip for the given symbol.
- *
- * @param sym Symbol
- * The symbol we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function symbolGrip(sym, pool) {
- if (!pool.symbolActors) {
- pool.symbolActors = Object.create(null);
- }
-
- if (sym in pool.symbolActors) {
- return pool.symbolActors[sym].grip();
- }
-
- let actor = new SymbolActor(sym);
- pool.addActor(actor);
- pool.symbolActors[sym] = actor;
- return actor.grip();
-}
-
-/**
- * Create a grip for the given ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The ArrayBuffer we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function arrayBufferGrip(buffer, pool) {
- if (!pool.arrayBufferActors) {
- pool.arrayBufferActors = new WeakMap();
- }
-
- if (pool.arrayBufferActors.has(buffer)) {
- return pool.arrayBufferActors.get(buffer).grip();
- }
-
- let actor = new ArrayBufferActor(buffer);
- pool.addActor(actor);
- pool.arrayBufferActors.set(buffer, actor);
- return actor.grip();
-}
-
-const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
- "Uint32Array", "Int8Array", "Int16Array", "Int32Array",
- "Float32Array", "Float64Array"];
-
-/**
- * Returns true if a debuggee object is a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isTypedArray(object) {
- return TYPED_ARRAY_CLASSES.includes(object.class);
-}
-
-/**
- * Returns true if a debuggee object is an array, including a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isArray(object) {
- return isTypedArray(object) || object.class === "Array";
-}
-
-/**
- * Returns the length of an array (or typed array).
- *
- * @param obj Debugger.Object
- * The debuggee object of the array.
- * @return Number
- * @throws if the object is not an array.
- */
-function getArrayLength(object) {
- if (!isArray(object)) {
- throw new Error("Expected an array, got a " + object.class);
- }
-
- // Real arrays have a reliable `length` own property.
- if (object.class === "Array") {
- return DevToolsUtils.getProperty(object, "length");
- }
-
- // For typed arrays, `DevToolsUtils.getProperty` is not reliable because the `length`
- // getter could be shadowed by an own property, and `getOwnPropertyNames` is
- // unnecessarily slow. Obtain the `length` getter safely and call it manually.
- let typedProto = Object.getPrototypeOf(Uint8Array.prototype);
- let getter = Object.getOwnPropertyDescriptor(typedProto, "length").get;
- return getter.call(object.unsafeDereference());
-}
-
-/**
- * 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;
-}
-
-/**
- * Returns true if the parameter is suitable to be an array index.
- *
- * @param str String
- * @return Boolean
- */
-function isArrayIndex(str) {
- // Transform the parameter to a 32-bit unsigned integer.
- let num = str >>> 0;
- // Check that the parameter is a canonical Uint32 index.
- return num + "" === str &&
- // Array indices cannot attain the maximum Uint32 value.
- num != -1 >>> 0;
-}
-
-exports.ObjectActor = ObjectActor;
-exports.PropertyIteratorActor = PropertyIteratorActor;
-exports.LongStringActor = LongStringActor;
-exports.SymbolActor = SymbolActor;
-exports.createValueGrip = createValueGrip;
-exports.stringIsLong = stringIsLong;
-exports.longStringGrip = longStringGrip;
-exports.arrayBufferGrip = arrayBufferGrip;
+exports.SymbolIteratorActor = SymbolIteratorActor;
copy from devtools/server/actors/object.js
copy to devtools/server/actors/object/symbol.js
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object/symbol.js
@@ -1,2353 +1,17 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 { Cu, Ci } = require("chrome");
-const { GeneratedLocation } = require("devtools/server/actors/common");
-const { DebuggerServer } = require("devtools/server/main");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { assert } = DevToolsUtils;
-
-loader.lazyRequireGetter(this, "ChromeUtils");
-
-// Number of items to preview in objects, arrays, maps, sets, lists,
-// collections, etc.
-const OBJECT_PREVIEW_MAX_ITEMS = 10;
-
-/**
- * Creates an actor for the specified object.
- *
- * @param obj Debugger.Object
- * The debuggee object.
- * @param hooks Object
- * A collection of abstract methods that are implemented by the caller.
- * ObjectActor requires the following functions to be implemented by
- * the caller:
- * - createValueGrip
- * Creates a value grip for the given object
- * - sources
- * TabSources getter that manages the sources of a thread
- * - createEnvironmentActor
- * Creates and return an environment actor
- * - getGripDepth
- * An actor's grip depth getter
- * - incrementGripDepth
- * Increment the actor's grip depth
- * - decrementGripDepth
- * Decrement the actor's grip depth
- * - globalDebugObject
- * The Debuggee Global Object as given by the ThreadActor
- */
-function ObjectActor(obj, {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
-}) {
- assert(!obj.optimizedOut,
- "Should not create object actors for optimized out values!");
- this.obj = obj;
- this.hooks = {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
- };
- this.iterators = new Set();
-}
-
-ObjectActor.prototype = {
- actorPrefix: "obj",
-
- rawValue: function() {
- return this.obj.unsafeDereference();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let g = {
- "type": "object",
- "actor": this.actorID,
- "class": this.obj.class,
- };
-
- let unwrapped = DevToolsUtils.unwrap(this.obj);
-
- // Unsafe objects must be treated carefully.
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- if (DevToolsUtils.isCPOW(this.obj)) {
- // Cross-process object wrappers can't be accessed.
- g.class = "CPOW: " + g.class;
- } else if (unwrapped === undefined) {
- // Objects belonging to an invisible-to-debugger compartment might be proxies,
- // so just in case they shouldn't be accessed.
- g.class = "InvisibleToDebugger: " + g.class;
- } else if (unwrapped.isProxy) {
- // Proxy objects can run traps when accessed, so just create a preview with
- // the target and the handler.
- g.class = "Proxy";
- this.hooks.incrementGripDepth();
- DebuggerServer.ObjectActorPreviewers.Proxy[0](this, g, null);
- this.hooks.decrementGripDepth();
- }
- return g;
- }
-
- // If the debuggee does not subsume the object's compartment, most properties won't
- // be accessible. Cross-orgin Window and Location objects might expose some, though.
- // Change the displayed class, but when creating the preview use the original one.
- if (unwrapped === null) {
- g.class = "Restricted";
- }
-
- this.hooks.incrementGripDepth();
-
- g.extensible = this.obj.isExtensible();
- g.frozen = this.obj.isFrozen();
- g.sealed = this.obj.isSealed();
-
- if (g.class == "Promise") {
- g.promiseState = this._createPromiseState();
- }
-
- // FF40+: Allow to know how many properties an object has to lazily display them
- // when there is a bunch.
- if (isTypedArray(g)) {
- // Bug 1348761: getOwnPropertyNames is unnecessary slow on TypedArrays
- g.ownPropertyLength = getArrayLength(this.obj);
- } else {
- try {
- g.ownPropertyLength = this.obj.getOwnPropertyNames().length;
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let raw = this.obj.unsafeDereference();
-
- // If Cu is not defined, we are running on a worker thread, where xrays
- // don't exist.
- if (Cu) {
- raw = Cu.unwaiveXrays(raw);
- }
-
- if (!DevToolsUtils.isSafeJSObject(raw)) {
- raw = null;
- }
-
- let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
- DebuggerServer.ObjectActorPreviewers.Object;
- for (let fn of previewers) {
- try {
- if (fn(this, g, raw)) {
- break;
- }
- } catch (e) {
- let msg = "ObjectActor.prototype.grip previewer function";
- DevToolsUtils.reportException(msg, e);
- }
- }
-
- this.hooks.decrementGripDepth();
- return g;
- },
-
- /**
- * Returns an object exposing the internal Promise state.
- */
- _createPromiseState: function() {
- const { state, value, reason } = getPromiseState(this.obj);
- let promiseState = { state };
-
- if (state == "fulfilled") {
- promiseState.value = this.hooks.createValueGrip(value);
- } else if (state == "rejected") {
- promiseState.reason = this.hooks.createValueGrip(reason);
- }
-
- promiseState.creationTimestamp = Date.now() - this.obj.promiseLifetime;
-
- // Only add the timeToSettle property if the Promise isn't pending.
- if (state !== "pending") {
- promiseState.timeToSettle = this.obj.promiseTimeToResolution;
- }
-
- return promiseState;
- },
-
- /**
- * Releases this actor from the pool.
- */
- release: function() {
- if (this.registeredPool.objectActors) {
- this.registeredPool.objectActors.delete(this.obj);
- }
- this.iterators.forEach(actor => this.registeredPool.removeActor(actor));
- this.iterators.clear();
- this.registeredPool.removeActor(this);
- },
-
- /**
- * Handle a protocol request to provide the definition site of this function
- * object.
- */
- onDefinitionSite: function() {
- if (this.obj.class != "Function") {
- return {
- from: this.actorID,
- error: "objectNotFunction",
- message: this.actorID + " is not a function."
- };
- }
-
- if (!this.obj.script) {
- return {
- from: this.actorID,
- error: "noScript",
- message: this.actorID + " has no Debugger.Script"
- };
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- this.hooks.sources().createNonSourceMappedActor(this.obj.script.source),
- this.obj.script.startLine,
- 0 // TODO bug 901138: use Debugger.Script.prototype.startColumn
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn
- };
- });
- },
-
- /**
- * Handle a protocol request to provide the names of the properties defined on
- * the object and not its prototype.
- */
- onOwnPropertyNames: function() {
- let props = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- props = this.obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
- return { from: this.actorID, ownPropertyNames: props };
- },
-
- /**
- * Creates an actor to iterate over an object property names and values.
- * See PropertyIteratorActor constructor for more info about options param.
- *
- * @param request object
- * The protocol request object.
- */
- onEnumProperties: function(request) {
- let actor = new PropertyIteratorActor(this, request.options);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over entries of a Map/Set-like object.
- */
- onEnumEntries: function() {
- let actor = new PropertyIteratorActor(this, { enumEntries: true });
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over an object symbols properties.
- */
- onEnumSymbols: function() {
- let actor = new SymbolIteratorActor(this);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Handle a protocol request to provide the prototype and own properties of
- * the object.
- *
- * @returns {Object} An object containing the data of this.obj, of the following form:
- * - {string} from: this.obj's actorID.
- * - {Object} prototype: The descriptor of this.obj's prototype.
- * - {Object} ownProperties: an object where the keys are the names of the
- * this.obj's ownProperties, and the values the descriptors of
- * the properties.
- * - {Array} ownSymbols: An array containing all descriptors of this.obj's
- * ownSymbols. Here we have an array, and not an object like for
- * ownProperties, because we can have multiple symbols with the same
- * name in this.obj, e.g. `{[Symbol()]: "a", [Symbol()]: "b"}`.
- * - {Object} safeGetterValues: an object that maps this.obj's property names
- * with safe getters descriptors.
- */
- onPrototypeAndProperties: function() {
- let proto = null;
- let names = [];
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- proto = this.obj.proto;
- names = this.obj.getOwnPropertyNames();
- symbols = this.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let ownProperties = Object.create(null);
- let ownSymbols = [];
-
- for (let name of names) {
- ownProperties[name] = this._propertyDescriptor(name);
- }
-
- for (let sym of symbols) {
- ownSymbols.push({
- name: sym.toString(),
- descriptor: this._propertyDescriptor(sym)
- });
- }
-
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto),
- ownProperties,
- ownSymbols,
- safeGetterValues: this._findSafeGetterValues(names) };
- },
-
- /**
- * Find the safe getter values for the current Debugger.Object, |this.obj|.
- *
- * @private
- * @param array ownProperties
- * The array that holds the list of known ownProperties names for
- * |this.obj|.
- * @param number [limit=0]
- * Optional limit of getter values to find.
- * @return object
- * An object that maps property names to safe getter descriptors as
- * defined by the remote debugging protocol.
- */
- _findSafeGetterValues: function(ownProperties, limit = 0) {
- let safeGetterValues = Object.create(null);
- let obj = this.obj;
- let level = 0, i = 0;
-
- // Do not search safe getters in unsafe objects.
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- return safeGetterValues;
- }
-
- // Most objects don't have any safe getters but inherit some from their
- // prototype. Avoid calling getOwnPropertyNames on objects that may have
- // many properties like Array, strings or js objects. That to avoid
- // freezing firefox when doing so.
- if (isArray(this.obj) || ["Object", "String"].includes(this.obj.class)) {
- obj = obj.proto;
- level++;
- }
-
- while (obj && DevToolsUtils.isSafeDebuggerObject(obj)) {
- let getters = this._findSafeGetters(obj);
- for (let name of getters) {
- // Avoid overwriting properties from prototypes closer to this.obj. Also
- // avoid providing safeGetterValues from prototypes if property |name|
- // is already defined as an own property.
- if (name in safeGetterValues ||
- (obj != this.obj && ownProperties.includes(name))) {
- continue;
- }
-
- // Ignore __proto__ on Object.prototye.
- if (!obj.proto && name == "__proto__") {
- continue;
- }
-
- let desc = null, getter = null;
- try {
- desc = obj.getOwnPropertyDescriptor(name);
- getter = desc.get;
- } catch (ex) {
- // The above can throw if the cache becomes stale.
- }
- if (!getter) {
- obj._safeGetters = null;
- continue;
- }
-
- let result = getter.call(this.obj);
- if (result && !("throw" in result)) {
- let getterValue = undefined;
- if ("return" in result) {
- getterValue = result.return;
- } else if ("yield" in result) {
- getterValue = result.yield;
- }
- // WebIDL attributes specified with the LenientThis extended attribute
- // return undefined and should be ignored.
- if (getterValue !== undefined) {
- safeGetterValues[name] = {
- getterValue: this.hooks.createValueGrip(getterValue),
- getterPrototypeLevel: level,
- enumerable: desc.enumerable,
- writable: level == 0 ? desc.writable : true,
- };
- if (limit && ++i == limit) {
- break;
- }
- }
- }
- }
- if (limit && i == limit) {
- break;
- }
-
- obj = obj.proto;
- level++;
- }
-
- return safeGetterValues;
- },
-
- /**
- * Find the safe getters for a given Debugger.Object. Safe getters are native
- * getters which are safe to execute.
- *
- * @private
- * @param Debugger.Object object
- * The Debugger.Object where you want to find safe getters.
- * @return Set
- * A Set of names of safe getters. This result is cached for each
- * Debugger.Object.
- */
- _findSafeGetters: function(object) {
- if (object._safeGetters) {
- return object._safeGetters;
- }
-
- let getters = new Set();
-
- if (!DevToolsUtils.isSafeDebuggerObject(object)) {
- object._safeGetters = getters;
- return getters;
- }
-
- let names = [];
- try {
- names = object.getOwnPropertyNames();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
-
- for (let name of names) {
- let desc = null;
- try {
- desc = object.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072).
- }
- if (!desc || desc.value !== undefined || !("get" in desc)) {
- continue;
- }
-
- if (DevToolsUtils.hasSafeGetter(desc)) {
- getters.add(name);
- }
- }
-
- object._safeGetters = getters;
- return getters;
- },
-
- /**
- * Handle a protocol request to provide the prototype of the object.
- */
- onPrototype: function() {
- let proto = null;
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- proto = this.obj.proto;
- }
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto) };
- },
-
- /**
- * Handle a protocol request to provide the property descriptor of the
- * object's specified property.
- *
- * @param request object
- * The protocol request object.
- */
- onProperty: function(request) {
- if (!request.name) {
- return { error: "missingParameter",
- message: "no property name was specified" };
- }
-
- return { from: this.actorID,
- descriptor: this._propertyDescriptor(request.name) };
- },
-
- /**
- * Handle a protocol request to provide the display string for the object.
- */
- onDisplayString: function() {
- const string = stringify(this.obj);
- return { from: this.actorID,
- displayString: this.hooks.createValueGrip(string) };
- },
-
- /**
- * A helper method that creates a property descriptor for the provided object,
- * properly formatted for sending in a protocol response.
- *
- * @private
- * @param string name
- * The property that the descriptor is generated for.
- * @param boolean [onlyEnumerable]
- * Optional: true if you want a descriptor only for an enumerable
- * property, false otherwise.
- * @return object|undefined
- * The property descriptor, or undefined if this is not an enumerable
- * property and onlyEnumerable=true.
- */
- _propertyDescriptor: function(name, onlyEnumerable) {
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- return undefined;
- }
-
- let desc;
- try {
- desc = this.obj.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072). Inform the user with a bogus, but hopefully
- // explanatory, descriptor.
- return {
- configurable: false,
- writable: false,
- enumerable: false,
- value: e.name
- };
- }
-
- if (!desc || onlyEnumerable && !desc.enumerable) {
- return undefined;
- }
-
- let retval = {
- configurable: desc.configurable,
- enumerable: desc.enumerable
- };
-
- if ("value" in desc) {
- retval.writable = desc.writable;
- retval.value = this.hooks.createValueGrip(desc.value);
- } else {
- if ("get" in desc) {
- retval.get = this.hooks.createValueGrip(desc.get);
- }
- if ("set" in desc) {
- retval.set = this.hooks.createValueGrip(desc.set);
- }
- }
- return retval;
- },
-
- /**
- * Handle a protocol request to provide the source code of a function.
- *
- * @param request object
- * The protocol request object.
- */
- onDecompile: function(request) {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "decompile request is only valid for object grips " +
- "with a 'Function' class." };
- }
-
- return { from: this.actorID,
- decompiledCode: this.obj.decompile(!!request.pretty) };
- },
-
- /**
- * Handle a protocol request to provide the parameters of a function.
- */
- onParameterNames: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "'parameterNames' request is only valid for object " +
- "grips with a 'Function' class." };
- }
-
- return { parameterNames: this.obj.parameterNames };
- },
-
- /**
- * Handle a protocol request to release a thread-lifetime grip.
- */
- onRelease: function() {
- this.release();
- return {};
- },
-
- /**
- * Handle a protocol request to provide the lexical scope of a function.
- */
- onScope: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "scope request is only valid for object grips with a" +
- " 'Function' class." };
- }
-
- let envActor = this.hooks.createEnvironmentActor(this.obj.environment,
- this.registeredPool);
- if (!envActor) {
- return { error: "notDebuggee",
- message: "cannot access the environment of this function." };
- }
-
- return { from: this.actorID, scope: envActor.form() };
- },
-
- /**
- * Handle a protocol request to get the list of dependent promises of a
- * promise.
- *
- * @return object
- * Returns an object containing an array of object grips of the
- * dependent promises
- */
- onDependentPromises: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'dependentPromises' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let promises = this.obj.promiseDependentPromises
- .map(p => this.hooks.createValueGrip(p));
-
- return { promises };
- },
-
- /**
- * Handle a protocol request to get the allocation stack of a promise.
- */
- onAllocationStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'allocationStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseAllocationSite;
- let allocationStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- allocationStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(allocationStacks).then(stacks => {
- return { allocationStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the fulfillment stack of a promise.
- */
- onFulfillmentStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'fulfillmentStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let fulfillmentStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- fulfillmentStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(fulfillmentStacks).then(stacks => {
- return { fulfillmentStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the rejection stack of a promise.
- */
- onRejectionStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'rejectionStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let rejectionStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- rejectionStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(rejectionStacks).then(stacks => {
- return { rejectionStack: stacks };
- });
- },
-
- /**
- * Helper function for fetching the source location of a SavedFrame stack.
- *
- * @param SavedFrame stack
- * The promise allocation stack frame
- * @return object
- * Returns an object containing the source location of the SavedFrame
- * stack.
- */
- _getSourceOriginalLocation: function(stack) {
- let source;
-
- // Catch any errors if the source actor cannot be found
- try {
- source = this.hooks.sources().getSourceActorByURL(stack.source);
- } catch (e) {
- // ignored
- }
-
- if (!source) {
- return null;
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- source,
- stack.line,
- stack.column
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn,
- functionDisplayName: stack.functionDisplayName
- };
- });
- }
-};
-
-ObjectActor.prototype.requestTypes = {
- "definitionSite": ObjectActor.prototype.onDefinitionSite,
- "parameterNames": ObjectActor.prototype.onParameterNames,
- "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
- "enumProperties": ObjectActor.prototype.onEnumProperties,
- "prototype": ObjectActor.prototype.onPrototype,
- "property": ObjectActor.prototype.onProperty,
- "displayString": ObjectActor.prototype.onDisplayString,
- "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
- "decompile": ObjectActor.prototype.onDecompile,
- "release": ObjectActor.prototype.onRelease,
- "scope": ObjectActor.prototype.onScope,
- "dependentPromises": ObjectActor.prototype.onDependentPromises,
- "allocationStack": ObjectActor.prototype.onAllocationStack,
- "fulfillmentStack": ObjectActor.prototype.onFulfillmentStack,
- "rejectionStack": ObjectActor.prototype.onRejectionStack,
- "enumEntries": ObjectActor.prototype.onEnumEntries,
- "enumSymbols": ObjectActor.prototype.onEnumSymbols,
-};
-
-/**
- * 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.
- */
-function PropertyIteratorActor(objectActor, options) {
- if (!DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- this.iterator = {
- size: 0,
- propertyName: index => undefined,
- propertyDescription: index => undefined,
- };
- } else if (options.enumEntries) {
- let cls = objectActor.obj.class;
- if (cls == "Map") {
- this.iterator = enumMapEntries(objectActor);
- } else if (cls == "WeakMap") {
- this.iterator = enumWeakMapEntries(objectActor);
- } else if (cls == "Set") {
- this.iterator = enumSetEntries(objectActor);
- } else if (cls == "WeakSet") {
- this.iterator = enumWeakSetEntries(objectActor);
- } else {
- throw new Error("Unsupported class to enumerate entries from: " + cls);
- }
- } else if (
- isArray(objectActor.obj)
- && options.ignoreNonIndexedProperties
- && !options.query
- ) {
- this.iterator = enumArrayProperties(objectActor, options);
- } else {
- this.iterator = enumObjectProperties(objectActor, options);
- }
-}
-
-PropertyIteratorActor.prototype = {
- actorPrefix: "propertyIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- names({ indexes }) {
- let list = [];
- for (let idx of indexes) {
- list.push(this.iterator.propertyName(idx));
- }
- return {
- names: indexes
- };
- },
-
- slice({ start, count }) {
- let ownProperties = Object.create(null);
- for (let i = start, m = start + count; i < m; i++) {
- let name = this.iterator.propertyName(i);
- ownProperties[name] = this.iterator.propertyDescription(i);
- }
- return {
- ownProperties
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-PropertyIteratorActor.prototype.requestTypes = {
- "names": PropertyIteratorActor.prototype.names,
- "slice": PropertyIteratorActor.prototype.slice,
- "all": PropertyIteratorActor.prototype.all,
-};
-
-function enumArrayProperties(objectActor, options) {
- return {
- size: getArrayLength(objectActor.obj),
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- return objectActor._propertyDescriptor(index);
- }
- };
-}
-
-function enumObjectProperties(objectActor, options) {
- 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) {
- let length = DevToolsUtils.getProperty(objectActor.obj, "length");
- let sliceIndex;
-
- const isLengthTrustworthy =
- isUint32(length)
- && (!length || isArrayIndex(names[length - 1]))
- && !isArrayIndex(names[length]);
-
- if (!isLengthTrustworthy) {
- // The length property may not reflect what the object looks like, let's find
- // where indexed properties end.
-
- if (!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 (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,
- // like webaudio buffers.
- if (options.ignoreIndexedProperties) {
- // Keep items after `sliceIndex` index
- names = names.slice(sliceIndex);
- } else if (options.ignoreNonIndexedProperties) {
- // Keep `sliceIndex` first items
- names.length = sliceIndex;
- }
- }
-
- let safeGetterValues = objectActor._findSafeGetterValues(names, 0);
- let safeGetterNames = Object.keys(safeGetterValues);
- // Merge the safe getter values into the existing properties list.
- for (let 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 && 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) {
- let name = names[index];
- let desc = objectActor._propertyDescriptor(name);
- if (!desc) {
- desc = safeGetterValues[name];
- } else if (name in safeGetterValues) {
- // Merge the safe getter values into the existing properties list.
- let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
- desc.getterValue = getterValue;
- desc.getterPrototypeLevel = getterPrototypeLevel;
- }
- return desc;
- }
- };
-}
-
-/**
- * Helper function to create a grip from a Map/Set entry
- */
-function gripFromEntry({ obj, hooks }, entry) {
- return hooks.createValueGrip(
- makeDebuggeeValueIfNeeded(obj, Cu.unwaiveXrays(entry)));
-}
-
-function enumMapEntries(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.
- let raw = objectActor.obj.unsafeDereference();
-
- let keys = [...Cu.waiveXrays(Map.prototype.keys.call(raw))];
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = Map.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = Map.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
-}
-
-function enumWeakMapEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakMapKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = WeakMap.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = WeakMap.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
-}
-
-function enumSetEntries(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.
- let raw = objectActor.obj.unsafeDereference();
- let values = [...Cu.waiveXrays(Set.prototype.values.call(raw))];
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of values) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: values.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = values[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-function enumWeakSetEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakSetKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of keys) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = keys[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-/**
- * Creates an actor to iterate over an object's symbols.
- *
- * @param objectActor ObjectActor
- * The object actor.
- */
-function SymbolIteratorActor(objectActor) {
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- try {
- symbols = objectActor.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- this.iterator = {
- size: symbols.length,
- symbolDescription(index) {
- const symbol = symbols[index];
- return {
- name: symbol.toString(),
- descriptor: objectActor._propertyDescriptor(symbol)
- };
- }
- };
-}
-
-SymbolIteratorActor.prototype = {
- actorPrefix: "symbolIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- slice({ start, count }) {
- let ownSymbols = [];
- for (let i = start, m = start + count; i < m; i++) {
- ownSymbols.push(this.iterator.symbolDescription(i));
- }
- return {
- ownSymbols
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-SymbolIteratorActor.prototype.requestTypes = {
- "slice": SymbolIteratorActor.prototype.slice,
- "all": SymbolIteratorActor.prototype.all,
-};
-
-/**
- * Functions for adding information to ObjectActor grips for the purpose of
- * having customized output. This object holds arrays mapped by
- * Debugger.Object.prototype.class.
- *
- * In each array you can add functions that take three
- * arguments:
- * - the ObjectActor instance and its hooks to make a preview for,
- * - the grip object being prepared for the client,
- * - the raw JS object after calling Debugger.Object.unsafeDereference(). This
- * argument is only provided if the object is safe for reading properties and
- * executing methods. See DevToolsUtils.isSafeJSObject().
- *
- * Functions must return false if they cannot provide preview
- * information for the debugger object, or true otherwise.
- */
-DebuggerServer.ObjectActorPreviewers = {
- String: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("String", String, objectActor, grip, rawObj);
- }],
-
- Boolean: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip, rawObj);
- }],
-
- Number: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Number", Number, objectActor, grip, rawObj);
- }],
-
- Symbol: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Symbol", Symbol, objectActor, grip, rawObj);
- }],
-
- Function: [function({obj, hooks}, grip) {
- if (obj.name) {
- grip.name = obj.name;
- }
-
- if (obj.displayName) {
- grip.displayName = obj.displayName.substr(0, 500);
- }
-
- if (obj.parameterNames) {
- grip.parameterNames = obj.parameterNames;
- }
-
- // Check if the developer has added a de-facto standard displayName
- // property for us to use.
- let userDisplayName;
- try {
- userDisplayName = obj.getOwnPropertyDescriptor("displayName");
- } catch (e) {
- // The above can throw "permission denied" errors when the debuggee
- // does not subsume the function's compartment.
- }
-
- if (userDisplayName && typeof userDisplayName.value == "string" &&
- userDisplayName.value) {
- grip.userDisplayName = hooks.createValueGrip(userDisplayName.value);
- }
-
- let dbgGlobal = hooks.getGlobalDebugObject();
- if (dbgGlobal) {
- let script = dbgGlobal.makeDebuggeeValue(obj.unsafeDereference()).script;
- if (script) {
- grip.location = {
- url: script.url,
- line: script.startLine
- };
- }
- }
-
- return true;
- }],
-
- RegExp: [function({obj, hooks}, grip) {
- let str = DevToolsUtils.callPropertyOnObject(obj, "toString");
- if (typeof str != "string") {
- return false;
- }
-
- grip.displayString = hooks.createValueGrip(str);
- return true;
- }],
-
- Date: [function({obj, hooks}, grip) {
- let time = DevToolsUtils.callPropertyOnObject(obj, "getTime");
- if (typeof time != "number") {
- return false;
- }
-
- grip.preview = {
- timestamp: hooks.createValueGrip(time),
- };
- return true;
- }],
-
- Array: [function({obj, hooks}, grip) {
- let length = getArrayLength(obj);
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let items = grip.preview.items = [];
-
- for (let i = 0; i < length; ++i) {
- // Array Xrays filter out various possibly-unsafe properties (like
- // functions, and claim that the value is undefined instead. This
- // is generally the right thing for privileged code accessing untrusted
- // objects, but quite confusing for Object previews. So we manually
- // override this protection by waiving Xrays on the array, and re-applying
- // Xrays on any indexed value props that we pull off of it.
- let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
- if (desc && !desc.get && !desc.set) {
- let value = Cu.unwaiveXrays(desc.value);
- value = makeDebuggeeValueIfNeeded(obj, value);
- items.push(hooks.createValueGrip(value));
- } else {
- items.push(null);
- }
-
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Set: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: size,
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumSetEntries(objectActor)) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakSet: [function(objectActor, grip) {
- let enumEntries = enumWeakSetEntries(objectActor);
-
- grip.preview = {
- kind: "ArrayLike",
- length: enumEntries.size
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumEntries) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Map: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: size,
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumMapEntries(objectActor)) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakMap: [function(objectActor, grip) {
- let enumEntries = enumWeakMapEntries(objectActor);
-
- grip.preview = {
- kind: "MapLike",
- size: enumEntries.size
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumEntries) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- DOMStringMap: [function({obj, hooks}, grip, rawObj) {
- if (!rawObj) {
- return false;
- }
-
- let keys = obj.getOwnPropertyNames();
- grip.preview = {
- kind: "MapLike",
- size: keys.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let key of keys) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
- entries.push([key, hooks.createValueGrip(value)]);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Proxy: [function({obj, hooks}, grip, rawObj) {
- // The `isProxy` getter of the debuggee object only detects proxies without
- // security wrappers. If false, the target and handler are not available.
- let hasTargetAndHandler = obj.isProxy;
- if (hasTargetAndHandler) {
- grip.proxyTarget = hooks.createValueGrip(obj.proxyTarget);
- grip.proxyHandler = hooks.createValueGrip(obj.proxyHandler);
- }
-
- grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownPropertiesLength: 2 * hasTargetAndHandler
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- if (hasTargetAndHandler) {
- grip.preview.ownProperties["<target>"] = {value: grip.proxyTarget};
- grip.preview.ownProperties["<handler>"] = {value: grip.proxyHandler};
- }
-
- return true;
- }],
-};
-
-/**
- * Generic previewer for classes wrapping primitives, like String,
- * Number and Boolean.
- *
- * @param string className
- * Class name to expect.
- * @param object classObj
- * The class to expect, eg. String. The valueOf() method of the class is
- * invoked on the given object.
- * @param ObjectActor objectActor
- * The object actor
- * @param Object grip
- * The result grip to fill in
- * @return Booolean true if the object was handled, false otherwise
- */
-function wrappedPrimitivePreviewer(className, classObj, objectActor, grip, rawObj) {
- let {obj, hooks} = objectActor;
-
- let v = null;
- try {
- v = classObj.prototype.valueOf.call(rawObj);
- } catch (ex) {
- // valueOf() can throw if the raw JS object is "misbehaved".
- return false;
- }
-
- if (v === null) {
- return false;
- }
-
- let canHandle = GenericObject(objectActor, grip, rawObj, className === "String");
- if (!canHandle) {
- return false;
- }
-
- grip.preview.wrappedValue =
- hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v));
- return true;
-}
-
-function GenericObject(objectActor, grip, rawObj, specialStringBehavior = false) {
- let {obj, hooks} = objectActor;
- if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
- return false;
- }
-
- let i = 0, names = [], symbols = [];
- let preview = grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownSymbols: [],
- };
-
- try {
- names = obj.getOwnPropertyNames();
- symbols = obj.getOwnPropertySymbols();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
- preview.ownPropertiesLength = names.length;
- preview.ownSymbolsLength = symbols.length;
-
- let length;
- if (specialStringBehavior) {
- length = DevToolsUtils.getProperty(obj, "length");
- if (typeof length != "number") {
- specialStringBehavior = false;
- }
- }
-
- for (let name of names) {
- if (specialStringBehavior && /^[0-9]+$/.test(name)) {
- let num = parseInt(name, 10);
- if (num.toString() === name && num >= 0 && num < length) {
- continue;
- }
- }
-
- let desc = objectActor._propertyDescriptor(name, true);
- if (!desc) {
- continue;
- }
-
- preview.ownProperties[name] = desc;
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- for (let symbol of symbols) {
- let descriptor = objectActor._propertyDescriptor(symbol, true);
- if (!descriptor) {
- continue;
- }
-
- preview.ownSymbols.push(Object.assign({
- descriptor
- }, hooks.createValueGrip(symbol)));
-
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- if (i < OBJECT_PREVIEW_MAX_ITEMS) {
- preview.safeGetterValues = objectActor._findSafeGetterValues(
- Object.keys(preview.ownProperties),
- OBJECT_PREVIEW_MAX_ITEMS - i);
- }
-
- return true;
-}
-
-// Preview functions that do not rely on the object class.
-DebuggerServer.ObjectActorPreviewers.Object = [
- function TypedArray({obj, hooks}, grip) {
- if (!isTypedArray(obj)) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: getArrayLength(obj),
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let global = Cu.getGlobalForObject(DebuggerServer);
- let classProto = global[obj.class].prototype;
- // The Xray machinery for TypedArrays denies indexed access on the grounds
- // that it's slow, and advises callers to do a structured clone instead.
- let safeView = Cu.cloneInto(classProto.subarray.call(raw, 0,
- OBJECT_PREVIEW_MAX_ITEMS), global);
- let items = grip.preview.items = [];
- for (let i = 0; i < safeView.length; i++) {
- items.push(safeView[i]);
- }
-
- return true;
- },
-
- function Error({obj, hooks}, grip) {
- switch (obj.class) {
- case "Error":
- case "EvalError":
- case "RangeError":
- case "ReferenceError":
- case "SyntaxError":
- case "TypeError":
- case "URIError":
- let name = DevToolsUtils.getProperty(obj, "name");
- let msg = DevToolsUtils.getProperty(obj, "message");
- let stack = DevToolsUtils.getProperty(obj, "stack");
- let fileName = DevToolsUtils.getProperty(obj, "fileName");
- let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
- let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
- grip.preview = {
- kind: "Error",
- name: hooks.createValueGrip(name),
- message: hooks.createValueGrip(msg),
- stack: hooks.createValueGrip(stack),
- fileName: hooks.createValueGrip(fileName),
- lineNumber: hooks.createValueGrip(lineNumber),
- columnNumber: hooks.createValueGrip(columnNumber),
- };
- return true;
- default:
- return false;
- }
- },
-
- function CSSMediaRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSMediaRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.conditionText),
- };
- return true;
- },
-
- function CSSStyleRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSStyleRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.selectorText),
- };
- return true;
- },
-
- function ObjectWithURL({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(obj.class == "CSSImportRule" ||
- obj.class == "CSSStyleSheet" ||
- obj.class == "Location" ||
- rawObj instanceof Ci.nsIDOMWindow)) {
- return false;
- }
-
- let url;
- if (rawObj instanceof Ci.nsIDOMWindow && rawObj.location) {
- url = rawObj.location.href;
- } else if (rawObj.href) {
- url = rawObj.href;
- } else {
- return false;
- }
-
- grip.preview = {
- kind: "ObjectWithURL",
- url: hooks.createValueGrip(url),
- };
-
- return true;
- },
-
- function ArrayLike({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- obj.class != "DOMStringList" &&
- obj.class != "DOMTokenList" &&
- obj.class != "CSSRuleList" &&
- obj.class != "MediaList" &&
- obj.class != "StyleSheetList" &&
- obj.class != "CSSValueList" &&
- obj.class != "NamedNodeMap" &&
- obj.class != "FileList" &&
- obj.class != "NodeList") {
- return false;
- }
-
- if (typeof rawObj.length != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: rawObj.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
-
- for (let i = 0; i < rawObj.length &&
- items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[i]);
- items.push(hooks.createValueGrip(value));
- }
-
- return true;
- },
-
- function CSSStyleDeclaration({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- (obj.class != "CSSStyleDeclaration" &&
- obj.class != "CSS2Properties")) {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: rawObj.length,
- };
-
- let entries = grip.preview.entries = [];
-
- for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
- i < rawObj.length; i++) {
- let prop = rawObj[i];
- let value = rawObj.getPropertyValue(prop);
- entries.push([prop, hooks.createValueGrip(value)]);
- }
-
- return true;
- },
-
- function DOMNode({obj, hooks}, grip, rawObj) {
- if (isWorker || obj.class == "Object" || !rawObj ||
- !(rawObj instanceof Ci.nsIDOMNode)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMNode",
- nodeType: rawObj.nodeType,
- nodeName: rawObj.nodeName,
- isConnected: rawObj.isConnected === true,
- };
-
- if (rawObj instanceof Ci.nsIDOMDocument && rawObj.location) {
- preview.location = hooks.createValueGrip(rawObj.location.href);
- } else if (rawObj instanceof Ci.nsIDOMDocumentFragment) {
- preview.childNodesLength = rawObj.childNodes.length;
-
- if (hooks.getGripDepth() < 2) {
- preview.childNodes = [];
- for (let node of rawObj.childNodes) {
- let actor = hooks.createValueGrip(obj.makeDebuggeeValue(node));
- preview.childNodes.push(actor);
- if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
- } else if (rawObj instanceof Ci.nsIDOMElement) {
- // For HTML elements (in an HTML document, at least), the nodeName is an
- // uppercased version of the actual element name. Check for HTML
- // elements, that is elements in the HTML namespace, and lowercase the
- // nodeName in that case.
- if (rawObj.namespaceURI == "http://www.w3.org/1999/xhtml") {
- preview.nodeName = preview.nodeName.toLowerCase();
- }
-
- // Add preview for DOM element attributes.
- preview.attributes = {};
- preview.attributesLength = rawObj.attributes.length;
- for (let attr of rawObj.attributes) {
- preview.attributes[attr.nodeName] = hooks.createValueGrip(attr.value);
- }
- } else if (obj.class == "Attr") {
- preview.value = hooks.createValueGrip(rawObj.value);
- } else if (obj.class == "Text" ||
- obj.class == "CDATASection" ||
- obj.class == "Comment") {
- preview.textContent = hooks.createValueGrip(rawObj.textContent);
- }
-
- return true;
- },
-
- function DOMEvent({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMEvent)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMEvent",
- type: rawObj.type,
- properties: Object.create(null),
- };
-
- if (hooks.getGripDepth() < 2) {
- let target = obj.makeDebuggeeValue(rawObj.target);
- preview.target = hooks.createValueGrip(target);
- }
-
- let props = [];
- if (obj.class == "MouseEvent" ||
- obj.class == "DragEvent" ||
- obj.class == "PointerEvent" ||
- obj.class == "SimpleGestureEvent" ||
- obj.class == "WheelEvent") {
- props.push("buttons", "clientX", "clientY", "layerX", "layerY");
- } else if (obj.class == "KeyboardEvent") {
- let modifiers = [];
- if (rawObj.altKey) {
- modifiers.push("Alt");
- }
- if (rawObj.ctrlKey) {
- modifiers.push("Control");
- }
- if (rawObj.metaKey) {
- modifiers.push("Meta");
- }
- if (rawObj.shiftKey) {
- modifiers.push("Shift");
- }
- preview.eventKind = "key";
- preview.modifiers = modifiers;
-
- props.push("key", "charCode", "keyCode");
- } else if (obj.class == "TransitionEvent") {
- props.push("propertyName", "pseudoElement");
- } else if (obj.class == "AnimationEvent") {
- props.push("animationName", "pseudoElement");
- } else if (obj.class == "ClipboardEvent") {
- props.push("clipboardData");
- }
-
- // Add event-specific properties.
- for (let prop of props) {
- let value = rawObj[prop];
- if (value && (typeof value == "object" || typeof value == "function")) {
- // Skip properties pointing to objects.
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- }
-
- // Add any properties we find on the event object.
- if (!props.length) {
- let i = 0;
- for (let prop in rawObj) {
- let value = rawObj[prop];
- if (prop == "target" || prop == "type" || value === null ||
- typeof value == "function") {
- continue;
- }
- if (value && typeof value == "object") {
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
-
- return true;
- },
-
- function DOMException({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMDOMException)) {
- return false;
- }
-
- grip.preview = {
- kind: "DOMException",
- name: hooks.createValueGrip(rawObj.name),
- message: hooks.createValueGrip(rawObj.message),
- code: hooks.createValueGrip(rawObj.code),
- result: hooks.createValueGrip(rawObj.result),
- filename: hooks.createValueGrip(rawObj.filename),
- lineNumber: hooks.createValueGrip(rawObj.lineNumber),
- columnNumber: hooks.createValueGrip(rawObj.columnNumber),
- };
-
- return true;
- },
-
- function PseudoArray({obj, hooks}, grip, rawObj) {
- // An object is considered a pseudo-array if all the following apply:
- // - All its properties are array indices except, optionally, a "length" property.
- // - At least it has the "0" array index.
- // - The array indices are consecutive.
- // - The value of "length", if present, is the number of array indices.
-
- let keys;
- try {
- keys = obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- return false;
- }
- let {length} = keys;
- if (length === 0) {
- return false;
- }
-
- // Array indices should be sorted at the beginning, from smallest to largest.
- // Other properties should be at the end, so check if the last one is "length".
- if (keys[length - 1] === "length") {
- --length;
- if (length === 0 || length !== DevToolsUtils.getProperty(obj, "length")) {
- return false;
- }
- }
-
- // Check that the last key is the array index expected at that position.
- let lastKey = keys[length - 1];
- if (!isArrayIndex(lastKey) || +lastKey !== length - 1) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- // Avoid recursive object grips.
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- let numItems = Math.min(OBJECT_PREVIEW_MAX_ITEMS, length);
-
- for (let i = 0; i < numItems; ++i) {
- let desc = obj.getOwnPropertyDescriptor(i);
- if (desc && "value" in desc) {
- items.push(hooks.createValueGrip(desc.value));
- } else {
- items.push(null);
- }
- }
-
- return true;
- },
-
- function Object(objectActor, grip, rawObj) {
- return GenericObject(objectActor, grip, rawObj, /* specialStringBehavior = */ false);
- },
-];
-
-/**
- * Get thisDebugger.Object referent's `promiseState`.
- *
- * @returns Object
- * An object of one of the following forms:
- * - { state: "pending" }
- * - { state: "fulfilled", value }
- * - { state: "rejected", reason }
- */
-function getPromiseState(obj) {
- if (obj.class != "Promise") {
- throw new Error(
- "Can't call `getPromiseState` on `Debugger.Object`s that don't " +
- "refer to Promise objects.");
- }
-
- let state = { state: obj.promiseState };
- if (state.state === "fulfilled") {
- state.value = obj.promiseValue;
- } else if (state.state === "rejected") {
- state.reason = obj.promiseReason;
- }
- return state;
-}
-
-/**
- * Determine if a given value is non-primitive.
- *
- * @param Any value
- * The value to test.
- * @return Boolean
- * Whether the value is non-primitive.
- */
-function isObject(value) {
- const type = typeof value;
- return type == "object" ? value !== null : type == "function";
-}
-
-/**
- * Create a function that can safely stringify Debugger.Objects of a given
- * builtin type.
- *
- * @param Function ctor
- * The builtin class constructor.
- * @return Function
- * The stringifier for the class.
- */
-function createBuiltinStringifier(ctor) {
- return obj => {
- try {
- return ctor.prototype.toString.call(obj.unsafeDereference());
- } catch (err) {
- // The debuggee will see a "Function" class if the object is callable and
- // its compartment is not subsumed. The above will throw if it's not really
- // a function, e.g. if it's a callable proxy.
- return "[object " + obj.class + "]";
- }
- };
-}
-
-/**
- * Stringify a Debugger.Object-wrapped Error instance.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification of the object.
- */
-function errorStringify(obj) {
- let name = DevToolsUtils.getProperty(obj, "name");
- if (name === "" || name === undefined) {
- name = obj.class;
- } else if (isObject(name)) {
- name = stringify(name);
- }
-
- let message = DevToolsUtils.getProperty(obj, "message");
- if (isObject(message)) {
- message = stringify(message);
- }
-
- if (message === "" || message === undefined) {
- return name;
- }
- return name + ": " + message;
-}
-
-/**
- * Stringify a Debugger.Object based on its class.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification for the object.
- */
-function stringify(obj) {
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- if (DevToolsUtils.isCPOW(obj)) {
- return "<cpow>";
- }
- let unwrapped = DevToolsUtils.unwrap(obj);
- if (unwrapped === undefined) {
- return "<invisibleToDebugger>";
- } else if (unwrapped.isProxy) {
- return "<proxy>";
- }
- // The following line should not be reached. It's there just in case somebody
- // modifies isSafeDebuggerObject to return false for additional kinds of objects.
- return "[object " + obj.class + "]";
- } else if (obj.class == "DeadObject") {
- return "<dead object>";
- }
-
- const stringifier = stringifiers[obj.class] || stringifiers.Object;
-
- try {
- return stringifier(obj);
- } catch (e) {
- DevToolsUtils.reportException("stringify", e);
- return "<failed to stringify object>";
- }
-}
-
-// Used to prevent infinite recursion when an array is found inside itself.
-var seen = null;
-
-var stringifiers = {
- Error: errorStringify,
- EvalError: errorStringify,
- RangeError: errorStringify,
- ReferenceError: errorStringify,
- SyntaxError: errorStringify,
- TypeError: errorStringify,
- URIError: errorStringify,
- Boolean: createBuiltinStringifier(Boolean),
- Function: createBuiltinStringifier(Function),
- Number: createBuiltinStringifier(Number),
- RegExp: createBuiltinStringifier(RegExp),
- String: createBuiltinStringifier(String),
- Object: obj => "[object " + obj.class + "]",
- Array: obj => {
- // If we're at the top level then we need to create the Set for tracking
- // previously stringified arrays.
- const topLevel = !seen;
- if (topLevel) {
- seen = new Set();
- } else if (seen.has(obj)) {
- return "";
- }
-
- seen.add(obj);
-
- const len = getArrayLength(obj);
- let string = "";
-
- // Array.length is always a non-negative safe integer.
- for (let i = 0; i < len; i++) {
- const desc = obj.getOwnPropertyDescriptor(i);
- if (desc) {
- const { value } = desc;
- if (value != null) {
- string += isObject(value) ? stringify(value) : value;
- }
- }
-
- if (i < len - 1) {
- string += ",";
- }
- }
-
- if (topLevel) {
- seen = null;
- }
-
- return string;
- },
- DOMException: obj => {
- const message = DevToolsUtils.getProperty(obj, "message") || "<no message>";
- const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16);
- const code = DevToolsUtils.getProperty(obj, "code");
- const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
-
- return '[Exception... "' + message + '" ' +
- 'code: "' + code + '" ' +
- 'nsresult: "0x' + result + " (" + name + ')"]';
- },
- Promise: obj => {
- const { state, value, reason } = getPromiseState(obj);
- let statePreview = state;
- if (state != "pending") {
- const settledValue = state === "fulfilled" ? value : reason;
- statePreview += ": " + (typeof settledValue === "object" && settledValue !== null
- ? stringify(settledValue)
- : settledValue);
- }
- return "Promise (" + statePreview + ")";
- },
-};
-
-/**
- * Make a debuggee value for the given object, if needed. Primitive values
- * are left the same.
- *
- * Use case: you have a raw JS object (after unsafe dereference) and you want to
- * send it to the client. In that case you need to use an ObjectActor which
- * requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
- * method works only for JS objects and functions.
- *
- * @param Debugger.Object obj
- * @param any value
- * @return object
- */
-function makeDebuggeeValueIfNeeded(obj, value) {
- if (value && (typeof value == "object" || typeof value == "function")) {
- return obj.makeDebuggeeValue(value);
- }
- return value;
-}
-
-/**
- * Creates an actor for the specified "very long" string. "Very long" is specified
- * at the server's discretion.
- *
- * @param string String
- * The string.
- */
-function LongStringActor(string) {
- this.string = string;
- this.stringLength = string.length;
-}
-
-LongStringActor.prototype = {
- actorPrefix: "longString",
-
- rawValue: function() {
- return this.string;
- },
-
- destroy: function() {
- // Because longStringActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- return {
- "type": "longString",
- "initial": this.string.substring(
- 0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
- "length": this.stringLength,
- "actor": this.actorID
- };
- },
-
- /**
- * Handle a request to extract part of this actor's string.
- *
- * @param request object
- * The protocol request object.
- */
- onSubstring: function(request) {
- return {
- "from": this.actorID,
- "substring": this.string.substring(request.start, request.end)
- };
- },
-
- /**
- * Handle a request to release this LongStringActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.longStringActors) {
- delete this.registeredPool.longStringActors[this.string];
- }
- }
-};
-
-LongStringActor.prototype.requestTypes = {
- "substring": LongStringActor.prototype.onSubstring,
- "release": LongStringActor.prototype.onRelease
-};
+loader.lazyRequireGetter(this, "createValueGrip", "devtools/server/actors/object/utils", true);
/**
* Creates an actor for the specified symbol.
*
* @param symbol Symbol
* The symbol.
*/
function SymbolActor(symbol) {
@@ -2402,164 +66,24 @@ SymbolActor.prototype = {
}
}
};
SymbolActor.prototype.requestTypes = {
"release": SymbolActor.prototype.onRelease
};
-/**
- * Creates an actor for the specified ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The buffer.
- */
-function ArrayBufferActor(buffer) {
- this.buffer = buffer;
- this.bufferLength = buffer.byteLength;
-}
-
-ArrayBufferActor.prototype = {
- actorPrefix: "arrayBuffer",
-
- rawValue: function() {
- return this.buffer;
- },
-
- destroy: function() {
- },
-
- grip() {
- return {
- "type": "arrayBuffer",
- "length": this.bufferLength,
- "actor": this.actorID
- };
- },
-
- onSlice({start, count}) {
- let slice = new Uint8Array(this.buffer, start, count);
- let parts = [], offset = 0;
- const PortionSize = 0x6000; // keep it divisible by 3 for btoa() and join()
- while (offset + PortionSize < count) {
- parts.push(btoa(
- String.fromCharCode.apply(null, slice.subarray(offset, offset + PortionSize))));
- offset += PortionSize;
- }
- parts.push(btoa(String.fromCharCode.apply(null, slice.subarray(offset, count))));
- return {
- "from": this.actorID,
- "encoded": parts.join(""),
- };
- }
-};
-
-ArrayBufferActor.prototype.requestTypes = {
- "slice": ArrayBufferActor.prototype.onSlice,
-};
-
-/**
- * Create a grip for the given debuggee value. If the value is an
- * object, will create an actor with the given lifetime.
- */
-function createValueGrip(value, pool, makeObjectGrip) {
- switch (typeof value) {
- case "boolean":
- return value;
-
- case "string":
- if (stringIsLong(value)) {
- return longStringGrip(value, pool);
- }
- return value;
-
- case "number":
- if (value === Infinity) {
- return { type: "Infinity" };
- } else if (value === -Infinity) {
- return { type: "-Infinity" };
- } else if (Number.isNaN(value)) {
- return { type: "NaN" };
- } else if (!value && 1 / value === -Infinity) {
- return { type: "-0" };
- }
- return value;
-
- case "undefined":
- return { type: "undefined" };
-
- case "object":
- if (value === null) {
- return { type: "null" };
- } else if (value.optimizedOut ||
- value.uninitialized ||
- value.missingArguments) {
- // The slot is optimized out, an uninitialized binding, or
- // arguments on a dead scope
- return {
- type: "null",
- optimizedOut: value.optimizedOut,
- uninitialized: value.uninitialized,
- missingArguments: value.missingArguments
- };
- }
- return makeObjectGrip(value, pool);
-
- case "symbol":
- return symbolGrip(value, pool);
-
- default:
- assert(false, "Failed to provide a grip for: " + value);
- return null;
- }
-}
-
const symbolProtoToString = Symbol.prototype.toString;
function getSymbolName(symbol) {
const name = symbolProtoToString.call(symbol).slice("Symbol(".length, -1);
return name || undefined;
}
/**
- * Returns true if the string is long enough to use a LongStringActor instead
- * of passing the value directly over the protocol.
- *
- * @param str String
- * The string we are checking the length of.
- */
-function stringIsLong(str) {
- return str.length >= DebuggerServer.LONG_STRING_LENGTH;
-}
-
-/**
- * Create a grip for the given string.
- *
- * @param str String
- * The string we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function longStringGrip(str, pool) {
- if (!pool.longStringActors) {
- pool.longStringActors = {};
- }
-
- if (pool.longStringActors.hasOwnProperty(str)) {
- return pool.longStringActors[str].grip();
- }
-
- let actor = new LongStringActor(str);
- pool.addActor(actor);
- pool.longStringActors[str] = actor;
- return actor.grip();
-}
-
-/**
* Create a grip for the given symbol.
*
* @param sym Symbol
* The symbol we are creating a grip for.
* @param pool ActorPool
* The actor pool where the new actor will be added.
*/
function symbolGrip(sym, pool) {
@@ -2572,118 +96,12 @@ function symbolGrip(sym, pool) {
}
let actor = new SymbolActor(sym);
pool.addActor(actor);
pool.symbolActors[sym] = actor;
return actor.grip();
}
-/**
- * Create a grip for the given ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The ArrayBuffer we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function arrayBufferGrip(buffer, pool) {
- if (!pool.arrayBufferActors) {
- pool.arrayBufferActors = new WeakMap();
- }
-
- if (pool.arrayBufferActors.has(buffer)) {
- return pool.arrayBufferActors.get(buffer).grip();
- }
-
- let actor = new ArrayBufferActor(buffer);
- pool.addActor(actor);
- pool.arrayBufferActors.set(buffer, actor);
- return actor.grip();
-}
-
-const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
- "Uint32Array", "Int8Array", "Int16Array", "Int32Array",
- "Float32Array", "Float64Array"];
-
-/**
- * Returns true if a debuggee object is a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isTypedArray(object) {
- return TYPED_ARRAY_CLASSES.includes(object.class);
-}
-
-/**
- * Returns true if a debuggee object is an array, including a typed array.
- *
- * @param obj Debugger.Object
- * The debuggee object to test.
- * @return Boolean
- */
-function isArray(object) {
- return isTypedArray(object) || object.class === "Array";
-}
-
-/**
- * Returns the length of an array (or typed array).
- *
- * @param obj Debugger.Object
- * The debuggee object of the array.
- * @return Number
- * @throws if the object is not an array.
- */
-function getArrayLength(object) {
- if (!isArray(object)) {
- throw new Error("Expected an array, got a " + object.class);
- }
-
- // Real arrays have a reliable `length` own property.
- if (object.class === "Array") {
- return DevToolsUtils.getProperty(object, "length");
- }
-
- // For typed arrays, `DevToolsUtils.getProperty` is not reliable because the `length`
- // getter could be shadowed by an own property, and `getOwnPropertyNames` is
- // unnecessarily slow. Obtain the `length` getter safely and call it manually.
- let typedProto = Object.getPrototypeOf(Uint8Array.prototype);
- let getter = Object.getOwnPropertyDescriptor(typedProto, "length").get;
- return getter.call(object.unsafeDereference());
-}
-
-/**
- * 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;
-}
-
-/**
- * Returns true if the parameter is suitable to be an array index.
- *
- * @param str String
- * @return Boolean
- */
-function isArrayIndex(str) {
- // Transform the parameter to a 32-bit unsigned integer.
- let num = str >>> 0;
- // Check that the parameter is a canonical Uint32 index.
- return num + "" === str &&
- // Array indices cannot attain the maximum Uint32 value.
- num != -1 >>> 0;
-}
-
-exports.ObjectActor = ObjectActor;
-exports.PropertyIteratorActor = PropertyIteratorActor;
-exports.LongStringActor = LongStringActor;
-exports.SymbolActor = SymbolActor;
-exports.createValueGrip = createValueGrip;
-exports.stringIsLong = stringIsLong;
-exports.longStringGrip = longStringGrip;
-exports.arrayBufferGrip = arrayBufferGrip;
+module.exports = {
+ SymbolActor,
+ symbolGrip,
+};
copy from devtools/server/actors/object.js
copy to devtools/server/actors/object/utils.js
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object/utils.js
@@ -1,2058 +1,22 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 { Cu, Ci } = require("chrome");
-const { GeneratedLocation } = require("devtools/server/actors/common");
const { DebuggerServer } = require("devtools/server/main");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { assert } = DevToolsUtils;
-loader.lazyRequireGetter(this, "ChromeUtils");
-
-// Number of items to preview in objects, arrays, maps, sets, lists,
-// collections, etc.
-const OBJECT_PREVIEW_MAX_ITEMS = 10;
-
-/**
- * Creates an actor for the specified object.
- *
- * @param obj Debugger.Object
- * The debuggee object.
- * @param hooks Object
- * A collection of abstract methods that are implemented by the caller.
- * ObjectActor requires the following functions to be implemented by
- * the caller:
- * - createValueGrip
- * Creates a value grip for the given object
- * - sources
- * TabSources getter that manages the sources of a thread
- * - createEnvironmentActor
- * Creates and return an environment actor
- * - getGripDepth
- * An actor's grip depth getter
- * - incrementGripDepth
- * Increment the actor's grip depth
- * - decrementGripDepth
- * Decrement the actor's grip depth
- * - globalDebugObject
- * The Debuggee Global Object as given by the ThreadActor
- */
-function ObjectActor(obj, {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
-}) {
- assert(!obj.optimizedOut,
- "Should not create object actors for optimized out values!");
- this.obj = obj;
- this.hooks = {
- createValueGrip: createValueGripHook,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
- };
- this.iterators = new Set();
-}
-
-ObjectActor.prototype = {
- actorPrefix: "obj",
-
- rawValue: function() {
- return this.obj.unsafeDereference();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let g = {
- "type": "object",
- "actor": this.actorID,
- "class": this.obj.class,
- };
-
- let unwrapped = DevToolsUtils.unwrap(this.obj);
-
- // Unsafe objects must be treated carefully.
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- if (DevToolsUtils.isCPOW(this.obj)) {
- // Cross-process object wrappers can't be accessed.
- g.class = "CPOW: " + g.class;
- } else if (unwrapped === undefined) {
- // Objects belonging to an invisible-to-debugger compartment might be proxies,
- // so just in case they shouldn't be accessed.
- g.class = "InvisibleToDebugger: " + g.class;
- } else if (unwrapped.isProxy) {
- // Proxy objects can run traps when accessed, so just create a preview with
- // the target and the handler.
- g.class = "Proxy";
- this.hooks.incrementGripDepth();
- DebuggerServer.ObjectActorPreviewers.Proxy[0](this, g, null);
- this.hooks.decrementGripDepth();
- }
- return g;
- }
-
- // If the debuggee does not subsume the object's compartment, most properties won't
- // be accessible. Cross-orgin Window and Location objects might expose some, though.
- // Change the displayed class, but when creating the preview use the original one.
- if (unwrapped === null) {
- g.class = "Restricted";
- }
-
- this.hooks.incrementGripDepth();
-
- g.extensible = this.obj.isExtensible();
- g.frozen = this.obj.isFrozen();
- g.sealed = this.obj.isSealed();
-
- if (g.class == "Promise") {
- g.promiseState = this._createPromiseState();
- }
-
- // FF40+: Allow to know how many properties an object has to lazily display them
- // when there is a bunch.
- if (isTypedArray(g)) {
- // Bug 1348761: getOwnPropertyNames is unnecessary slow on TypedArrays
- g.ownPropertyLength = getArrayLength(this.obj);
- } else {
- try {
- g.ownPropertyLength = this.obj.getOwnPropertyNames().length;
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let raw = this.obj.unsafeDereference();
-
- // If Cu is not defined, we are running on a worker thread, where xrays
- // don't exist.
- if (Cu) {
- raw = Cu.unwaiveXrays(raw);
- }
-
- if (!DevToolsUtils.isSafeJSObject(raw)) {
- raw = null;
- }
-
- let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
- DebuggerServer.ObjectActorPreviewers.Object;
- for (let fn of previewers) {
- try {
- if (fn(this, g, raw)) {
- break;
- }
- } catch (e) {
- let msg = "ObjectActor.prototype.grip previewer function";
- DevToolsUtils.reportException(msg, e);
- }
- }
-
- this.hooks.decrementGripDepth();
- return g;
- },
-
- /**
- * Returns an object exposing the internal Promise state.
- */
- _createPromiseState: function() {
- const { state, value, reason } = getPromiseState(this.obj);
- let promiseState = { state };
-
- if (state == "fulfilled") {
- promiseState.value = this.hooks.createValueGrip(value);
- } else if (state == "rejected") {
- promiseState.reason = this.hooks.createValueGrip(reason);
- }
-
- promiseState.creationTimestamp = Date.now() - this.obj.promiseLifetime;
-
- // Only add the timeToSettle property if the Promise isn't pending.
- if (state !== "pending") {
- promiseState.timeToSettle = this.obj.promiseTimeToResolution;
- }
-
- return promiseState;
- },
-
- /**
- * Releases this actor from the pool.
- */
- release: function() {
- if (this.registeredPool.objectActors) {
- this.registeredPool.objectActors.delete(this.obj);
- }
- this.iterators.forEach(actor => this.registeredPool.removeActor(actor));
- this.iterators.clear();
- this.registeredPool.removeActor(this);
- },
-
- /**
- * Handle a protocol request to provide the definition site of this function
- * object.
- */
- onDefinitionSite: function() {
- if (this.obj.class != "Function") {
- return {
- from: this.actorID,
- error: "objectNotFunction",
- message: this.actorID + " is not a function."
- };
- }
-
- if (!this.obj.script) {
- return {
- from: this.actorID,
- error: "noScript",
- message: this.actorID + " has no Debugger.Script"
- };
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- this.hooks.sources().createNonSourceMappedActor(this.obj.script.source),
- this.obj.script.startLine,
- 0 // TODO bug 901138: use Debugger.Script.prototype.startColumn
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn
- };
- });
- },
-
- /**
- * Handle a protocol request to provide the names of the properties defined on
- * the object and not its prototype.
- */
- onOwnPropertyNames: function() {
- let props = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- props = this.obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
- return { from: this.actorID, ownPropertyNames: props };
- },
-
- /**
- * Creates an actor to iterate over an object property names and values.
- * See PropertyIteratorActor constructor for more info about options param.
- *
- * @param request object
- * The protocol request object.
- */
- onEnumProperties: function(request) {
- let actor = new PropertyIteratorActor(this, request.options);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over entries of a Map/Set-like object.
- */
- onEnumEntries: function() {
- let actor = new PropertyIteratorActor(this, { enumEntries: true });
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Creates an actor to iterate over an object symbols properties.
- */
- onEnumSymbols: function() {
- let actor = new SymbolIteratorActor(this);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
-
- /**
- * Handle a protocol request to provide the prototype and own properties of
- * the object.
- *
- * @returns {Object} An object containing the data of this.obj, of the following form:
- * - {string} from: this.obj's actorID.
- * - {Object} prototype: The descriptor of this.obj's prototype.
- * - {Object} ownProperties: an object where the keys are the names of the
- * this.obj's ownProperties, and the values the descriptors of
- * the properties.
- * - {Array} ownSymbols: An array containing all descriptors of this.obj's
- * ownSymbols. Here we have an array, and not an object like for
- * ownProperties, because we can have multiple symbols with the same
- * name in this.obj, e.g. `{[Symbol()]: "a", [Symbol()]: "b"}`.
- * - {Object} safeGetterValues: an object that maps this.obj's property names
- * with safe getters descriptors.
- */
- onPrototypeAndProperties: function() {
- let proto = null;
- let names = [];
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- try {
- proto = this.obj.proto;
- names = this.obj.getOwnPropertyNames();
- symbols = this.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- let ownProperties = Object.create(null);
- let ownSymbols = [];
-
- for (let name of names) {
- ownProperties[name] = this._propertyDescriptor(name);
- }
-
- for (let sym of symbols) {
- ownSymbols.push({
- name: sym.toString(),
- descriptor: this._propertyDescriptor(sym)
- });
- }
-
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto),
- ownProperties,
- ownSymbols,
- safeGetterValues: this._findSafeGetterValues(names) };
- },
-
- /**
- * Find the safe getter values for the current Debugger.Object, |this.obj|.
- *
- * @private
- * @param array ownProperties
- * The array that holds the list of known ownProperties names for
- * |this.obj|.
- * @param number [limit=0]
- * Optional limit of getter values to find.
- * @return object
- * An object that maps property names to safe getter descriptors as
- * defined by the remote debugging protocol.
- */
- _findSafeGetterValues: function(ownProperties, limit = 0) {
- let safeGetterValues = Object.create(null);
- let obj = this.obj;
- let level = 0, i = 0;
-
- // Do not search safe getters in unsafe objects.
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- return safeGetterValues;
- }
-
- // Most objects don't have any safe getters but inherit some from their
- // prototype. Avoid calling getOwnPropertyNames on objects that may have
- // many properties like Array, strings or js objects. That to avoid
- // freezing firefox when doing so.
- if (isArray(this.obj) || ["Object", "String"].includes(this.obj.class)) {
- obj = obj.proto;
- level++;
- }
-
- while (obj && DevToolsUtils.isSafeDebuggerObject(obj)) {
- let getters = this._findSafeGetters(obj);
- for (let name of getters) {
- // Avoid overwriting properties from prototypes closer to this.obj. Also
- // avoid providing safeGetterValues from prototypes if property |name|
- // is already defined as an own property.
- if (name in safeGetterValues ||
- (obj != this.obj && ownProperties.includes(name))) {
- continue;
- }
-
- // Ignore __proto__ on Object.prototye.
- if (!obj.proto && name == "__proto__") {
- continue;
- }
-
- let desc = null, getter = null;
- try {
- desc = obj.getOwnPropertyDescriptor(name);
- getter = desc.get;
- } catch (ex) {
- // The above can throw if the cache becomes stale.
- }
- if (!getter) {
- obj._safeGetters = null;
- continue;
- }
-
- let result = getter.call(this.obj);
- if (result && !("throw" in result)) {
- let getterValue = undefined;
- if ("return" in result) {
- getterValue = result.return;
- } else if ("yield" in result) {
- getterValue = result.yield;
- }
- // WebIDL attributes specified with the LenientThis extended attribute
- // return undefined and should be ignored.
- if (getterValue !== undefined) {
- safeGetterValues[name] = {
- getterValue: this.hooks.createValueGrip(getterValue),
- getterPrototypeLevel: level,
- enumerable: desc.enumerable,
- writable: level == 0 ? desc.writable : true,
- };
- if (limit && ++i == limit) {
- break;
- }
- }
- }
- }
- if (limit && i == limit) {
- break;
- }
-
- obj = obj.proto;
- level++;
- }
-
- return safeGetterValues;
- },
-
- /**
- * Find the safe getters for a given Debugger.Object. Safe getters are native
- * getters which are safe to execute.
- *
- * @private
- * @param Debugger.Object object
- * The Debugger.Object where you want to find safe getters.
- * @return Set
- * A Set of names of safe getters. This result is cached for each
- * Debugger.Object.
- */
- _findSafeGetters: function(object) {
- if (object._safeGetters) {
- return object._safeGetters;
- }
-
- let getters = new Set();
-
- if (!DevToolsUtils.isSafeDebuggerObject(object)) {
- object._safeGetters = getters;
- return getters;
- }
-
- let names = [];
- try {
- names = object.getOwnPropertyNames();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
-
- for (let name of names) {
- let desc = null;
- try {
- desc = object.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072).
- }
- if (!desc || desc.value !== undefined || !("get" in desc)) {
- continue;
- }
-
- if (DevToolsUtils.hasSafeGetter(desc)) {
- getters.add(name);
- }
- }
-
- object._safeGetters = getters;
- return getters;
- },
-
- /**
- * Handle a protocol request to provide the prototype of the object.
- */
- onPrototype: function() {
- let proto = null;
- if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- proto = this.obj.proto;
- }
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(proto) };
- },
-
- /**
- * Handle a protocol request to provide the property descriptor of the
- * object's specified property.
- *
- * @param request object
- * The protocol request object.
- */
- onProperty: function(request) {
- if (!request.name) {
- return { error: "missingParameter",
- message: "no property name was specified" };
- }
-
- return { from: this.actorID,
- descriptor: this._propertyDescriptor(request.name) };
- },
-
- /**
- * Handle a protocol request to provide the display string for the object.
- */
- onDisplayString: function() {
- const string = stringify(this.obj);
- return { from: this.actorID,
- displayString: this.hooks.createValueGrip(string) };
- },
-
- /**
- * A helper method that creates a property descriptor for the provided object,
- * properly formatted for sending in a protocol response.
- *
- * @private
- * @param string name
- * The property that the descriptor is generated for.
- * @param boolean [onlyEnumerable]
- * Optional: true if you want a descriptor only for an enumerable
- * property, false otherwise.
- * @return object|undefined
- * The property descriptor, or undefined if this is not an enumerable
- * property and onlyEnumerable=true.
- */
- _propertyDescriptor: function(name, onlyEnumerable) {
- if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
- return undefined;
- }
-
- let desc;
- try {
- desc = this.obj.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072). Inform the user with a bogus, but hopefully
- // explanatory, descriptor.
- return {
- configurable: false,
- writable: false,
- enumerable: false,
- value: e.name
- };
- }
-
- if (!desc || onlyEnumerable && !desc.enumerable) {
- return undefined;
- }
-
- let retval = {
- configurable: desc.configurable,
- enumerable: desc.enumerable
- };
-
- if ("value" in desc) {
- retval.writable = desc.writable;
- retval.value = this.hooks.createValueGrip(desc.value);
- } else {
- if ("get" in desc) {
- retval.get = this.hooks.createValueGrip(desc.get);
- }
- if ("set" in desc) {
- retval.set = this.hooks.createValueGrip(desc.set);
- }
- }
- return retval;
- },
-
- /**
- * Handle a protocol request to provide the source code of a function.
- *
- * @param request object
- * The protocol request object.
- */
- onDecompile: function(request) {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "decompile request is only valid for object grips " +
- "with a 'Function' class." };
- }
-
- return { from: this.actorID,
- decompiledCode: this.obj.decompile(!!request.pretty) };
- },
-
- /**
- * Handle a protocol request to provide the parameters of a function.
- */
- onParameterNames: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "'parameterNames' request is only valid for object " +
- "grips with a 'Function' class." };
- }
-
- return { parameterNames: this.obj.parameterNames };
- },
-
- /**
- * Handle a protocol request to release a thread-lifetime grip.
- */
- onRelease: function() {
- this.release();
- return {};
- },
-
- /**
- * Handle a protocol request to provide the lexical scope of a function.
- */
- onScope: function() {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "scope request is only valid for object grips with a" +
- " 'Function' class." };
- }
-
- let envActor = this.hooks.createEnvironmentActor(this.obj.environment,
- this.registeredPool);
- if (!envActor) {
- return { error: "notDebuggee",
- message: "cannot access the environment of this function." };
- }
-
- return { from: this.actorID, scope: envActor.form() };
- },
-
- /**
- * Handle a protocol request to get the list of dependent promises of a
- * promise.
- *
- * @return object
- * Returns an object containing an array of object grips of the
- * dependent promises
- */
- onDependentPromises: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'dependentPromises' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let promises = this.obj.promiseDependentPromises
- .map(p => this.hooks.createValueGrip(p));
-
- return { promises };
- },
-
- /**
- * Handle a protocol request to get the allocation stack of a promise.
- */
- onAllocationStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'allocationStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseAllocationSite;
- let allocationStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- allocationStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(allocationStacks).then(stacks => {
- return { allocationStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the fulfillment stack of a promise.
- */
- onFulfillmentStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'fulfillmentStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let fulfillmentStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- fulfillmentStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(fulfillmentStacks).then(stacks => {
- return { fulfillmentStack: stacks };
- });
- },
-
- /**
- * Handle a protocol request to get the rejection stack of a promise.
- */
- onRejectionStack: function() {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'rejectionStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
-
- let stack = this.obj.promiseResolutionSite;
- let rejectionStacks = [];
-
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
-
- if (source) {
- rejectionStacks.push(source);
- }
- }
- stack = stack.parent;
- }
-
- return Promise.all(rejectionStacks).then(stacks => {
- return { rejectionStack: stacks };
- });
- },
-
- /**
- * Helper function for fetching the source location of a SavedFrame stack.
- *
- * @param SavedFrame stack
- * The promise allocation stack frame
- * @return object
- * Returns an object containing the source location of the SavedFrame
- * stack.
- */
- _getSourceOriginalLocation: function(stack) {
- let source;
-
- // Catch any errors if the source actor cannot be found
- try {
- source = this.hooks.sources().getSourceActorByURL(stack.source);
- } catch (e) {
- // ignored
- }
-
- if (!source) {
- return null;
- }
-
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- source,
- stack.line,
- stack.column
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn,
- functionDisplayName: stack.functionDisplayName
- };
- });
- }
-};
-
-ObjectActor.prototype.requestTypes = {
- "definitionSite": ObjectActor.prototype.onDefinitionSite,
- "parameterNames": ObjectActor.prototype.onParameterNames,
- "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
- "enumProperties": ObjectActor.prototype.onEnumProperties,
- "prototype": ObjectActor.prototype.onPrototype,
- "property": ObjectActor.prototype.onProperty,
- "displayString": ObjectActor.prototype.onDisplayString,
- "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
- "decompile": ObjectActor.prototype.onDecompile,
- "release": ObjectActor.prototype.onRelease,
- "scope": ObjectActor.prototype.onScope,
- "dependentPromises": ObjectActor.prototype.onDependentPromises,
- "allocationStack": ObjectActor.prototype.onAllocationStack,
- "fulfillmentStack": ObjectActor.prototype.onFulfillmentStack,
- "rejectionStack": ObjectActor.prototype.onRejectionStack,
- "enumEntries": ObjectActor.prototype.onEnumEntries,
- "enumSymbols": ObjectActor.prototype.onEnumSymbols,
-};
-
-/**
- * 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.
- */
-function PropertyIteratorActor(objectActor, options) {
- if (!DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- this.iterator = {
- size: 0,
- propertyName: index => undefined,
- propertyDescription: index => undefined,
- };
- } else if (options.enumEntries) {
- let cls = objectActor.obj.class;
- if (cls == "Map") {
- this.iterator = enumMapEntries(objectActor);
- } else if (cls == "WeakMap") {
- this.iterator = enumWeakMapEntries(objectActor);
- } else if (cls == "Set") {
- this.iterator = enumSetEntries(objectActor);
- } else if (cls == "WeakSet") {
- this.iterator = enumWeakSetEntries(objectActor);
- } else {
- throw new Error("Unsupported class to enumerate entries from: " + cls);
- }
- } else if (
- isArray(objectActor.obj)
- && options.ignoreNonIndexedProperties
- && !options.query
- ) {
- this.iterator = enumArrayProperties(objectActor, options);
- } else {
- this.iterator = enumObjectProperties(objectActor, options);
- }
-}
-
-PropertyIteratorActor.prototype = {
- actorPrefix: "propertyIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- names({ indexes }) {
- let list = [];
- for (let idx of indexes) {
- list.push(this.iterator.propertyName(idx));
- }
- return {
- names: indexes
- };
- },
-
- slice({ start, count }) {
- let ownProperties = Object.create(null);
- for (let i = start, m = start + count; i < m; i++) {
- let name = this.iterator.propertyName(i);
- ownProperties[name] = this.iterator.propertyDescription(i);
- }
- return {
- ownProperties
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-PropertyIteratorActor.prototype.requestTypes = {
- "names": PropertyIteratorActor.prototype.names,
- "slice": PropertyIteratorActor.prototype.slice,
- "all": PropertyIteratorActor.prototype.all,
-};
-
-function enumArrayProperties(objectActor, options) {
- return {
- size: getArrayLength(objectActor.obj),
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- return objectActor._propertyDescriptor(index);
- }
- };
-}
-
-function enumObjectProperties(objectActor, options) {
- 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) {
- let length = DevToolsUtils.getProperty(objectActor.obj, "length");
- let sliceIndex;
-
- const isLengthTrustworthy =
- isUint32(length)
- && (!length || isArrayIndex(names[length - 1]))
- && !isArrayIndex(names[length]);
-
- if (!isLengthTrustworthy) {
- // The length property may not reflect what the object looks like, let's find
- // where indexed properties end.
-
- if (!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 (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,
- // like webaudio buffers.
- if (options.ignoreIndexedProperties) {
- // Keep items after `sliceIndex` index
- names = names.slice(sliceIndex);
- } else if (options.ignoreNonIndexedProperties) {
- // Keep `sliceIndex` first items
- names.length = sliceIndex;
- }
- }
-
- let safeGetterValues = objectActor._findSafeGetterValues(names, 0);
- let safeGetterNames = Object.keys(safeGetterValues);
- // Merge the safe getter values into the existing properties list.
- for (let 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 && 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) {
- let name = names[index];
- let desc = objectActor._propertyDescriptor(name);
- if (!desc) {
- desc = safeGetterValues[name];
- } else if (name in safeGetterValues) {
- // Merge the safe getter values into the existing properties list.
- let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
- desc.getterValue = getterValue;
- desc.getterPrototypeLevel = getterPrototypeLevel;
- }
- return desc;
- }
- };
-}
-
-/**
- * Helper function to create a grip from a Map/Set entry
- */
-function gripFromEntry({ obj, hooks }, entry) {
- return hooks.createValueGrip(
- makeDebuggeeValueIfNeeded(obj, Cu.unwaiveXrays(entry)));
-}
-
-function enumMapEntries(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.
- let raw = objectActor.obj.unsafeDereference();
-
- let keys = [...Cu.waiveXrays(Map.prototype.keys.call(raw))];
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = Map.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = Map.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
-}
-
-function enumWeakMapEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakMapKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = WeakMap.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = WeakMap.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
-}
-
-function enumSetEntries(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.
- let raw = objectActor.obj.unsafeDereference();
- let values = [...Cu.waiveXrays(Set.prototype.values.call(raw))];
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of values) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: values.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = values[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-function enumWeakSetEntries(objectActor) {
- // 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.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ChromeUtils.nondeterministicGetWeakSetKeys(raw));
-
- return {
- [Symbol.iterator]: function* () {
- for (let item of keys) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = keys[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
-}
-
-/**
- * Creates an actor to iterate over an object's symbols.
- *
- * @param objectActor ObjectActor
- * The object actor.
- */
-function SymbolIteratorActor(objectActor) {
- let symbols = [];
- if (DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
- try {
- symbols = objectActor.obj.getOwnPropertySymbols();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- }
- }
-
- this.iterator = {
- size: symbols.length,
- symbolDescription(index) {
- const symbol = symbols[index];
- return {
- name: symbol.toString(),
- descriptor: objectActor._propertyDescriptor(symbol)
- };
- }
- };
-}
-
-SymbolIteratorActor.prototype = {
- actorPrefix: "symbolIterator",
-
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
-
- slice({ start, count }) {
- let ownSymbols = [];
- for (let i = start, m = start + count; i < m; i++) {
- ownSymbols.push(this.iterator.symbolDescription(i));
- }
- return {
- ownSymbols
- };
- },
-
- all() {
- return this.slice({ start: 0, count: this.iterator.size });
- }
-};
-
-SymbolIteratorActor.prototype.requestTypes = {
- "slice": SymbolIteratorActor.prototype.slice,
- "all": SymbolIteratorActor.prototype.all,
-};
-
-/**
- * Functions for adding information to ObjectActor grips for the purpose of
- * having customized output. This object holds arrays mapped by
- * Debugger.Object.prototype.class.
- *
- * In each array you can add functions that take three
- * arguments:
- * - the ObjectActor instance and its hooks to make a preview for,
- * - the grip object being prepared for the client,
- * - the raw JS object after calling Debugger.Object.unsafeDereference(). This
- * argument is only provided if the object is safe for reading properties and
- * executing methods. See DevToolsUtils.isSafeJSObject().
- *
- * Functions must return false if they cannot provide preview
- * information for the debugger object, or true otherwise.
- */
-DebuggerServer.ObjectActorPreviewers = {
- String: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("String", String, objectActor, grip, rawObj);
- }],
-
- Boolean: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip, rawObj);
- }],
-
- Number: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Number", Number, objectActor, grip, rawObj);
- }],
-
- Symbol: [function(objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Symbol", Symbol, objectActor, grip, rawObj);
- }],
-
- Function: [function({obj, hooks}, grip) {
- if (obj.name) {
- grip.name = obj.name;
- }
-
- if (obj.displayName) {
- grip.displayName = obj.displayName.substr(0, 500);
- }
-
- if (obj.parameterNames) {
- grip.parameterNames = obj.parameterNames;
- }
-
- // Check if the developer has added a de-facto standard displayName
- // property for us to use.
- let userDisplayName;
- try {
- userDisplayName = obj.getOwnPropertyDescriptor("displayName");
- } catch (e) {
- // The above can throw "permission denied" errors when the debuggee
- // does not subsume the function's compartment.
- }
-
- if (userDisplayName && typeof userDisplayName.value == "string" &&
- userDisplayName.value) {
- grip.userDisplayName = hooks.createValueGrip(userDisplayName.value);
- }
-
- let dbgGlobal = hooks.getGlobalDebugObject();
- if (dbgGlobal) {
- let script = dbgGlobal.makeDebuggeeValue(obj.unsafeDereference()).script;
- if (script) {
- grip.location = {
- url: script.url,
- line: script.startLine
- };
- }
- }
-
- return true;
- }],
-
- RegExp: [function({obj, hooks}, grip) {
- let str = DevToolsUtils.callPropertyOnObject(obj, "toString");
- if (typeof str != "string") {
- return false;
- }
-
- grip.displayString = hooks.createValueGrip(str);
- return true;
- }],
-
- Date: [function({obj, hooks}, grip) {
- let time = DevToolsUtils.callPropertyOnObject(obj, "getTime");
- if (typeof time != "number") {
- return false;
- }
-
- grip.preview = {
- timestamp: hooks.createValueGrip(time),
- };
- return true;
- }],
-
- Array: [function({obj, hooks}, grip) {
- let length = getArrayLength(obj);
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let items = grip.preview.items = [];
-
- for (let i = 0; i < length; ++i) {
- // Array Xrays filter out various possibly-unsafe properties (like
- // functions, and claim that the value is undefined instead. This
- // is generally the right thing for privileged code accessing untrusted
- // objects, but quite confusing for Object previews. So we manually
- // override this protection by waiving Xrays on the array, and re-applying
- // Xrays on any indexed value props that we pull off of it.
- let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
- if (desc && !desc.get && !desc.set) {
- let value = Cu.unwaiveXrays(desc.value);
- value = makeDebuggeeValueIfNeeded(obj, value);
- items.push(hooks.createValueGrip(value));
- } else {
- items.push(null);
- }
-
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Set: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: size,
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumSetEntries(objectActor)) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakSet: [function(objectActor, grip) {
- let enumEntries = enumWeakSetEntries(objectActor);
-
- grip.preview = {
- kind: "ArrayLike",
- length: enumEntries.size
- };
-
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- for (let item of enumEntries) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Map: [function(objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: size,
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumMapEntries(objectActor)) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- WeakMap: [function(objectActor, grip) {
- let enumEntries = enumWeakMapEntries(objectActor);
-
- grip.preview = {
- kind: "MapLike",
- size: enumEntries.size
- };
-
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let entry of enumEntries) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- DOMStringMap: [function({obj, hooks}, grip, rawObj) {
- if (!rawObj) {
- return false;
- }
-
- let keys = obj.getOwnPropertyNames();
- grip.preview = {
- kind: "MapLike",
- size: keys.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let entries = grip.preview.entries = [];
- for (let key of keys) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
- entries.push([key, hooks.createValueGrip(value)]);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- return true;
- }],
-
- Proxy: [function({obj, hooks}, grip, rawObj) {
- // The `isProxy` getter of the debuggee object only detects proxies without
- // security wrappers. If false, the target and handler are not available.
- let hasTargetAndHandler = obj.isProxy;
- if (hasTargetAndHandler) {
- grip.proxyTarget = hooks.createValueGrip(obj.proxyTarget);
- grip.proxyHandler = hooks.createValueGrip(obj.proxyHandler);
- }
-
- grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownPropertiesLength: 2 * hasTargetAndHandler
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- if (hasTargetAndHandler) {
- grip.preview.ownProperties["<target>"] = {value: grip.proxyTarget};
- grip.preview.ownProperties["<handler>"] = {value: grip.proxyHandler};
- }
-
- return true;
- }],
-};
-
-/**
- * Generic previewer for classes wrapping primitives, like String,
- * Number and Boolean.
- *
- * @param string className
- * Class name to expect.
- * @param object classObj
- * The class to expect, eg. String. The valueOf() method of the class is
- * invoked on the given object.
- * @param ObjectActor objectActor
- * The object actor
- * @param Object grip
- * The result grip to fill in
- * @return Booolean true if the object was handled, false otherwise
- */
-function wrappedPrimitivePreviewer(className, classObj, objectActor, grip, rawObj) {
- let {obj, hooks} = objectActor;
-
- let v = null;
- try {
- v = classObj.prototype.valueOf.call(rawObj);
- } catch (ex) {
- // valueOf() can throw if the raw JS object is "misbehaved".
- return false;
- }
-
- if (v === null) {
- return false;
- }
-
- let canHandle = GenericObject(objectActor, grip, rawObj, className === "String");
- if (!canHandle) {
- return false;
- }
-
- grip.preview.wrappedValue =
- hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v));
- return true;
-}
-
-function GenericObject(objectActor, grip, rawObj, specialStringBehavior = false) {
- let {obj, hooks} = objectActor;
- if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
- return false;
- }
-
- let i = 0, names = [], symbols = [];
- let preview = grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownSymbols: [],
- };
-
- try {
- names = obj.getOwnPropertyNames();
- symbols = obj.getOwnPropertySymbols();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
- preview.ownPropertiesLength = names.length;
- preview.ownSymbolsLength = symbols.length;
-
- let length;
- if (specialStringBehavior) {
- length = DevToolsUtils.getProperty(obj, "length");
- if (typeof length != "number") {
- specialStringBehavior = false;
- }
- }
-
- for (let name of names) {
- if (specialStringBehavior && /^[0-9]+$/.test(name)) {
- let num = parseInt(name, 10);
- if (num.toString() === name && num >= 0 && num < length) {
- continue;
- }
- }
-
- let desc = objectActor._propertyDescriptor(name, true);
- if (!desc) {
- continue;
- }
-
- preview.ownProperties[name] = desc;
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- for (let symbol of symbols) {
- let descriptor = objectActor._propertyDescriptor(symbol, true);
- if (!descriptor) {
- continue;
- }
-
- preview.ownSymbols.push(Object.assign({
- descriptor
- }, hooks.createValueGrip(symbol)));
-
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
-
- if (i < OBJECT_PREVIEW_MAX_ITEMS) {
- preview.safeGetterValues = objectActor._findSafeGetterValues(
- Object.keys(preview.ownProperties),
- OBJECT_PREVIEW_MAX_ITEMS - i);
- }
-
- return true;
-}
-
-// Preview functions that do not rely on the object class.
-DebuggerServer.ObjectActorPreviewers.Object = [
- function TypedArray({obj, hooks}, grip) {
- if (!isTypedArray(obj)) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: getArrayLength(obj),
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let raw = obj.unsafeDereference();
- let global = Cu.getGlobalForObject(DebuggerServer);
- let classProto = global[obj.class].prototype;
- // The Xray machinery for TypedArrays denies indexed access on the grounds
- // that it's slow, and advises callers to do a structured clone instead.
- let safeView = Cu.cloneInto(classProto.subarray.call(raw, 0,
- OBJECT_PREVIEW_MAX_ITEMS), global);
- let items = grip.preview.items = [];
- for (let i = 0; i < safeView.length; i++) {
- items.push(safeView[i]);
- }
-
- return true;
- },
-
- function Error({obj, hooks}, grip) {
- switch (obj.class) {
- case "Error":
- case "EvalError":
- case "RangeError":
- case "ReferenceError":
- case "SyntaxError":
- case "TypeError":
- case "URIError":
- let name = DevToolsUtils.getProperty(obj, "name");
- let msg = DevToolsUtils.getProperty(obj, "message");
- let stack = DevToolsUtils.getProperty(obj, "stack");
- let fileName = DevToolsUtils.getProperty(obj, "fileName");
- let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
- let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
- grip.preview = {
- kind: "Error",
- name: hooks.createValueGrip(name),
- message: hooks.createValueGrip(msg),
- stack: hooks.createValueGrip(stack),
- fileName: hooks.createValueGrip(fileName),
- lineNumber: hooks.createValueGrip(lineNumber),
- columnNumber: hooks.createValueGrip(columnNumber),
- };
- return true;
- default:
- return false;
- }
- },
-
- function CSSMediaRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSMediaRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.conditionText),
- };
- return true;
- },
-
- function CSSStyleRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || obj.class != "CSSStyleRule") {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.selectorText),
- };
- return true;
- },
-
- function ObjectWithURL({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(obj.class == "CSSImportRule" ||
- obj.class == "CSSStyleSheet" ||
- obj.class == "Location" ||
- rawObj instanceof Ci.nsIDOMWindow)) {
- return false;
- }
-
- let url;
- if (rawObj instanceof Ci.nsIDOMWindow && rawObj.location) {
- url = rawObj.location.href;
- } else if (rawObj.href) {
- url = rawObj.href;
- } else {
- return false;
- }
-
- grip.preview = {
- kind: "ObjectWithURL",
- url: hooks.createValueGrip(url),
- };
-
- return true;
- },
-
- function ArrayLike({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- obj.class != "DOMStringList" &&
- obj.class != "DOMTokenList" &&
- obj.class != "CSSRuleList" &&
- obj.class != "MediaList" &&
- obj.class != "StyleSheetList" &&
- obj.class != "CSSValueList" &&
- obj.class != "NamedNodeMap" &&
- obj.class != "FileList" &&
- obj.class != "NodeList") {
- return false;
- }
-
- if (typeof rawObj.length != "number") {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: rawObj.length,
- };
-
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
-
- for (let i = 0; i < rawObj.length &&
- items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[i]);
- items.push(hooks.createValueGrip(value));
- }
-
- return true;
- },
-
- function CSSStyleDeclaration({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- (obj.class != "CSSStyleDeclaration" &&
- obj.class != "CSS2Properties")) {
- return false;
- }
-
- grip.preview = {
- kind: "MapLike",
- size: rawObj.length,
- };
-
- let entries = grip.preview.entries = [];
-
- for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
- i < rawObj.length; i++) {
- let prop = rawObj[i];
- let value = rawObj.getPropertyValue(prop);
- entries.push([prop, hooks.createValueGrip(value)]);
- }
-
- return true;
- },
-
- function DOMNode({obj, hooks}, grip, rawObj) {
- if (isWorker || obj.class == "Object" || !rawObj ||
- !(rawObj instanceof Ci.nsIDOMNode)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMNode",
- nodeType: rawObj.nodeType,
- nodeName: rawObj.nodeName,
- isConnected: rawObj.isConnected === true,
- };
-
- if (rawObj instanceof Ci.nsIDOMDocument && rawObj.location) {
- preview.location = hooks.createValueGrip(rawObj.location.href);
- } else if (rawObj instanceof Ci.nsIDOMDocumentFragment) {
- preview.childNodesLength = rawObj.childNodes.length;
-
- if (hooks.getGripDepth() < 2) {
- preview.childNodes = [];
- for (let node of rawObj.childNodes) {
- let actor = hooks.createValueGrip(obj.makeDebuggeeValue(node));
- preview.childNodes.push(actor);
- if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
- } else if (rawObj instanceof Ci.nsIDOMElement) {
- // For HTML elements (in an HTML document, at least), the nodeName is an
- // uppercased version of the actual element name. Check for HTML
- // elements, that is elements in the HTML namespace, and lowercase the
- // nodeName in that case.
- if (rawObj.namespaceURI == "http://www.w3.org/1999/xhtml") {
- preview.nodeName = preview.nodeName.toLowerCase();
- }
-
- // Add preview for DOM element attributes.
- preview.attributes = {};
- preview.attributesLength = rawObj.attributes.length;
- for (let attr of rawObj.attributes) {
- preview.attributes[attr.nodeName] = hooks.createValueGrip(attr.value);
- }
- } else if (obj.class == "Attr") {
- preview.value = hooks.createValueGrip(rawObj.value);
- } else if (obj.class == "Text" ||
- obj.class == "CDATASection" ||
- obj.class == "Comment") {
- preview.textContent = hooks.createValueGrip(rawObj.textContent);
- }
-
- return true;
- },
-
- function DOMEvent({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMEvent)) {
- return false;
- }
-
- let preview = grip.preview = {
- kind: "DOMEvent",
- type: rawObj.type,
- properties: Object.create(null),
- };
-
- if (hooks.getGripDepth() < 2) {
- let target = obj.makeDebuggeeValue(rawObj.target);
- preview.target = hooks.createValueGrip(target);
- }
-
- let props = [];
- if (obj.class == "MouseEvent" ||
- obj.class == "DragEvent" ||
- obj.class == "PointerEvent" ||
- obj.class == "SimpleGestureEvent" ||
- obj.class == "WheelEvent") {
- props.push("buttons", "clientX", "clientY", "layerX", "layerY");
- } else if (obj.class == "KeyboardEvent") {
- let modifiers = [];
- if (rawObj.altKey) {
- modifiers.push("Alt");
- }
- if (rawObj.ctrlKey) {
- modifiers.push("Control");
- }
- if (rawObj.metaKey) {
- modifiers.push("Meta");
- }
- if (rawObj.shiftKey) {
- modifiers.push("Shift");
- }
- preview.eventKind = "key";
- preview.modifiers = modifiers;
-
- props.push("key", "charCode", "keyCode");
- } else if (obj.class == "TransitionEvent") {
- props.push("propertyName", "pseudoElement");
- } else if (obj.class == "AnimationEvent") {
- props.push("animationName", "pseudoElement");
- } else if (obj.class == "ClipboardEvent") {
- props.push("clipboardData");
- }
-
- // Add event-specific properties.
- for (let prop of props) {
- let value = rawObj[prop];
- if (value && (typeof value == "object" || typeof value == "function")) {
- // Skip properties pointing to objects.
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- }
-
- // Add any properties we find on the event object.
- if (!props.length) {
- let i = 0;
- for (let prop in rawObj) {
- let value = rawObj[prop];
- if (prop == "target" || prop == "type" || value === null ||
- typeof value == "function") {
- continue;
- }
- if (value && typeof value == "object") {
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
-
- return true;
- },
-
- function DOMException({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMDOMException)) {
- return false;
- }
-
- grip.preview = {
- kind: "DOMException",
- name: hooks.createValueGrip(rawObj.name),
- message: hooks.createValueGrip(rawObj.message),
- code: hooks.createValueGrip(rawObj.code),
- result: hooks.createValueGrip(rawObj.result),
- filename: hooks.createValueGrip(rawObj.filename),
- lineNumber: hooks.createValueGrip(rawObj.lineNumber),
- columnNumber: hooks.createValueGrip(rawObj.columnNumber),
- };
-
- return true;
- },
-
- function PseudoArray({obj, hooks}, grip, rawObj) {
- // An object is considered a pseudo-array if all the following apply:
- // - All its properties are array indices except, optionally, a "length" property.
- // - At least it has the "0" array index.
- // - The array indices are consecutive.
- // - The value of "length", if present, is the number of array indices.
-
- let keys;
- try {
- keys = obj.getOwnPropertyNames();
- } catch (err) {
- // The above can throw when the debuggee does not subsume the object's
- // compartment, or for some WrappedNatives like Cu.Sandbox.
- return false;
- }
- let {length} = keys;
- if (length === 0) {
- return false;
- }
-
- // Array indices should be sorted at the beginning, from smallest to largest.
- // Other properties should be at the end, so check if the last one is "length".
- if (keys[length - 1] === "length") {
- --length;
- if (length === 0 || length !== DevToolsUtils.getProperty(obj, "length")) {
- return false;
- }
- }
-
- // Check that the last key is the array index expected at that position.
- let lastKey = keys[length - 1];
- if (!isArrayIndex(lastKey) || +lastKey !== length - 1) {
- return false;
- }
-
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
-
- // Avoid recursive object grips.
- if (hooks.getGripDepth() > 1) {
- return true;
- }
-
- let items = grip.preview.items = [];
- let numItems = Math.min(OBJECT_PREVIEW_MAX_ITEMS, length);
-
- for (let i = 0; i < numItems; ++i) {
- let desc = obj.getOwnPropertyDescriptor(i);
- if (desc && "value" in desc) {
- items.push(hooks.createValueGrip(desc.value));
- } else {
- items.push(null);
- }
- }
-
- return true;
- },
-
- function Object(objectActor, grip, rawObj) {
- return GenericObject(objectActor, grip, rawObj, /* specialStringBehavior = */ false);
- },
-];
+loader.lazyRequireGetter(this, "longStringGrip", "devtools/server/actors/object/long-string", true);
+loader.lazyRequireGetter(this, "symbolGrip", "devtools/server/actors/object/symbol", true);
/**
* Get thisDebugger.Object referent's `promiseState`.
*
* @returns Object
* An object of one of the following forms:
* - { state: "pending" }
* - { state: "fulfilled", value }
@@ -2070,190 +34,16 @@ function getPromiseState(obj) {
state.value = obj.promiseValue;
} else if (state.state === "rejected") {
state.reason = obj.promiseReason;
}
return state;
}
/**
- * Determine if a given value is non-primitive.
- *
- * @param Any value
- * The value to test.
- * @return Boolean
- * Whether the value is non-primitive.
- */
-function isObject(value) {
- const type = typeof value;
- return type == "object" ? value !== null : type == "function";
-}
-
-/**
- * Create a function that can safely stringify Debugger.Objects of a given
- * builtin type.
- *
- * @param Function ctor
- * The builtin class constructor.
- * @return Function
- * The stringifier for the class.
- */
-function createBuiltinStringifier(ctor) {
- return obj => {
- try {
- return ctor.prototype.toString.call(obj.unsafeDereference());
- } catch (err) {
- // The debuggee will see a "Function" class if the object is callable and
- // its compartment is not subsumed. The above will throw if it's not really
- // a function, e.g. if it's a callable proxy.
- return "[object " + obj.class + "]";
- }
- };
-}
-
-/**
- * Stringify a Debugger.Object-wrapped Error instance.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification of the object.
- */
-function errorStringify(obj) {
- let name = DevToolsUtils.getProperty(obj, "name");
- if (name === "" || name === undefined) {
- name = obj.class;
- } else if (isObject(name)) {
- name = stringify(name);
- }
-
- let message = DevToolsUtils.getProperty(obj, "message");
- if (isObject(message)) {
- message = stringify(message);
- }
-
- if (message === "" || message === undefined) {
- return name;
- }
- return name + ": " + message;
-}
-
-/**
- * Stringify a Debugger.Object based on its class.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification for the object.
- */
-function stringify(obj) {
- if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
- if (DevToolsUtils.isCPOW(obj)) {
- return "<cpow>";
- }
- let unwrapped = DevToolsUtils.unwrap(obj);
- if (unwrapped === undefined) {
- return "<invisibleToDebugger>";
- } else if (unwrapped.isProxy) {
- return "<proxy>";
- }
- // The following line should not be reached. It's there just in case somebody
- // modifies isSafeDebuggerObject to return false for additional kinds of objects.
- return "[object " + obj.class + "]";
- } else if (obj.class == "DeadObject") {
- return "<dead object>";
- }
-
- const stringifier = stringifiers[obj.class] || stringifiers.Object;
-
- try {
- return stringifier(obj);
- } catch (e) {
- DevToolsUtils.reportException("stringify", e);
- return "<failed to stringify object>";
- }
-}
-
-// Used to prevent infinite recursion when an array is found inside itself.
-var seen = null;
-
-var stringifiers = {
- Error: errorStringify,
- EvalError: errorStringify,
- RangeError: errorStringify,
- ReferenceError: errorStringify,
- SyntaxError: errorStringify,
- TypeError: errorStringify,
- URIError: errorStringify,
- Boolean: createBuiltinStringifier(Boolean),
- Function: createBuiltinStringifier(Function),
- Number: createBuiltinStringifier(Number),
- RegExp: createBuiltinStringifier(RegExp),
- String: createBuiltinStringifier(String),
- Object: obj => "[object " + obj.class + "]",
- Array: obj => {
- // If we're at the top level then we need to create the Set for tracking
- // previously stringified arrays.
- const topLevel = !seen;
- if (topLevel) {
- seen = new Set();
- } else if (seen.has(obj)) {
- return "";
- }
-
- seen.add(obj);
-
- const len = getArrayLength(obj);
- let string = "";
-
- // Array.length is always a non-negative safe integer.
- for (let i = 0; i < len; i++) {
- const desc = obj.getOwnPropertyDescriptor(i);
- if (desc) {
- const { value } = desc;
- if (value != null) {
- string += isObject(value) ? stringify(value) : value;
- }
- }
-
- if (i < len - 1) {
- string += ",";
- }
- }
-
- if (topLevel) {
- seen = null;
- }
-
- return string;
- },
- DOMException: obj => {
- const message = DevToolsUtils.getProperty(obj, "message") || "<no message>";
- const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16);
- const code = DevToolsUtils.getProperty(obj, "code");
- const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
-
- return '[Exception... "' + message + '" ' +
- 'code: "' + code + '" ' +
- 'nsresult: "0x' + result + " (" + name + ')"]';
- },
- Promise: obj => {
- const { state, value, reason } = getPromiseState(obj);
- let statePreview = state;
- if (state != "pending") {
- const settledValue = state === "fulfilled" ? value : reason;
- statePreview += ": " + (typeof settledValue === "object" && settledValue !== null
- ? stringify(settledValue)
- : settledValue);
- }
- return "Promise (" + statePreview + ")";
- },
-};
-
-/**
* Make a debuggee value for the given object, if needed. Primitive values
* are left the same.
*
* Use case: you have a raw JS object (after unsafe dereference) and you want to
* send it to the client. In that case you need to use an ObjectActor which
* requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
* method works only for JS objects and functions.
*
@@ -2264,205 +54,16 @@ var stringifiers = {
function makeDebuggeeValueIfNeeded(obj, value) {
if (value && (typeof value == "object" || typeof value == "function")) {
return obj.makeDebuggeeValue(value);
}
return value;
}
/**
- * Creates an actor for the specified "very long" string. "Very long" is specified
- * at the server's discretion.
- *
- * @param string String
- * The string.
- */
-function LongStringActor(string) {
- this.string = string;
- this.stringLength = string.length;
-}
-
-LongStringActor.prototype = {
- actorPrefix: "longString",
-
- rawValue: function() {
- return this.string;
- },
-
- destroy: function() {
- // Because longStringActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- return {
- "type": "longString",
- "initial": this.string.substring(
- 0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
- "length": this.stringLength,
- "actor": this.actorID
- };
- },
-
- /**
- * Handle a request to extract part of this actor's string.
- *
- * @param request object
- * The protocol request object.
- */
- onSubstring: function(request) {
- return {
- "from": this.actorID,
- "substring": this.string.substring(request.start, request.end)
- };
- },
-
- /**
- * Handle a request to release this LongStringActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.longStringActors) {
- delete this.registeredPool.longStringActors[this.string];
- }
- }
-};
-
-LongStringActor.prototype.requestTypes = {
- "substring": LongStringActor.prototype.onSubstring,
- "release": LongStringActor.prototype.onRelease
-};
-
-/**
- * Creates an actor for the specified symbol.
- *
- * @param symbol Symbol
- * The symbol.
- */
-function SymbolActor(symbol) {
- this.symbol = symbol;
-}
-
-SymbolActor.prototype = {
- actorPrefix: "symbol",
-
- rawValue: function() {
- return this.symbol;
- },
-
- destroy: function() {
- // Because symbolActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on destroy so that we don't leak
- // memory.
- this._releaseActor();
- },
-
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function() {
- let form = {
- type: "symbol",
- actor: this.actorID,
- };
- let name = getSymbolName(this.symbol);
- if (name !== undefined) {
- // Create a grip for the name because it might be a longString.
- form.name = createValueGrip(name, this.registeredPool);
- }
- return form;
- },
-
- /**
- * Handle a request to release this SymbolActor instance.
- */
- onRelease: function() {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves away from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
-
- _releaseActor: function() {
- if (this.registeredPool && this.registeredPool.symbolActors) {
- delete this.registeredPool.symbolActors[this.symbol];
- }
- }
-};
-
-SymbolActor.prototype.requestTypes = {
- "release": SymbolActor.prototype.onRelease
-};
-
-/**
- * Creates an actor for the specified ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The buffer.
- */
-function ArrayBufferActor(buffer) {
- this.buffer = buffer;
- this.bufferLength = buffer.byteLength;
-}
-
-ArrayBufferActor.prototype = {
- actorPrefix: "arrayBuffer",
-
- rawValue: function() {
- return this.buffer;
- },
-
- destroy: function() {
- },
-
- grip() {
- return {
- "type": "arrayBuffer",
- "length": this.bufferLength,
- "actor": this.actorID
- };
- },
-
- onSlice({start, count}) {
- let slice = new Uint8Array(this.buffer, start, count);
- let parts = [], offset = 0;
- const PortionSize = 0x6000; // keep it divisible by 3 for btoa() and join()
- while (offset + PortionSize < count) {
- parts.push(btoa(
- String.fromCharCode.apply(null, slice.subarray(offset, offset + PortionSize))));
- offset += PortionSize;
- }
- parts.push(btoa(String.fromCharCode.apply(null, slice.subarray(offset, count))));
- return {
- "from": this.actorID,
- "encoded": parts.join(""),
- };
- }
-};
-
-ArrayBufferActor.prototype.requestTypes = {
- "slice": ArrayBufferActor.prototype.onSlice,
-};
-
-/**
* Create a grip for the given debuggee value. If the value is an
* object, will create an actor with the given lifetime.
*/
function createValueGrip(value, pool, makeObjectGrip) {
switch (typeof value) {
case "boolean":
return value;
@@ -2508,103 +109,26 @@ function createValueGrip(value, pool, ma
return symbolGrip(value, pool);
default:
assert(false, "Failed to provide a grip for: " + value);
return null;
}
}
-const symbolProtoToString = Symbol.prototype.toString;
-
-function getSymbolName(symbol) {
- const name = symbolProtoToString.call(symbol).slice("Symbol(".length, -1);
- return name || undefined;
-}
-
/**
- * Returns true if the string is long enough to use a LongStringActor instead
* of passing the value directly over the protocol.
*
* @param str String
* The string we are checking the length of.
*/
function stringIsLong(str) {
return str.length >= DebuggerServer.LONG_STRING_LENGTH;
}
-/**
- * Create a grip for the given string.
- *
- * @param str String
- * The string we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function longStringGrip(str, pool) {
- if (!pool.longStringActors) {
- pool.longStringActors = {};
- }
-
- if (pool.longStringActors.hasOwnProperty(str)) {
- return pool.longStringActors[str].grip();
- }
-
- let actor = new LongStringActor(str);
- pool.addActor(actor);
- pool.longStringActors[str] = actor;
- return actor.grip();
-}
-
-/**
- * Create a grip for the given symbol.
- *
- * @param sym Symbol
- * The symbol we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function symbolGrip(sym, pool) {
- if (!pool.symbolActors) {
- pool.symbolActors = Object.create(null);
- }
-
- if (sym in pool.symbolActors) {
- return pool.symbolActors[sym].grip();
- }
-
- let actor = new SymbolActor(sym);
- pool.addActor(actor);
- pool.symbolActors[sym] = actor;
- return actor.grip();
-}
-
-/**
- * Create a grip for the given ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- * The ArrayBuffer we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
-function arrayBufferGrip(buffer, pool) {
- if (!pool.arrayBufferActors) {
- pool.arrayBufferActors = new WeakMap();
- }
-
- if (pool.arrayBufferActors.has(buffer)) {
- return pool.arrayBufferActors.get(buffer).grip();
- }
-
- let actor = new ArrayBufferActor(buffer);
- pool.addActor(actor);
- pool.arrayBufferActors.set(buffer, actor);
- return actor.grip();
-}
-
const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
"Uint32Array", "Int8Array", "Int16Array", "Int32Array",
"Float32Array", "Float64Array"];
/**
* Returns true if a debuggee object is a typed array.
*
* @param obj Debugger.Object
@@ -2648,42 +172,32 @@ function getArrayLength(object) {
// getter could be shadowed by an own property, and `getOwnPropertyNames` is
// unnecessarily slow. Obtain the `length` getter safely and call it manually.
let typedProto = Object.getPrototypeOf(Uint8Array.prototype);
let getter = Object.getOwnPropertyDescriptor(typedProto, "length").get;
return getter.call(object.unsafeDereference());
}
/**
- * 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;
-}
-
-/**
* Returns true if the parameter is suitable to be an array index.
*
* @param str String
* @return Boolean
*/
function isArrayIndex(str) {
// Transform the parameter to a 32-bit unsigned integer.
let num = str >>> 0;
// Check that the parameter is a canonical Uint32 index.
return num + "" === str &&
// Array indices cannot attain the maximum Uint32 value.
num != -1 >>> 0;
}
-exports.ObjectActor = ObjectActor;
-exports.PropertyIteratorActor = PropertyIteratorActor;
-exports.LongStringActor = LongStringActor;
-exports.SymbolActor = SymbolActor;
-exports.createValueGrip = createValueGrip;
-exports.stringIsLong = stringIsLong;
-exports.longStringGrip = longStringGrip;
-exports.arrayBufferGrip = arrayBufferGrip;
+module.exports = {
+ getPromiseState,
+ makeDebuggeeValueIfNeeded,
+ createValueGrip,
+ stringIsLong,
+ isTypedArray,
+ isArray,
+ getArrayLength,
+ isArrayIndex,
+};
--- a/devtools/server/actors/promises.js
+++ b/devtools/server/actors/promises.js
@@ -2,17 +2,18 @@
* 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 protocol = require("devtools/shared/protocol");
const { promisesSpec } = require("devtools/shared/specs/promises");
const { expectState, ActorPool } = require("devtools/server/actors/common");
-const { ObjectActor, createValueGrip } = require("devtools/server/actors/object");
+const { ObjectActor } = require("devtools/server/actors/object");
+const { createValueGrip } = require("devtools/server/actors/object/utils");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const EventEmitter = require("devtools/shared/event-emitter");
/**
* The Promises Actor provides support for getting the list of live promises and
* observing changes to their settlement state.
*/
var PromisesActor = protocol.ActorClassWithSpec(promisesSpec, {
--- a/devtools/server/actors/source.js
+++ b/devtools/server/actors/source.js
@@ -5,26 +5,27 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Cc, Ci } = require("chrome");
const Services = require("Services");
const { BreakpointActor, setBreakpointAtEntryPoints } = require("devtools/server/actors/breakpoint");
const { OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
-const { createValueGrip, arrayBufferGrip } = require("devtools/server/actors/object");
+const { createValueGrip } = require("devtools/server/actors/object/utils");
const { ActorClassWithSpec } = require("devtools/shared/protocol");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { assert, fetch } = DevToolsUtils;
const { joinURI } = require("devtools/shared/path");
const { sourceSpec } = require("devtools/shared/specs/source");
loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true);
loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
+loader.lazyRequireGetter(this, "arrayBufferGrip", "devtools/server/actors/array-buffer", true);
function isEvalSource(source) {
let introType = source.introductionType;
// These are all the sources that are essentially eval-ed (either
// by calling eval or passing a string to one of these functions).
return (introType === "eval" ||
introType === "debugger eval" ||
introType === "Function" ||
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -4,17 +4,18 @@
* 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 Services = require("Services");
const { Cr } = require("chrome");
const { ActorPool, GeneratedLocation } = require("devtools/server/actors/common");
-const { createValueGrip, longStringGrip } = require("devtools/server/actors/object");
+const { createValueGrip } = require("devtools/server/actors/object/utils");
+const { longStringGrip } = require("devtools/server/actors/object/long-string");
const { ActorClassWithSpec } = require("devtools/shared/protocol");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const flags = require("devtools/shared/flags");
const { assert, dumpn } = DevToolsUtils;
const { DevToolsWorker } = require("devtools/shared/worker/worker");
const { threadSpec } = require("devtools/shared/specs/script");
loader.lazyGetter(this, "Debugger", () => {
--- a/devtools/server/actors/webaudio.js
+++ b/devtools/server/actors/webaudio.js
@@ -4,17 +4,17 @@
"use strict";
/* global XPCNativeWrapper */
const { Cu } = require("chrome");
const protocol = require("devtools/shared/protocol");
const { CallWatcherActor } = require("devtools/server/actors/call-watcher");
-const { createValueGrip } = require("devtools/server/actors/object");
+const { createValueGrip } = require("devtools/server/actors/object/utils");
const AutomationTimeline = require("./utils/automation-timeline");
const {
audionodeSpec,
webAudioSpec
} = require("devtools/shared/specs/webaudio");
const { WebAudioFront } = require("devtools/shared/fronts/webaudio");
const Services = require("Services");
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -7,17 +7,19 @@
"use strict";
/* global XPCNativeWrapper */
const Services = require("Services");
const { Cc, Ci, Cu } = require("chrome");
const { DebuggerServer, ActorPool } = require("devtools/server/main");
const { ThreadActor } = require("devtools/server/actors/thread");
-const { ObjectActor, LongStringActor, createValueGrip, stringIsLong } = require("devtools/server/actors/object");
+const { ObjectActor } = require("devtools/server/actors/object");
+const { LongStringActor } = require("devtools/server/actors/object/long-string");
+const { createValueGrip, stringIsLong } = require("devtools/server/actors/object/utils");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const ErrorDocs = require("devtools/server/actors/errordocs");
loader.lazyRequireGetter(this, "NetworkMonitor", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "NetworkMonitorChild", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "ConsoleProgressListener", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "StackTraceCollector", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "ServerLoggingListener", "devtools/shared/webconsole/server-logger", true);
--- a/devtools/server/tests/unit/test_longstringactor.js
+++ b/devtools/server/tests/unit/test_longstringactor.js
@@ -1,15 +1,15 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
-const { LongStringActor } = require("devtools/server/actors/object");
+const { LongStringActor } = require("devtools/server/actors/object/long-string");
function run_test() {
test_LSA_destroy();
test_LSA_grip();
test_LSA_onSubstring();
}
const TEST_STRING = "This is a very long string!";
--- a/devtools/server/tests/unit/test_symbolactor.js
+++ b/devtools/server/tests/unit/test_symbolactor.js
@@ -1,15 +1,15 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
-const { SymbolActor } = require("devtools/server/actors/object");
+const { SymbolActor } = require("devtools/server/actors/object/symbol");
function run_test() {
test_SA_destroy();
test_SA_grip();
test_SA_raw();
}
const SYMBOL_NAME = "abc";