blob: bb715379d3a47dcf75f7bbd7544dd9058fd72dd0 [file] [log] [blame]
/*
* Copyright (C) 2007, 2014-2015 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 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.
*/
//# sourceURL=__InjectedScript_InjectedScriptSource.js
(function (InjectedScriptHost, inspectedGlobalObject, injectedScriptId) {
// FIXME: <https://webkit.org/b/152294> Web Inspector: Parse InjectedScriptSource as a built-in to get guaranteed non-user-overriden built-ins
var Object = {}.constructor;
function toString(obj)
{
return String(obj);
}
function toStringDescription(obj)
{
if (obj === 0 && 1 / obj < 0)
return "-0";
return toString(obj);
}
function isUInt32(obj)
{
if (typeof obj === "number")
return obj >>> 0 === obj && (obj > 0 || 1 / obj > 0);
return "" + (obj >>> 0) === obj;
}
function endsWith(str, suffix)
{
var position = str.length - suffix.length;
if (position < 0)
return false;
return str.indexOf(suffix, position) === position;
}
function isSymbol(obj)
{
return typeof obj === "symbol";
}
var InjectedScript = function()
{
this._lastBoundObjectId = 1;
this._idToWrappedObject = {};
this._idToObjectGroupName = {};
this._objectGroups = {};
this._modules = {};
this._nextSavedResultIndex = 1;
this._savedResults = [];
}
InjectedScript.primitiveTypes = {
undefined: true,
boolean: true,
number: true,
string: true,
}
InjectedScript.CollectionMode = {
OwnProperties: 1 << 0, // own properties.
NativeGetterProperties: 1 << 1, // native getter properties in the prototype chain.
AllProperties: 1 << 2, // all properties in the prototype chain.
}
InjectedScript.prototype = {
isPrimitiveValue: function(object)
{
// FIXME(33716): typeof document.all is always 'undefined'.
return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object);
},
previewValue: function(value)
{
return InjectedScript.RemoteObject.createObjectPreviewForValue(value, true);
},
functionDetails: function(func, previewOnly)
{
var details = InjectedScriptHost.functionDetails(func);
if (!details)
return "Cannot resolve function details.";
// FIXME: provide function scope data in "scopesRaw" property when JSC supports it.
// <https://webkit.org/b/87192> [JSC] expose function (closure) inner context to debugger
if ("rawScopes" in details) {
if (previewOnly)
delete details.rawScopes;
else {
var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id];
var rawScopes = details.rawScopes;
var scopes = [];
delete details.rawScopes;
for (var i = 0; i < rawScopes.length; i++)
scopes.push(InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName));
details.scopeChain = scopes;
}
}
return details;
},
wrapObject: function(object, groupName, canAccessInspectedGlobalObject, generatePreview)
{
if (canAccessInspectedGlobalObject)
return this._wrapObject(object, groupName, false, generatePreview);
return this._fallbackWrapper(object);
},
setExceptionValue: function(value)
{
this._exceptionValue = value;
},
clearExceptionValue: function()
{
delete this._exceptionValue;
},
_fallbackWrapper: function(object)
{
var result = {};
result.type = typeof object;
if (this.isPrimitiveValue(object))
result.value = object;
else
result.description = toString(object);
return result;
},
wrapTable: function(canAccessInspectedGlobalObject, table, columns)
{
if (!canAccessInspectedGlobalObject)
return this._fallbackWrapper(table);
// FIXME: Currently columns are ignored. Instead, the frontend filters all
// properties based on the provided column names and in the provided order.
// Should we filter here too?
var columnNames = null;
if (typeof columns === "string")
columns = [columns];
if (InjectedScriptHost.subtype(columns) === "array") {
columnNames = [];
for (var i = 0; i < columns.length; ++i)
columnNames.push(toString(columns[i]));
}
return this._wrapObject(table, "console", false, true, columnNames);
},
inspectObject: function(object)
{
if (this._commandLineAPIImpl)
this._commandLineAPIImpl.inspect(object);
},
_wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames)
{
try {
return new InjectedScript.RemoteObject(object, objectGroupName, forceValueType, generatePreview, columnNames);
} catch (e) {
try {
var description = injectedScript._describe(e);
} catch (ex) {
var description = "<failed to convert exception to string>";
}
return new InjectedScript.RemoteObject(description);
}
},
_bind: function(object, objectGroupName)
{
var id = this._lastBoundObjectId++;
this._idToWrappedObject[id] = object;
var objectId = "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}";
if (objectGroupName) {
var group = this._objectGroups[objectGroupName];
if (!group) {
group = [];
this._objectGroups[objectGroupName] = group;
}
group.push(id);
this._idToObjectGroupName[id] = objectGroupName;
}
return objectId;
},
_parseObjectId: function(objectId)
{
return InjectedScriptHost.evaluate("(" + objectId + ")");
},
releaseObjectGroup: function(objectGroupName)
{
if (objectGroupName === "console") {
delete this._lastResult;
this._nextSavedResultIndex = 1;
this._savedResults = [];
}
var group = this._objectGroups[objectGroupName];
if (!group)
return;
for (var i = 0; i < group.length; i++)
this._releaseObject(group[i]);
delete this._objectGroups[objectGroupName];
},
dispatch: function(methodName, args)
{
var argsArray = InjectedScriptHost.evaluate("(" + args + ")");
var result = this[methodName].apply(this, argsArray);
if (typeof result === "undefined") {
if (inspectedGlobalObject.console)
inspectedGlobalObject.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
result = null;
}
return result;
},
_getProperties: function(objectId, collectionMode, generatePreview, nativeGettersAsValues)
{
var parsedObjectId = this._parseObjectId(objectId);
var object = this._objectForId(parsedObjectId);
var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
if (!this._isDefined(object))
return false;
if (isSymbol(object))
return false;
var descriptors = this._propertyDescriptors(object, collectionMode, nativeGettersAsValues);
// Go over properties, wrap object values.
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;
},
getProperties: function(objectId, ownProperties, generatePreview)
{
var nativeGettersAsValues = false;
var collectionMode = ownProperties ? InjectedScript.CollectionMode.OwnProperties : InjectedScript.CollectionMode.AllProperties;
return this._getProperties(objectId, collectionMode, generatePreview, nativeGettersAsValues);
},
getDisplayableProperties: function(objectId, generatePreview)
{
var nativeGettersAsValues = true;
var collectionMode = InjectedScript.CollectionMode.OwnProperties | InjectedScript.CollectionMode.NativeGetterProperties;
return this._getProperties(objectId, collectionMode, generatePreview, nativeGettersAsValues);
},
getInternalProperties: function(objectId, generatePreview)
{
var parsedObjectId = this._parseObjectId(objectId);
var object = this._objectForId(parsedObjectId);
var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
if (!this._isDefined(object))
return false;
if (isSymbol(object))
return false;
var descriptors = this._internalPropertyDescriptors(object);
if (!descriptors)
return [];
// Go over properties, wrap object values.
for (var i = 0; i < descriptors.length; ++i) {
var descriptor = descriptors[i];
if ("value" in descriptor)
descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview);
}
return descriptors;
},
getCollectionEntries: function(objectId, objectGroupName, startIndex, numberToFetch)
{
var parsedObjectId = this._parseObjectId(objectId);
var object = this._objectForId(parsedObjectId);
var objectGroupName = objectGroupName || this._idToObjectGroupName[parsedObjectId.id];
if (!this._isDefined(object))
return;
if (typeof object !== "object")
return;
var entries = this._entries(object, InjectedScriptHost.subtype(object), startIndex, numberToFetch);
return entries.map(function(entry) {
entry.value = injectedScript._wrapObject(entry.value, objectGroupName, false, true);
if ("key" in entry)
entry.key = injectedScript._wrapObject(entry.key, objectGroupName, false, true);
return entry;
});
},
saveResult: function(callArgumentJSON)
{
this._savedResultIndex = 0;
try {
var callArgument = InjectedScriptHost.evaluate("(" + callArgumentJSON + ")");
var value = this._resolveCallArgument(callArgument);
this._saveResult(value);
} catch (e) {}
return this._savedResultIndex;
},
getFunctionDetails: function(functionId)
{
var parsedFunctionId = this._parseObjectId(functionId);
var func = this._objectForId(parsedFunctionId);
if (typeof func !== "function")
return "Cannot resolve function by id.";
return injectedScript.functionDetails(func);
},
releaseObject: function(objectId)
{
var parsedObjectId = this._parseObjectId(objectId);
this._releaseObject(parsedObjectId.id);
},
_releaseObject: function(id)
{
delete this._idToWrappedObject[id];
delete this._idToObjectGroupName[id];
},
evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
{
return this._evaluateAndWrap(InjectedScriptHost.evaluateWithScopeExtension, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview, saveResult);
},
callFunctionOn: function(objectId, expression, args, returnByValue, generatePreview)
{
var parsedObjectId = this._parseObjectId(objectId);
var object = this._objectForId(parsedObjectId);
if (!this._isDefined(object))
return "Could not find object with given id";
if (args) {
var resolvedArgs = [];
var callArgs = InjectedScriptHost.evaluate(args);
for (var i = 0; i < callArgs.length; ++i) {
try {
resolvedArgs[i] = this._resolveCallArgument(callArgs[i]);
} catch (e) {
return String(e);
}
}
}
try {
var objectGroup = this._idToObjectGroupName[parsedObjectId.id];
var func = InjectedScriptHost.evaluate("(" + expression + ")");
if (typeof func !== "function")
return "Given expression does not evaluate to a function";
return {
wasThrown: false,
result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue, generatePreview)
};
} catch (e) {
return this._createThrownValue(e, objectGroup);
}
},
_resolveCallArgument: function(callArgumentJSON)
{
if ("value" in callArgumentJSON)
return callArgumentJSON.value;
var objectId = callArgumentJSON.objectId;
if (objectId) {
var parsedArgId = this._parseObjectId(objectId);
if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId)
throw "Arguments should belong to the same JavaScript world as the target object.";
var resolvedArg = this._objectForId(parsedArgId);
if (!this._isDefined(resolvedArg))
throw "Could not find object with given id";
return resolvedArg;
}
return undefined;
},
_evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
{
try {
this._savedResultIndex = 0;
var returnObject = {
wasThrown: false,
result: this._wrapObject(this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult), objectGroup, returnByValue, generatePreview)
};
if (saveResult && this._savedResultIndex)
returnObject.savedResultIndex = this._savedResultIndex;
return returnObject;
} catch (e) {
return this._createThrownValue(e, objectGroup);
}
},
_createThrownValue: function(value, objectGroup)
{
var remoteObject = this._wrapObject(value, objectGroup);
try {
remoteObject.description = toStringDescription(value);
} catch (e) {}
return {
wasThrown: true,
result: remoteObject
};
},
_evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult)
{
var commandLineAPI = null;
if (injectCommandLineAPI) {
if (this.CommandLineAPI)
commandLineAPI = new this.CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null);
else
commandLineAPI = new BasicCommandLineAPI(isEvalOnCallFrame ? object : null);
}
var result = evalFunction.call(object, expression, commandLineAPI);
if (saveResult)
this._saveResult(result);
return result;
},
wrapCallFrames: function(callFrame)
{
if (!callFrame)
return false;
var result = [];
var depth = 0;
do {
result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
callFrame = callFrame.caller;
} while (callFrame);
return result;
},
evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
{
var callFrame = this._callFrameForId(topCallFrame, callFrameId);
if (!callFrame)
return "Could not find call frame with given id";
return this._evaluateAndWrap(callFrame.evaluateWithScopeExtension, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview, saveResult);
},
_callFrameForId: function(topCallFrame, callFrameId)
{
var parsedCallFrameId = InjectedScriptHost.evaluate("(" + callFrameId + ")");
var ordinal = parsedCallFrameId["ordinal"];
var callFrame = topCallFrame;
while (--ordinal >= 0 && callFrame)
callFrame = callFrame.caller;
return callFrame;
},
_objectForId: function(objectId)
{
return this._idToWrappedObject[objectId.id];
},
findObjectById: function(objectId)
{
var parsedObjectId = this._parseObjectId(objectId);
return this._objectForId(parsedObjectId);
},
module: function(name)
{
return this._modules[name];
},
injectModule: function(name, source, host)
{
delete this._modules[name];
var moduleFunction = InjectedScriptHost.evaluate("(" + source + ")");
if (typeof moduleFunction !== "function") {
if (inspectedGlobalObject.console)
inspectedGlobalObject.console.error("Web Inspector error: A function was expected for module %s evaluation", name);
return null;
}
var module = moduleFunction.call(inspectedGlobalObject, InjectedScriptHost, inspectedGlobalObject, injectedScriptId, this, host);
this._modules[name] = module;
return module;
},
_internalPropertyDescriptors: function(object, completeDescriptor)
{
var internalProperties = InjectedScriptHost.getInternalProperties(object);
if (!internalProperties)
return null;
var descriptors = [];
for (var i = 0; i < internalProperties.length; i++) {
var property = internalProperties[i];
var descriptor = {name: property.name, value: property.value};
if (completeDescriptor) {
descriptor.writable = false;
descriptor.configurable = false;
descriptor.enumerable = false;
descriptor.isOwn = true;
}
descriptors.push(descriptor);
}
return descriptors;
},
_propertyDescriptors: function(object, collectionMode, nativeGettersAsValues)
{
var descriptors = [];
var nameProcessed = new Set;
function createFakeValueDescriptor(name, symbol, descriptor, isOwnProperty, possibleNativeBindingGetter)
{
try {
var descriptor = {name, value: object[name], writable: descriptor.writable || false, configurable: descriptor.configurable || false, enumerable: descriptor.enumerable || false};
if (possibleNativeBindingGetter)
descriptor.nativeGetter = true;
if (isOwnProperty)
descriptor.isOwn = true;
if (symbol)
descriptor.symbol = symbol;
return descriptor;
} catch (e) {
var errorDescriptor = {name, value: e, wasThrown: true};
if (isOwnProperty)
errorDescriptor.isOwn = true;
if (symbol)
errorDescriptor.symbol = symbol;
return errorDescriptor;
}
}
function processDescriptor(descriptor, isOwnProperty, possibleNativeBindingGetter)
{
// All properties.
if (collectionMode & InjectedScript.CollectionMode.AllProperties) {
descriptors.push(descriptor);
return;
}
// Own properties.
if (collectionMode & InjectedScript.CollectionMode.OwnProperties && isOwnProperty) {
descriptors.push(descriptor);
return;
}
// Native Getter properties.
if (collectionMode & InjectedScript.CollectionMode.NativeGetterProperties) {
if (possibleNativeBindingGetter) {
descriptors.push(descriptor);
return;
}
}
}
function processProperties(o, properties, isOwnProperty)
{
for (var i = 0; i < properties.length; ++i) {
var property = properties[i];
if (nameProcessed.has(property) || property === "__proto__")
continue;
nameProcessed.add(property);
var name = toString(property);
var symbol = isSymbol(property) ? property : null;
var descriptor = Object.getOwnPropertyDescriptor(o, property);
if (!descriptor) {
// FIXME: Bad descriptor. Can we get here?
// Fall back to very restrictive settings.
var fakeDescriptor = createFakeValueDescriptor(name, symbol, {writable: false, configurable: false, enumerable: false}, isOwnProperty);
processDescriptor(fakeDescriptor, isOwnProperty);
continue;
}
if (nativeGettersAsValues) {
if (endsWith(String(descriptor.get), "[native code]\n}") || (!descriptor.get && descriptor.hasOwnProperty("get") && !descriptor.set && descriptor.hasOwnProperty("set"))) {
// Developers may create such a descriptor, so we should be resilient:
// var x = {}; Object.defineProperty(x, "p", {get:undefined}); Object.getOwnPropertyDescriptor(x, "p")
var fakeDescriptor = createFakeValueDescriptor(name, symbol, descriptor, isOwnProperty, true);
processDescriptor(fakeDescriptor, isOwnProperty, true);
continue;
}
}
descriptor.name = name;
if (isOwnProperty)
descriptor.isOwn = true;
if (symbol)
descriptor.symbol = symbol;
processDescriptor(descriptor, isOwnProperty);
}
}
function arrayIndexPropertyNames(o, length)
{
var array = [];
for (var i = 0; i < length; ++i) {
if (i in o)
array.push("" + i);
}
return array;
}
// FIXME: <https://webkit.org/b/143589> Web Inspector: Better handling for large collections in Object Trees
// For array types with a large length we attempt to skip getOwnPropertyNames and instead just sublist of indexes.
var isArrayLike = false;
try {
isArrayLike = injectedScript._subtype(object) === "array" && isFinite(object.length) && object.length > 0;
} catch(e) {}
for (var o = object; this._isDefined(o); o = o.__proto__) {
var isOwnProperty = o === object;
if (isArrayLike && isOwnProperty)
processProperties(o, arrayIndexPropertyNames(o, Math.min(object.length, 100)), isOwnProperty);
else {
processProperties(o, Object.getOwnPropertyNames(o), isOwnProperty);
if (Object.getOwnPropertySymbols)
processProperties(o, Object.getOwnPropertySymbols(o), isOwnProperty);
}
if (collectionMode === InjectedScript.CollectionMode.OwnProperties)
break;
}
// Always include __proto__ at the end.
try {
if (object.__proto__)
descriptors.push({name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true});
} catch (e) {}
return descriptors;
},
_isDefined: function(object)
{
return !!object || this._isHTMLAllCollection(object);
},
_isHTMLAllCollection: function(object)
{
// document.all is reported as undefined, but we still want to process it.
return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object);
},
_subtype: function(obj)
{
if (obj === null)
return "null";
if (this.isPrimitiveValue(obj) || isSymbol(obj))
return null;
if (this._isHTMLAllCollection(obj))
return "array";
var preciseType = InjectedScriptHost.subtype(obj);
if (preciseType)
return preciseType;
// FireBug's array detection.
try {
if (typeof obj.splice === "function" && isFinite(obj.length))
return "array";
} catch (e) {
}
return null;
},
_classPreview: function(classConstructorValue)
{
return "class " + classConstructorValue.name;
},
_nodePreview: function(node)
{
var isXMLDocument = node.ownerDocument && !!node.ownerDocument.xmlVersion;
var nodeName = isXMLDocument ? node.nodeName : node.nodeName.toLowerCase();
switch (node.nodeType) {
case 1: // Node.ELEMENT_NODE
if (node.id)
return "<" + nodeName + " id=\"" + node.id + "\">";
if (node.classList.length)
return "<" + nodeName + " class=\"" + node.classList.toString().replace(/\s+/, " ") + "\">";
if (nodeName === "input" && node.type)
return "<" + nodeName + " type=\"" + node.type + "\">";
return "<" + nodeName + ">";
case 3: // Node.TEXT_NODE
return nodeName + " \"" + node.nodeValue + "\"";
case 8: // Node.COMMENT_NODE
return "<!--" + node.nodeValue + "-->";
case 10: // Node.DOCUMENT_TYPE_NODE
return "<!DOCTYPE " + nodeName + ">";
default:
return nodeName;
}
},
_describe: function(obj)
{
if (this.isPrimitiveValue(obj))
return null;
if (isSymbol(obj))
return toString(obj);
var subtype = this._subtype(obj);
if (subtype === "regexp")
return toString(obj);
if (subtype === "date")
return toString(obj);
if (subtype === "error")
return toString(obj);
if (subtype === "node")
return this._nodePreview(obj);
var className = InjectedScriptHost.internalConstructorName(obj);
if (subtype === "array")
return className;
if (subtype === "iterator" && Symbol.toStringTag in obj)
return obj[Symbol.toStringTag];
// NodeList in JSC is a function, check for array prior to this.
if (typeof obj === "function")
return toString(obj);
// If Object, try for a better name from the constructor.
if (className === "Object") {
var constructorName = obj.constructor && obj.constructor.name;
if (constructorName)
return constructorName;
}
return className;
},
_getSetEntries: function(object, skip, numberToFetch)
{
var entries = [];
for (var value of object) {
if (skip > 0) {
skip--;
continue;
}
entries.push({value});
if (numberToFetch && entries.length === numberToFetch)
break;
}
return entries;
},
_getMapEntries: function(object, skip, numberToFetch)
{
var entries = [];
for (var [key, value] of object) {
if (skip > 0) {
skip--;
continue;
}
entries.push({key, value});
if (numberToFetch && entries.length === numberToFetch)
break;
}
return entries;
},
_getWeakMapEntries: function(object, numberToFetch)
{
return InjectedScriptHost.weakMapEntries(object, numberToFetch);
},
_getWeakSetEntries: function(object, numberToFetch)
{
return InjectedScriptHost.weakSetEntries(object, numberToFetch);
},
_getIteratorEntries: function(object, numberToFetch)
{
return InjectedScriptHost.iteratorEntries(object, numberToFetch);
},
_entries: function(object, subtype, startIndex, numberToFetch)
{
if (subtype === "set")
return this._getSetEntries(object, startIndex, numberToFetch);
if (subtype === "map")
return this._getMapEntries(object, startIndex, numberToFetch);
if (subtype === "weakmap")
return this._getWeakMapEntries(object, numberToFetch);
if (subtype === "weakset")
return this._getWeakSetEntries(object, numberToFetch);
if (subtype === "iterator")
return this._getIteratorEntries(object, numberToFetch);
throw "unexpected type";
},
_saveResult: function(result)
{
this._lastResult = result;
if (result === undefined || result === null)
return;
var existingIndex = this._savedResults.indexOf(result);
if (existingIndex !== -1) {
this._savedResultIndex = existingIndex;
return;
}
this._savedResultIndex = this._nextSavedResultIndex;
this._savedResults[this._nextSavedResultIndex++] = result;
// $n is limited from $1-$99. $0 is special.
if (this._nextSavedResultIndex >= 100)
this._nextSavedResultIndex = 1;
},
_savedResult: function(index)
{
return this._savedResults[index];
}
}
var injectedScript = new InjectedScript;
InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType, generatePreview, columnNames)
{
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);
return;
}
this.objectId = injectedScript._bind(object, objectGroupName);
var subtype = injectedScript._subtype(object);
if (subtype)
this.subtype = subtype;
this.className = InjectedScriptHost.internalConstructorName(object);
this.description = injectedScript._describe(object);
if (subtype === "array")
this.size = typeof object.length === "number" ? object.length : 0;
else if (subtype === "set" || subtype === "map")
this.size = object.size;
else if (subtype === "weakmap")
this.size = InjectedScriptHost.weakMapSize(object);
else if (subtype === "weakset")
this.size = InjectedScriptHost.weakSetSize(object);
else if (subtype === "class") {
this.classPrototype = injectedScript._wrapObject(object.prototype, objectGroupName);
this.className = object.name;
}
if (generatePreview && this.type === "object")
this.preview = this._generatePreview(object, undefined, columnNames);
};
InjectedScript.RemoteObject.createObjectPreviewForValue = function(value, generatePreview)
{
var remoteObject = new InjectedScript.RemoteObject(value, undefined, false, generatePreview, undefined);
if (remoteObject.objectId)
injectedScript.releaseObject(remoteObject.objectId);
if (remoteObject.classPrototype && remoteObject.classPrototype.objectId)
injectedScript.releaseObject(remoteObject.classPrototype.objectId);
return remoteObject.preview || remoteObject._emptyPreview();
};
InjectedScript.RemoteObject.prototype = {
_initialPreview: function()
{
var preview = {
type: this.type,
description: this.description || toString(this.value),
lossless: true,
};
if (this.subtype) {
preview.subtype = this.subtype;
if (this.subtype !== "null") {
preview.overflow = false;
preview.properties = [];
}
}
if ("size" in this)
preview.size = this.size;
return preview;
},
_emptyPreview: function()
{
var preview = this._initialPreview();
if (this.subtype === "map" || this.subtype === "set" || this.subtype === "weakmap" || this.subtype === "weakset" || this.subtype === "iterator") {
if (this.size) {
preview.entries = [];
preview.lossless = false;
preview.overflow = true;
}
}
return preview;
},
_generatePreview: function(object, firstLevelKeys, secondLevelKeys)
{
var preview = this._initialPreview();
var isTableRowsRequest = secondLevelKeys === null || secondLevelKeys;
var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0;
var propertiesThreshold = {
properties: isTableRowsRequest ? 1000 : Math.max(5, firstLevelKeysCount),
indexes: isTableRowsRequest ? 1000 : Math.max(10, firstLevelKeysCount)
};
try {
// Maps, Sets, and Iterators have entries.
if (this.subtype === "map" || this.subtype === "set" || this.subtype === "weakmap" || this.subtype === "weakset" || this.subtype === "iterator")
this._appendEntryPreviews(object, preview);
preview.properties = [];
// Internal Properties.
var internalPropertyDescriptors = injectedScript._internalPropertyDescriptors(object, true);
if (internalPropertyDescriptors) {
this._appendPropertyPreviews(object, preview, internalPropertyDescriptors, true, propertiesThreshold, firstLevelKeys, secondLevelKeys);
if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
return preview;
}
if (preview.entries)
return preview;
// Properties.
var nativeGettersAsValues = true;
var descriptors = injectedScript._propertyDescriptors(object, InjectedScript.CollectionMode.AllProperties, nativeGettersAsValues);
this._appendPropertyPreviews(object, preview, descriptors, false, propertiesThreshold, firstLevelKeys, secondLevelKeys);
if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
return preview;
} catch (e) {
preview.lossless = false;
}
return preview;
},
_appendPropertyPreviews: function(object, preview, descriptors, internal, propertiesThreshold, firstLevelKeys, secondLevelKeys)
{
for (var descriptor of descriptors) {
// Seen enough.
if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
break;
// Error in descriptor.
if (descriptor.wasThrown) {
preview.lossless = false;
continue;
}
// Do not show "__proto__" in preview.
var name = descriptor.name;
if (name === "__proto__") {
// Non basic __proto__ objects may have interesting, non-enumerable, methods to show.
if (descriptor.value && descriptor.value.constructor
&& descriptor.value.constructor !== Object
&& descriptor.value.constructor !== Array
&& descriptor.value.constructor !== RegExp)
preview.lossless = false;
continue;
}
// For arrays, only allow indexes.
if (this.subtype === "array" && !isUInt32(name))
continue;
// Do not show non-enumerable non-own properties.
// Special case to allow array indexes that may be on the prototype.
// Special case to allow native getters on non-RegExp objects.
if (!descriptor.enumerable && !descriptor.isOwn && !(this.subtype === "array" || (this.subtype !== "regexp" && descriptor.nativeGetter)))
continue;
// If we have a filter, only show properties in the filter.
// FIXME: Currently these filters do nothing on the backend.
if (firstLevelKeys && !firstLevelKeys.includes(name))
continue;
// Getter/setter.
if (!("value" in descriptor)) {
preview.lossless = false;
this._appendPropertyPreview(preview, internal, {name, type: "accessor"}, propertiesThreshold);
continue;
}
// Null value.
var value = descriptor.value;
if (value === null) {
this._appendPropertyPreview(preview, internal, {name, type: "object", subtype: "null", value: "null"}, propertiesThreshold);
continue;
}
// Ignore non-enumerable functions.
var type = typeof value;
if (!descriptor.enumerable && type === "function")
continue;
// Fix type of document.all.
if (type === "undefined" && injectedScript._isHTMLAllCollection(value))
type = "object";
// Primitive.
const maxLength = 100;
if (InjectedScript.primitiveTypes[type]) {
if (type === "string" && value.length > maxLength) {
value = this._abbreviateString(value, maxLength, true);
preview.lossless = false;
}
this._appendPropertyPreview(preview, internal, {name, type, value: toStringDescription(value)}, propertiesThreshold);
continue;
}
// Symbol.
if (isSymbol(value)) {
var symbolString = toString(value);
if (symbolString.length > maxLength) {
symbolString = this._abbreviateString(symbolString, maxLength, true);
preview.lossless = false;
}
this._appendPropertyPreview(preview, internal, {name, type, value: symbolString}, propertiesThreshold);
continue;
}
// Object.
var property = {name, type};
var subtype = injectedScript._subtype(value);
if (subtype)
property.subtype = subtype;
// Second level.
if ((secondLevelKeys === null || secondLevelKeys) || this._isPreviewableObject(value, object)) {
// FIXME: If we want secondLevelKeys filter to continue we would need some refactoring.
var subPreview = InjectedScript.RemoteObject.createObjectPreviewForValue(value, value !== object);
property.valuePreview = subPreview;
if (!subPreview.lossless)
preview.lossless = false;
if (subPreview.overflow)
preview.overflow = true;
} else {
var description = "";
if (type !== "function" || subtype === "class") {
var fullDescription;
if (subtype === "class")
fullDescription = "class " + value.name;
else if (subtype === "node")
fullDescription = injectedScript._nodePreview(value);
else
fullDescription = injectedScript._describe(value);
description = this._abbreviateString(fullDescription, maxLength, subtype === "regexp");
}
property.value = description;
preview.lossless = false;
}
this._appendPropertyPreview(preview, internal, property, propertiesThreshold);
}
},
_appendPropertyPreview: function(preview, internal, property, propertiesThreshold)
{
if (toString(property.name >>> 0) === property.name)
propertiesThreshold.indexes--;
else
propertiesThreshold.properties--;
if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) {
preview.overflow = true;
preview.lossless = false;
return;
}
if (internal)
property.internal = true;
preview.properties.push(property);
},
_appendEntryPreviews: function(object, preview)
{
// Fetch 6, but only return 5, so we can tell if we overflowed.
var entries = injectedScript._entries(object, this.subtype, 0, 6);
if (!entries)
return;
if (entries.length > 5) {
entries.pop();
preview.overflow = true;
preview.lossless = false;
}
function updateMainPreview(subPreview) {
if (!subPreview.lossless)
preview.lossless = false;
}
preview.entries = entries.map(function(entry) {
entry.value = InjectedScript.RemoteObject.createObjectPreviewForValue(entry.value, entry.value !== object);
updateMainPreview(entry.value);
if ("key" in entry) {
entry.key = InjectedScript.RemoteObject.createObjectPreviewForValue(entry.key, entry.key !== object);
updateMainPreview(entry.key);
}
return entry;
}, this);
},
_isPreviewableObject: function(value, object)
{
return this._isPreviewableObjectInternal(value, new Set([object]), 1);
},
_isPreviewableObjectInternal: function(object, knownObjects, depth)
{
// Deep object.
if (depth > 3)
return false;
// Primitive.
if (injectedScript.isPrimitiveValue(object) || isSymbol(object))
return true;
// Null.
if (object === null)
return true;
// Cyclic objects.
if (knownObjects.has(object))
return false;
++depth;
knownObjects.add(object);
// Arrays are simple if they have 5 or less simple objects.
var subtype = injectedScript._subtype(object);
if (subtype === "array") {
var length = object.length;
if (length > 5)
return false;
for (var i = 0; i < length; ++i) {
if (!this._isPreviewableObjectInternal(object[i], knownObjects, depth))
return false;
}
return true;
}
// Not a basic object.
if (object.__proto__ && object.__proto__.__proto__)
return false;
// Objects are simple if they have 3 or less simple properties.
var ownPropertyNames = Object.getOwnPropertyNames(object);
if (ownPropertyNames.length > 3)
return false;
for (var propertyName of ownPropertyNames) {
if (!this._isPreviewableObjectInternal(object[propertyName], knownObjects, depth))
return false;
}
return true;
},
_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";
}
}
InjectedScript.CallFrameProxy = function(ordinal, callFrame)
{
this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}";
this.functionName = callFrame.functionName;
this.location = {scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column};
this.scopeChain = this._wrapScopeChain(callFrame);
this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace", false, true);
this.isTailDeleted = callFrame.isTailDeleted;
}
InjectedScript.CallFrameProxy.prototype = {
_wrapScopeChain: function(callFrame)
{
var scopeChain = callFrame.scopeChain;
var scopeDescriptions = callFrame.scopeDescriptions();
var scopeChainProxy = [];
for (var i = 0; i < scopeChain.length; i++)
scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(scopeChain[i], scopeDescriptions[i], "backtrace");
return scopeChainProxy;
}
}
InjectedScript.CallFrameProxy._scopeTypeNames = {
0: "global", // GLOBAL_SCOPE
1: "with", // WITH_SCOPE
2: "closure", // CLOSURE_SCOPE
3: "catch", // CATCH_SCOPE
4: "functionName", // FUNCTION_NAME_SCOPE
5: "globalLexicalEnvironment", // GLOBAL_LEXICAL_ENVIRONMENT_SCOPE
6: "nestedLexical", // NESTED_LEXICAL_SCOPE
};
InjectedScript.CallFrameProxy._createScopeJson = function(object, {name, type, location}, groupId)
{
var scope = {
object: injectedScript._wrapObject(object, groupId),
type: InjectedScript.CallFrameProxy._scopeTypeNames[type],
};
if (name)
scope.name = name;
if (location)
scope.location = location;
return scope;
}
function bind(func, thisObject, ...outerArgs)
{
return function(...innerArgs) {
return func.apply(thisObject, outerArgs.concat(innerArgs));
};
}
function BasicCommandLineAPI(callFrame)
{
this.$_ = injectedScript._lastResult;
this.$exception = injectedScript._exceptionValue;
// $1-$99
for (let i = 1; i <= injectedScript._savedResults.length; ++i)
this.__defineGetter__("$" + i, bind(injectedScript._savedResult, injectedScript, i));
// Command Line API methods.
for (let method of BasicCommandLineAPI.methods)
this[method.name] = method;
}
BasicCommandLineAPI.methods = [
function dir() { return inspectedGlobalObject.console.dir(...arguments); },
function clear() { return inspectedGlobalObject.console.clear(...arguments); },
function table() { return inspectedGlobalObject.console.table(...arguments); },
function profile() { return inspectedGlobalObject.console.profile(...arguments); },
function profileEnd() { return inspectedGlobalObject.console.profileEnd(...arguments); },
function keys(object) { return Object.keys(object); },
function values(object) {
let result = [];
for (let key in object)
result.push(object[key]);
return result;
},
];
for (let method of BasicCommandLineAPI.methods)
method.toString = function() { return "function " + method.name + "() { [Command Line API] }"; };
return injectedScript;
})