• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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