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