blob: 0849d44202ef31563f38b29b5bc3ec03f2465dea [file] [log] [blame]
/*
* Copyright (C) 2007 Apple Inc. All rights reserved.
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
/**
* @param {!InjectedScriptHostClass} InjectedScriptHost
* @param {!Window|!WorkerGlobalScope} inspectedGlobalObject
* @param {number} injectedScriptId
* @suppress {uselessCode}
*/
(function (InjectedScriptHost, inspectedGlobalObject, injectedScriptId) {
/**
* @param {!Array.<T>} array
* @param {...} var_args
* @template T
*/
function push(array, var_args)
{
for (var i = 1; i < arguments.length; ++i)
array[array.length] = arguments[i];
}
/**
* @param {*} obj
* @return {string}
* @suppress {uselessCode}
*/
function toString(obj)
{
// We don't use String(obj) because String could be overridden.
// Also the ("" + obj) expression may throw.
try {
return "" + obj;
} catch (e) {
var name = InjectedScriptHost.internalConstructorName(obj) || InjectedScriptHost.subtype(obj) || (typeof obj);
return "#<" + name + ">";
}
}
/**
* @param {*} obj
* @return {string}
*/
function toStringDescription(obj)
{
if (typeof obj === "number" && obj === 0 && 1 / obj < 0)
return "-0"; // Negative zero.
return toString(obj);
}
/**
* FireBug's array detection.
* @param {*} obj
* @return {boolean}
*/
function isArrayLike(obj)
{
if (typeof obj !== "object")
return false;
var splice = InjectedScriptHost.getProperty(obj, "splice");
if (typeof splice === "function") {
if (!InjectedScriptHost.objectHasOwnProperty(/** @type {!Object} */ (obj), "length"))
return false;
var len = InjectedScriptHost.getProperty(obj, "length");
// is len uint32?
return typeof len === "number" && len >>> 0 === len && (len > 0 || 1 / len > 0);
}
return false;
}
/**
* @param {number} a
* @param {number} b
* @return {number}
*/
function max(a, b)
{
return a > b ? a : b;
}
/**
* FIXME: Remove once ES6 is supported natively by JS compiler.
* @param {*} obj
* @return {boolean}
*/
function isSymbol(obj)
{
var type = typeof obj;
return (type === "symbol");
}
/**
* DOM Attributes which have observable side effect on getter, in the form of
* {interfaceName1: {attributeName1: true,
* attributeName2: true,
* ...},
* interfaceName2: {...},
* ...}
* @type {!Object<string, !Object<string, boolean>>}
* @const
*/
var domAttributesWithObservableSideEffectOnGet = {
Request: { body: true, __proto__: null },
Response: { body: true, __proto__: null },
__proto__: null
}
/**
* @param {!Object} object
* @param {string} attribute
* @return {boolean}
*/
function doesAttributeHaveObservableSideEffectOnGet(object, attribute)
{
for (var interfaceName in domAttributesWithObservableSideEffectOnGet) {
var interfaceFunction = inspectedGlobalObject[interfaceName];
// Call to instanceOf looks safe after typeof check.
var isInstance = typeof interfaceFunction === "function" && /* suppressBlacklist */ object instanceof interfaceFunction;
if (isInstance)
return attribute in domAttributesWithObservableSideEffectOnGet[interfaceName];
}
return false;
}
/**
* @constructor
*/
var InjectedScript = function()
{
}
InjectedScriptHost.nullifyPrototype(InjectedScript);
/**
* @type {!Object<string, boolean>}
* @const
*/
InjectedScript.primitiveTypes = {
"undefined": true,
"boolean": true,
"number": true,
"string": true,
__proto__: null
}
/**
* @type {!Object<string, string>}
* @const
*/
InjectedScript.closureTypes = {
"local": "Local",
"closure": "Closure",
"catch": "Catch",
"block": "Block",
"script": "Script",
"with": "With Block",
"global": "Global",
"eval": "Eval",
"module": "Module",
__proto__: null
};
InjectedScript.prototype = {
/**
* @param {*} object
* @return {boolean}
*/
isPrimitiveValue: function(object)
{
// FIXME(33716): typeof document.all is always 'undefined'.
return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object);
},
/**
* @param {*} object
* @return {boolean}
*/
_shouldPassByValue: function(object)
{
return typeof object === "object" && InjectedScriptHost.subtype(object) === "internal#location";
},
/**
* @param {*} object
* @param {string} groupName
* @param {boolean} forceValueType
* @param {boolean} generatePreview
* @return {!RuntimeAgent.RemoteObject}
*/
wrapObject: function(object, groupName, forceValueType, generatePreview)
{
return this._wrapObject(object, groupName, forceValueType, generatePreview);
},
/**
* @param {!Object} table
* @param {!Array.<string>|string|boolean} columns
* @return {!RuntimeAgent.RemoteObject}
*/
wrapTable: function(table, columns)
{
var columnNames = null;
if (typeof columns === "string")
columns = [columns];
if (InjectedScriptHost.subtype(columns) === "array") {
columnNames = [];
InjectedScriptHost.nullifyPrototype(columnNames);
for (var i = 0; i < columns.length; ++i)
columnNames[i] = toString(columns[i]);
}
return this._wrapObject(table, "console", false, true, columnNames, true);
},
/**
* This method cannot throw.
* @param {*} object
* @param {string=} objectGroupName
* @param {boolean=} forceValueType
* @param {boolean=} generatePreview
* @param {?Array.<string>=} columnNames
* @param {boolean=} isTable
* @param {boolean=} doNotBind
* @param {*=} customObjectConfig
* @return {!RuntimeAgent.RemoteObject}
* @suppress {checkTypes}
*/
_wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames, isTable, doNotBind, customObjectConfig)
{
try {
return new InjectedScript.RemoteObject(object, objectGroupName, doNotBind, forceValueType, generatePreview, columnNames, isTable, undefined, customObjectConfig);
} catch (e) {
try {
var description = injectedScript._describe(e);
} catch (ex) {
var description = "<failed to convert exception to string>";
}
return new InjectedScript.RemoteObject(description);
}
},
/**
* @param {!Object|symbol} object
* @param {string=} objectGroupName
* @return {string}
*/
_bind: function(object, objectGroupName)
{
var id = InjectedScriptHost.bind(object, objectGroupName || "");
return "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}";
},
/**
* @param {!Object} object
* @param {string} objectGroupName
* @param {boolean} ownProperties
* @param {boolean} accessorPropertiesOnly
* @param {boolean} generatePreview
* @return {!Array<!RuntimeAgent.PropertyDescriptor>|boolean}
*/
getProperties: function(object, objectGroupName, ownProperties, accessorPropertiesOnly, generatePreview)
{
var subtype = this._subtype(object);
if (subtype === "internal#scope") {
// Internally, scope contains object with scope variables and additional information like type,
// we use additional information for preview and would like to report variables as scope
// properties.
object = object.object;
}
// Go over properties, wrap object values.
var descriptors = this._propertyDescriptors(object, addPropertyIfNeeded, ownProperties, accessorPropertiesOnly);
for (var i = 0; i < descriptors.length; ++i) {
var descriptor = descriptors[i];
if ("get" in descriptor)
descriptor.get = this._wrapObject(descriptor.get, objectGroupName);
if ("set" in descriptor)
descriptor.set = this._wrapObject(descriptor.set, objectGroupName);
if ("value" in descriptor)
descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview);
if (!("configurable" in descriptor))
descriptor.configurable = false;
if (!("enumerable" in descriptor))
descriptor.enumerable = false;
if ("symbol" in descriptor)
descriptor.symbol = this._wrapObject(descriptor.symbol, objectGroupName);
}
return descriptors;
/**
* @param {!Array<!Object>} descriptors
* @param {!Object} descriptor
* @return {boolean}
*/
function addPropertyIfNeeded(descriptors, descriptor) {
push(descriptors, descriptor);
return true;
}
},
/**
* @param {!Object} object
* @return {?Object}
*/
_objectPrototype: function(object)
{
if (InjectedScriptHost.subtype(object) === "proxy")
return null;
try {
return InjectedScriptHost.getPrototypeOf(object);
} catch (e) {
return null;
}
},
/**
* @param {!Object} object
* @param {!function(!Array<!Object>, !Object)} addPropertyIfNeeded
* @param {boolean=} ownProperties
* @param {boolean=} accessorPropertiesOnly
* @param {?Array<string>=} propertyNamesOnly
* @return {!Array<!Object>}
*/
_propertyDescriptors: function(object, addPropertyIfNeeded, ownProperties, accessorPropertiesOnly, propertyNamesOnly)
{
var descriptors = [];
InjectedScriptHost.nullifyPrototype(descriptors);
var propertyProcessed = { __proto__: null };
var subtype = InjectedScriptHost.subtype(object);
/**
* @param {!Object} o
* @param {!Array<string|number|symbol>=} properties
* @param {number=} objectLength
* @return {boolean}
*/
function process(o, properties, objectLength)
{
// When properties is not provided, iterate over the object's indices.
var length = properties ? properties.length : objectLength;
for (var i = 0; i < length; ++i) {
var property = properties ? properties[i] : ("" + i);
if (propertyProcessed[property])
continue;
propertyProcessed[property] = true;
var name;
if (isSymbol(property))
name = /** @type {string} */ (injectedScript._describe(property));
else
name = typeof property === "number" ? ("" + property) : /** @type {string} */(property);
if (subtype === "internal#scopeList" && name === "length")
continue;
var descriptor;
try {
var nativeAccessorDescriptor = InjectedScriptHost.nativeAccessorDescriptor(o, property);
if (nativeAccessorDescriptor && !nativeAccessorDescriptor.isBuiltin) {
descriptor = { __proto__: null };
if (nativeAccessorDescriptor.hasGetter)
descriptor.get = function nativeGetter() { return o[property]; };
if (nativeAccessorDescriptor.hasSetter)
descriptor.set = function nativeSetter(v) { o[property] = v; };
} else {
descriptor = InjectedScriptHost.getOwnPropertyDescriptor(o, property);
if (descriptor) {
InjectedScriptHost.nullifyPrototype(descriptor);
}
}
var isAccessorProperty = descriptor && ("get" in descriptor || "set" in descriptor);
if (accessorPropertiesOnly && !isAccessorProperty)
continue;
if (descriptor && "get" in descriptor && "set" in descriptor && name !== "__proto__" &&
InjectedScriptHost.formatAccessorsAsProperties(object, descriptor.get) &&
!doesAttributeHaveObservableSideEffectOnGet(object, name)) {
descriptor.value = object[property];
descriptor.isOwn = true;
delete descriptor.get;
delete descriptor.set;
}
} catch (e) {
if (accessorPropertiesOnly)
continue;
descriptor = { value: e, wasThrown: true, __proto__: null };
}
// Not all bindings provide proper descriptors. Fall back to the non-configurable, non-enumerable,
// non-writable property.
if (!descriptor) {
try {
descriptor = { value: o[property], writable: false, __proto__: null };
} catch (e) {
// Silent catch.
continue;
}
}
descriptor.name = name;
if (o === object)
descriptor.isOwn = true;
if (isSymbol(property))
descriptor.symbol = property;
if (!addPropertyIfNeeded(descriptors, descriptor))
return false;
}
return true;
}
if (propertyNamesOnly) {
for (var i = 0; i < propertyNamesOnly.length; ++i) {
var name = propertyNamesOnly[i];
for (var o = object; this._isDefined(o); o = this._objectPrototype(/** @type {!Object} */ (o))) {
o = /** @type {!Object} */ (o);
if (InjectedScriptHost.objectHasOwnProperty(o, name)) {
if (!process(o, [name]))
return descriptors;
break;
}
if (ownProperties)
break;
}
}
return descriptors;
}
var skipGetOwnPropertyNames;
try {
skipGetOwnPropertyNames = subtype === "typedarray" && object.length > 500000;
} catch (e) {
}
for (var o = object; this._isDefined(o); o = this._objectPrototype(/** @type {!Object} */ (o))) {
o = /** @type {!Object} */ (o);
if (InjectedScriptHost.subtype(o) === "proxy")
continue;
var typedArrays = subtype === "arraybuffer" ? InjectedScriptHost.typedArrayProperties(o) || [] : [];
for (var i = 0; i < typedArrays.length; i += 2)
addPropertyIfNeeded(descriptors, { name: typedArrays[i], value: typedArrays[i + 1], isOwn: true, enumerable: false, configurable: false, __proto__: null });
try {
if (skipGetOwnPropertyNames && o === object) {
if (!process(o, undefined, o.length))
return descriptors;
} else {
// First call Object.keys() to enforce ordering of the property descriptors.
if (!process(o, InjectedScriptHost.keys(o)))
return descriptors;
if (!process(o, InjectedScriptHost.getOwnPropertyNames(o)))
return descriptors;
}
if (!process(o, InjectedScriptHost.getOwnPropertySymbols(o)))
return descriptors;
if (ownProperties) {
var proto = this._objectPrototype(o);
if (proto && !accessorPropertiesOnly) {
var descriptor = { name: "__proto__", value: proto, writable: true, configurable: true, enumerable: false, isOwn: true, __proto__: null };
if (!addPropertyIfNeeded(descriptors, descriptor))
return descriptors;
}
}
} catch (e) {
}
if (ownProperties)
break;
}
return descriptors;
},
/**
* @param {string|undefined} objectGroupName
* @param {*} jsonMLObject
* @throws {string} error message
*/
_substituteObjectTagsInCustomPreview: function(objectGroupName, jsonMLObject)
{
var maxCustomPreviewRecursionDepth = 20;
this._customPreviewRecursionDepth = (this._customPreviewRecursionDepth || 0) + 1
try {
if (this._customPreviewRecursionDepth >= maxCustomPreviewRecursionDepth)
throw new Error("Too deep hierarchy of inlined custom previews");
if (!isArrayLike(jsonMLObject))
return;
if (jsonMLObject[0] === "object") {
var attributes = jsonMLObject[1];
var originObject = attributes["object"];
var config = attributes["config"];
if (typeof originObject === "undefined")
throw new Error("Illegal format: obligatory attribute \"object\" isn't specified");
jsonMLObject[1] = this._wrapObject(originObject, objectGroupName, false, false, null, false, false, config);
return;
}
for (var i = 0; i < jsonMLObject.length; ++i)
this._substituteObjectTagsInCustomPreview(objectGroupName, jsonMLObject[i]);
} finally {
this._customPreviewRecursionDepth--;
}
},
/**
* @param {*} object
* @return {boolean}
*/
_isDefined: function(object)
{
return !!object || this._isHTMLAllCollection(object);
},
/**
* @param {*} object
* @return {boolean}
*/
_isHTMLAllCollection: function(object)
{
// document.all is reported as undefined, but we still want to process it.
return (typeof object === "undefined") && !!InjectedScriptHost.subtype(object);
},
/**
* @param {*} obj
* @return {?string}
*/
_subtype: function(obj)
{
if (obj === null)
return "null";
if (this.isPrimitiveValue(obj))
return null;
var subtype = InjectedScriptHost.subtype(obj);
if (subtype)
return subtype;
if (isArrayLike(obj))
return "array";
// If owning frame has navigated to somewhere else window properties will be undefined.
return null;
},
/**
* @param {*} obj
* @return {?string}
*/
_describe: function(obj)
{
if (this.isPrimitiveValue(obj))
return null;
var subtype = this._subtype(obj);
if (subtype === "regexp")
return toString(obj);
if (subtype === "date")
return toString(obj);
if (subtype === "node") {
var description = "";
var nodeName = InjectedScriptHost.getProperty(obj, "nodeName");
if (nodeName) {
description = nodeName.toLowerCase();
} else {
var constructor = InjectedScriptHost.getProperty(obj, "constructor");
if (constructor)
description = (InjectedScriptHost.getProperty(constructor, "name") || "").toLowerCase();
}
var nodeType = InjectedScriptHost.getProperty(obj, "nodeType");
switch (nodeType) {
case 1 /* Node.ELEMENT_NODE */:
var id = InjectedScriptHost.getProperty(obj, "id");
description += id ? "#" + id : "";
var className = InjectedScriptHost.getProperty(obj, "className");
description += (className && typeof className === "string") ? "." + className.trim().replace(/\s+/g, ".") : "";
break;
case 10 /*Node.DOCUMENT_TYPE_NODE */:
description = "<!DOCTYPE " + description + ">";
break;
}
return description;
}
if (subtype === "proxy")
return "Proxy";
var className = InjectedScriptHost.internalConstructorName(obj);
if (subtype === "array" || subtype === "typedarray") {
if (typeof obj.length === "number")
return className + "(" + obj.length + ")";
return className;
}
if (subtype === "map" || subtype === "set" || subtype === "blob") {
if (typeof obj.size === "number")
return className + "(" + obj.size + ")";
return className;
}
if (subtype === "arraybuffer" || subtype === "dataview") {
if (typeof obj.byteLength === "number")
return className + "(" + obj.byteLength + ")";
return className;
}
if (typeof obj === "function")
return toString(obj);
if (isSymbol(obj)) {
try {
// It isn't safe, because Symbol.prototype.toString can be overriden.
return /* suppressBlacklist */ obj.toString() || "Symbol";
} catch (e) {
return "Symbol";
}
}
if (InjectedScriptHost.subtype(obj) === "error") {
try {
var stack = obj.stack;
var message = obj.message && obj.message.length ? ": " + obj.message : "";
var firstCallFrame = /^\s+at\s/m.exec(stack);
var stackMessageEnd = firstCallFrame ? firstCallFrame.index : -1;
if (stackMessageEnd !== -1) {
var stackTrace = stack.substr(stackMessageEnd);
return className + message + "\n" + stackTrace;
}
return className + message;
} catch(e) {
}
}
if (subtype === "internal#entry") {
if ("key" in obj)
return "{" + this._describeIncludingPrimitives(obj.key) + " => " + this._describeIncludingPrimitives(obj.value) + "}";
return this._describeIncludingPrimitives(obj.value);
}
if (subtype === "internal#scopeList")
return "Scopes[" + obj.length + "]";
if (subtype === "internal#scope")
return (InjectedScript.closureTypes[obj.type] || "Unknown") + (obj.name ? " (" + obj.name + ")" : "");
return className;
},
/**
* @param {*} value
* @return {string}
*/
_describeIncludingPrimitives: function(value)
{
if (typeof value === "string")
return "\"" + value.replace(/\n/g, "\u21B5") + "\"";
if (value === null)
return "" + value;
return this.isPrimitiveValue(value) ? toStringDescription(value) : (this._describe(value) || "");
},
/**
* @param {boolean} enabled
*/
setCustomObjectFormatterEnabled: function(enabled)
{
this._customObjectFormatterEnabled = enabled;
}
}
/**
* @type {!InjectedScript}
* @const
*/
var injectedScript = new InjectedScript();
/**
* @constructor
* @param {*} object
* @param {string=} objectGroupName
* @param {boolean=} doNotBind
* @param {boolean=} forceValueType
* @param {boolean=} generatePreview
* @param {?Array.<string>=} columnNames
* @param {boolean=} isTable
* @param {boolean=} skipEntriesPreview
* @param {*=} customObjectConfig
*/
InjectedScript.RemoteObject = function(object, objectGroupName, doNotBind, forceValueType, generatePreview, columnNames, isTable, skipEntriesPreview, customObjectConfig)
{
this.type = typeof object;
if (this.type === "undefined" && injectedScript._isHTMLAllCollection(object))
this.type = "object";
if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) {
// We don't send undefined values over JSON.
if (this.type !== "undefined")
this.value = object;
// Null object is object with 'null' subtype.
if (object === null)
this.subtype = "null";
// Provide user-friendly number values.
if (this.type === "number") {
this.description = toStringDescription(object);
switch (this.description) {
case "NaN":
case "Infinity":
case "-Infinity":
case "-0":
delete this.value;
this.unserializableValue = this.description;
break;
}
}
return;
}
if (injectedScript._shouldPassByValue(object)) {
this.value = object;
this.subtype = injectedScript._subtype(object);
this.description = injectedScript._describeIncludingPrimitives(object);
return;
}
object = /** @type {!Object} */ (object);
if (!doNotBind)
this.objectId = injectedScript._bind(object, objectGroupName);
var subtype = injectedScript._subtype(object);
if (subtype)
this.subtype = subtype;
var className = InjectedScriptHost.internalConstructorName(object);
if (className)
this.className = className;
this.description = injectedScript._describe(object);
if (generatePreview && this.type === "object") {
if (this.subtype === "proxy")
this.preview = this._generatePreview(InjectedScriptHost.proxyTargetValue(object), undefined, columnNames, isTable, skipEntriesPreview);
else if (this.subtype !== "node")
this.preview = this._generatePreview(object, undefined, columnNames, isTable, skipEntriesPreview);
}
if (injectedScript._customObjectFormatterEnabled) {
var customPreview = this._customPreview(object, objectGroupName, customObjectConfig);
if (customPreview)
this.customPreview = customPreview;
}
}
InjectedScript.RemoteObject.prototype = {
/**
* @param {*} object
* @param {string=} objectGroupName
* @param {*=} customObjectConfig
* @return {?RuntimeAgent.CustomPreview}
*/
_customPreview: function(object, objectGroupName, customObjectConfig)
{
/**
* @param {!Error} error
*/
function logError(error)
{
// We use user code to generate custom output for object, we can use user code for reporting error too.
Promise.resolve().then(/* suppressBlacklist */ inspectedGlobalObject.console.error.bind(inspectedGlobalObject.console, "Custom Formatter Failed: " + error.message));
}
/**
* @param {*} object
* @param {*=} customObjectConfig
* @return {*}
*/
function wrap(object, customObjectConfig)
{
return injectedScript._wrapObject(object, objectGroupName, false, false, null, false, false, customObjectConfig);
}
try {
var formatters = inspectedGlobalObject["devtoolsFormatters"];
if (!formatters || !isArrayLike(formatters))
return null;
for (var i = 0; i < formatters.length; ++i) {
try {
var formatted = formatters[i].header(object, customObjectConfig);
if (!formatted)
continue;
var hasBody = formatters[i].hasBody(object, customObjectConfig);
injectedScript._substituteObjectTagsInCustomPreview(objectGroupName, formatted);
var formatterObjectId = injectedScript._bind(formatters[i], objectGroupName);
var bindRemoteObjectFunctionId = injectedScript._bind(wrap, objectGroupName);
var result = {header: JSON.stringify(formatted), hasBody: !!hasBody, formatterObjectId: formatterObjectId, bindRemoteObjectFunctionId: bindRemoteObjectFunctionId};
if (customObjectConfig)
result["configObjectId"] = injectedScript._bind(customObjectConfig, objectGroupName);
return result;
} catch (e) {
logError(e);
}
}
} catch (e) {
logError(e);
}
return null;
},
/**
* @return {!RuntimeAgent.ObjectPreview} preview
*/
_createEmptyPreview: function()
{
var preview = {
type: /** @type {!RuntimeAgent.ObjectPreviewType.<string>} */ (this.type),
description: this.description || toStringDescription(this.value),
overflow: false,
properties: [],
__proto__: null
};
InjectedScriptHost.nullifyPrototype(preview.properties);
if (this.subtype)
preview.subtype = /** @type {!RuntimeAgent.ObjectPreviewSubtype.<string>} */ (this.subtype);
return preview;
},
/**
* @param {!Object} object
* @param {?Array.<string>=} firstLevelKeys
* @param {?Array.<string>=} secondLevelKeys
* @param {boolean=} isTable
* @param {boolean=} skipEntriesPreview
* @return {!RuntimeAgent.ObjectPreview} preview
*/
_generatePreview: function(object, firstLevelKeys, secondLevelKeys, isTable, skipEntriesPreview)
{
var preview = this._createEmptyPreview();
var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0;
var propertiesThreshold = {
properties: isTable ? 1000 : max(5, firstLevelKeysCount),
indexes: isTable ? 1000 : max(100, firstLevelKeysCount),
__proto__: null
};
var subtype = this.subtype;
var primitiveString;
try {
var descriptors = [];
InjectedScriptHost.nullifyPrototype(descriptors);
// Add internal properties to preview.
var rawInternalProperties = InjectedScriptHost.getInternalProperties(object) || [];
var internalProperties = [];
InjectedScriptHost.nullifyPrototype(rawInternalProperties);
InjectedScriptHost.nullifyPrototype(internalProperties);
var entries = null;
for (var i = 0; i < rawInternalProperties.length; i += 2) {
if (rawInternalProperties[i] === "[[Entries]]") {
entries = /** @type {!Array<*>} */(rawInternalProperties[i + 1]);
continue;
}
if (rawInternalProperties[i] === "[[PrimitiveValue]]" && typeof rawInternalProperties[i + 1] === 'string')
primitiveString = rawInternalProperties[i + 1];
var internalPropertyDescriptor = {
name: rawInternalProperties[i],
value: rawInternalProperties[i + 1],
isOwn: true,
enumerable: true,
__proto__: null
};
push(descriptors, internalPropertyDescriptor);
}
var naturalDescriptors = injectedScript._propertyDescriptors(object, addPropertyIfNeeded, false /* ownProperties */, undefined /* accessorPropertiesOnly */, firstLevelKeys);
for (var i = 0; i < naturalDescriptors.length; i++)
push(descriptors, naturalDescriptors[i]);
this._appendPropertyPreviewDescriptors(preview, descriptors, secondLevelKeys, isTable);
if (subtype === "map" || subtype === "set" || subtype === "weakmap" || subtype === "weakset" || subtype === "iterator")
this._appendEntriesPreview(entries, preview, skipEntriesPreview);
} catch (e) {}
return preview;
/**
* @param {!Array<!Object>} descriptors
* @param {!Object} descriptor
* @return {boolean}
*/
function addPropertyIfNeeded(descriptors, descriptor) {
if (descriptor.wasThrown)
return true;
// Ignore __proto__ property.
if (descriptor.name === "__proto__")
return true;
// Ignore length property of array.
if ((subtype === "array" || subtype === "typedarray") && descriptor.name === "length")
return true;
// Ignore size property of map, set.
if ((subtype === "map" || subtype === "set") && descriptor.name === "size")
return true;
// Ignore ArrayBuffer previews
if (subtype === 'arraybuffer' && (descriptor.name === "[[Int8Array]]" || descriptor.name === "[[Uint8Array]]" || descriptor.name === "[[Int16Array]]" || descriptor.name === "[[Int32Array]]"))
return true;
// Never preview prototype properties.
if (!descriptor.isOwn)
return true;
// Ignore computed properties unless they have getters.
if (!("value" in descriptor) && !descriptor.get)
return true;
// Ignore index properties when there is a primitive string.
if (primitiveString && primitiveString[descriptor.name] === descriptor.value)
return true;
if (toString(descriptor.name >>> 0) === descriptor.name)
propertiesThreshold.indexes--;
else
propertiesThreshold.properties--;
var canContinue = propertiesThreshold.indexes >= 0 && propertiesThreshold.properties >= 0;
if (!canContinue) {
preview.overflow = true;
return false;
}
push(descriptors, descriptor);
return true;
}
},
/**
* @param {!RuntimeAgent.ObjectPreview} preview
* @param {!Array.<*>|!Iterable.<*>} descriptors
* @param {?Array.<string>=} secondLevelKeys
* @param {boolean=} isTable
*/
_appendPropertyPreviewDescriptors: function(preview, descriptors, secondLevelKeys, isTable)
{
for (var i = 0; i < descriptors.length; ++i) {
var descriptor = descriptors[i];
var name = descriptor.name;
var value = descriptor.value;
var type = typeof value;
// Special-case HTMLAll.
if (type === "undefined" && injectedScript._isHTMLAllCollection(value))
type = "object";
// Ignore computed properties unless they have getters.
if (descriptor.get && !("value" in descriptor)) {
push(preview.properties, { name: name, type: "accessor", __proto__: null });
continue;
}
// Render own properties.
if (value === null) {
push(preview.properties, { name: name, type: "object", subtype: "null", value: "null", __proto__: null });
continue;
}
var maxLength = 100;
if (InjectedScript.primitiveTypes[type]) {
if (type === "string" && value.length > maxLength)
value = this._abbreviateString(value, maxLength, true);
push(preview.properties, { name: name, type: type, value: toStringDescription(value), __proto__: null });
continue;
}
var property = { name: name, type: type, __proto__: null };
var subtype = injectedScript._subtype(value);
if (subtype)
property.subtype = subtype;
if (secondLevelKeys === null || secondLevelKeys) {
var subPreview = this._generatePreview(value, secondLevelKeys || undefined, undefined, isTable);
property.valuePreview = subPreview;
if (subPreview.overflow)
preview.overflow = true;
} else {
var description = "";
if (type !== "function")
description = this._abbreviateString(/** @type {string} */ (injectedScript._describe(value)), maxLength, subtype === "regexp");
property.value = description;
}
push(preview.properties, property);
}
},
/**
* @param {?Array<*>} entries
* @param {!RuntimeAgent.ObjectPreview} preview
* @param {boolean=} skipEntriesPreview
*/
_appendEntriesPreview: function(entries, preview, skipEntriesPreview)
{
if (!entries)
return;
if (skipEntriesPreview) {
if (entries.length)
preview.overflow = true;
return;
}
preview.entries = [];
InjectedScriptHost.nullifyPrototype(preview.entries);
var entriesThreshold = 5;
for (var i = 0; i < entries.length; ++i) {
if (preview.entries.length >= entriesThreshold) {
preview.overflow = true;
break;
}
var entry = entries[i];
InjectedScriptHost.nullifyPrototype(entry);
var previewEntry = {
value: generateValuePreview(entry.value),
__proto__: null
};
if ("key" in entry)
previewEntry.key = generateValuePreview(entry.key);
push(preview.entries, previewEntry);
}
/**
* @param {*} value
* @return {!RuntimeAgent.ObjectPreview}
*/
function generateValuePreview(value)
{
var remoteObject = new InjectedScript.RemoteObject(value, undefined, true, undefined, true, undefined, undefined, true);
var valuePreview = remoteObject.preview || remoteObject._createEmptyPreview();
return valuePreview;
}
},
/**
* @param {string} string
* @param {number} maxLength
* @param {boolean=} middle
* @return {string}
*/
_abbreviateString: function(string, maxLength, middle)
{
if (string.length <= maxLength)
return string;
if (middle) {
var leftHalf = maxLength >> 1;
var rightHalf = maxLength - leftHalf - 1;
return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf);
}
return string.substr(0, maxLength) + "\u2026";
},
__proto__: null
}
return injectedScript;
})