v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
custom-preview.cc
Go to the documentation of this file.
1// Copyright 2018 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#include "../../third_party/inspector_protocol/crdtp/json.h"
10#include "include/v8-function.h"
11#include "include/v8-json.h"
21
22namespace v8_inspector {
23
24using protocol::Runtime::CustomPreview;
25
26namespace {
27void reportError(v8::Local<v8::Context> context, const v8::TryCatch& tryCatch) {
28 DCHECK(tryCatch.HasCaught());
29 v8::Isolate* isolate = context->GetIsolate();
30 V8InspectorImpl* inspector =
31 static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate));
32 int contextId = InspectedContext::contextId(context);
33 int groupId = inspector->contextGroupId(contextId);
34 v8::Local<v8::String> message = toV8String(isolate, "<no message available>");
35 if (!tryCatch.Message().IsEmpty()) message = tryCatch.Message()->Get();
37 toV8String(isolate, "Custom Formatter Failed: ");
38 message = v8::String::Concat(isolate, prefix, message);
39 v8::LocalVector<v8::Value> arguments(isolate);
40 arguments.push_back(message);
41 V8ConsoleMessageStorage* storage =
42 inspector->ensureConsoleMessageStorage(groupId);
43 if (!storage) return;
44 storage->addMessage(V8ConsoleMessage::createForConsoleAPI(
45 context, contextId, groupId, inspector,
46 inspector->client()->currentTimeMS(), ConsoleAPIType::kError,
47 {arguments.begin(), arguments.end()}, String16(), nullptr));
48}
49
50void reportError(v8::Local<v8::Context> context, const v8::TryCatch& tryCatch,
51 const String16& message) {
52 v8::Isolate* isolate = context->GetIsolate();
53 isolate->ThrowException(toV8String(isolate, message));
54 reportError(context, tryCatch);
55}
56
57InjectedScript* getInjectedScript(v8::Local<v8::Context> context,
58 int sessionId) {
59 v8::Isolate* isolate = context->GetIsolate();
60 V8InspectorImpl* inspector =
61 static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate));
62 InspectedContext* inspectedContext =
63 inspector->getContext(InspectedContext::contextId(context));
64 if (!inspectedContext) return nullptr;
65 return inspectedContext->getInjectedScript(sessionId);
66}
67
68bool substituteObjectTags(int sessionId, const String16& groupName,
70 v8::Local<v8::Array> jsonML, int maxDepth) {
71 if (!jsonML->Length()) return true;
72 v8::Isolate* isolate = context->GetIsolate();
73 v8::TryCatch tryCatch(isolate);
74
75 if (maxDepth <= 0) {
76 reportError(context, tryCatch,
77 "Too deep hierarchy of inlined custom previews");
78 return false;
79 }
80
81 v8::Local<v8::Value> firstValue;
82 if (!jsonML->Get(context, 0).ToLocal(&firstValue)) {
83 reportError(context, tryCatch);
84 return false;
85 }
86 v8::Local<v8::String> objectLiteral = toV8String(isolate, "object");
87 if (jsonML->Length() == 2 && firstValue->IsString() &&
88 firstValue.As<v8::String>()->StringEquals(objectLiteral)) {
89 v8::Local<v8::Value> attributesValue;
90 if (!jsonML->Get(context, 1).ToLocal(&attributesValue)) {
91 reportError(context, tryCatch);
92 return false;
93 }
94 if (!attributesValue->IsObject()) {
95 reportError(context, tryCatch, "attributes should be an Object");
96 return false;
97 }
98 v8::Local<v8::Object> attributes = attributesValue.As<v8::Object>();
99 v8::Local<v8::Value> originValue;
100 if (!attributes->Get(context, objectLiteral).ToLocal(&originValue)) {
101 reportError(context, tryCatch);
102 return false;
103 }
104 if (originValue->IsUndefined()) {
105 reportError(context, tryCatch,
106 "obligatory attribute \"object\" isn't specified");
107 return false;
108 }
109
110 v8::Local<v8::Value> configValue;
111 if (!attributes->Get(context, toV8String(isolate, "config"))
112 .ToLocal(&configValue)) {
113 reportError(context, tryCatch);
114 return false;
115 }
116
117 InjectedScript* injectedScript = getInjectedScript(context, sessionId);
118 if (!injectedScript) {
119 reportError(context, tryCatch, "cannot find context with specified id");
120 return false;
121 }
122 std::unique_ptr<protocol::Runtime::RemoteObject> wrapper;
123 protocol::Response response = injectedScript->wrapObject(
124 originValue, groupName, WrapOptions({WrapMode::kIdOnly}), configValue,
125 maxDepth - 1, &wrapper);
126 if (!response.IsSuccess() || !wrapper) {
127 reportError(context, tryCatch, "cannot wrap value");
128 return false;
129 }
130 std::vector<uint8_t> json;
131 v8_crdtp::json::ConvertCBORToJSON(v8_crdtp::SpanFrom(wrapper->Serialize()),
132 &json);
133 v8::Local<v8::Value> jsonWrapper;
134 v8_inspector::StringView serialized(json.data(), json.size());
135 if (!v8::JSON::Parse(context, toV8String(isolate, serialized))
136 .ToLocal(&jsonWrapper)) {
137 reportError(context, tryCatch, "cannot wrap value");
138 return false;
139 }
140 if (jsonML->Set(context, 1, jsonWrapper).IsNothing()) {
141 reportError(context, tryCatch);
142 return false;
143 }
144 } else {
145 for (uint32_t i = 0; i < jsonML->Length(); ++i) {
147 if (!jsonML->Get(context, i).ToLocal(&value)) {
148 reportError(context, tryCatch);
149 return false;
150 }
151 if (value->IsArray() && value.As<v8::Array>()->Length() > 0 &&
152 !substituteObjectTags(sessionId, groupName, context,
153 value.As<v8::Array>(), maxDepth - 1)) {
154 return false;
155 }
156 }
157 }
158 return true;
159}
160
161void bodyCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
162 v8::Isolate* isolate = info.GetIsolate();
163 v8::TryCatch tryCatch(isolate);
164 v8::Local<v8::Context> context = isolate->GetCurrentContext();
165 v8::Local<v8::Object> bodyConfig = info.Data().As<v8::Object>();
166
167 v8::Local<v8::Value> objectValue;
168 if (!bodyConfig->Get(context, toV8String(isolate, "object"))
169 .ToLocal(&objectValue)) {
170 reportError(context, tryCatch);
171 return;
172 }
173 if (!objectValue->IsObject()) {
174 reportError(context, tryCatch, "object should be an Object");
175 return;
176 }
177 v8::Local<v8::Object> object = objectValue.As<v8::Object>();
178
179 v8::Local<v8::Value> formatterValue;
180 if (!bodyConfig->Get(context, toV8String(isolate, "formatter"))
181 .ToLocal(&formatterValue)) {
182 reportError(context, tryCatch);
183 return;
184 }
185 if (!formatterValue->IsObject()) {
186 reportError(context, tryCatch, "formatter should be an Object");
187 return;
188 }
189 v8::Local<v8::Object> formatter = formatterValue.As<v8::Object>();
190
191 v8::Local<v8::Value> bodyValue;
192 if (!formatter->Get(context, toV8String(isolate, "body"))
193 .ToLocal(&bodyValue)) {
194 reportError(context, tryCatch);
195 return;
196 }
197 if (!bodyValue->IsFunction()) {
198 reportError(context, tryCatch, "body should be a Function");
199 return;
200 }
201 v8::Local<v8::Function> bodyFunction = bodyValue.As<v8::Function>();
202
203 v8::Local<v8::Value> configValue;
204 if (!bodyConfig->Get(context, toV8String(isolate, "config"))
205 .ToLocal(&configValue)) {
206 reportError(context, tryCatch);
207 return;
208 }
209
210 v8::Local<v8::Value> sessionIdValue;
211 if (!bodyConfig->Get(context, toV8String(isolate, "sessionId"))
212 .ToLocal(&sessionIdValue)) {
213 reportError(context, tryCatch);
214 return;
215 }
216 if (!sessionIdValue->IsInt32()) {
217 reportError(context, tryCatch, "sessionId should be an Int32");
218 return;
219 }
220
221 v8::Local<v8::Value> groupNameValue;
222 if (!bodyConfig->Get(context, toV8String(isolate, "groupName"))
223 .ToLocal(&groupNameValue)) {
224 reportError(context, tryCatch);
225 return;
226 }
227 if (!groupNameValue->IsString()) {
228 reportError(context, tryCatch, "groupName should be a string");
229 return;
230 }
231
232 v8::Local<v8::Value> formattedValue;
233 v8::Local<v8::Value> args[] = {object, configValue};
234 if (!bodyFunction->Call(context, formatter, 2, args)
235 .ToLocal(&formattedValue)) {
236 reportError(context, tryCatch);
237 return;
238 }
239 if (formattedValue->IsNull()) {
240 info.GetReturnValue().Set(formattedValue);
241 return;
242 }
243 if (!formattedValue->IsArray()) {
244 reportError(context, tryCatch, "body should return an Array");
245 return;
246 }
247 v8::Local<v8::Array> jsonML = formattedValue.As<v8::Array>();
248 if (jsonML->Length() &&
249 !substituteObjectTags(
250 sessionIdValue.As<v8::Int32>()->Value(),
251 toProtocolString(isolate, groupNameValue.As<v8::String>()), context,
252 jsonML, kMaxCustomPreviewDepth)) {
253 return;
254 }
255 info.GetReturnValue().Set(jsonML);
256}
257} // anonymous namespace
258
259void generateCustomPreview(v8::Isolate* isolate, int sessionId,
260 const String16& groupName,
262 v8::MaybeLocal<v8::Value> maybeConfig, int maxDepth,
263 std::unique_ptr<CustomPreview>* preview) {
265 if (!object->GetCreationContext(isolate).ToLocal(&context)) {
266 return;
267 }
268
269 v8::MicrotasksScope microtasksScope(context,
271 v8::TryCatch tryCatch(isolate);
272
273 v8::Local<v8::Value> configValue;
274 if (!maybeConfig.ToLocal(&configValue)) configValue = v8::Undefined(isolate);
275
276 v8::Local<v8::Object> global = context->Global();
277 v8::Local<v8::Value> formattersValue;
278 if (!global->Get(context, toV8String(isolate, "devtoolsFormatters"))
279 .ToLocal(&formattersValue)) {
280 reportError(context, tryCatch);
281 return;
282 }
283 if (!formattersValue->IsArray()) return;
284 v8::Local<v8::Array> formatters = formattersValue.As<v8::Array>();
285 v8::Local<v8::String> headerLiteral = toV8String(isolate, "header");
286 v8::Local<v8::String> hasBodyLiteral = toV8String(isolate, "hasBody");
287 for (uint32_t i = 0; i < formatters->Length(); ++i) {
288 v8::Local<v8::Value> formatterValue;
289 if (!formatters->Get(context, i).ToLocal(&formatterValue)) {
290 reportError(context, tryCatch);
291 return;
292 }
293 if (!formatterValue->IsObject()) {
294 reportError(context, tryCatch, "formatter should be an Object");
295 return;
296 }
297 v8::Local<v8::Object> formatter = formatterValue.As<v8::Object>();
298
299 v8::Local<v8::Value> headerValue;
300 if (!formatter->Get(context, headerLiteral).ToLocal(&headerValue)) {
301 reportError(context, tryCatch);
302 return;
303 }
304 if (!headerValue->IsFunction()) {
305 reportError(context, tryCatch, "header should be a Function");
306 return;
307 }
308 v8::Local<v8::Function> headerFunction = headerValue.As<v8::Function>();
309
310 v8::Local<v8::Value> formattedValue;
311 v8::Local<v8::Value> args[] = {object, configValue};
312 if (!headerFunction->Call(context, formatter, 2, args)
313 .ToLocal(&formattedValue)) {
314 reportError(context, tryCatch);
315 return;
316 }
317 if (!formattedValue->IsArray()) continue;
318 v8::Local<v8::Array> jsonML = formattedValue.As<v8::Array>();
319
320 v8::Local<v8::Value> hasBodyFunctionValue;
321 if (!formatter->Get(context, hasBodyLiteral)
322 .ToLocal(&hasBodyFunctionValue)) {
323 reportError(context, tryCatch);
324 return;
325 }
326 if (!hasBodyFunctionValue->IsFunction()) continue;
327 v8::Local<v8::Function> hasBodyFunction =
328 hasBodyFunctionValue.As<v8::Function>();
329 v8::Local<v8::Value> hasBodyValue;
330 if (!hasBodyFunction->Call(context, formatter, 2, args)
331 .ToLocal(&hasBodyValue)) {
332 reportError(context, tryCatch);
333 return;
334 }
335 bool hasBody = hasBodyValue->ToBoolean(isolate)->Value();
336
337 if (jsonML->Length() && !substituteObjectTags(sessionId, groupName, context,
338 jsonML, maxDepth)) {
339 return;
340 }
341
343 if (!v8::JSON::Stringify(context, jsonML).ToLocal(&header)) {
344 reportError(context, tryCatch);
345 return;
346 }
347
348 v8::Local<v8::Function> bodyFunction;
349 if (hasBody) {
350 v8::Local<v8::Object> bodyConfig = v8::Object::New(isolate);
351 if (bodyConfig
352 ->CreateDataProperty(context, toV8String(isolate, "sessionId"),
353 v8::Integer::New(isolate, sessionId))
354 .IsNothing()) {
355 reportError(context, tryCatch);
356 return;
357 }
358 if (bodyConfig
359 ->CreateDataProperty(context, toV8String(isolate, "formatter"),
360 formatter)
361 .IsNothing()) {
362 reportError(context, tryCatch);
363 return;
364 }
365 if (bodyConfig
366 ->CreateDataProperty(context, toV8String(isolate, "groupName"),
367 toV8String(isolate, groupName))
368 .IsNothing()) {
369 reportError(context, tryCatch);
370 return;
371 }
372 if (bodyConfig
373 ->CreateDataProperty(context, toV8String(isolate, "config"),
374 configValue)
375 .IsNothing()) {
376 reportError(context, tryCatch);
377 return;
378 }
379 if (bodyConfig
380 ->CreateDataProperty(context, toV8String(isolate, "object"),
381 object)
382 .IsNothing()) {
383 reportError(context, tryCatch);
384 return;
385 }
386 if (!v8::Function::New(context, bodyCallback, bodyConfig)
387 .ToLocal(&bodyFunction)) {
388 reportError(context, tryCatch);
389 return;
390 }
391 }
392 *preview = CustomPreview::create()
393 .setHeader(toProtocolString(isolate, header))
394 .build();
395 if (!bodyFunction.IsEmpty()) {
396 InjectedScript* injectedScript = getInjectedScript(context, sessionId);
397 if (!injectedScript) {
398 reportError(context, tryCatch, "cannot find context with specified id");
399 return;
400 }
401 (*preview)->setBodyGetterId(
402 injectedScript->bindObject(bodyFunction, groupName));
403 }
404 return;
405 }
406}
407} // namespace v8_inspector
uint32_t Length() const
Definition api.cc:8187
static MaybeLocal< Function > New(Local< Context > context, FunctionCallback callback, Local< Value > data=Local< Value >(), int length=0, ConstructorBehavior behavior=ConstructorBehavior::kAllow, SideEffectType side_effect_type=SideEffectType::kHasSideEffect)
Definition api.cc:5348
int32_t Value() const
Definition api.cc:6258
static Local< Integer > New(Isolate *isolate, int32_t value)
Definition api.cc:9568
Local< Value > ThrowException(Local< Value > exception)
Definition api.cc:9793
static V8_WARN_UNUSED_RESULT MaybeLocal< String > Stringify(Local< Context > context, Local< Value > json_object, Local< String > gap=Local< String >())
Definition api.cc:3188
static V8_WARN_UNUSED_RESULT MaybeLocal< Value > Parse(Local< Context > context, Local< String > json_string)
Definition api.cc:3172
V8_INLINE Local< S > As() const
V8_WARN_UNUSED_RESULT V8_INLINE bool ToLocal(Local< S > *out) const
static Local< Object > New(Isolate *isolate)
Definition api.cc:7756
V8_WARN_UNUSED_RESULT Maybe< bool > Set(Local< Context > context, Local< Value > key, Local< Value > value)
Definition api.cc:4295
bool StringEquals(Local< String > str) const
Definition api.cc:7745
static Local< String > Concat(Isolate *isolate, Local< String > left, Local< String > right)
Definition api.cc:7613
Local< v8::Message > Message() const
Definition api.cc:2829
bool HasCaught() const
Definition api.cc:2781
String16 bindObject(v8::Local< v8::Value >, const String16 &groupName)
static std::unique_ptr< V8ConsoleMessage > createForConsoleAPI(v8::Local< v8::Context > v8Context, int contextId, int groupId, V8InspectorImpl *inspector, double timestamp, ConsoleAPIType, v8::MemorySpan< const v8::Local< v8::Value > > arguments, const String16 &consoleContext, std::unique_ptr< V8StackTraceImpl >)
base::Vector< const DirectHandle< Object > > args
Definition execution.cc:74
TNode< Context > context
v8_inspector::V8Inspector * GetInspector(Isolate *isolate)
void generateCustomPreview(v8::Isolate *isolate, int sessionId, const String16 &groupName, v8::Local< v8::Object > object, v8::MaybeLocal< v8::Value > maybeConfig, int maxDepth, std::unique_ptr< CustomPreview > *preview)
String16 toProtocolString(v8::Isolate *isolate, v8::Local< v8::String > value)
const int kMaxCustomPreviewDepth
v8::Local< v8::String > toV8String(v8::Isolate *isolate, const String16 &string)
bool ToLocal(v8::internal::MaybeDirectHandle< v8::internal::Object > maybe, Local< T > *local)
Definition api.h:303
V8_INLINE Local< Primitive > Undefined(Isolate *isolate)
#define DCHECK(condition)
Definition logging.h:482
std::unique_ptr< ValueMirror > value