• 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 "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