blob: 1986a38ada2d3e4cf84fb6bb2acb632e1d2f7050 [file] [log] [blame]
/*
* Copyright (C) 2009 Google Inc. All rights reserved.
* Copyright (C) 2015 Apple 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:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
* OWNER OR 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.
*/
WebInspector.RemoteObject = class RemoteObject
{
constructor(target, objectId, type, subtype, value, description, size, classPrototype, className, preview)
{
console.assert(type);
console.assert(!preview || preview instanceof WebInspector.ObjectPreview);
console.assert(!target || target instanceof WebInspector.Target);
this._target = target || WebInspector.mainTarget;
this._type = type;
this._subtype = subtype;
if (objectId) {
// Object, Function, or Symbol.
console.assert(!subtype || typeof subtype === "string");
console.assert(!description || typeof description === "string");
console.assert(!value);
this._objectId = objectId;
this._description = description || "";
this._hasChildren = type !== "symbol";
this._size = size;
this._classPrototype = classPrototype;
this._preview = preview;
if (subtype === "class") {
this._functionDescription = this._description;
this._description = "class " + className;
}
} else {
// Primitive or null.
console.assert(type !== "object" || value === null);
console.assert(!preview);
this._description = description || (value + "");
this._hasChildren = false;
this._value = value;
}
}
// Static
static createFakeRemoteObject()
{
return new WebInspector.RemoteObject(undefined, WebInspector.RemoteObject.FakeRemoteObjectId, "object");
}
static fromPrimitiveValue(value)
{
return new WebInspector.RemoteObject(undefined, undefined, typeof value, undefined, value, undefined, undefined, undefined, undefined);
}
static fromPayload(payload, target)
{
console.assert(typeof payload === "object", "Remote object payload should only be an object");
if (payload.subtype === "array") {
// COMPATIBILITY (iOS 8): Runtime.RemoteObject did not have size property,
// instead it was tacked onto the end of the description, like "Array[#]".
var match = payload.description.match(/\[(\d+)\]$/);
if (match) {
payload.size = parseInt(match[1]);
payload.description = payload.description.replace(/\[\d+\]$/, "");
}
}
if (payload.classPrototype)
payload.classPrototype = WebInspector.RemoteObject.fromPayload(payload.classPrototype, target);
if (payload.preview) {
// COMPATIBILITY (iOS 8): iOS 7 and 8 did not have type/subtype/description on
// Runtime.ObjectPreview. Copy them over from the RemoteObject.
if (!payload.preview.type) {
payload.preview.type = payload.type;
payload.preview.subtype = payload.subtype;
payload.preview.description = payload.description;
payload.preview.size = payload.size;
}
payload.preview = WebInspector.ObjectPreview.fromPayload(payload.preview);
}
return new WebInspector.RemoteObject(target, payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.size, payload.classPrototype, payload.className, payload.preview);
}
static createCallArgument(valueOrObject)
{
if (valueOrObject instanceof WebInspector.RemoteObject) {
if (valueOrObject.objectId)
return {objectId: valueOrObject.objectId};
return {value: valueOrObject.value};
}
return {value: valueOrObject};
}
static resolveNode(node, objectGroup, callback)
{
DOMAgent.resolveNode(node.id, objectGroup, function(error, object) {
if (!callback)
return;
if (error || !object)
callback(null);
else
callback(WebInspector.RemoteObject.fromPayload(object, WebInspector.mainTarget));
});
}
static type(remoteObject)
{
if (remoteObject === null)
return "null";
var type = typeof remoteObject;
if (type !== "object" && type !== "function")
return type;
return remoteObject.type;
}
// Public
get target()
{
return this._target;
}
get objectId()
{
return this._objectId;
}
get type()
{
return this._type;
}
get subtype()
{
return this._subtype;
}
get description()
{
return this._description;
}
get functionDescription()
{
console.assert(this.type === "function");
return this._functionDescription || this._description;
}
get hasChildren()
{
return this._hasChildren;
}
get value()
{
return this._value;
}
get size()
{
return this._size || 0;
}
get classPrototype()
{
return this._classPrototype;
}
get preview()
{
return this._preview;
}
hasSize()
{
return this.isArray() || this.isCollectionType();
}
hasValue()
{
return "_value" in this;
}
getOwnPropertyDescriptors(callback)
{
this._getPropertyDescriptors(true, callback);
}
getAllPropertyDescriptors(callback)
{
this._getPropertyDescriptors(false, callback);
}
getDisplayablePropertyDescriptors(callback)
{
if (!this._objectId || this._isSymbol() || this._isFakeObject()) {
callback([]);
return;
}
// COMPATIBILITY (iOS 8): RuntimeAgent.getDisplayableProperties did not exist.
// Here we do our best to reimplement it by getting all properties and reducing them down.
if (!RuntimeAgent.getDisplayableProperties) {
this._target.RuntimeAgent.getProperties(this._objectId, function(error, allProperties) {
var ownOrGetterPropertiesList = [];
if (allProperties) {
for (var property of allProperties) {
if (property.isOwn || property.name === "__proto__") {
// Own property or getter property in prototype chain.
ownOrGetterPropertiesList.push(property);
} else if (property.value && property.name !== property.name.toUpperCase()) {
var type = property.value.type;
if (type && type !== "function" && property.name !== "constructor") {
// Possible native binding getter property converted to a value. Also, no CONSTANT name style and not "constructor".
// There is no way of knowing if this is native or not, so just go with it.
ownOrGetterPropertiesList.push(property);
}
}
}
}
this._getPropertyDescriptorsResolver(callback, error, ownOrGetterPropertiesList);
}.bind(this));
return;
}
this._target.RuntimeAgent.getDisplayableProperties(this._objectId, true, this._getPropertyDescriptorsResolver.bind(this, callback));
}
// FIXME: Phase out these deprecated functions. They return DeprecatedRemoteObjectProperty instead of PropertyDescriptors.
deprecatedGetOwnProperties(callback)
{
this._deprecatedGetProperties(true, callback);
}
deprecatedGetAllProperties(callback)
{
this._deprecatedGetProperties(false, callback);
}
deprecatedGetDisplayableProperties(callback)
{
if (!this._objectId || this._isSymbol() || this._isFakeObject()) {
callback([]);
return;
}
// COMPATIBILITY (iOS 8): RuntimeAgent.getProperties did not support ownerAndGetterProperties.
// Here we do our best to reimplement it by getting all properties and reducing them down.
if (!RuntimeAgent.getDisplayableProperties) {
this._target.RuntimeAgent.getProperties(this._objectId, function(error, allProperties) {
var ownOrGetterPropertiesList = [];
if (allProperties) {
for (var property of allProperties) {
if (property.isOwn || property.get || property.name === "__proto__") {
// Own property or getter property in prototype chain.
ownOrGetterPropertiesList.push(property);
} else if (property.value && property.name !== property.name.toUpperCase()) {
var type = property.value.type;
if (type && type !== "function" && property.name !== "constructor") {
// Possible native binding getter property converted to a value. Also, no CONSTANT name style and not "constructor".
ownOrGetterPropertiesList.push(property);
}
}
}
}
this._deprecatedGetPropertiesResolver(callback, error, ownOrGetterPropertiesList);
}.bind(this));
return;
}
this._target.RuntimeAgent.getDisplayableProperties(this._objectId, this._deprecatedGetPropertiesResolver.bind(this, callback));
}
setPropertyValue(name, value, callback)
{
if (!this._objectId || this._isSymbol() || this._isFakeObject()) {
callback("Can't set a property of non-object.");
return;
}
// FIXME: It doesn't look like setPropertyValue is used yet. This will need to be tested when it is again (editable ObjectTrees).
this._target.RuntimeAgent.evaluate.invoke({expression: appendWebInspectorSourceURL(value), doNotPauseOnExceptionsAndMuteConsole: true}, evaluatedCallback.bind(this), this._target.RuntimeAgent);
function evaluatedCallback(error, result, wasThrown)
{
if (error || wasThrown) {
callback(error || result.description);
return;
}
function setPropertyValue(propertyName, propertyValue)
{
this[propertyName] = propertyValue;
}
delete result.description; // Optimize on traffic.
this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(setPropertyValue.toString()), [{value: name}, result], true, undefined, propertySetCallback.bind(this));
if (result._objectId)
this._target.RuntimeAgent.releaseObject(result._objectId);
}
function propertySetCallback(error, result, wasThrown)
{
if (error || wasThrown) {
callback(error || result.description);
return;
}
callback();
}
}
isUndefined()
{
return this._type === "undefined";
}
isNode()
{
return this._subtype === "node";
}
isArray()
{
return this._subtype === "array";
}
isClass()
{
return this._subtype === "class";
}
isCollectionType()
{
return this._subtype === "map" || this._subtype === "set" || this._subtype === "weakmap" || this._subtype === "weakset";
}
isWeakCollection()
{
return this._subtype === "weakmap" || this._subtype === "weakset";
}
getCollectionEntries(start, numberToFetch, callback)
{
start = typeof start === "number" ? start : 0;
numberToFetch = typeof numberToFetch === "number" ? numberToFetch : 100;
console.assert(start >= 0);
console.assert(numberToFetch >= 0);
console.assert(this.isCollectionType());
// WeakMaps and WeakSets are not ordered. We should never send a non-zero start.
console.assert((this._subtype === "weakmap" && start === 0) || this._subtype !== "weakmap");
console.assert((this._subtype === "weakset" && start === 0) || this._subtype !== "weakset");
let objectGroup = this.isWeakCollection() ? this._weakCollectionObjectGroup() : "";
this._target.RuntimeAgent.getCollectionEntries(this._objectId, objectGroup, start, numberToFetch, (error, entries) => {
entries = entries.map((x) => WebInspector.CollectionEntry.fromPayload(x, this._target));
callback(entries);
});
}
releaseWeakCollectionEntries()
{
console.assert(this.isWeakCollection());
this._target.RuntimeAgent.releaseObjectGroup(this._weakCollectionObjectGroup());
}
pushNodeToFrontend(callback)
{
if (this._objectId)
WebInspector.domTreeManager.pushNodeToFrontend(this._objectId, callback);
else
callback(0);
}
callFunction(functionDeclaration, args, generatePreview, callback)
{
function mycallback(error, result, wasThrown)
{
result = result ? WebInspector.RemoteObject.fromPayload(result, this._target) : null;
if (callback && typeof callback === "function")
callback(error, result, wasThrown);
}
if (args)
args = args.map(WebInspector.RemoteObject.createCallArgument);
this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(functionDeclaration.toString()), args, true, undefined, !!generatePreview, mycallback.bind(this));
}
callFunctionJSON(functionDeclaration, args, callback)
{
function mycallback(error, result, wasThrown)
{
callback((error || wasThrown) ? null : result.value);
}
this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(functionDeclaration.toString()), args, true, true, mycallback);
}
invokeGetter(getterRemoteObject, callback)
{
console.assert(getterRemoteObject instanceof WebInspector.RemoteObject);
function backendInvokeGetter(getter)
{
return getter ? getter.call(this) : undefined;
}
this.callFunction(backendInvokeGetter, [getterRemoteObject], true, callback);
}
getOwnPropertyDescriptor(propertyName, callback)
{
function backendGetOwnPropertyDescriptor(propertyName)
{
return this[propertyName];
}
function wrappedCallback(error, result, wasThrown)
{
if (error || wasThrown || !(result instanceof WebInspector.RemoteObject)) {
callback(null);
return;
}
var fakeDescriptor = {name: propertyName, value: result, writable: true, configurable: true, enumerable: false};
var fakePropertyDescriptor = new WebInspector.PropertyDescriptor(fakeDescriptor, null, true, false, false, false);
callback(fakePropertyDescriptor);
}
// FIXME: Implement a real RuntimeAgent.getOwnPropertyDescriptor?
this.callFunction(backendGetOwnPropertyDescriptor, [propertyName], false, wrappedCallback.bind(this));
}
release()
{
if (this._objectId && !this._isFakeObject())
this._target.RuntimeAgent.releaseObject(this._objectId);
}
arrayLength()
{
if (this._subtype !== "array")
return 0;
var matches = this._description.match(/\[([0-9]+)\]/);
if (!matches)
return 0;
return parseInt(matches[1], 10);
}
asCallArgument()
{
return WebInspector.RemoteObject.createCallArgument(this);
}
findFunctionSourceCodeLocation()
{
var result = new WebInspector.WrappedPromise;
if (!this._isFunction() || !this._objectId) {
result.resolve(WebInspector.RemoteObject.SourceCodeLocationPromise.MissingObjectId);
return result.promise;
}
DebuggerAgent.getFunctionDetails(this._objectId, function(error, response) {
if (error) {
result.reject(error);
return;
}
var location = response.location;
var sourceCode = WebInspector.debuggerManager.scriptForIdentifier(location.scriptId);
if (!sourceCode || (!WebInspector.isDebugUIEnabled() && isWebKitInternalScript(sourceCode.sourceURL))) {
result.resolve(WebInspector.RemoteObject.SourceCodeLocationPromise.NoSourceFound);
return;
}
var sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber || 0);
result.resolve(sourceCodeLocation);
});
return result.promise;
}
// Private
_isFakeObject()
{
return this._objectId === WebInspector.RemoteObject.FakeRemoteObjectId;
}
_isSymbol()
{
return this._type === "symbol";
}
_isFunction()
{
return this._type === "function";
}
_weakCollectionObjectGroup()
{
return JSON.stringify(this._objectId) + "-" + this._subtype;
}
_getPropertyDescriptors(ownProperties, callback)
{
if (!this._objectId || this._isSymbol() || this._isFakeObject()) {
callback([]);
return;
}
this._target.RuntimeAgent.getProperties(this._objectId, ownProperties, true, this._getPropertyDescriptorsResolver.bind(this, callback));
}
getOwnPropertyDescriptorsAsObject(callback)
{
this.getOwnPropertyDescriptors(function(properties) {
var propertiesResult = {};
var internalPropertiesResult = {};
for (var propertyDescriptor of properties) {
var object = propertyDescriptor.isInternalProperty ? internalPropertiesResult : propertiesResult;
object[propertyDescriptor.name] = propertyDescriptor;
}
callback(propertiesResult, internalPropertiesResult);
});
}
_getPropertyDescriptorsResolver(callback, error, properties, internalProperties)
{
if (error) {
callback(null);
return;
}
let descriptors = properties.map((payload) => {
return WebInspector.PropertyDescriptor.fromPayload(payload, false, this._target);
});
if (internalProperties) {
descriptors = descriptors.concat(internalProperties.map((payload) => {
return WebInspector.PropertyDescriptor.fromPayload(payload, true, this._target);
}));
}
callback(descriptors);
}
// FIXME: Phase out these deprecated functions. They return DeprecatedRemoteObjectProperty instead of PropertyDescriptors.
_deprecatedGetProperties(ownProperties, callback)
{
if (!this._objectId || this._isSymbol() || this._isFakeObject()) {
callback([]);
return;
}
this._target.RuntimeAgent.getProperties(this._objectId, ownProperties, this._deprecatedGetPropertiesResolver.bind(this, callback));
}
_deprecatedGetPropertiesResolver(callback, error, properties, internalProperties)
{
if (error) {
callback(null);
return;
}
if (internalProperties) {
properties = properties.concat(internalProperties.map(function(descriptor) {
descriptor.writable = false;
descriptor.configurable = false;
descriptor.enumerable = false;
descriptor.isOwn = true;
return descriptor;
}));
}
var result = [];
for (var i = 0; properties && i < properties.length; ++i) {
var property = properties[i];
if (property.get || property.set) {
if (property.get)
result.push(new WebInspector.DeprecatedRemoteObjectProperty("get " + property.name, WebInspector.RemoteObject.fromPayload(property.get, this._target), property));
if (property.set)
result.push(new WebInspector.DeprecatedRemoteObjectProperty("set " + property.name, WebInspector.RemoteObject.fromPayload(property.set, this._target), property));
} else
result.push(new WebInspector.DeprecatedRemoteObjectProperty(property.name, WebInspector.RemoteObject.fromPayload(property.value, this._target), property));
}
callback(result);
}
};
WebInspector.RemoteObject.FakeRemoteObjectId = "fake-remote-object";
WebInspector.RemoteObject.SourceCodeLocationPromise = {
NoSourceFound: "remote-object-source-code-location-promise-no-source-found",
MissingObjectId: "remote-object-source-code-location-promise-missing-object-id"
};
// FIXME: Phase out this deprecated class.
WebInspector.DeprecatedRemoteObjectProperty = class DeprecatedRemoteObjectProperty
{
constructor(name, value, descriptor)
{
this.name = name;
this.value = value;
this.enumerable = descriptor ? !!descriptor.enumerable : true;
this.writable = descriptor ? !!descriptor.writable : true;
if (descriptor && descriptor.wasThrown)
this.wasThrown = true;
}
// Static
fromPrimitiveValue(name, value)
{
return new WebInspector.DeprecatedRemoteObjectProperty(name, WebInspector.RemoteObject.fromPrimitiveValue(value));
}
};