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