1/* 2 * Copyright (C) 2012 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31#include "src/inspector/injected-script.h" 32 33#include "src/inspector/injected-script-native.h" 34#include "src/inspector/injected-script-source.h" 35#include "src/inspector/inspected-context.h" 36#include "src/inspector/protocol/Protocol.h" 37#include "src/inspector/remote-object-id.h" 38#include "src/inspector/string-util.h" 39#include "src/inspector/v8-console.h" 40#include "src/inspector/v8-function-call.h" 41#include "src/inspector/v8-injected-script-host.h" 42#include "src/inspector/v8-inspector-impl.h" 43#include "src/inspector/v8-inspector-session-impl.h" 44#include "src/inspector/v8-stack-trace-impl.h" 45#include "src/inspector/v8-value-copier.h" 46 47#include "include/v8-inspector.h" 48 49namespace v8_inspector { 50 51using protocol::Array; 52using protocol::Runtime::PropertyDescriptor; 53using protocol::Runtime::InternalPropertyDescriptor; 54using protocol::Runtime::RemoteObject; 55using protocol::Maybe; 56 57std::unique_ptr<InjectedScript> InjectedScript::create( 58 InspectedContext* inspectedContext) { 59 v8::Isolate* isolate = inspectedContext->isolate(); 60 v8::HandleScope handles(isolate); 61 v8::Local<v8::Context> context = inspectedContext->context(); 62 v8::Context::Scope scope(context); 63 64 std::unique_ptr<InjectedScriptNative> injectedScriptNative( 65 new InjectedScriptNative(isolate)); 66 v8::Local<v8::Object> scriptHostWrapper = 67 V8InjectedScriptHost::create(context, inspectedContext->inspector()); 68 injectedScriptNative->setOnInjectedScriptHost(scriptHostWrapper); 69 70 // Inject javascript into the context. The compiled script is supposed to 71 // evaluate into 72 // a single anonymous function(it's anonymous to avoid cluttering the global 73 // object with 74 // inspector's stuff) the function is called a few lines below with 75 // InjectedScriptHost wrapper, 76 // injected script id and explicit reference to the inspected global object. 77 // The function is expected 78 // to create and configure InjectedScript instance that is going to be used by 79 // the inspector. 80 String16 injectedScriptSource( 81 reinterpret_cast<const char*>(InjectedScriptSource_js), 82 sizeof(InjectedScriptSource_js)); 83 v8::Local<v8::Value> value; 84 if (!inspectedContext->inspector() 85 ->compileAndRunInternalScript( 86 context, toV8String(isolate, injectedScriptSource)) 87 .ToLocal(&value)) 88 return nullptr; 89 DCHECK(value->IsFunction()); 90 v8::Local<v8::Function> function = v8::Local<v8::Function>::Cast(value); 91 v8::Local<v8::Object> windowGlobal = context->Global(); 92 v8::Local<v8::Value> info[] = { 93 scriptHostWrapper, windowGlobal, 94 v8::Number::New(isolate, inspectedContext->contextId())}; 95 v8::MicrotasksScope microtasksScope(isolate, 96 v8::MicrotasksScope::kDoNotRunMicrotasks); 97 98 int contextGroupId = inspectedContext->contextGroupId(); 99 int contextId = inspectedContext->contextId(); 100 V8InspectorImpl* inspector = inspectedContext->inspector(); 101 v8::Local<v8::Value> injectedScriptValue; 102 if (!function->Call(context, windowGlobal, arraysize(info), info) 103 .ToLocal(&injectedScriptValue)) 104 return nullptr; 105 if (inspector->getContext(contextGroupId, contextId) != inspectedContext) 106 return nullptr; 107 if (!injectedScriptValue->IsObject()) return nullptr; 108 return std::unique_ptr<InjectedScript>( 109 new InjectedScript(inspectedContext, injectedScriptValue.As<v8::Object>(), 110 std::move(injectedScriptNative))); 111} 112 113InjectedScript::InjectedScript( 114 InspectedContext* context, v8::Local<v8::Object> object, 115 std::unique_ptr<InjectedScriptNative> injectedScriptNative) 116 : m_context(context), 117 m_value(context->isolate(), object), 118 m_native(std::move(injectedScriptNative)) {} 119 120InjectedScript::~InjectedScript() {} 121 122Response InjectedScript::getProperties( 123 v8::Local<v8::Object> object, const String16& groupName, bool ownProperties, 124 bool accessorPropertiesOnly, bool generatePreview, 125 std::unique_ptr<Array<PropertyDescriptor>>* properties, 126 Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) { 127 v8::HandleScope handles(m_context->isolate()); 128 v8::Local<v8::Context> context = m_context->context(); 129 V8FunctionCall function(m_context->inspector(), m_context->context(), 130 v8Value(), "getProperties"); 131 function.appendArgument(object); 132 function.appendArgument(groupName); 133 function.appendArgument(ownProperties); 134 function.appendArgument(accessorPropertiesOnly); 135 function.appendArgument(generatePreview); 136 137 v8::TryCatch tryCatch(m_context->isolate()); 138 v8::Local<v8::Value> resultValue = function.callWithoutExceptionHandling(); 139 if (tryCatch.HasCaught()) { 140 Response response = createExceptionDetails( 141 tryCatch, groupName, generatePreview, exceptionDetails); 142 if (!response.isSuccess()) return response; 143 // FIXME: make properties optional 144 *properties = Array<PropertyDescriptor>::create(); 145 return Response::OK(); 146 } 147 if (resultValue.IsEmpty()) return Response::InternalError(); 148 std::unique_ptr<protocol::Value> protocolValue; 149 Response response = toProtocolValue(context, resultValue, &protocolValue); 150 if (!response.isSuccess()) return response; 151 protocol::ErrorSupport errors; 152 std::unique_ptr<Array<PropertyDescriptor>> result = 153 Array<PropertyDescriptor>::fromValue(protocolValue.get(), &errors); 154 if (errors.hasErrors()) return Response::Error(errors.errors()); 155 *properties = std::move(result); 156 return Response::OK(); 157} 158 159void InjectedScript::releaseObject(const String16& objectId) { 160 std::unique_ptr<protocol::Value> parsedObjectId = 161 protocol::StringUtil::parseJSON(objectId); 162 if (!parsedObjectId) return; 163 protocol::DictionaryValue* object = 164 protocol::DictionaryValue::cast(parsedObjectId.get()); 165 if (!object) return; 166 int boundId = 0; 167 if (!object->getInteger("id", &boundId)) return; 168 m_native->unbind(boundId); 169} 170 171Response InjectedScript::wrapObject( 172 v8::Local<v8::Value> value, const String16& groupName, bool forceValueType, 173 bool generatePreview, 174 std::unique_ptr<protocol::Runtime::RemoteObject>* result) const { 175 v8::HandleScope handles(m_context->isolate()); 176 v8::Local<v8::Value> wrappedObject; 177 v8::Local<v8::Context> context = m_context->context(); 178 Response response = wrapValue(value, groupName, forceValueType, 179 generatePreview, &wrappedObject); 180 if (!response.isSuccess()) return response; 181 protocol::ErrorSupport errors; 182 std::unique_ptr<protocol::Value> protocolValue; 183 response = toProtocolValue(context, wrappedObject, &protocolValue); 184 if (!response.isSuccess()) return response; 185 186 *result = 187 protocol::Runtime::RemoteObject::fromValue(protocolValue.get(), &errors); 188 if (!result->get()) return Response::Error(errors.errors()); 189 return Response::OK(); 190} 191 192Response InjectedScript::wrapObjectProperty(v8::Local<v8::Object> object, 193 v8::Local<v8::Name> key, 194 const String16& groupName, 195 bool forceValueType, 196 bool generatePreview) const { 197 v8::Local<v8::Value> property; 198 v8::Local<v8::Context> context = m_context->context(); 199 if (!object->Get(context, key).ToLocal(&property)) 200 return Response::InternalError(); 201 v8::Local<v8::Value> wrappedProperty; 202 Response response = wrapValue(property, groupName, forceValueType, 203 generatePreview, &wrappedProperty); 204 if (!response.isSuccess()) return response; 205 v8::Maybe<bool> success = 206 createDataProperty(context, object, key, wrappedProperty); 207 if (success.IsNothing() || !success.FromJust()) 208 return Response::InternalError(); 209 return Response::OK(); 210} 211 212Response InjectedScript::wrapPropertyInArray(v8::Local<v8::Array> array, 213 v8::Local<v8::String> property, 214 const String16& groupName, 215 bool forceValueType, 216 bool generatePreview) const { 217 V8FunctionCall function(m_context->inspector(), m_context->context(), 218 v8Value(), "wrapPropertyInArray"); 219 function.appendArgument(array); 220 function.appendArgument(property); 221 function.appendArgument(groupName); 222 function.appendArgument(forceValueType); 223 function.appendArgument(generatePreview); 224 bool hadException = false; 225 function.call(hadException); 226 return hadException ? Response::InternalError() : Response::OK(); 227} 228 229Response InjectedScript::wrapValue(v8::Local<v8::Value> value, 230 const String16& groupName, 231 bool forceValueType, bool generatePreview, 232 v8::Local<v8::Value>* result) const { 233 V8FunctionCall function(m_context->inspector(), m_context->context(), 234 v8Value(), "wrapObject"); 235 function.appendArgument(value); 236 function.appendArgument(groupName); 237 function.appendArgument(forceValueType); 238 function.appendArgument(generatePreview); 239 bool hadException = false; 240 *result = function.call(hadException); 241 if (hadException || result->IsEmpty()) return Response::InternalError(); 242 return Response::OK(); 243} 244 245std::unique_ptr<protocol::Runtime::RemoteObject> InjectedScript::wrapTable( 246 v8::Local<v8::Value> table, v8::Local<v8::Value> columns) const { 247 v8::HandleScope handles(m_context->isolate()); 248 v8::Local<v8::Context> context = m_context->context(); 249 V8FunctionCall function(m_context->inspector(), context, v8Value(), 250 "wrapTable"); 251 function.appendArgument(table); 252 if (columns.IsEmpty()) 253 function.appendArgument(false); 254 else 255 function.appendArgument(columns); 256 bool hadException = false; 257 v8::Local<v8::Value> r = function.call(hadException); 258 if (hadException || r.IsEmpty()) return nullptr; 259 std::unique_ptr<protocol::Value> protocolValue; 260 Response response = toProtocolValue(context, r, &protocolValue); 261 if (!response.isSuccess()) return nullptr; 262 protocol::ErrorSupport errors; 263 return protocol::Runtime::RemoteObject::fromValue(protocolValue.get(), 264 &errors); 265} 266 267Response InjectedScript::findObject(const RemoteObjectId& objectId, 268 v8::Local<v8::Value>* outObject) const { 269 *outObject = m_native->objectForId(objectId.id()); 270 if (outObject->IsEmpty()) 271 return Response::Error("Could not find object with given id"); 272 return Response::OK(); 273} 274 275String16 InjectedScript::objectGroupName(const RemoteObjectId& objectId) const { 276 return m_native->groupName(objectId.id()); 277} 278 279void InjectedScript::releaseObjectGroup(const String16& objectGroup) { 280 m_native->releaseObjectGroup(objectGroup); 281 if (objectGroup == "console") m_lastEvaluationResult.Reset(); 282} 283 284void InjectedScript::setCustomObjectFormatterEnabled(bool enabled) { 285 v8::HandleScope handles(m_context->isolate()); 286 V8FunctionCall function(m_context->inspector(), m_context->context(), 287 v8Value(), "setCustomObjectFormatterEnabled"); 288 function.appendArgument(enabled); 289 bool hadException = false; 290 function.call(hadException); 291 DCHECK(!hadException); 292} 293 294v8::Local<v8::Value> InjectedScript::v8Value() const { 295 return m_value.Get(m_context->isolate()); 296} 297 298v8::Local<v8::Value> InjectedScript::lastEvaluationResult() const { 299 if (m_lastEvaluationResult.IsEmpty()) 300 return v8::Undefined(m_context->isolate()); 301 return m_lastEvaluationResult.Get(m_context->isolate()); 302} 303 304Response InjectedScript::resolveCallArgument( 305 protocol::Runtime::CallArgument* callArgument, 306 v8::Local<v8::Value>* result) { 307 if (callArgument->hasObjectId()) { 308 std::unique_ptr<RemoteObjectId> remoteObjectId; 309 Response response = 310 RemoteObjectId::parse(callArgument->getObjectId(""), &remoteObjectId); 311 if (!response.isSuccess()) return response; 312 if (remoteObjectId->contextId() != m_context->contextId()) 313 return Response::Error( 314 "Argument should belong to the same JavaScript world as target " 315 "object"); 316 return findObject(*remoteObjectId, result); 317 } 318 if (callArgument->hasValue() || callArgument->hasUnserializableValue()) { 319 String16 value = 320 callArgument->hasValue() 321 ? callArgument->getValue(nullptr)->serialize() 322 : "Number(\"" + callArgument->getUnserializableValue("") + "\")"; 323 if (!m_context->inspector() 324 ->compileAndRunInternalScript( 325 m_context->context(), toV8String(m_context->isolate(), value)) 326 .ToLocal(result)) { 327 return Response::Error("Couldn't parse value object in call argument"); 328 } 329 return Response::OK(); 330 } 331 *result = v8::Undefined(m_context->isolate()); 332 return Response::OK(); 333} 334 335Response InjectedScript::createExceptionDetails( 336 const v8::TryCatch& tryCatch, const String16& objectGroup, 337 bool generatePreview, Maybe<protocol::Runtime::ExceptionDetails>* result) { 338 if (!tryCatch.HasCaught()) return Response::InternalError(); 339 v8::Local<v8::Message> message = tryCatch.Message(); 340 v8::Local<v8::Value> exception = tryCatch.Exception(); 341 String16 messageText = 342 message.IsEmpty() ? String16() : toProtocolString(message->Get()); 343 std::unique_ptr<protocol::Runtime::ExceptionDetails> exceptionDetails = 344 protocol::Runtime::ExceptionDetails::create() 345 .setExceptionId(m_context->inspector()->nextExceptionId()) 346 .setText(exception.IsEmpty() ? messageText : String16("Uncaught")) 347 .setLineNumber( 348 message.IsEmpty() 349 ? 0 350 : message->GetLineNumber(m_context->context()).FromMaybe(1) - 351 1) 352 .setColumnNumber( 353 message.IsEmpty() 354 ? 0 355 : message->GetStartColumn(m_context->context()).FromMaybe(0)) 356 .build(); 357 if (!message.IsEmpty()) { 358 exceptionDetails->setScriptId(String16::fromInteger( 359 static_cast<int>(message->GetScriptOrigin().ScriptID()->Value()))); 360 v8::Local<v8::StackTrace> stackTrace = message->GetStackTrace(); 361 if (!stackTrace.IsEmpty() && stackTrace->GetFrameCount() > 0) 362 exceptionDetails->setStackTrace(m_context->inspector() 363 ->debugger() 364 ->createStackTrace(stackTrace) 365 ->buildInspectorObjectImpl()); 366 } 367 if (!exception.IsEmpty()) { 368 std::unique_ptr<protocol::Runtime::RemoteObject> wrapped; 369 Response response = 370 wrapObject(exception, objectGroup, false /* forceValueType */, 371 generatePreview && !exception->IsNativeError(), &wrapped); 372 if (!response.isSuccess()) return response; 373 exceptionDetails->setException(std::move(wrapped)); 374 } 375 *result = std::move(exceptionDetails); 376 return Response::OK(); 377} 378 379Response InjectedScript::wrapEvaluateResult( 380 v8::MaybeLocal<v8::Value> maybeResultValue, const v8::TryCatch& tryCatch, 381 const String16& objectGroup, bool returnByValue, bool generatePreview, 382 std::unique_ptr<protocol::Runtime::RemoteObject>* result, 383 Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) { 384 v8::Local<v8::Value> resultValue; 385 if (!tryCatch.HasCaught()) { 386 if (!maybeResultValue.ToLocal(&resultValue)) 387 return Response::InternalError(); 388 Response response = wrapObject(resultValue, objectGroup, returnByValue, 389 generatePreview, result); 390 if (!response.isSuccess()) return response; 391 if (objectGroup == "console") 392 m_lastEvaluationResult.Reset(m_context->isolate(), resultValue); 393 } else { 394 v8::Local<v8::Value> exception = tryCatch.Exception(); 395 Response response = 396 wrapObject(exception, objectGroup, false, 397 generatePreview && !exception->IsNativeError(), result); 398 if (!response.isSuccess()) return response; 399 // We send exception in result for compatibility reasons, even though it's 400 // accessible through exceptionDetails.exception. 401 response = createExceptionDetails(tryCatch, objectGroup, generatePreview, 402 exceptionDetails); 403 if (!response.isSuccess()) return response; 404 } 405 return Response::OK(); 406} 407 408v8::Local<v8::Object> InjectedScript::commandLineAPI() { 409 if (m_commandLineAPI.IsEmpty()) 410 m_commandLineAPI.Reset(m_context->isolate(), 411 V8Console::createCommandLineAPI(m_context)); 412 return m_commandLineAPI.Get(m_context->isolate()); 413} 414 415InjectedScript::Scope::Scope(V8InspectorImpl* inspector, int contextGroupId) 416 : m_inspector(inspector), 417 m_contextGroupId(contextGroupId), 418 m_injectedScript(nullptr), 419 m_handleScope(inspector->isolate()), 420 m_tryCatch(inspector->isolate()), 421 m_ignoreExceptionsAndMuteConsole(false), 422 m_previousPauseOnExceptionsState(v8::debug::NoBreakOnException), 423 m_userGesture(false) {} 424 425Response InjectedScript::Scope::initialize() { 426 cleanup(); 427 // TODO(dgozman): what if we reattach to the same context group during 428 // evaluate? Introduce a session id? 429 V8InspectorSessionImpl* session = 430 m_inspector->sessionForContextGroup(m_contextGroupId); 431 if (!session) return Response::InternalError(); 432 Response response = findInjectedScript(session); 433 if (!response.isSuccess()) return response; 434 m_context = m_injectedScript->context()->context(); 435 m_context->Enter(); 436 return Response::OK(); 437} 438 439void InjectedScript::Scope::installCommandLineAPI() { 440 DCHECK(m_injectedScript && !m_context.IsEmpty() && 441 !m_commandLineAPIScope.get()); 442 m_commandLineAPIScope.reset(new V8Console::CommandLineAPIScope( 443 m_context, m_injectedScript->commandLineAPI(), m_context->Global())); 444} 445 446void InjectedScript::Scope::ignoreExceptionsAndMuteConsole() { 447 DCHECK(!m_ignoreExceptionsAndMuteConsole); 448 m_ignoreExceptionsAndMuteConsole = true; 449 m_inspector->client()->muteMetrics(m_contextGroupId); 450 m_inspector->muteExceptions(m_contextGroupId); 451 m_previousPauseOnExceptionsState = 452 setPauseOnExceptionsState(v8::debug::NoBreakOnException); 453} 454 455v8::debug::ExceptionBreakState InjectedScript::Scope::setPauseOnExceptionsState( 456 v8::debug::ExceptionBreakState newState) { 457 if (!m_inspector->debugger()->enabled()) return newState; 458 v8::debug::ExceptionBreakState presentState = 459 m_inspector->debugger()->getPauseOnExceptionsState(); 460 if (presentState != newState) 461 m_inspector->debugger()->setPauseOnExceptionsState(newState); 462 return presentState; 463} 464 465void InjectedScript::Scope::pretendUserGesture() { 466 DCHECK(!m_userGesture); 467 m_userGesture = true; 468 m_inspector->client()->beginUserGesture(); 469} 470 471void InjectedScript::Scope::cleanup() { 472 m_commandLineAPIScope.reset(); 473 if (!m_context.IsEmpty()) { 474 m_context->Exit(); 475 m_context.Clear(); 476 } 477} 478 479InjectedScript::Scope::~Scope() { 480 if (m_ignoreExceptionsAndMuteConsole) { 481 setPauseOnExceptionsState(m_previousPauseOnExceptionsState); 482 m_inspector->client()->unmuteMetrics(m_contextGroupId); 483 m_inspector->unmuteExceptions(m_contextGroupId); 484 } 485 if (m_userGesture) m_inspector->client()->endUserGesture(); 486 cleanup(); 487} 488 489InjectedScript::ContextScope::ContextScope(V8InspectorImpl* inspector, 490 int contextGroupId, 491 int executionContextId) 492 : InjectedScript::Scope(inspector, contextGroupId), 493 m_executionContextId(executionContextId) {} 494 495InjectedScript::ContextScope::~ContextScope() {} 496 497Response InjectedScript::ContextScope::findInjectedScript( 498 V8InspectorSessionImpl* session) { 499 return session->findInjectedScript(m_executionContextId, m_injectedScript); 500} 501 502InjectedScript::ObjectScope::ObjectScope(V8InspectorImpl* inspector, 503 int contextGroupId, 504 const String16& remoteObjectId) 505 : InjectedScript::Scope(inspector, contextGroupId), 506 m_remoteObjectId(remoteObjectId) {} 507 508InjectedScript::ObjectScope::~ObjectScope() {} 509 510Response InjectedScript::ObjectScope::findInjectedScript( 511 V8InspectorSessionImpl* session) { 512 std::unique_ptr<RemoteObjectId> remoteId; 513 Response response = RemoteObjectId::parse(m_remoteObjectId, &remoteId); 514 if (!response.isSuccess()) return response; 515 InjectedScript* injectedScript = nullptr; 516 response = session->findInjectedScript(remoteId.get(), injectedScript); 517 if (!response.isSuccess()) return response; 518 m_objectGroupName = injectedScript->objectGroupName(*remoteId); 519 response = injectedScript->findObject(*remoteId, &m_object); 520 if (!response.isSuccess()) return response; 521 m_injectedScript = injectedScript; 522 return Response::OK(); 523} 524 525InjectedScript::CallFrameScope::CallFrameScope(V8InspectorImpl* inspector, 526 int contextGroupId, 527 const String16& remoteObjectId) 528 : InjectedScript::Scope(inspector, contextGroupId), 529 m_remoteCallFrameId(remoteObjectId) {} 530 531InjectedScript::CallFrameScope::~CallFrameScope() {} 532 533Response InjectedScript::CallFrameScope::findInjectedScript( 534 V8InspectorSessionImpl* session) { 535 std::unique_ptr<RemoteCallFrameId> remoteId; 536 Response response = RemoteCallFrameId::parse(m_remoteCallFrameId, &remoteId); 537 if (!response.isSuccess()) return response; 538 m_frameOrdinal = static_cast<size_t>(remoteId->frameOrdinal()); 539 return session->findInjectedScript(remoteId.get(), m_injectedScript); 540} 541 542} // namespace v8_inspector 543