| /* | |
| * 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 + ">"; | |
| } | |
| } | |
| /** | |
| * TODO(luoe): remove type-check suppression once bigint is supported by closure. | |
| * @suppress {checkTypes} | |
| * @param {*} obj | |
| * @return {string} | |
| */ | |
| function toStringDescription(obj) | |
| { | |
| if (typeof obj === "number" && obj === 0 && 1 / obj < 0) | |
| return "-0"; // Negative zero. | |
| if (typeof obj === "bigint") | |
| return toString(obj) + "n"; | |
| 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, | |
| "bigint": 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; | |
| // Special case for Symbol.prototype.description where the receiver of the getter is not an actual object. | |
| // Should only occur for nested previews. | |
| var isSymbolDescription = isSymbol(object) && name === 'description'; | |
| if (isSymbolDescription || (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") { | |
| // We should warmup blink dom binding before calling anything, | |
| // see (crbug.com/827585) for details. | |
| InjectedScriptHost.getOwnPropertyDescriptor(/** @type {!Object} */(obj), "nodeName"); | |
| 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; | |
| } | |
| } | |
| // The "n" suffix of bigint primitives are not JSON serializable. | |
| if (this.type === "bigint") { | |
| delete this.value; | |
| this.description = toStringDescription(object); | |
| this.unserializableValue = this.description; | |
| } | |
| 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 | |
| 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]) { | |
| var valueString = type === "string" ? value : toStringDescription(value); | |
| if (valueString.length > maxLength) | |
| valueString = this._abbreviateString(valueString, maxLength, true); | |
| push(preview.properties, { name: name, type: type, value: valueString, __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; | |
| }) |