diff options
Diffstat (limited to 'deps/v8/src/inspector/value-mirror.cc')
-rw-r--r-- | deps/v8/src/inspector/value-mirror.cc | 1617 |
1 files changed, 1617 insertions, 0 deletions
diff --git a/deps/v8/src/inspector/value-mirror.cc b/deps/v8/src/inspector/value-mirror.cc new file mode 100644 index 0000000000..aac6481828 --- /dev/null +++ b/deps/v8/src/inspector/value-mirror.cc @@ -0,0 +1,1617 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/inspector/value-mirror.h" + +#include <algorithm> +#include <cmath> + +#include "src/debug/debug-interface.h" +#include "src/inspector/v8-debugger.h" +#include "src/inspector/v8-inspector-impl.h" +#include "src/inspector/v8-value-utils.h" + +namespace v8_inspector { + +using protocol::Response; +using protocol::Runtime::RemoteObject; +using protocol::Runtime::ObjectPreview; +using protocol::Runtime::PropertyPreview; +using protocol::Runtime::EntryPreview; +using protocol::Runtime::InternalPropertyDescriptor; + +namespace { +V8InspectorClient* clientFor(v8::Local<v8::Context> context) { + return static_cast<V8InspectorImpl*>( + v8::debug::GetInspector(context->GetIsolate())) + ->client(); +} + +V8InternalValueType v8InternalValueTypeFrom(v8::Local<v8::Context> context, + v8::Local<v8::Value> value) { + if (!value->IsObject()) return V8InternalValueType::kNone; + V8InspectorImpl* inspector = static_cast<V8InspectorImpl*>( + v8::debug::GetInspector(context->GetIsolate())); + int contextId = InspectedContext::contextId(context); + InspectedContext* inspectedContext = inspector->getContext(contextId); + if (!inspectedContext) return V8InternalValueType::kNone; + return inspectedContext->getInternalType(value.As<v8::Object>()); +} + +Response toProtocolValue(v8::Local<v8::Context> context, + v8::Local<v8::Value> value, int maxDepth, + std::unique_ptr<protocol::Value>* result) { + if (!maxDepth) return Response::Error("Object reference chain is too long"); + maxDepth--; + + if (value->IsNull() || value->IsUndefined()) { + *result = protocol::Value::null(); + return Response::OK(); + } + if (value->IsBoolean()) { + *result = + protocol::FundamentalValue::create(value.As<v8::Boolean>()->Value()); + return Response::OK(); + } + if (value->IsNumber()) { + double doubleValue = value.As<v8::Number>()->Value(); + int intValue = static_cast<int>(doubleValue); + if (intValue == doubleValue) { + *result = protocol::FundamentalValue::create(intValue); + return Response::OK(); + } + *result = protocol::FundamentalValue::create(doubleValue); + return Response::OK(); + } + if (value->IsString()) { + *result = protocol::StringValue::create( + toProtocolString(context->GetIsolate(), value.As<v8::String>())); + return Response::OK(); + } + if (value->IsArray()) { + v8::Local<v8::Array> array = value.As<v8::Array>(); + std::unique_ptr<protocol::ListValue> inspectorArray = + protocol::ListValue::create(); + uint32_t length = array->Length(); + for (uint32_t i = 0; i < length; i++) { + v8::Local<v8::Value> value; + if (!array->Get(context, i).ToLocal(&value)) + return Response::InternalError(); + std::unique_ptr<protocol::Value> element; + Response response = toProtocolValue(context, value, maxDepth, &element); + if (!response.isSuccess()) return response; + inspectorArray->pushValue(std::move(element)); + } + *result = std::move(inspectorArray); + return Response::OK(); + } + if (value->IsObject()) { + std::unique_ptr<protocol::DictionaryValue> jsonObject = + protocol::DictionaryValue::create(); + v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(value); + v8::Local<v8::Array> propertyNames; + if (!object->GetPropertyNames(context).ToLocal(&propertyNames)) + return Response::InternalError(); + uint32_t length = propertyNames->Length(); + for (uint32_t i = 0; i < length; i++) { + v8::Local<v8::Value> name; + if (!propertyNames->Get(context, i).ToLocal(&name)) + return Response::InternalError(); + // FIXME(yurys): v8::Object should support GetOwnPropertyNames + if (name->IsString()) { + v8::Maybe<bool> hasRealNamedProperty = object->HasRealNamedProperty( + context, v8::Local<v8::String>::Cast(name)); + if (hasRealNamedProperty.IsNothing() || + !hasRealNamedProperty.FromJust()) + continue; + } + v8::Local<v8::String> propertyName; + if (!name->ToString(context).ToLocal(&propertyName)) continue; + v8::Local<v8::Value> property; + if (!object->Get(context, name).ToLocal(&property)) + return Response::InternalError(); + if (property->IsUndefined()) continue; + std::unique_ptr<protocol::Value> propertyValue; + Response response = + toProtocolValue(context, property, maxDepth, &propertyValue); + if (!response.isSuccess()) return response; + jsonObject->setValue( + toProtocolString(context->GetIsolate(), propertyName), + std::move(propertyValue)); + } + *result = std::move(jsonObject); + return Response::OK(); + } + return Response::Error("Object couldn't be returned by value"); +} + +Response toProtocolValue(v8::Local<v8::Context> context, + v8::Local<v8::Value> value, + std::unique_ptr<protocol::Value>* result) { + if (value->IsUndefined()) return Response::OK(); + return toProtocolValue(context, value, 1000, result); +} + +enum AbbreviateMode { kMiddle, kEnd }; + +String16 abbreviateString(const String16& value, AbbreviateMode mode) { + const size_t maxLength = 100; + if (value.length() <= maxLength) return value; + UChar ellipsis = static_cast<UChar>(0x2026); + if (mode == kMiddle) { + return String16::concat( + value.substring(0, maxLength / 2), String16(&ellipsis, 1), + value.substring(value.length() - maxLength / 2 + 1)); + } + return String16::concat(value.substring(0, maxLength - 1), ellipsis); +} + +String16 descriptionForSymbol(v8::Local<v8::Context> context, + v8::Local<v8::Symbol> symbol) { + return String16::concat( + "Symbol(", + toProtocolStringWithTypeCheck(context->GetIsolate(), symbol->Name()), + ")"); +} + +String16 descriptionForBigInt(v8::Local<v8::Context> context, + v8::Local<v8::BigInt> value) { + v8::Isolate* isolate = context->GetIsolate(); + v8::TryCatch tryCatch(isolate); + v8::Local<v8::String> description; + if (!value->ToString(context).ToLocal(&description)) return String16(); + return toProtocolString(isolate, description) + "n"; +} + +String16 descriptionForPrimitiveType(v8::Local<v8::Context> context, + v8::Local<v8::Value> value) { + if (value->IsUndefined()) return RemoteObject::TypeEnum::Undefined; + if (value->IsNull()) return RemoteObject::SubtypeEnum::Null; + if (value->IsBoolean()) { + return value.As<v8::Boolean>()->Value() ? "true" : "false"; + } + if (value->IsString()) { + return toProtocolString(context->GetIsolate(), value.As<v8::String>()); + } + UNREACHABLE(); + return String16(); +} + +String16 descriptionForRegExp(v8::Isolate* isolate, + v8::Local<v8::RegExp> value) { + String16Builder description; + description.append('/'); + description.append(toProtocolString(isolate, value->GetSource())); + description.append('/'); + v8::RegExp::Flags flags = value->GetFlags(); + if (flags & v8::RegExp::Flags::kGlobal) description.append('g'); + if (flags & v8::RegExp::Flags::kIgnoreCase) description.append('i'); + if (flags & v8::RegExp::Flags::kMultiline) description.append('m'); + if (flags & v8::RegExp::Flags::kDotAll) description.append('s'); + if (flags & v8::RegExp::Flags::kUnicode) description.append('u'); + if (flags & v8::RegExp::Flags::kSticky) description.append('y'); + return description.toString(); +} + +enum class ErrorType { kNative, kClient }; + +String16 descriptionForError(v8::Local<v8::Context> context, + v8::Local<v8::Object> object, ErrorType type) { + v8::Isolate* isolate = context->GetIsolate(); + v8::TryCatch tryCatch(isolate); + String16 className = toProtocolString(isolate, object->GetConstructorName()); + v8::Local<v8::Value> stackValue; + if (!object->Get(context, toV8String(isolate, "stack")) + .ToLocal(&stackValue) || + !stackValue->IsString()) { + return className; + } + String16 stack = toProtocolString(isolate, stackValue.As<v8::String>()); + String16 description = stack; + if (type == ErrorType::kClient) { + if (stack.substring(0, className.length()) != className) { + v8::Local<v8::Value> messageValue; + if (!object->Get(context, toV8String(isolate, "message")) + .ToLocal(&messageValue) || + !messageValue->IsString()) { + return stack; + } + String16 message = toProtocolStringWithTypeCheck(isolate, messageValue); + size_t index = stack.find(message); + String16 stackWithoutMessage = + index != String16::kNotFound + ? stack.substring(index + message.length()) + : String16(); + description = className + ": " + message + stackWithoutMessage; + } + } + return description; +} + +String16 descriptionForObject(v8::Isolate* isolate, + v8::Local<v8::Object> object) { + return toProtocolString(isolate, object->GetConstructorName()); +} + +String16 descriptionForDate(v8::Local<v8::Context> context, + v8::Local<v8::Date> date) { + v8::Isolate* isolate = context->GetIsolate(); + v8::TryCatch tryCatch(isolate); + v8::Local<v8::String> description; + if (!date->ToString(context).ToLocal(&description)) { + return descriptionForObject(isolate, date); + } + return toProtocolString(isolate, description); +} + +String16 descriptionForScopeList(v8::Local<v8::Array> list) { + return String16::concat( + "Scopes[", String16::fromInteger(static_cast<size_t>(list->Length())), + ']'); +} + +String16 descriptionForScope(v8::Local<v8::Context> context, + v8::Local<v8::Object> object) { + v8::Isolate* isolate = context->GetIsolate(); + v8::Local<v8::Value> value; + if (!object->GetRealNamedProperty(context, toV8String(isolate, "description")) + .ToLocal(&value)) { + return String16(); + } + return toProtocolStringWithTypeCheck(isolate, value); +} + +String16 descriptionForCollection(v8::Isolate* isolate, + v8::Local<v8::Object> object, size_t length) { + String16 className = toProtocolString(isolate, object->GetConstructorName()); + return String16::concat(className, '(', String16::fromInteger(length), ')'); +} + +String16 descriptionForEntry(v8::Local<v8::Context> context, + v8::Local<v8::Object> object) { + v8::Isolate* isolate = context->GetIsolate(); + String16 key; + v8::Local<v8::Value> tmp; + if (object->GetRealNamedProperty(context, toV8String(isolate, "key")) + .ToLocal(&tmp)) { + auto wrapper = ValueMirror::create(context, tmp); + if (wrapper) { + std::unique_ptr<ObjectPreview> preview; + int limit = 5; + wrapper->buildEntryPreview(context, &limit, &limit, &preview); + if (preview) { + key = preview->getDescription(String16()); + if (preview->getType() == RemoteObject::TypeEnum::String) { + key = String16::concat('\"', key, '\"'); + } + } + } + } + + String16 value; + if (object->GetRealNamedProperty(context, toV8String(isolate, "value")) + .ToLocal(&tmp)) { + auto wrapper = ValueMirror::create(context, tmp); + if (wrapper) { + std::unique_ptr<ObjectPreview> preview; + int limit = 5; + wrapper->buildEntryPreview(context, &limit, &limit, &preview); + if (preview) { + value = preview->getDescription(String16()); + if (preview->getType() == RemoteObject::TypeEnum::String) { + value = String16::concat('\"', value, '\"'); + } + } + } + } + + return key.length() ? ("{" + key + " => " + value + "}") : value; +} + +String16 descriptionForFunction(v8::Local<v8::Context> context, + v8::Local<v8::Function> value) { + v8::Isolate* isolate = context->GetIsolate(); + v8::TryCatch tryCatch(isolate); + v8::Local<v8::String> description; + if (!value->ToString(context).ToLocal(&description)) { + return descriptionForObject(isolate, value); + } + return toProtocolString(isolate, description); +} + +class PrimitiveValueMirror final : public ValueMirror { + public: + PrimitiveValueMirror(v8::Local<v8::Value> value, const String16& type) + : m_value(value), m_type(type) {} + + v8::Local<v8::Value> v8Value() const override { return m_value; } + Response buildRemoteObject( + v8::Local<v8::Context> context, WrapMode mode, + std::unique_ptr<RemoteObject>* result) const override { + std::unique_ptr<protocol::Value> protocolValue; + toProtocolValue(context, m_value, &protocolValue); + *result = RemoteObject::create() + .setType(m_type) + .setValue(std::move(protocolValue)) + .build(); + if (m_value->IsNull()) + (*result)->setSubtype(RemoteObject::SubtypeEnum::Null); + return Response::OK(); + } + + void buildEntryPreview( + v8::Local<v8::Context> context, int* nameLimit, int* indexLimit, + std::unique_ptr<ObjectPreview>* preview) const override { + *preview = + ObjectPreview::create() + .setType(m_type) + .setDescription(descriptionForPrimitiveType(context, m_value)) + .setOverflow(false) + .setProperties(protocol::Array<PropertyPreview>::create()) + .build(); + if (m_value->IsNull()) + (*preview)->setSubtype(RemoteObject::SubtypeEnum::Null); + } + + void buildPropertyPreview( + v8::Local<v8::Context> context, const String16& name, + std::unique_ptr<PropertyPreview>* preview) const override { + *preview = PropertyPreview::create() + .setName(name) + .setValue(abbreviateString( + descriptionForPrimitiveType(context, m_value), kMiddle)) + .setType(m_type) + .build(); + if (m_value->IsNull()) + (*preview)->setSubtype(RemoteObject::SubtypeEnum::Null); + } + + private: + v8::Local<v8::Value> m_value; + String16 m_type; + String16 m_subtype; +}; + +class NumberMirror final : public ValueMirror { + public: + explicit NumberMirror(v8::Local<v8::Number> value) : m_value(value) {} + v8::Local<v8::Value> v8Value() const override { return m_value; } + + Response buildRemoteObject( + v8::Local<v8::Context> context, WrapMode mode, + std::unique_ptr<RemoteObject>* result) const override { + bool unserializable = false; + String16 descriptionValue = description(&unserializable); + *result = RemoteObject::create() + .setType(RemoteObject::TypeEnum::Number) + .setDescription(descriptionValue) + .build(); + if (unserializable) { + (*result)->setUnserializableValue(descriptionValue); + } else { + (*result)->setValue(protocol::FundamentalValue::create(m_value->Value())); + } + return Response::OK(); + } + void buildPropertyPreview( + v8::Local<v8::Context> context, const String16& name, + std::unique_ptr<PropertyPreview>* result) const override { + bool unserializable = false; + *result = PropertyPreview::create() + .setName(name) + .setType(RemoteObject::TypeEnum::Number) + .setValue(description(&unserializable)) + .build(); + } + void buildEntryPreview( + v8::Local<v8::Context> context, int* nameLimit, int* indexLimit, + std::unique_ptr<ObjectPreview>* preview) const override { + bool unserializable = false; + *preview = ObjectPreview::create() + .setType(RemoteObject::TypeEnum::Number) + .setDescription(description(&unserializable)) + .setOverflow(false) + .setProperties(protocol::Array<PropertyPreview>::create()) + .build(); + } + + private: + String16 description(bool* unserializable) const { + *unserializable = true; + double rawValue = m_value->Value(); + if (std::isnan(rawValue)) return "NaN"; + if (rawValue == 0.0 && std::signbit(rawValue)) return "-0"; + if (std::isinf(rawValue)) { + return std::signbit(rawValue) ? "-Infinity" : "Infinity"; + } + *unserializable = false; + return String16::fromDouble(rawValue); + } + + v8::Local<v8::Number> m_value; +}; + +class BigIntMirror final : public ValueMirror { + public: + explicit BigIntMirror(v8::Local<v8::BigInt> value) : m_value(value) {} + + Response buildRemoteObject( + v8::Local<v8::Context> context, WrapMode mode, + std::unique_ptr<RemoteObject>* result) const override { + String16 description = descriptionForBigInt(context, m_value); + *result = RemoteObject::create() + .setType(RemoteObject::TypeEnum::Bigint) + .setUnserializableValue(description) + .setDescription(description) + .build(); + return Response::OK(); + } + + void buildPropertyPreview(v8::Local<v8::Context> context, + const String16& name, + std::unique_ptr<protocol::Runtime::PropertyPreview>* + preview) const override { + *preview = PropertyPreview::create() + .setName(name) + .setType(RemoteObject::TypeEnum::Bigint) + .setValue(abbreviateString( + descriptionForBigInt(context, m_value), kMiddle)) + .build(); + } + + void buildEntryPreview(v8::Local<v8::Context> context, int* nameLimit, + int* indexLimit, + std::unique_ptr<protocol::Runtime::ObjectPreview>* + preview) const override { + *preview = ObjectPreview::create() + .setType(RemoteObject::TypeEnum::Bigint) + .setDescription(descriptionForBigInt(context, m_value)) + .setOverflow(false) + .setProperties(protocol::Array<PropertyPreview>::create()) + .build(); + } + + v8::Local<v8::Value> v8Value() const override { return m_value; } + + private: + v8::Local<v8::BigInt> m_value; +}; + +class SymbolMirror final : public ValueMirror { + public: + explicit SymbolMirror(v8::Local<v8::Value> value) + : m_symbol(value.As<v8::Symbol>()) {} + + Response buildRemoteObject( + v8::Local<v8::Context> context, WrapMode mode, + std::unique_ptr<RemoteObject>* result) const override { + if (mode == WrapMode::kForceValue) { + return Response::Error("Object couldn't be returned by value"); + } + *result = RemoteObject::create() + .setType(RemoteObject::TypeEnum::Symbol) + .setDescription(descriptionForSymbol(context, m_symbol)) + .build(); + return Response::OK(); + } + + void buildPropertyPreview(v8::Local<v8::Context> context, + const String16& name, + std::unique_ptr<protocol::Runtime::PropertyPreview>* + preview) const override { + *preview = PropertyPreview::create() + .setName(name) + .setType(RemoteObject::TypeEnum::Symbol) + .setValue(abbreviateString( + descriptionForSymbol(context, m_symbol), kEnd)) + .build(); + } + + v8::Local<v8::Value> v8Value() const override { return m_symbol; } + + private: + v8::Local<v8::Symbol> m_symbol; +}; + +class LocationMirror final : public ValueMirror { + public: + static std::unique_ptr<LocationMirror> create( + v8::Local<v8::Function> function) { + return create(function, function->ScriptId(), + function->GetScriptLineNumber(), + function->GetScriptColumnNumber()); + } + static std::unique_ptr<LocationMirror> createForGenerator( + v8::Local<v8::Value> value) { + v8::Local<v8::debug::GeneratorObject> generatorObject = + v8::debug::GeneratorObject::Cast(value); + if (!generatorObject->IsSuspended()) { + return create(generatorObject->Function()); + } + v8::Local<v8::debug::Script> script; + if (!generatorObject->Script().ToLocal(&script)) return nullptr; + v8::debug::Location suspendedLocation = + generatorObject->SuspendedLocation(); + return create(value, script->Id(), suspendedLocation.GetLineNumber(), + suspendedLocation.GetColumnNumber()); + } + + Response buildRemoteObject( + v8::Local<v8::Context> context, WrapMode mode, + std::unique_ptr<RemoteObject>* result) const override { + auto location = protocol::DictionaryValue::create(); + location->setString("scriptId", String16::fromInteger(m_scriptId)); + location->setInteger("lineNumber", m_lineNumber); + location->setInteger("columnNumber", m_columnNumber); + *result = RemoteObject::create() + .setType(RemoteObject::TypeEnum::Object) + .setSubtype("internal#location") + .setDescription("Object") + .setValue(std::move(location)) + .build(); + return Response::OK(); + } + v8::Local<v8::Value> v8Value() const override { return m_value; } + + private: + static std::unique_ptr<LocationMirror> create(v8::Local<v8::Value> value, + int scriptId, int lineNumber, + int columnNumber) { + if (scriptId == v8::UnboundScript::kNoScriptId) return nullptr; + if (lineNumber == v8::Function::kLineOffsetNotFound || + columnNumber == v8::Function::kLineOffsetNotFound) { + return nullptr; + } + return std::unique_ptr<LocationMirror>( + new LocationMirror(value, scriptId, lineNumber, columnNumber)); + } + + LocationMirror(v8::Local<v8::Value> value, int scriptId, int lineNumber, + int columnNumber) + : m_value(value), + m_scriptId(scriptId), + m_lineNumber(lineNumber), + m_columnNumber(columnNumber) {} + + v8::Local<v8::Value> m_value; + int m_scriptId; + int m_lineNumber; + int m_columnNumber; +}; + +class FunctionMirror final : public ValueMirror { + public: + explicit FunctionMirror(v8::Local<v8::Value> value) + : m_value(value.As<v8::Function>()) {} + + v8::Local<v8::Value> v8Value() const override { return m_value; } + + Response buildRemoteObject( + v8::Local<v8::Context> context, WrapMode mode, + std::unique_ptr<RemoteObject>* result) const override { + // TODO(alph): drop this functionality. + if (mode == WrapMode::kForceValue) { + std::unique_ptr<protocol::Value> protocolValue; + Response response = toProtocolValue(context, m_value, &protocolValue); + if (!response.isSuccess()) return response; + *result = RemoteObject::create() + .setType(RemoteObject::TypeEnum::Function) + .setValue(std::move(protocolValue)) + .build(); + } else { + *result = RemoteObject::create() + .setType(RemoteObject::TypeEnum::Function) + .setClassName(toProtocolStringWithTypeCheck( + context->GetIsolate(), m_value->GetConstructorName())) + .setDescription(descriptionForFunction(context, m_value)) + .build(); + } + return Response::OK(); + } + + void buildPropertyPreview( + v8::Local<v8::Context> context, const String16& name, + std::unique_ptr<PropertyPreview>* result) const override { + *result = PropertyPreview::create() + .setName(name) + .setType(RemoteObject::TypeEnum::Function) + .setValue(String16()) + .build(); + } + void buildEntryPreview( + v8::Local<v8::Context> context, int* nameLimit, int* indexLimit, + std::unique_ptr<ObjectPreview>* preview) const override { + *preview = ObjectPreview::create() + .setType(RemoteObject::TypeEnum::Function) + .setDescription(descriptionForFunction(context, m_value)) + .setOverflow(false) + .setProperties(protocol::Array<PropertyPreview>::create()) + .build(); + } + + private: + v8::Local<v8::Function> m_value; +}; + +bool isArrayLike(v8::Local<v8::Context> context, v8::Local<v8::Value> value, + size_t* length) { + if (!value->IsObject()) return false; + v8::Isolate* isolate = context->GetIsolate(); + v8::TryCatch tryCatch(isolate); + v8::MicrotasksScope microtasksScope(isolate, + v8::MicrotasksScope::kDoNotRunMicrotasks); + v8::Local<v8::Object> object = value.As<v8::Object>(); + v8::Local<v8::Value> spliceValue; + if (!object->IsArgumentsObject() && + (!object->GetRealNamedProperty(context, toV8String(isolate, "splice")) + .ToLocal(&spliceValue) || + !spliceValue->IsFunction())) { + return false; + } + v8::Local<v8::Value> lengthValue; + v8::Maybe<bool> result = + object->HasOwnProperty(context, toV8String(isolate, "length")); + if (result.IsNothing()) return false; + if (!result.FromJust() || + !object->Get(context, toV8String(isolate, "length")) + .ToLocal(&lengthValue) || + !lengthValue->IsUint32()) { + return false; + } + *length = v8::Local<v8::Uint32>::Cast(lengthValue)->Value(); + return true; +} + +struct EntryMirror { + std::unique_ptr<ValueMirror> key; + std::unique_ptr<ValueMirror> value; + + static bool getEntries(v8::Local<v8::Context> context, + v8::Local<v8::Object> object, size_t limit, + bool* overflow, std::vector<EntryMirror>* mirrors) { + bool isKeyValue = false; + v8::Local<v8::Array> entries; + if (!object->PreviewEntries(&isKeyValue).ToLocal(&entries)) return false; + for (uint32_t i = 0; i < entries->Length(); i += isKeyValue ? 2 : 1) { + v8::Local<v8::Value> tmp; + + std::unique_ptr<ValueMirror> keyMirror; + if (isKeyValue && entries->Get(context, i).ToLocal(&tmp)) { + keyMirror = ValueMirror::create(context, tmp); + } + std::unique_ptr<ValueMirror> valueMirror; + if (entries->Get(context, isKeyValue ? i + 1 : i).ToLocal(&tmp)) { + valueMirror = ValueMirror::create(context, tmp); + } else { + continue; + } + if (mirrors->size() == limit) { + *overflow = true; + return true; + } + mirrors->emplace_back( + EntryMirror{std::move(keyMirror), std::move(valueMirror)}); + } + return mirrors->size() > 0; + } +}; + +class PreviewPropertyAccumulator : public ValueMirror::PropertyAccumulator { + public: + PreviewPropertyAccumulator(const std::vector<String16>& blacklist, + int skipIndex, int* nameLimit, int* indexLimit, + bool* overflow, + std::vector<PropertyMirror>* mirrors) + : m_blacklist(blacklist), + m_skipIndex(skipIndex), + m_nameLimit(nameLimit), + m_indexLimit(indexLimit), + m_overflow(overflow), + m_mirrors(mirrors) {} + + bool Add(PropertyMirror mirror) override { + if (mirror.exception) return true; + if ((!mirror.getter || !mirror.getter->v8Value()->IsFunction()) && + !mirror.value) { + return true; + } + if (!mirror.isOwn) return true; + if (std::find(m_blacklist.begin(), m_blacklist.end(), mirror.name) != + m_blacklist.end()) { + return true; + } + if (mirror.isIndex && m_skipIndex > 0) { + --m_skipIndex; + if (m_skipIndex > 0) return true; + } + int* limit = mirror.isIndex ? m_indexLimit : m_nameLimit; + if (!*limit) { + *m_overflow = true; + return false; + } + --*limit; + m_mirrors->push_back(std::move(mirror)); + return true; + } + + private: + std::vector<String16> m_blacklist; + int m_skipIndex; + int* m_nameLimit; + int* m_indexLimit; + bool* m_overflow; + std::vector<PropertyMirror>* m_mirrors; +}; + +bool getPropertiesForPreview(v8::Local<v8::Context> context, + v8::Local<v8::Object> object, int* nameLimit, + int* indexLimit, bool* overflow, + std::vector<PropertyMirror>* properties) { + std::vector<String16> blacklist; + size_t length = 0; + if (object->IsArray() || isArrayLike(context, object, &length) || + object->IsStringObject()) { + blacklist.push_back("length"); + } else { + auto clientSubtype = clientFor(context)->valueSubtype(object); + if (clientSubtype && toString16(clientSubtype->string()) == "array") { + blacklist.push_back("length"); + } + } + if (object->IsArrayBuffer() || object->IsSharedArrayBuffer()) { + blacklist.push_back("[[Int8Array]]"); + blacklist.push_back("[[Uint8Array]]"); + blacklist.push_back("[[Int16Array]]"); + blacklist.push_back("[[Int32Array]]"); + } + int skipIndex = object->IsStringObject() + ? object.As<v8::StringObject>()->ValueOf()->Length() + 1 + : -1; + PreviewPropertyAccumulator accumulator(blacklist, skipIndex, nameLimit, + indexLimit, overflow, properties); + return ValueMirror::getProperties(context, object, false, false, + &accumulator); +} + +void getInternalPropertiesForPreview( + v8::Local<v8::Context> context, v8::Local<v8::Object> object, + int* nameLimit, bool* overflow, + std::vector<InternalPropertyMirror>* properties) { + std::vector<InternalPropertyMirror> mirrors; + ValueMirror::getInternalProperties(context, object, &mirrors); + std::vector<String16> whitelist; + if (object->IsBooleanObject() || object->IsNumberObject() || + object->IsStringObject() || object->IsSymbolObject() || + object->IsBigIntObject()) { + whitelist.emplace_back("[[PrimitiveValue]]"); + } else if (object->IsPromise()) { + whitelist.emplace_back("[[PromiseStatus]]"); + whitelist.emplace_back("[[PromiseValue]]"); + } else if (object->IsGeneratorObject()) { + whitelist.emplace_back("[[GeneratorStatus]]"); + } + for (auto& mirror : mirrors) { + if (std::find(whitelist.begin(), whitelist.end(), mirror.name) == + whitelist.end()) { + continue; + } + if (!*nameLimit) { + *overflow = true; + return; + } + --*nameLimit; + properties->push_back(std::move(mirror)); + } +} + +class ObjectMirror final : public ValueMirror { + public: + ObjectMirror(v8::Local<v8::Value> value, const String16& description) + : m_value(value.As<v8::Object>()), + m_description(description), + m_hasSubtype(false) {} + ObjectMirror(v8::Local<v8::Value> value, const String16& subtype, + const String16& description) + : m_value(value.As<v8::Object>()), + m_description(description), + m_hasSubtype(true), + m_subtype(subtype) {} + + v8::Local<v8::Value> v8Value() const override { return m_value; } + + Response buildRemoteObject( + v8::Local<v8::Context> context, WrapMode mode, + std::unique_ptr<RemoteObject>* result) const override { + if (mode == WrapMode::kForceValue) { + std::unique_ptr<protocol::Value> protocolValue; + Response response = toProtocolValue(context, m_value, &protocolValue); + if (!response.isSuccess()) return response; + *result = RemoteObject::create() + .setType(RemoteObject::TypeEnum::Object) + .setValue(std::move(protocolValue)) + .build(); + } else { + v8::Isolate* isolate = context->GetIsolate(); + *result = RemoteObject::create() + .setType(RemoteObject::TypeEnum::Object) + .setClassName(toProtocolString( + isolate, m_value->GetConstructorName())) + .setDescription(m_description) + .build(); + if (m_hasSubtype) (*result)->setSubtype(m_subtype); + if (mode == WrapMode::kWithPreview) { + std::unique_ptr<ObjectPreview> previewValue; + int nameLimit = 5; + int indexLimit = 100; + buildObjectPreview(context, false, &nameLimit, &indexLimit, + &previewValue); + (*result)->setPreview(std::move(previewValue)); + } + } + return Response::OK(); + } + + void buildObjectPreview( + v8::Local<v8::Context> context, bool generatePreviewForTable, + int* nameLimit, int* indexLimit, + std::unique_ptr<ObjectPreview>* result) const override { + buildObjectPreviewInternal(context, false /* forEntry */, + generatePreviewForTable, nameLimit, indexLimit, + result); + } + + void buildEntryPreview( + v8::Local<v8::Context> context, int* nameLimit, int* indexLimit, + std::unique_ptr<ObjectPreview>* result) const override { + buildObjectPreviewInternal(context, true /* forEntry */, + false /* generatePreviewForTable */, nameLimit, + indexLimit, result); + } + + void buildPropertyPreview( + v8::Local<v8::Context> context, const String16& name, + std::unique_ptr<PropertyPreview>* result) const override { + *result = PropertyPreview::create() + .setName(name) + .setType(RemoteObject::TypeEnum::Object) + .setValue(abbreviateString( + m_description, + m_subtype == RemoteObject::SubtypeEnum::Regexp ? kMiddle + : kEnd)) + .build(); + if (m_hasSubtype) (*result)->setSubtype(m_subtype); + } + + private: + void buildObjectPreviewInternal( + v8::Local<v8::Context> context, bool forEntry, + bool generatePreviewForTable, int* nameLimit, int* indexLimit, + std::unique_ptr<ObjectPreview>* result) const { + std::unique_ptr<protocol::Array<PropertyPreview>> properties = + protocol::Array<PropertyPreview>::create(); + std::unique_ptr<protocol::Array<EntryPreview>> entriesPreview; + bool overflow = false; + + v8::Local<v8::Value> value = m_value; + while (value->IsProxy()) value = value.As<v8::Proxy>()->GetTarget(); + if (value->IsObject() && !value->IsProxy()) { + v8::Local<v8::Object> objectForPreview = value.As<v8::Object>(); + std::vector<InternalPropertyMirror> internalProperties; + getInternalPropertiesForPreview(context, objectForPreview, nameLimit, + &overflow, &internalProperties); + for (size_t i = 0; i < internalProperties.size(); ++i) { + std::unique_ptr<PropertyPreview> propertyPreview; + internalProperties[i].value->buildPropertyPreview( + context, internalProperties[i].name, &propertyPreview); + if (propertyPreview) { + properties->addItem(std::move(propertyPreview)); + } + } + + std::vector<PropertyMirror> mirrors; + if (getPropertiesForPreview(context, objectForPreview, nameLimit, + indexLimit, &overflow, &mirrors)) { + for (size_t i = 0; i < mirrors.size(); ++i) { + std::unique_ptr<PropertyPreview> preview; + std::unique_ptr<ObjectPreview> valuePreview; + if (mirrors[i].value) { + mirrors[i].value->buildPropertyPreview(context, mirrors[i].name, + &preview); + if (generatePreviewForTable) { + int tableLimit = 1000; + mirrors[i].value->buildObjectPreview(context, false, &tableLimit, + &tableLimit, &valuePreview); + } + } else { + preview = PropertyPreview::create() + .setName(mirrors[i].name) + .setType(PropertyPreview::TypeEnum::Accessor) + .build(); + } + if (valuePreview) { + preview->setValuePreview(std::move(valuePreview)); + } + properties->addItem(std::move(preview)); + } + } + + std::vector<EntryMirror> entries; + if (EntryMirror::getEntries(context, objectForPreview, 5, &overflow, + &entries)) { + if (forEntry) { + overflow = true; + } else { + entriesPreview = protocol::Array<EntryPreview>::create(); + for (const auto& entry : entries) { + std::unique_ptr<ObjectPreview> valuePreview; + entry.value->buildEntryPreview(context, nameLimit, indexLimit, + &valuePreview); + if (!valuePreview) continue; + std::unique_ptr<ObjectPreview> keyPreview; + if (entry.key) { + entry.key->buildEntryPreview(context, nameLimit, indexLimit, + &keyPreview); + if (!keyPreview) continue; + } + std::unique_ptr<EntryPreview> entryPreview = + EntryPreview::create() + .setValue(std::move(valuePreview)) + .build(); + if (keyPreview) entryPreview->setKey(std::move(keyPreview)); + entriesPreview->addItem(std::move(entryPreview)); + } + } + } + } + *result = ObjectPreview::create() + .setType(RemoteObject::TypeEnum::Object) + .setDescription(m_description) + .setOverflow(overflow) + .setProperties(std::move(properties)) + .build(); + if (m_hasSubtype) (*result)->setSubtype(m_subtype); + if (entriesPreview) (*result)->setEntries(std::move(entriesPreview)); + } + + v8::Local<v8::Object> m_value; + String16 m_description; + bool m_hasSubtype; + String16 m_subtype; +}; + +void nativeGetterCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { + v8::Local<v8::Object> data = info.Data().As<v8::Object>(); + v8::Isolate* isolate = info.GetIsolate(); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Value> name; + if (!data->GetRealNamedProperty(context, toV8String(isolate, "name")) + .ToLocal(&name)) { + return; + } + v8::Local<v8::Value> object; + if (!data->GetRealNamedProperty(context, toV8String(isolate, "object")) + .ToLocal(&object) || + !object->IsObject()) { + return; + } + v8::Local<v8::Value> value; + if (!object.As<v8::Object>()->Get(context, name).ToLocal(&value)) return; + info.GetReturnValue().Set(value); +} + +std::unique_ptr<ValueMirror> createNativeGetter(v8::Local<v8::Context> context, + v8::Local<v8::Value> object, + v8::Local<v8::Name> name) { + v8::Isolate* isolate = context->GetIsolate(); + v8::TryCatch tryCatch(isolate); + + v8::Local<v8::Object> data = v8::Object::New(isolate); + if (data->Set(context, toV8String(isolate, "name"), name).IsNothing()) { + return nullptr; + } + if (data->Set(context, toV8String(isolate, "object"), object).IsNothing()) { + return nullptr; + } + + v8::Local<v8::Function> function; + if (!v8::Function::New(context, nativeGetterCallback, data, 0, + v8::ConstructorBehavior::kThrow) + .ToLocal(&function)) { + return nullptr; + } + return ValueMirror::create(context, function); +} + +void nativeSetterCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { + if (info.Length() < 1) return; + v8::Local<v8::Object> data = info.Data().As<v8::Object>(); + v8::Isolate* isolate = info.GetIsolate(); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Value> name; + if (!data->GetRealNamedProperty(context, toV8String(isolate, "name")) + .ToLocal(&name)) { + return; + } + v8::Local<v8::Value> object; + if (!data->GetRealNamedProperty(context, toV8String(isolate, "object")) + .ToLocal(&object) || + !object->IsObject()) { + return; + } + v8::Local<v8::Value> value; + if (!object.As<v8::Object>()->Set(context, name, info[0]).IsNothing()) return; +} + +std::unique_ptr<ValueMirror> createNativeSetter(v8::Local<v8::Context> context, + v8::Local<v8::Value> object, + v8::Local<v8::Name> name) { + v8::Isolate* isolate = context->GetIsolate(); + v8::TryCatch tryCatch(isolate); + + v8::Local<v8::Object> data = v8::Object::New(isolate); + if (data->Set(context, toV8String(isolate, "name"), name).IsNothing()) { + return nullptr; + } + if (data->Set(context, toV8String(isolate, "object"), object).IsNothing()) { + return nullptr; + } + + v8::Local<v8::Function> function; + if (!v8::Function::New(context, nativeSetterCallback, data, 1, + v8::ConstructorBehavior::kThrow) + .ToLocal(&function)) { + return nullptr; + } + return ValueMirror::create(context, function); +} + +bool doesAttributeHaveObservableSideEffectOnGet(v8::Local<v8::Context> context, + v8::Local<v8::Object> object, + v8::Local<v8::Name> name) { + // TODO(dgozman): we should remove this, annotate more embedder properties as + // side-effect free, and call all getters which do not produce side effects. + if (!name->IsString()) return false; + v8::Isolate* isolate = context->GetIsolate(); + if (!name.As<v8::String>()->StringEquals(toV8String(isolate, "body"))) { + return false; + } + + v8::TryCatch tryCatch(isolate); + v8::Local<v8::Value> request; + if (context->Global() + ->GetRealNamedProperty(context, toV8String(isolate, "Request")) + .ToLocal(&request)) { + if (request->IsObject() && + object->InstanceOf(context, request.As<v8::Object>()) + .FromMaybe(false)) { + return true; + } + } + if (tryCatch.HasCaught()) tryCatch.Reset(); + + v8::Local<v8::Value> response; + if (context->Global() + ->GetRealNamedProperty(context, toV8String(isolate, "Response")) + .ToLocal(&response)) { + if (response->IsObject() && + object->InstanceOf(context, response.As<v8::Object>()) + .FromMaybe(false)) { + return true; + } + } + return false; +} +template <typename ArrayView, typename ArrayBuffer> +void addTypedArrayView(v8::Local<v8::Context> context, + v8::Local<ArrayBuffer> buffer, size_t length, + const char* name, + ValueMirror::PropertyAccumulator* accumulator) { + accumulator->Add(PropertyMirror{ + String16(name), false, false, false, true, false, + ValueMirror::create(context, ArrayView::New(buffer, 0, length)), nullptr, + nullptr, nullptr, nullptr}); +} + +template <typename ArrayBuffer> +void addTypedArrayViews(v8::Local<v8::Context> context, + v8::Local<ArrayBuffer> buffer, + ValueMirror::PropertyAccumulator* accumulator) { + // TODO(alph): these should be internal properties. + size_t length = buffer->ByteLength(); + addTypedArrayView<v8::Int8Array>(context, buffer, length, "[[Int8Array]]", + accumulator); + addTypedArrayView<v8::Uint8Array>(context, buffer, length, "[[Uint8Array]]", + accumulator); + if (buffer->ByteLength() % 2 == 0) { + addTypedArrayView<v8::Int16Array>(context, buffer, length / 2, + "[[Int16Array]]", accumulator); + } + if (buffer->ByteLength() % 4 == 0) { + addTypedArrayView<v8::Int32Array>(context, buffer, length / 4, + "[[Int32Array]]", accumulator); + } +} +} // anonymous namespace + +ValueMirror::~ValueMirror() = default; + +// static +bool ValueMirror::getProperties(v8::Local<v8::Context> context, + v8::Local<v8::Object> object, + bool ownProperties, bool accessorPropertiesOnly, + PropertyAccumulator* accumulator) { + v8::Isolate* isolate = context->GetIsolate(); + v8::TryCatch tryCatch(isolate); + v8::Local<v8::Set> set = v8::Set::New(isolate); + + v8::MicrotasksScope microtasksScope(isolate, + v8::MicrotasksScope::kDoNotRunMicrotasks); + V8InternalValueType internalType = v8InternalValueTypeFrom(context, object); + if (internalType == V8InternalValueType::kScope) { + v8::Local<v8::Value> value; + if (!object->Get(context, toV8String(isolate, "object")).ToLocal(&value) || + !value->IsObject()) { + return false; + } else { + object = value.As<v8::Object>(); + } + } + if (internalType == V8InternalValueType::kScopeList) { + if (!set->Add(context, toV8String(isolate, "length")).ToLocal(&set)) { + return false; + } + } + bool shouldSkipProto = internalType == V8InternalValueType::kScopeList; + + bool formatAccessorsAsProperties = + clientFor(context)->formatAccessorsAsProperties(object); + + if (object->IsArrayBuffer()) { + addTypedArrayViews(context, object.As<v8::ArrayBuffer>(), accumulator); + } + if (object->IsSharedArrayBuffer()) { + addTypedArrayViews(context, object.As<v8::SharedArrayBuffer>(), + accumulator); + } + + for (auto iterator = v8::debug::PropertyIterator::Create(object); + !iterator->Done(); iterator->Advance()) { + bool isOwn = iterator->is_own(); + if (!isOwn && ownProperties) break; + v8::Local<v8::Name> v8Name = iterator->name(); + v8::Maybe<bool> result = set->Has(context, v8Name); + if (result.IsNothing()) return false; + if (result.FromJust()) continue; + if (!set->Add(context, v8Name).ToLocal(&set)) return false; + + String16 name; + std::unique_ptr<ValueMirror> symbolMirror; + if (v8Name->IsString()) { + name = toProtocolString(isolate, v8Name.As<v8::String>()); + } else { + v8::Local<v8::Symbol> symbol = v8Name.As<v8::Symbol>(); + name = descriptionForSymbol(context, symbol); + symbolMirror = ValueMirror::create(context, symbol); + } + + v8::PropertyAttribute attributes; + std::unique_ptr<ValueMirror> valueMirror; + std::unique_ptr<ValueMirror> getterMirror; + std::unique_ptr<ValueMirror> setterMirror; + std::unique_ptr<ValueMirror> exceptionMirror; + bool writable = false; + bool enumerable = false; + bool configurable = false; + + bool isAccessorProperty = false; + v8::TryCatch tryCatch(isolate); + if (!iterator->attributes().To(&attributes)) { + exceptionMirror = ValueMirror::create(context, tryCatch.Exception()); + } else { + if (iterator->is_native_accessor()) { + if (iterator->has_native_getter()) { + getterMirror = createNativeGetter(context, object, v8Name); + } + if (iterator->has_native_setter()) { + setterMirror = createNativeSetter(context, object, v8Name); + } + writable = !(attributes & v8::PropertyAttribute::ReadOnly); + enumerable = !(attributes & v8::PropertyAttribute::DontEnum); + configurable = !(attributes & v8::PropertyAttribute::DontDelete); + isAccessorProperty = getterMirror || setterMirror; + } else { + v8::TryCatch tryCatch(isolate); + v8::debug::PropertyDescriptor descriptor; + if (!iterator->descriptor().To(&descriptor)) { + exceptionMirror = ValueMirror::create(context, tryCatch.Exception()); + } else { + writable = descriptor.has_writable ? descriptor.writable : false; + enumerable = + descriptor.has_enumerable ? descriptor.enumerable : false; + configurable = + descriptor.has_configurable ? descriptor.configurable : false; + if (!descriptor.value.IsEmpty()) { + valueMirror = ValueMirror::create(context, descriptor.value); + } + bool getterIsNativeFunction = false; + if (!descriptor.get.IsEmpty()) { + v8::Local<v8::Value> get = descriptor.get; + getterMirror = ValueMirror::create(context, get); + getterIsNativeFunction = + get->IsFunction() && get.As<v8::Function>()->ScriptId() == + v8::UnboundScript::kNoScriptId; + } + if (!descriptor.set.IsEmpty()) { + setterMirror = ValueMirror::create(context, descriptor.set); + } + isAccessorProperty = getterMirror || setterMirror; + bool isSymbolDescription = + object->IsSymbol() && name == "description"; + if (isSymbolDescription || + (name != "__proto__" && getterIsNativeFunction && + formatAccessorsAsProperties && + !doesAttributeHaveObservableSideEffectOnGet(context, object, + v8Name))) { + v8::TryCatch tryCatch(isolate); + v8::Local<v8::Value> value; + if (object->Get(context, v8Name).ToLocal(&value)) { + valueMirror = ValueMirror::create(context, value); + isOwn = true; + setterMirror = nullptr; + getterMirror = nullptr; + } + } + } + } + } + if (accessorPropertiesOnly && !isAccessorProperty) continue; + auto mirror = PropertyMirror{name, + writable, + configurable, + enumerable, + isOwn, + iterator->is_array_index(), + std::move(valueMirror), + std::move(getterMirror), + std::move(setterMirror), + std::move(symbolMirror), + std::move(exceptionMirror)}; + if (!accumulator->Add(std::move(mirror))) return true; + } + if (!shouldSkipProto && ownProperties && !object->IsProxy() && + !accessorPropertiesOnly) { + v8::Local<v8::Value> prototype = object->GetPrototype(); + if (prototype->IsObject()) { + accumulator->Add(PropertyMirror{String16("__proto__"), true, true, false, + true, false, + ValueMirror::create(context, prototype), + nullptr, nullptr, nullptr, nullptr}); + } + } + return true; +} + +// static +void ValueMirror::getInternalProperties( + v8::Local<v8::Context> context, v8::Local<v8::Object> object, + std::vector<InternalPropertyMirror>* mirrors) { + v8::Isolate* isolate = context->GetIsolate(); + v8::MicrotasksScope microtasksScope(isolate, + v8::MicrotasksScope::kDoNotRunMicrotasks); + v8::TryCatch tryCatch(isolate); + if (object->IsFunction()) { + v8::Local<v8::Function> function = object.As<v8::Function>(); + auto location = LocationMirror::create(function); + if (location) { + mirrors->emplace_back(InternalPropertyMirror{ + String16("[[FunctionLocation]]"), std::move(location)}); + } + if (function->IsGeneratorFunction()) { + mirrors->emplace_back(InternalPropertyMirror{ + String16("[[IsGenerator]]"), + ValueMirror::create(context, v8::True(context->GetIsolate()))}); + } + } + if (object->IsGeneratorObject()) { + auto location = LocationMirror::createForGenerator(object); + if (location) { + mirrors->emplace_back(InternalPropertyMirror{ + String16("[[GeneratorLocation]]"), std::move(location)}); + } + } + V8Debugger* debugger = + static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate)) + ->debugger(); + v8::Local<v8::Array> properties; + if (debugger->internalProperties(context, object).ToLocal(&properties)) { + for (uint32_t i = 0; i < properties->Length(); i += 2) { + v8::Local<v8::Value> name; + if (!properties->Get(context, i).ToLocal(&name) || !name->IsString()) { + tryCatch.Reset(); + continue; + } + v8::Local<v8::Value> value; + if (!properties->Get(context, i + 1).ToLocal(&value)) { + tryCatch.Reset(); + continue; + } + auto wrapper = ValueMirror::create(context, value); + if (wrapper) { + mirrors->emplace_back(InternalPropertyMirror{ + toProtocolStringWithTypeCheck(context->GetIsolate(), name), + std::move(wrapper)}); + } + } + } +} + +String16 descriptionForNode(v8::Local<v8::Context> context, + v8::Local<v8::Value> value) { + if (!value->IsObject()) return String16(); + v8::Local<v8::Object> object = value.As<v8::Object>(); + v8::Isolate* isolate = context->GetIsolate(); + v8::TryCatch tryCatch(isolate); + v8::Local<v8::Value> nodeName; + if (!object->Get(context, toV8String(isolate, "nodeName")) + .ToLocal(&nodeName)) { + return String16(); + } + String16 description; + v8::Local<v8::Function> toLowerCase = + v8::debug::GetBuiltin(isolate, v8::debug::kStringToLowerCase); + if (nodeName->IsString()) { + if (!toLowerCase->Call(context, nodeName, 0, nullptr).ToLocal(&nodeName)) + return String16(); + if (nodeName->IsString()) { + description = toProtocolString(isolate, nodeName.As<v8::String>()); + } + } + if (!description.length()) { + v8::Local<v8::Value> value; + if (!object->Get(context, toV8String(isolate, "constructor")) + .ToLocal(&value) || + !value->IsObject()) { + return String16(); + } + if (!value.As<v8::Object>() + ->Get(context, toV8String(isolate, "name")) + .ToLocal(&value) || + !value->IsString()) { + return String16(); + } + description = toProtocolString(isolate, value.As<v8::String>()); + } + v8::Local<v8::Value> nodeType; + if (!object->Get(context, toV8String(isolate, "nodeType")) + .ToLocal(&nodeType) || + !nodeType->IsInt32()) { + return description; + } + if (nodeType.As<v8::Int32>()->Value() == 1) { + v8::Local<v8::Value> idValue; + if (!object->Get(context, toV8String(isolate, "id")).ToLocal(&idValue)) { + return description; + } + if (idValue->IsString()) { + String16 id = toProtocolString(isolate, idValue.As<v8::String>()); + if (id.length()) { + description = String16::concat(description, '#', id); + } + } + v8::Local<v8::Value> classNameValue; + if (!object->Get(context, toV8String(isolate, "className")) + .ToLocal(&classNameValue)) { + return description; + } + if (classNameValue->IsString() && + classNameValue.As<v8::String>()->Length()) { + String16 classes = + toProtocolString(isolate, classNameValue.As<v8::String>()); + String16Builder output; + bool previousIsDot = false; + for (size_t i = 0; i < classes.length(); ++i) { + if (classes[i] == ' ') { + if (!previousIsDot) { + output.append('.'); + previousIsDot = true; + } + } else { + output.append(classes[i]); + previousIsDot = classes[i] == '.'; + } + } + description = String16::concat(description, '.', output.toString()); + } + } else if (nodeType.As<v8::Int32>()->Value() == 1) { + return String16::concat("<!DOCTYPE ", description, '>'); + } + return description; +} + +std::unique_ptr<ValueMirror> clientMirror(v8::Local<v8::Context> context, + v8::Local<v8::Value> value, + const String16& subtype) { + // TODO(alph): description and length retrieval should move to embedder. + if (subtype == "node") { + return v8::base::make_unique<ObjectMirror>( + value, subtype, descriptionForNode(context, value)); + } + if (subtype == "error") { + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Error, + descriptionForError(context, value.As<v8::Object>(), + ErrorType::kClient)); + } + if (subtype == "array" && value->IsObject()) { + v8::Isolate* isolate = context->GetIsolate(); + v8::TryCatch tryCatch(isolate); + v8::Local<v8::Object> object = value.As<v8::Object>(); + v8::Local<v8::Value> lengthValue; + if (object->Get(context, toV8String(isolate, "length")) + .ToLocal(&lengthValue)) { + if (lengthValue->IsInt32()) { + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Array, + descriptionForCollection(isolate, object, + lengthValue.As<v8::Int32>()->Value())); + } + } + } + return v8::base::make_unique<ObjectMirror>( + value, + descriptionForObject(context->GetIsolate(), value.As<v8::Object>())); +} + +std::unique_ptr<ValueMirror> ValueMirror::create(v8::Local<v8::Context> context, + v8::Local<v8::Value> value) { + if (value->IsNull()) { + return v8::base::make_unique<PrimitiveValueMirror>( + value, RemoteObject::TypeEnum::Object); + } + if (value->IsBoolean()) { + return v8::base::make_unique<PrimitiveValueMirror>( + value, RemoteObject::TypeEnum::Boolean); + } + if (value->IsNumber()) { + return v8::base::make_unique<NumberMirror>(value.As<v8::Number>()); + } + v8::Isolate* isolate = context->GetIsolate(); + if (value->IsString()) { + return v8::base::make_unique<PrimitiveValueMirror>( + value, RemoteObject::TypeEnum::String); + } + if (value->IsBigInt()) { + return v8::base::make_unique<BigIntMirror>(value.As<v8::BigInt>()); + } + if (value->IsSymbol()) { + return v8::base::make_unique<SymbolMirror>(value.As<v8::Symbol>()); + } + auto clientSubtype = (value->IsUndefined() || value->IsObject()) + ? clientFor(context)->valueSubtype(value) + : nullptr; + if (clientSubtype) { + String16 subtype = toString16(clientSubtype->string()); + return clientMirror(context, value, subtype); + } + if (value->IsUndefined()) { + return v8::base::make_unique<PrimitiveValueMirror>( + value, RemoteObject::TypeEnum::Undefined); + } + if (value->IsRegExp()) { + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Regexp, + descriptionForRegExp(isolate, value.As<v8::RegExp>())); + } + if (value->IsFunction()) { + return v8::base::make_unique<FunctionMirror>(value); + } + if (value->IsProxy()) { + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Proxy, "Proxy"); + } + if (value->IsDate()) { + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Date, + descriptionForDate(context, value.As<v8::Date>())); + } + if (value->IsPromise()) { + v8::Local<v8::Promise> promise = value.As<v8::Promise>(); + return v8::base::make_unique<ObjectMirror>( + promise, RemoteObject::SubtypeEnum::Promise, + descriptionForObject(isolate, promise)); + } + if (value->IsNativeError()) { + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Error, + descriptionForError(context, value.As<v8::Object>(), + ErrorType::kNative)); + } + if (value->IsMap()) { + v8::Local<v8::Map> map = value.As<v8::Map>(); + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Map, + descriptionForCollection(isolate, map, map->Size())); + } + if (value->IsSet()) { + v8::Local<v8::Set> set = value.As<v8::Set>(); + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Set, + descriptionForCollection(isolate, set, set->Size())); + } + if (value->IsWeakMap()) { + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Weakmap, + descriptionForObject(isolate, value.As<v8::Object>())); + } + if (value->IsWeakSet()) { + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Weakset, + descriptionForObject(isolate, value.As<v8::Object>())); + } + if (value->IsMapIterator() || value->IsSetIterator()) { + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Iterator, + descriptionForObject(isolate, value.As<v8::Object>())); + } + if (value->IsGeneratorObject()) { + v8::Local<v8::Object> object = value.As<v8::Object>(); + return v8::base::make_unique<ObjectMirror>( + object, RemoteObject::SubtypeEnum::Generator, + descriptionForObject(isolate, object)); + } + if (value->IsTypedArray()) { + v8::Local<v8::TypedArray> array = value.As<v8::TypedArray>(); + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Typedarray, + descriptionForCollection(isolate, array, array->Length())); + } + if (value->IsArrayBuffer()) { + v8::Local<v8::ArrayBuffer> buffer = value.As<v8::ArrayBuffer>(); + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Arraybuffer, + descriptionForCollection(isolate, buffer, buffer->ByteLength())); + } + if (value->IsSharedArrayBuffer()) { + v8::Local<v8::SharedArrayBuffer> buffer = value.As<v8::SharedArrayBuffer>(); + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Arraybuffer, + descriptionForCollection(isolate, buffer, buffer->ByteLength())); + } + if (value->IsDataView()) { + v8::Local<v8::DataView> view = value.As<v8::DataView>(); + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Dataview, + descriptionForCollection(isolate, view, view->ByteLength())); + } + V8InternalValueType internalType = + v8InternalValueTypeFrom(context, v8::Local<v8::Object>::Cast(value)); + if (value->IsArray() && internalType == V8InternalValueType::kScopeList) { + return v8::base::make_unique<ObjectMirror>( + value, "internal#scopeList", + descriptionForScopeList(value.As<v8::Array>())); + } + if (value->IsObject() && internalType == V8InternalValueType::kEntry) { + return v8::base::make_unique<ObjectMirror>( + value, "internal#entry", + descriptionForEntry(context, value.As<v8::Object>())); + } + if (value->IsObject() && internalType == V8InternalValueType::kScope) { + return v8::base::make_unique<ObjectMirror>( + value, "internal#scope", + descriptionForScope(context, value.As<v8::Object>())); + } + size_t length = 0; + if (value->IsArray() || isArrayLike(context, value, &length)) { + length = value->IsArray() ? value.As<v8::Array>()->Length() : length; + return v8::base::make_unique<ObjectMirror>( + value, RemoteObject::SubtypeEnum::Array, + descriptionForCollection(isolate, value.As<v8::Object>(), length)); + } + if (value->IsObject()) { + return v8::base::make_unique<ObjectMirror>( + value, descriptionForObject(isolate, value.As<v8::Object>())); + } + return nullptr; +} +} // namespace v8_inspector |