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
5 #include "src/inspector/custom-preview.h"
6
7 #include "../../third_party/inspector_protocol/crdtp/json.h"
8 #include "include/v8-container.h"
9 #include "include/v8-context.h"
10 #include "include/v8-function.h"
11 #include "include/v8-json.h"
12 #include "include/v8-microtask-queue.h"
13 #include "src/debug/debug-interface.h"
14 #include "src/inspector/injected-script.h"
15 #include "src/inspector/inspected-context.h"
16 #include "src/inspector/string-util.h"
17 #include "src/inspector/v8-console-message.h"
18 #include "src/inspector/v8-inspector-impl.h"
19 #include "src/inspector/v8-stack-trace-impl.h"
20
21 namespace v8_inspector {
22
23 using protocol::Runtime::CustomPreview;
24
25 namespace {
reportError(v8::Local<v8::Context> context,const v8::TryCatch & tryCatch)26 void reportError(v8::Local<v8::Context> context, const v8::TryCatch& tryCatch) {
27 DCHECK(tryCatch.HasCaught());
28 v8::Isolate* isolate = context->GetIsolate();
29 V8InspectorImpl* inspector =
30 static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate));
31 int contextId = InspectedContext::contextId(context);
32 int groupId = inspector->contextGroupId(contextId);
33 v8::Local<v8::String> message = tryCatch.Message()->Get();
34 v8::Local<v8::String> prefix =
35 toV8String(isolate, "Custom Formatter Failed: ");
36 message = v8::String::Concat(isolate, prefix, message);
37 std::vector<v8::Local<v8::Value>> arguments;
38 arguments.push_back(message);
39 V8ConsoleMessageStorage* storage =
40 inspector->ensureConsoleMessageStorage(groupId);
41 if (!storage) return;
42 storage->addMessage(V8ConsoleMessage::createForConsoleAPI(
43 context, contextId, groupId, inspector,
44 inspector->client()->currentTimeMS(), ConsoleAPIType::kError, arguments,
45 String16(), nullptr));
46 }
47
reportError(v8::Local<v8::Context> context,const v8::TryCatch & tryCatch,const String16 & message)48 void reportError(v8::Local<v8::Context> context, const v8::TryCatch& tryCatch,
49 const String16& message) {
50 v8::Isolate* isolate = context->GetIsolate();
51 isolate->ThrowException(toV8String(isolate, message));
52 reportError(context, tryCatch);
53 }
54
getInjectedScript(v8::Local<v8::Context> context,int sessionId)55 InjectedScript* getInjectedScript(v8::Local<v8::Context> context,
56 int sessionId) {
57 v8::Isolate* isolate = context->GetIsolate();
58 V8InspectorImpl* inspector =
59 static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate));
60 InspectedContext* inspectedContext =
61 inspector->getContext(InspectedContext::contextId(context));
62 if (!inspectedContext) return nullptr;
63 return inspectedContext->getInjectedScript(sessionId);
64 }
65
substituteObjectTags(int sessionId,const String16 & groupName,v8::Local<v8::Context> context,v8::Local<v8::Array> jsonML,int maxDepth)66 bool substituteObjectTags(int sessionId, const String16& groupName,
67 v8::Local<v8::Context> context,
68 v8::Local<v8::Array> jsonML, int maxDepth) {
69 if (!jsonML->Length()) return true;
70 v8::Isolate* isolate = context->GetIsolate();
71 v8::TryCatch tryCatch(isolate);
72
73 if (maxDepth <= 0) {
74 reportError(context, tryCatch,
75 "Too deep hierarchy of inlined custom previews");
76 return false;
77 }
78
79 v8::Local<v8::Value> firstValue;
80 if (!jsonML->Get(context, 0).ToLocal(&firstValue)) {
81 reportError(context, tryCatch);
82 return false;
83 }
84 v8::Local<v8::String> objectLiteral = toV8String(isolate, "object");
85 if (jsonML->Length() == 2 && firstValue->IsString() &&
86 firstValue.As<v8::String>()->StringEquals(objectLiteral)) {
87 v8::Local<v8::Value> attributesValue;
88 if (!jsonML->Get(context, 1).ToLocal(&attributesValue)) {
89 reportError(context, tryCatch);
90 return false;
91 }
92 if (!attributesValue->IsObject()) {
93 reportError(context, tryCatch, "attributes should be an Object");
94 return false;
95 }
96 v8::Local<v8::Object> attributes = attributesValue.As<v8::Object>();
97 v8::Local<v8::Value> originValue;
98 if (!attributes->Get(context, objectLiteral).ToLocal(&originValue)) {
99 reportError(context, tryCatch);
100 return false;
101 }
102 if (originValue->IsUndefined()) {
103 reportError(context, tryCatch,
104 "obligatory attribute \"object\" isn't specified");
105 return false;
106 }
107
108 v8::Local<v8::Value> configValue;
109 if (!attributes->Get(context, toV8String(isolate, "config"))
110 .ToLocal(&configValue)) {
111 reportError(context, tryCatch);
112 return false;
113 }
114
115 InjectedScript* injectedScript = getInjectedScript(context, sessionId);
116 if (!injectedScript) {
117 reportError(context, tryCatch, "cannot find context with specified id");
118 return false;
119 }
120 std::unique_ptr<protocol::Runtime::RemoteObject> wrapper;
121 protocol::Response response =
122 injectedScript->wrapObject(originValue, groupName, WrapMode::kNoPreview,
123 configValue, maxDepth - 1, &wrapper);
124 if (!response.IsSuccess() || !wrapper) {
125 reportError(context, tryCatch, "cannot wrap value");
126 return false;
127 }
128 std::vector<uint8_t> json;
129 v8_crdtp::json::ConvertCBORToJSON(v8_crdtp::SpanFrom(wrapper->Serialize()),
130 &json);
131 v8::Local<v8::Value> jsonWrapper;
132 v8_inspector::StringView serialized(json.data(), json.size());
133 if (!v8::JSON::Parse(context, toV8String(isolate, serialized))
134 .ToLocal(&jsonWrapper)) {
135 reportError(context, tryCatch, "cannot wrap value");
136 return false;
137 }
138 if (jsonML->Set(context, 1, jsonWrapper).IsNothing()) {
139 reportError(context, tryCatch);
140 return false;
141 }
142 } else {
143 for (uint32_t i = 0; i < jsonML->Length(); ++i) {
144 v8::Local<v8::Value> value;
145 if (!jsonML->Get(context, i).ToLocal(&value)) {
146 reportError(context, tryCatch);
147 return false;
148 }
149 if (value->IsArray() && value.As<v8::Array>()->Length() > 0 &&
150 !substituteObjectTags(sessionId, groupName, context,
151 value.As<v8::Array>(), maxDepth - 1)) {
152 return false;
153 }
154 }
155 }
156 return true;
157 }
158
bodyCallback(const v8::FunctionCallbackInfo<v8::Value> & info)159 void bodyCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
160 v8::Isolate* isolate = info.GetIsolate();
161 v8::TryCatch tryCatch(isolate);
162 v8::Local<v8::Context> context = isolate->GetCurrentContext();
163 v8::Local<v8::Object> bodyConfig = info.Data().As<v8::Object>();
164
165 v8::Local<v8::Value> objectValue;
166 if (!bodyConfig->Get(context, toV8String(isolate, "object"))
167 .ToLocal(&objectValue)) {
168 reportError(context, tryCatch);
169 return;
170 }
171 if (!objectValue->IsObject()) {
172 reportError(context, tryCatch, "object should be an Object");
173 return;
174 }
175 v8::Local<v8::Object> object = objectValue.As<v8::Object>();
176
177 v8::Local<v8::Value> formatterValue;
178 if (!bodyConfig->Get(context, toV8String(isolate, "formatter"))
179 .ToLocal(&formatterValue)) {
180 reportError(context, tryCatch);
181 return;
182 }
183 if (!formatterValue->IsObject()) {
184 reportError(context, tryCatch, "formatter should be an Object");
185 return;
186 }
187 v8::Local<v8::Object> formatter = formatterValue.As<v8::Object>();
188
189 v8::Local<v8::Value> bodyValue;
190 if (!formatter->Get(context, toV8String(isolate, "body"))
191 .ToLocal(&bodyValue)) {
192 reportError(context, tryCatch);
193 return;
194 }
195 if (!bodyValue->IsFunction()) {
196 reportError(context, tryCatch, "body should be a Function");
197 return;
198 }
199 v8::Local<v8::Function> bodyFunction = bodyValue.As<v8::Function>();
200
201 v8::Local<v8::Value> configValue;
202 if (!bodyConfig->Get(context, toV8String(isolate, "config"))
203 .ToLocal(&configValue)) {
204 reportError(context, tryCatch);
205 return;
206 }
207
208 v8::Local<v8::Value> sessionIdValue;
209 if (!bodyConfig->Get(context, toV8String(isolate, "sessionId"))
210 .ToLocal(&sessionIdValue)) {
211 reportError(context, tryCatch);
212 return;
213 }
214 if (!sessionIdValue->IsInt32()) {
215 reportError(context, tryCatch, "sessionId should be an Int32");
216 return;
217 }
218
219 v8::Local<v8::Value> groupNameValue;
220 if (!bodyConfig->Get(context, toV8String(isolate, "groupName"))
221 .ToLocal(&groupNameValue)) {
222 reportError(context, tryCatch);
223 return;
224 }
225 if (!groupNameValue->IsString()) {
226 reportError(context, tryCatch, "groupName should be a string");
227 return;
228 }
229
230 v8::Local<v8::Value> formattedValue;
231 v8::Local<v8::Value> args[] = {object, configValue};
232 if (!bodyFunction->Call(context, formatter, 2, args)
233 .ToLocal(&formattedValue)) {
234 reportError(context, tryCatch);
235 return;
236 }
237 if (!formattedValue->IsArray()) {
238 reportError(context, tryCatch, "body should return an Array");
239 return;
240 }
241 v8::Local<v8::Array> jsonML = formattedValue.As<v8::Array>();
242 if (jsonML->Length() &&
243 !substituteObjectTags(
244 sessionIdValue.As<v8::Int32>()->Value(),
245 toProtocolString(isolate, groupNameValue.As<v8::String>()), context,
246 jsonML, kMaxCustomPreviewDepth)) {
247 return;
248 }
249 info.GetReturnValue().Set(jsonML);
250 }
251 } // anonymous namespace
252
generateCustomPreview(int sessionId,const String16 & groupName,v8::Local<v8::Object> object,v8::MaybeLocal<v8::Value> maybeConfig,int maxDepth,std::unique_ptr<CustomPreview> * preview)253 void generateCustomPreview(int sessionId, const String16& groupName,
254 v8::Local<v8::Object> object,
255 v8::MaybeLocal<v8::Value> maybeConfig, int maxDepth,
256 std::unique_ptr<CustomPreview>* preview) {
257 v8::Local<v8::Context> context;
258 if (!object->GetCreationContext().ToLocal(&context)) {
259 return;
260 }
261
262 v8::Isolate* isolate = context->GetIsolate();
263 v8::MicrotasksScope microtasksScope(isolate,
264 v8::MicrotasksScope::kDoNotRunMicrotasks);
265 v8::TryCatch tryCatch(isolate);
266
267 v8::Local<v8::Value> configValue;
268 if (!maybeConfig.ToLocal(&configValue)) configValue = v8::Undefined(isolate);
269
270 v8::Local<v8::Object> global = context->Global();
271 v8::Local<v8::Value> formattersValue;
272 if (!global->Get(context, toV8String(isolate, "devtoolsFormatters"))
273 .ToLocal(&formattersValue)) {
274 reportError(context, tryCatch);
275 return;
276 }
277 if (!formattersValue->IsArray()) return;
278 v8::Local<v8::Array> formatters = formattersValue.As<v8::Array>();
279 v8::Local<v8::String> headerLiteral = toV8String(isolate, "header");
280 v8::Local<v8::String> hasBodyLiteral = toV8String(isolate, "hasBody");
281 for (uint32_t i = 0; i < formatters->Length(); ++i) {
282 v8::Local<v8::Value> formatterValue;
283 if (!formatters->Get(context, i).ToLocal(&formatterValue)) {
284 reportError(context, tryCatch);
285 return;
286 }
287 if (!formatterValue->IsObject()) {
288 reportError(context, tryCatch, "formatter should be an Object");
289 return;
290 }
291 v8::Local<v8::Object> formatter = formatterValue.As<v8::Object>();
292
293 v8::Local<v8::Value> headerValue;
294 if (!formatter->Get(context, headerLiteral).ToLocal(&headerValue)) {
295 reportError(context, tryCatch);
296 return;
297 }
298 if (!headerValue->IsFunction()) {
299 reportError(context, tryCatch, "header should be a Function");
300 return;
301 }
302 v8::Local<v8::Function> headerFunction = headerValue.As<v8::Function>();
303
304 v8::Local<v8::Value> formattedValue;
305 v8::Local<v8::Value> args[] = {object, configValue};
306 if (!headerFunction->Call(context, formatter, 2, args)
307 .ToLocal(&formattedValue)) {
308 reportError(context, tryCatch);
309 return;
310 }
311 if (!formattedValue->IsArray()) continue;
312 v8::Local<v8::Array> jsonML = formattedValue.As<v8::Array>();
313
314 v8::Local<v8::Value> hasBodyFunctionValue;
315 if (!formatter->Get(context, hasBodyLiteral)
316 .ToLocal(&hasBodyFunctionValue)) {
317 reportError(context, tryCatch);
318 return;
319 }
320 if (!hasBodyFunctionValue->IsFunction()) continue;
321 v8::Local<v8::Function> hasBodyFunction =
322 hasBodyFunctionValue.As<v8::Function>();
323 v8::Local<v8::Value> hasBodyValue;
324 if (!hasBodyFunction->Call(context, formatter, 2, args)
325 .ToLocal(&hasBodyValue)) {
326 reportError(context, tryCatch);
327 return;
328 }
329 bool hasBody = hasBodyValue->ToBoolean(isolate)->Value();
330
331 if (jsonML->Length() && !substituteObjectTags(sessionId, groupName, context,
332 jsonML, maxDepth)) {
333 return;
334 }
335
336 v8::Local<v8::String> header;
337 if (!v8::JSON::Stringify(context, jsonML).ToLocal(&header)) {
338 reportError(context, tryCatch);
339 return;
340 }
341
342 v8::Local<v8::Function> bodyFunction;
343 if (hasBody) {
344 v8::Local<v8::Object> bodyConfig = v8::Object::New(isolate);
345 if (bodyConfig
346 ->CreateDataProperty(context, toV8String(isolate, "sessionId"),
347 v8::Integer::New(isolate, sessionId))
348 .IsNothing()) {
349 reportError(context, tryCatch);
350 return;
351 }
352 if (bodyConfig
353 ->CreateDataProperty(context, toV8String(isolate, "formatter"),
354 formatter)
355 .IsNothing()) {
356 reportError(context, tryCatch);
357 return;
358 }
359 if (bodyConfig
360 ->CreateDataProperty(context, toV8String(isolate, "groupName"),
361 toV8String(isolate, groupName))
362 .IsNothing()) {
363 reportError(context, tryCatch);
364 return;
365 }
366 if (bodyConfig
367 ->CreateDataProperty(context, toV8String(isolate, "config"),
368 configValue)
369 .IsNothing()) {
370 reportError(context, tryCatch);
371 return;
372 }
373 if (bodyConfig
374 ->CreateDataProperty(context, toV8String(isolate, "object"),
375 object)
376 .IsNothing()) {
377 reportError(context, tryCatch);
378 return;
379 }
380 if (!v8::Function::New(context, bodyCallback, bodyConfig)
381 .ToLocal(&bodyFunction)) {
382 reportError(context, tryCatch);
383 return;
384 }
385 }
386 *preview = CustomPreview::create()
387 .setHeader(toProtocolString(isolate, header))
388 .build();
389 if (!bodyFunction.IsEmpty()) {
390 InjectedScript* injectedScript = getInjectedScript(context, sessionId);
391 if (!injectedScript) {
392 reportError(context, tryCatch, "cannot find context with specified id");
393 return;
394 }
395 (*preview)->setBodyGetterId(
396 injectedScript->bindObject(bodyFunction, groupName));
397 }
398 return;
399 }
400 }
401 } // namespace v8_inspector
402