a9017cb018
Make some methods on V8DebuggerScript virtual and provide the implementations ActualScript for scripts which are backed by scripts on V8's side, and WasmVirtualScript for wasm scripts. The added test case ensures that we at least don't crash on the attempt to get breakable locations for wasm "scripts", which we did previously. Returning a reasonable result for wasm will be implemented in a follow-up commit. R=yangguo@chromium.org, jgruber@chromium.org BUG=chromium:667767,chromium:613110 Review-Url: https://codereview.chromium.org/2532433003 Cr-Commit-Position: refs/heads/master@{#41527}
1011 lines
38 KiB
C++
1011 lines
38 KiB
C++
// Copyright 2016 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "src/inspector/v8-debugger.h"
|
|
|
|
#include "src/inspector/debugger-script.h"
|
|
#include "src/inspector/protocol/Protocol.h"
|
|
#include "src/inspector/script-breakpoint.h"
|
|
#include "src/inspector/string-util.h"
|
|
#include "src/inspector/v8-debugger-agent-impl.h"
|
|
#include "src/inspector/v8-inspector-impl.h"
|
|
#include "src/inspector/v8-internal-value-type.h"
|
|
#include "src/inspector/v8-stack-trace-impl.h"
|
|
#include "src/inspector/v8-value-copier.h"
|
|
|
|
#include "include/v8-util.h"
|
|
|
|
namespace v8_inspector {
|
|
|
|
namespace {
|
|
static const char v8AsyncTaskEventEnqueue[] = "enqueue";
|
|
static const char v8AsyncTaskEventEnqueueRecurring[] = "enqueueRecurring";
|
|
static const char v8AsyncTaskEventWillHandle[] = "willHandle";
|
|
static const char v8AsyncTaskEventDidHandle[] = "didHandle";
|
|
static const char v8AsyncTaskEventCancel[] = "cancel";
|
|
|
|
inline v8::Local<v8::Boolean> v8Boolean(bool value, v8::Isolate* isolate) {
|
|
return value ? v8::True(isolate) : v8::False(isolate);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
static bool inLiveEditScope = false;
|
|
|
|
v8::MaybeLocal<v8::Value> V8Debugger::callDebuggerMethod(
|
|
const char* functionName, int argc, v8::Local<v8::Value> argv[]) {
|
|
v8::MicrotasksScope microtasks(m_isolate,
|
|
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
|
DCHECK(m_isolate->InContext());
|
|
v8::Local<v8::Context> context = m_isolate->GetCurrentContext();
|
|
v8::Local<v8::Object> debuggerScript = m_debuggerScript.Get(m_isolate);
|
|
v8::Local<v8::Function> function = v8::Local<v8::Function>::Cast(
|
|
debuggerScript
|
|
->Get(context, toV8StringInternalized(m_isolate, functionName))
|
|
.ToLocalChecked());
|
|
return function->Call(context, debuggerScript, argc, argv);
|
|
}
|
|
|
|
V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
|
|
: m_isolate(isolate),
|
|
m_inspector(inspector),
|
|
m_lastContextId(0),
|
|
m_enableCount(0),
|
|
m_breakpointsActivated(true),
|
|
m_runningNestedMessageLoop(false),
|
|
m_ignoreScriptParsedEventsCounter(0),
|
|
m_maxAsyncCallStackDepth(0),
|
|
m_pauseOnExceptionsState(v8::debug::NoBreakOnException),
|
|
m_wasmTranslation(isolate) {}
|
|
|
|
V8Debugger::~V8Debugger() {}
|
|
|
|
void V8Debugger::enable() {
|
|
if (m_enableCount++) return;
|
|
DCHECK(!enabled());
|
|
v8::HandleScope scope(m_isolate);
|
|
v8::debug::SetDebugEventListener(m_isolate, &V8Debugger::v8DebugEventCallback,
|
|
v8::External::New(m_isolate, this));
|
|
m_debuggerContext.Reset(m_isolate, v8::debug::GetDebugContext(m_isolate));
|
|
v8::debug::ChangeBreakOnException(m_isolate, v8::debug::NoBreakOnException);
|
|
m_pauseOnExceptionsState = v8::debug::NoBreakOnException;
|
|
compileDebuggerScript();
|
|
}
|
|
|
|
void V8Debugger::disable() {
|
|
if (--m_enableCount) return;
|
|
DCHECK(enabled());
|
|
clearBreakpoints();
|
|
m_debuggerScript.Reset();
|
|
m_debuggerContext.Reset();
|
|
allAsyncTasksCanceled();
|
|
m_wasmTranslation.Clear();
|
|
v8::debug::SetDebugEventListener(m_isolate, nullptr);
|
|
}
|
|
|
|
bool V8Debugger::enabled() const { return !m_debuggerScript.IsEmpty(); }
|
|
|
|
// static
|
|
int V8Debugger::contextId(v8::Local<v8::Context> context) {
|
|
v8::Local<v8::Value> data =
|
|
context->GetEmbedderData(static_cast<int>(v8::Context::kDebugIdIndex));
|
|
if (data.IsEmpty() || !data->IsString()) return 0;
|
|
String16 dataString = toProtocolString(data.As<v8::String>());
|
|
if (dataString.isEmpty()) return 0;
|
|
size_t commaPos = dataString.find(",");
|
|
if (commaPos == String16::kNotFound) return 0;
|
|
size_t commaPos2 = dataString.find(",", commaPos + 1);
|
|
if (commaPos2 == String16::kNotFound) return 0;
|
|
return dataString.substring(commaPos + 1, commaPos2 - commaPos - 1)
|
|
.toInteger();
|
|
}
|
|
|
|
// static
|
|
int V8Debugger::getGroupId(v8::Local<v8::Context> context) {
|
|
v8::Local<v8::Value> data =
|
|
context->GetEmbedderData(static_cast<int>(v8::Context::kDebugIdIndex));
|
|
if (data.IsEmpty() || !data->IsString()) return 0;
|
|
String16 dataString = toProtocolString(data.As<v8::String>());
|
|
if (dataString.isEmpty()) return 0;
|
|
size_t commaPos = dataString.find(",");
|
|
if (commaPos == String16::kNotFound) return 0;
|
|
return dataString.substring(0, commaPos).toInteger();
|
|
}
|
|
|
|
void V8Debugger::getCompiledScripts(
|
|
int contextGroupId,
|
|
std::vector<std::unique_ptr<V8DebuggerScript>>& result) {
|
|
v8::HandleScope scope(m_isolate);
|
|
v8::PersistentValueVector<v8::debug::Script> scripts(m_isolate);
|
|
v8::debug::GetLoadedScripts(m_isolate, scripts);
|
|
String16 contextPrefix = String16::fromInteger(contextGroupId) + ",";
|
|
for (size_t i = 0; i < scripts.Size(); ++i) {
|
|
v8::Local<v8::debug::Script> script = scripts.Get(i);
|
|
if (!script->WasCompiled()) continue;
|
|
v8::Local<v8::String> v8ContextData;
|
|
if (!script->ContextData().ToLocal(&v8ContextData)) continue;
|
|
String16 contextData = toProtocolString(v8ContextData);
|
|
if (contextData.find(contextPrefix) != 0) continue;
|
|
result.push_back(V8DebuggerScript::Create(m_isolate, script, false));
|
|
}
|
|
}
|
|
|
|
String16 V8Debugger::setBreakpoint(const ScriptBreakpoint& breakpoint,
|
|
int* actualLineNumber,
|
|
int* actualColumnNumber) {
|
|
v8::HandleScope scope(m_isolate);
|
|
v8::Local<v8::Context> context = debuggerContext();
|
|
v8::Context::Scope contextScope(context);
|
|
|
|
v8::Local<v8::Object> info = v8::Object::New(m_isolate);
|
|
bool success = false;
|
|
success = info->Set(context, toV8StringInternalized(m_isolate, "sourceID"),
|
|
toV8String(m_isolate, breakpoint.script_id))
|
|
.FromMaybe(false);
|
|
DCHECK(success);
|
|
success = info->Set(context, toV8StringInternalized(m_isolate, "lineNumber"),
|
|
v8::Integer::New(m_isolate, breakpoint.line_number))
|
|
.FromMaybe(false);
|
|
DCHECK(success);
|
|
success =
|
|
info->Set(context, toV8StringInternalized(m_isolate, "columnNumber"),
|
|
v8::Integer::New(m_isolate, breakpoint.column_number))
|
|
.FromMaybe(false);
|
|
DCHECK(success);
|
|
success = info->Set(context, toV8StringInternalized(m_isolate, "condition"),
|
|
toV8String(m_isolate, breakpoint.condition))
|
|
.FromMaybe(false);
|
|
DCHECK(success);
|
|
|
|
v8::Local<v8::Function> setBreakpointFunction = v8::Local<v8::Function>::Cast(
|
|
m_debuggerScript.Get(m_isolate)
|
|
->Get(context, toV8StringInternalized(m_isolate, "setBreakpoint"))
|
|
.ToLocalChecked());
|
|
v8::Local<v8::Value> breakpointId =
|
|
v8::debug::Call(debuggerContext(), setBreakpointFunction, info)
|
|
.ToLocalChecked();
|
|
if (!breakpointId->IsString()) return "";
|
|
*actualLineNumber =
|
|
info->Get(context, toV8StringInternalized(m_isolate, "lineNumber"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust();
|
|
*actualColumnNumber =
|
|
info->Get(context, toV8StringInternalized(m_isolate, "columnNumber"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust();
|
|
return toProtocolString(breakpointId.As<v8::String>());
|
|
}
|
|
|
|
void V8Debugger::removeBreakpoint(const String16& breakpointId) {
|
|
v8::HandleScope scope(m_isolate);
|
|
v8::Local<v8::Context> context = debuggerContext();
|
|
v8::Context::Scope contextScope(context);
|
|
|
|
v8::Local<v8::Object> info = v8::Object::New(m_isolate);
|
|
bool success = false;
|
|
success =
|
|
info->Set(context, toV8StringInternalized(m_isolate, "breakpointId"),
|
|
toV8String(m_isolate, breakpointId))
|
|
.FromMaybe(false);
|
|
DCHECK(success);
|
|
|
|
v8::Local<v8::Function> removeBreakpointFunction =
|
|
v8::Local<v8::Function>::Cast(
|
|
m_debuggerScript.Get(m_isolate)
|
|
->Get(context,
|
|
toV8StringInternalized(m_isolate, "removeBreakpoint"))
|
|
.ToLocalChecked());
|
|
v8::debug::Call(debuggerContext(), removeBreakpointFunction, info)
|
|
.ToLocalChecked();
|
|
}
|
|
|
|
void V8Debugger::clearBreakpoints() {
|
|
v8::HandleScope scope(m_isolate);
|
|
v8::Local<v8::Context> context = debuggerContext();
|
|
v8::Context::Scope contextScope(context);
|
|
|
|
v8::Local<v8::Function> clearBreakpoints = v8::Local<v8::Function>::Cast(
|
|
m_debuggerScript.Get(m_isolate)
|
|
->Get(context, toV8StringInternalized(m_isolate, "clearBreakpoints"))
|
|
.ToLocalChecked());
|
|
v8::debug::Call(debuggerContext(), clearBreakpoints).ToLocalChecked();
|
|
}
|
|
|
|
void V8Debugger::setBreakpointsActivated(bool activated) {
|
|
if (!enabled()) {
|
|
UNREACHABLE();
|
|
return;
|
|
}
|
|
v8::HandleScope scope(m_isolate);
|
|
v8::Local<v8::Context> context = debuggerContext();
|
|
v8::Context::Scope contextScope(context);
|
|
|
|
v8::Local<v8::Object> info = v8::Object::New(m_isolate);
|
|
bool success = false;
|
|
success = info->Set(context, toV8StringInternalized(m_isolate, "enabled"),
|
|
v8::Boolean::New(m_isolate, activated))
|
|
.FromMaybe(false);
|
|
DCHECK(success);
|
|
v8::Local<v8::Function> setBreakpointsActivated =
|
|
v8::Local<v8::Function>::Cast(
|
|
m_debuggerScript.Get(m_isolate)
|
|
->Get(context, toV8StringInternalized(m_isolate,
|
|
"setBreakpointsActivated"))
|
|
.ToLocalChecked());
|
|
v8::debug::Call(debuggerContext(), setBreakpointsActivated, info)
|
|
.ToLocalChecked();
|
|
|
|
m_breakpointsActivated = activated;
|
|
}
|
|
|
|
v8::debug::ExceptionBreakState V8Debugger::getPauseOnExceptionsState() {
|
|
DCHECK(enabled());
|
|
return m_pauseOnExceptionsState;
|
|
}
|
|
|
|
void V8Debugger::setPauseOnExceptionsState(
|
|
v8::debug::ExceptionBreakState pauseOnExceptionsState) {
|
|
DCHECK(enabled());
|
|
if (m_pauseOnExceptionsState == pauseOnExceptionsState) return;
|
|
v8::debug::ChangeBreakOnException(m_isolate, pauseOnExceptionsState);
|
|
m_pauseOnExceptionsState = pauseOnExceptionsState;
|
|
}
|
|
|
|
void V8Debugger::setPauseOnNextStatement(bool pause) {
|
|
if (m_runningNestedMessageLoop) return;
|
|
if (pause)
|
|
v8::debug::DebugBreak(m_isolate);
|
|
else
|
|
v8::debug::CancelDebugBreak(m_isolate);
|
|
}
|
|
|
|
bool V8Debugger::canBreakProgram() {
|
|
if (!m_breakpointsActivated) return false;
|
|
return m_isolate->InContext();
|
|
}
|
|
|
|
void V8Debugger::breakProgram() {
|
|
if (isPaused()) {
|
|
DCHECK(!m_runningNestedMessageLoop);
|
|
v8::Local<v8::Value> exception;
|
|
v8::Local<v8::Array> hitBreakpoints;
|
|
handleProgramBreak(m_pausedContext, m_executionState, exception,
|
|
hitBreakpoints);
|
|
return;
|
|
}
|
|
|
|
if (!canBreakProgram()) return;
|
|
|
|
v8::HandleScope scope(m_isolate);
|
|
v8::Local<v8::Function> breakFunction;
|
|
if (!v8::Function::New(m_isolate->GetCurrentContext(),
|
|
&V8Debugger::breakProgramCallback,
|
|
v8::External::New(m_isolate, this), 0,
|
|
v8::ConstructorBehavior::kThrow)
|
|
.ToLocal(&breakFunction))
|
|
return;
|
|
v8::debug::Call(debuggerContext(), breakFunction).ToLocalChecked();
|
|
}
|
|
|
|
void V8Debugger::continueProgram() {
|
|
if (isPaused()) m_inspector->client()->quitMessageLoopOnPause();
|
|
m_pausedContext.Clear();
|
|
m_executionState.Clear();
|
|
}
|
|
|
|
void V8Debugger::stepIntoStatement() {
|
|
DCHECK(isPaused());
|
|
DCHECK(!m_executionState.IsEmpty());
|
|
v8::debug::PrepareStep(m_isolate, v8::debug::StepIn);
|
|
continueProgram();
|
|
}
|
|
|
|
void V8Debugger::stepOverStatement() {
|
|
DCHECK(isPaused());
|
|
DCHECK(!m_executionState.IsEmpty());
|
|
v8::debug::PrepareStep(m_isolate, v8::debug::StepNext);
|
|
continueProgram();
|
|
}
|
|
|
|
void V8Debugger::stepOutOfFunction() {
|
|
DCHECK(isPaused());
|
|
DCHECK(!m_executionState.IsEmpty());
|
|
v8::debug::PrepareStep(m_isolate, v8::debug::StepOut);
|
|
continueProgram();
|
|
}
|
|
|
|
void V8Debugger::clearStepping() {
|
|
DCHECK(enabled());
|
|
v8::debug::ClearStepping(m_isolate);
|
|
}
|
|
|
|
Response V8Debugger::setScriptSource(
|
|
const String16& sourceID, v8::Local<v8::String> newSource, bool dryRun,
|
|
Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails,
|
|
JavaScriptCallFrames* newCallFrames, Maybe<bool>* stackChanged,
|
|
bool* compileError) {
|
|
class EnableLiveEditScope {
|
|
public:
|
|
explicit EnableLiveEditScope(v8::Isolate* isolate) : m_isolate(isolate) {
|
|
v8::debug::SetLiveEditEnabled(m_isolate, true);
|
|
inLiveEditScope = true;
|
|
}
|
|
~EnableLiveEditScope() {
|
|
v8::debug::SetLiveEditEnabled(m_isolate, false);
|
|
inLiveEditScope = false;
|
|
}
|
|
|
|
private:
|
|
v8::Isolate* m_isolate;
|
|
};
|
|
|
|
*compileError = false;
|
|
DCHECK(enabled());
|
|
v8::HandleScope scope(m_isolate);
|
|
|
|
std::unique_ptr<v8::Context::Scope> contextScope;
|
|
if (!isPaused())
|
|
contextScope.reset(new v8::Context::Scope(debuggerContext()));
|
|
|
|
v8::Local<v8::Value> argv[] = {toV8String(m_isolate, sourceID), newSource,
|
|
v8Boolean(dryRun, m_isolate)};
|
|
|
|
v8::Local<v8::Value> v8result;
|
|
{
|
|
EnableLiveEditScope enableLiveEditScope(m_isolate);
|
|
v8::TryCatch tryCatch(m_isolate);
|
|
tryCatch.SetVerbose(false);
|
|
v8::MaybeLocal<v8::Value> maybeResult =
|
|
callDebuggerMethod("liveEditScriptSource", 3, argv);
|
|
if (tryCatch.HasCaught()) {
|
|
v8::Local<v8::Message> message = tryCatch.Message();
|
|
if (!message.IsEmpty())
|
|
return Response::Error(toProtocolStringWithTypeCheck(message->Get()));
|
|
else
|
|
return Response::InternalError();
|
|
}
|
|
v8result = maybeResult.ToLocalChecked();
|
|
}
|
|
DCHECK(!v8result.IsEmpty());
|
|
v8::Local<v8::Context> context = m_isolate->GetCurrentContext();
|
|
v8::Local<v8::Object> resultTuple =
|
|
v8result->ToObject(context).ToLocalChecked();
|
|
int code = static_cast<int>(resultTuple->Get(context, 0)
|
|
.ToLocalChecked()
|
|
->ToInteger(context)
|
|
.ToLocalChecked()
|
|
->Value());
|
|
switch (code) {
|
|
case 0: {
|
|
*stackChanged = resultTuple->Get(context, 1)
|
|
.ToLocalChecked()
|
|
->BooleanValue(context)
|
|
.FromJust();
|
|
// Call stack may have changed after if the edited function was on the
|
|
// stack.
|
|
if (!dryRun && isPaused()) {
|
|
JavaScriptCallFrames frames = currentCallFrames();
|
|
newCallFrames->swap(frames);
|
|
}
|
|
return Response::OK();
|
|
}
|
|
// Compile error.
|
|
case 1: {
|
|
*exceptionDetails =
|
|
protocol::Runtime::ExceptionDetails::create()
|
|
.setExceptionId(m_inspector->nextExceptionId())
|
|
.setText(toProtocolStringWithTypeCheck(
|
|
resultTuple->Get(context, 2).ToLocalChecked()))
|
|
.setLineNumber(static_cast<int>(resultTuple->Get(context, 3)
|
|
.ToLocalChecked()
|
|
->ToInteger(context)
|
|
.ToLocalChecked()
|
|
->Value()) -
|
|
1)
|
|
.setColumnNumber(static_cast<int>(resultTuple->Get(context, 4)
|
|
.ToLocalChecked()
|
|
->ToInteger(context)
|
|
.ToLocalChecked()
|
|
->Value()) -
|
|
1)
|
|
.build();
|
|
*compileError = true;
|
|
return Response::OK();
|
|
}
|
|
}
|
|
return Response::InternalError();
|
|
}
|
|
|
|
JavaScriptCallFrames V8Debugger::currentCallFrames(int limit) {
|
|
if (!m_isolate->InContext()) return JavaScriptCallFrames();
|
|
v8::Local<v8::Value> currentCallFramesV8;
|
|
if (m_executionState.IsEmpty()) {
|
|
v8::Local<v8::Function> currentCallFramesFunction =
|
|
v8::Local<v8::Function>::Cast(
|
|
m_debuggerScript.Get(m_isolate)
|
|
->Get(debuggerContext(),
|
|
toV8StringInternalized(m_isolate, "currentCallFrames"))
|
|
.ToLocalChecked());
|
|
currentCallFramesV8 =
|
|
v8::debug::Call(debuggerContext(), currentCallFramesFunction,
|
|
v8::Integer::New(m_isolate, limit))
|
|
.ToLocalChecked();
|
|
} else {
|
|
v8::Local<v8::Value> argv[] = {m_executionState,
|
|
v8::Integer::New(m_isolate, limit)};
|
|
currentCallFramesV8 =
|
|
callDebuggerMethod("currentCallFrames", arraysize(argv), argv)
|
|
.ToLocalChecked();
|
|
}
|
|
DCHECK(!currentCallFramesV8.IsEmpty());
|
|
if (!currentCallFramesV8->IsArray()) return JavaScriptCallFrames();
|
|
v8::Local<v8::Array> callFramesArray = currentCallFramesV8.As<v8::Array>();
|
|
JavaScriptCallFrames callFrames;
|
|
for (uint32_t i = 0; i < callFramesArray->Length(); ++i) {
|
|
v8::Local<v8::Value> callFrameValue;
|
|
if (!callFramesArray->Get(debuggerContext(), i).ToLocal(&callFrameValue))
|
|
return JavaScriptCallFrames();
|
|
if (!callFrameValue->IsObject()) return JavaScriptCallFrames();
|
|
v8::Local<v8::Object> callFrameObject = callFrameValue.As<v8::Object>();
|
|
callFrames.push_back(JavaScriptCallFrame::create(
|
|
debuggerContext(), v8::Local<v8::Object>::Cast(callFrameObject)));
|
|
}
|
|
return callFrames;
|
|
}
|
|
|
|
static V8Debugger* toV8Debugger(v8::Local<v8::Value> data) {
|
|
void* p = v8::Local<v8::External>::Cast(data)->Value();
|
|
return static_cast<V8Debugger*>(p);
|
|
}
|
|
|
|
void V8Debugger::breakProgramCallback(
|
|
const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
DCHECK_EQ(info.Length(), 2);
|
|
V8Debugger* thisPtr = toV8Debugger(info.Data());
|
|
if (!thisPtr->enabled()) return;
|
|
v8::Local<v8::Context> pausedContext =
|
|
thisPtr->m_isolate->GetCurrentContext();
|
|
v8::Local<v8::Value> exception;
|
|
v8::Local<v8::Array> hitBreakpoints;
|
|
thisPtr->handleProgramBreak(pausedContext,
|
|
v8::Local<v8::Object>::Cast(info[0]), exception,
|
|
hitBreakpoints);
|
|
}
|
|
|
|
void V8Debugger::handleProgramBreak(v8::Local<v8::Context> pausedContext,
|
|
v8::Local<v8::Object> executionState,
|
|
v8::Local<v8::Value> exception,
|
|
v8::Local<v8::Array> hitBreakpointNumbers,
|
|
bool isPromiseRejection, bool isUncaught) {
|
|
// Don't allow nested breaks.
|
|
if (m_runningNestedMessageLoop) return;
|
|
|
|
V8DebuggerAgentImpl* agent =
|
|
m_inspector->enabledDebuggerAgentForGroup(getGroupId(pausedContext));
|
|
if (!agent) return;
|
|
|
|
std::vector<String16> breakpointIds;
|
|
if (!hitBreakpointNumbers.IsEmpty()) {
|
|
breakpointIds.reserve(hitBreakpointNumbers->Length());
|
|
for (uint32_t i = 0; i < hitBreakpointNumbers->Length(); i++) {
|
|
v8::Local<v8::Value> hitBreakpointNumber =
|
|
hitBreakpointNumbers->Get(debuggerContext(), i).ToLocalChecked();
|
|
DCHECK(hitBreakpointNumber->IsInt32());
|
|
breakpointIds.push_back(String16::fromInteger(
|
|
hitBreakpointNumber->Int32Value(debuggerContext()).FromJust()));
|
|
}
|
|
}
|
|
|
|
m_pausedContext = pausedContext;
|
|
m_executionState = executionState;
|
|
V8DebuggerAgentImpl::SkipPauseRequest result = agent->didPause(
|
|
pausedContext, exception, breakpointIds, isPromiseRejection, isUncaught);
|
|
if (result == V8DebuggerAgentImpl::RequestNoSkip) {
|
|
m_runningNestedMessageLoop = true;
|
|
int groupId = getGroupId(pausedContext);
|
|
DCHECK(groupId);
|
|
m_inspector->client()->runMessageLoopOnPause(groupId);
|
|
// The agent may have been removed in the nested loop.
|
|
agent =
|
|
m_inspector->enabledDebuggerAgentForGroup(getGroupId(pausedContext));
|
|
if (agent) agent->didContinue();
|
|
m_runningNestedMessageLoop = false;
|
|
}
|
|
m_pausedContext.Clear();
|
|
m_executionState.Clear();
|
|
|
|
if (result == V8DebuggerAgentImpl::RequestStepFrame) {
|
|
v8::debug::PrepareStep(m_isolate, v8::debug::StepFrame);
|
|
} else if (result == V8DebuggerAgentImpl::RequestStepInto) {
|
|
v8::debug::PrepareStep(m_isolate, v8::debug::StepIn);
|
|
} else if (result == V8DebuggerAgentImpl::RequestStepOut) {
|
|
v8::debug::PrepareStep(m_isolate, v8::debug::StepOut);
|
|
}
|
|
}
|
|
|
|
void V8Debugger::v8DebugEventCallback(
|
|
const v8::debug::EventDetails& eventDetails) {
|
|
V8Debugger* thisPtr = toV8Debugger(eventDetails.GetCallbackData());
|
|
thisPtr->handleV8DebugEvent(eventDetails);
|
|
}
|
|
|
|
v8::Local<v8::Value> V8Debugger::callInternalGetterFunction(
|
|
v8::Local<v8::Object> object, const char* functionName) {
|
|
v8::MicrotasksScope microtasks(m_isolate,
|
|
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
|
v8::Local<v8::Value> getterValue =
|
|
object
|
|
->Get(m_isolate->GetCurrentContext(),
|
|
toV8StringInternalized(m_isolate, functionName))
|
|
.ToLocalChecked();
|
|
DCHECK(!getterValue.IsEmpty() && getterValue->IsFunction());
|
|
return v8::Local<v8::Function>::Cast(getterValue)
|
|
->Call(m_isolate->GetCurrentContext(), object, 0, nullptr)
|
|
.ToLocalChecked();
|
|
}
|
|
|
|
void V8Debugger::handleV8DebugEvent(
|
|
const v8::debug::EventDetails& eventDetails) {
|
|
if (!enabled()) return;
|
|
v8::DebugEvent event = eventDetails.GetEvent();
|
|
if (event != v8::AsyncTaskEvent && event != v8::Break &&
|
|
event != v8::Exception && event != v8::AfterCompile &&
|
|
event != v8::CompileError)
|
|
return;
|
|
|
|
v8::Local<v8::Context> eventContext = eventDetails.GetEventContext();
|
|
DCHECK(!eventContext.IsEmpty());
|
|
|
|
if (event == v8::AsyncTaskEvent) {
|
|
v8::HandleScope scope(m_isolate);
|
|
handleV8AsyncTaskEvent(eventContext, eventDetails.GetExecutionState(),
|
|
eventDetails.GetEventData());
|
|
return;
|
|
}
|
|
|
|
V8DebuggerAgentImpl* agent =
|
|
m_inspector->enabledDebuggerAgentForGroup(getGroupId(eventContext));
|
|
if (!agent) return;
|
|
|
|
v8::HandleScope scope(m_isolate);
|
|
if (event == v8::AfterCompile || event == v8::CompileError) {
|
|
v8::Context::Scope contextScope(debuggerContext());
|
|
// Determine if the script is a wasm script.
|
|
v8::Local<v8::Value> scriptMirror =
|
|
callInternalGetterFunction(eventDetails.GetEventData(), "script");
|
|
DCHECK(scriptMirror->IsObject());
|
|
v8::Local<v8::Value> scriptWrapper =
|
|
callInternalGetterFunction(scriptMirror.As<v8::Object>(), "value");
|
|
DCHECK(scriptWrapper->IsObject());
|
|
v8::Local<v8::debug::Script> script;
|
|
if (!v8::debug::Script::Wrap(m_isolate, scriptWrapper.As<v8::Object>())
|
|
.ToLocal(&script)) {
|
|
return;
|
|
}
|
|
if (script->IsWasm()) {
|
|
m_wasmTranslation.AddScript(script.As<v8::debug::WasmScript>(), agent);
|
|
} else if (m_ignoreScriptParsedEventsCounter == 0) {
|
|
agent->didParseSource(
|
|
V8DebuggerScript::Create(m_isolate, script, inLiveEditScope),
|
|
event == v8::AfterCompile);
|
|
}
|
|
} else if (event == v8::Exception) {
|
|
v8::Local<v8::Context> context = debuggerContext();
|
|
v8::Local<v8::Object> eventData = eventDetails.GetEventData();
|
|
v8::Local<v8::Value> exception =
|
|
callInternalGetterFunction(eventData, "exception");
|
|
v8::Local<v8::Value> promise =
|
|
callInternalGetterFunction(eventData, "promise");
|
|
bool isPromiseRejection = !promise.IsEmpty() && promise->IsObject();
|
|
v8::Local<v8::Value> uncaught =
|
|
callInternalGetterFunction(eventData, "uncaught");
|
|
bool isUncaught = uncaught->BooleanValue(context).FromJust();
|
|
handleProgramBreak(eventContext, eventDetails.GetExecutionState(),
|
|
exception, v8::Local<v8::Array>(), isPromiseRejection,
|
|
isUncaught);
|
|
} else if (event == v8::Break) {
|
|
v8::Local<v8::Value> argv[] = {eventDetails.GetEventData()};
|
|
v8::Local<v8::Value> hitBreakpoints =
|
|
callDebuggerMethod("getBreakpointNumbers", 1, argv).ToLocalChecked();
|
|
DCHECK(hitBreakpoints->IsArray());
|
|
handleProgramBreak(eventContext, eventDetails.GetExecutionState(),
|
|
v8::Local<v8::Value>(), hitBreakpoints.As<v8::Array>());
|
|
}
|
|
}
|
|
|
|
void V8Debugger::handleV8AsyncTaskEvent(v8::Local<v8::Context> context,
|
|
v8::Local<v8::Object> executionState,
|
|
v8::Local<v8::Object> eventData) {
|
|
if (!m_maxAsyncCallStackDepth) return;
|
|
|
|
String16 type = toProtocolStringWithTypeCheck(
|
|
callInternalGetterFunction(eventData, "type"));
|
|
String16 name = toProtocolStringWithTypeCheck(
|
|
callInternalGetterFunction(eventData, "name"));
|
|
int id = static_cast<int>(callInternalGetterFunction(eventData, "id")
|
|
->ToInteger(context)
|
|
.ToLocalChecked()
|
|
->Value());
|
|
// Async task events from Promises are given misaligned pointers to prevent
|
|
// from overlapping with other Blink task identifiers. There is a single
|
|
// namespace of such ids, managed by src/js/promise.js.
|
|
void* ptr = reinterpret_cast<void*>(id * 2 + 1);
|
|
if (type == v8AsyncTaskEventEnqueue)
|
|
asyncTaskScheduled(name, ptr, false);
|
|
else if (type == v8AsyncTaskEventEnqueueRecurring)
|
|
asyncTaskScheduled(name, ptr, true);
|
|
else if (type == v8AsyncTaskEventWillHandle)
|
|
asyncTaskStarted(ptr);
|
|
else if (type == v8AsyncTaskEventDidHandle)
|
|
asyncTaskFinished(ptr);
|
|
else if (type == v8AsyncTaskEventCancel)
|
|
asyncTaskCanceled(ptr);
|
|
else
|
|
UNREACHABLE();
|
|
}
|
|
|
|
V8StackTraceImpl* V8Debugger::currentAsyncCallChain() {
|
|
if (!m_currentStacks.size()) return nullptr;
|
|
return m_currentStacks.back().get();
|
|
}
|
|
|
|
void V8Debugger::compileDebuggerScript() {
|
|
if (!m_debuggerScript.IsEmpty()) {
|
|
UNREACHABLE();
|
|
return;
|
|
}
|
|
|
|
v8::HandleScope scope(m_isolate);
|
|
v8::Context::Scope contextScope(debuggerContext());
|
|
|
|
v8::Local<v8::String> scriptValue =
|
|
v8::String::NewFromUtf8(m_isolate, DebuggerScript_js,
|
|
v8::NewStringType::kInternalized,
|
|
sizeof(DebuggerScript_js))
|
|
.ToLocalChecked();
|
|
v8::Local<v8::Value> value;
|
|
if (!m_inspector->compileAndRunInternalScript(debuggerContext(), scriptValue)
|
|
.ToLocal(&value)) {
|
|
UNREACHABLE();
|
|
return;
|
|
}
|
|
DCHECK(value->IsObject());
|
|
m_debuggerScript.Reset(m_isolate, value.As<v8::Object>());
|
|
}
|
|
|
|
v8::Local<v8::Context> V8Debugger::debuggerContext() const {
|
|
DCHECK(!m_debuggerContext.IsEmpty());
|
|
return m_debuggerContext.Get(m_isolate);
|
|
}
|
|
|
|
v8::MaybeLocal<v8::Value> V8Debugger::getTargetScopes(
|
|
v8::Local<v8::Context> context, v8::Local<v8::Value> value,
|
|
ScopeTargetKind kind) {
|
|
if (!enabled()) {
|
|
UNREACHABLE();
|
|
return v8::Local<v8::Value>::New(m_isolate, v8::Undefined(m_isolate));
|
|
}
|
|
v8::Local<v8::Value> argv[] = {value};
|
|
v8::Local<v8::Value> scopesValue;
|
|
|
|
const char* debuggerMethod = nullptr;
|
|
switch (kind) {
|
|
case FUNCTION:
|
|
debuggerMethod = "getFunctionScopes";
|
|
break;
|
|
case GENERATOR:
|
|
debuggerMethod = "getGeneratorScopes";
|
|
break;
|
|
}
|
|
|
|
if (!callDebuggerMethod(debuggerMethod, 1, argv).ToLocal(&scopesValue))
|
|
return v8::MaybeLocal<v8::Value>();
|
|
v8::Local<v8::Value> copied;
|
|
if (!copyValueFromDebuggerContext(m_isolate, debuggerContext(), context,
|
|
scopesValue)
|
|
.ToLocal(&copied) ||
|
|
!copied->IsArray())
|
|
return v8::MaybeLocal<v8::Value>();
|
|
if (!markAsInternal(context, v8::Local<v8::Array>::Cast(copied),
|
|
V8InternalValueType::kScopeList))
|
|
return v8::MaybeLocal<v8::Value>();
|
|
if (!markArrayEntriesAsInternal(context, v8::Local<v8::Array>::Cast(copied),
|
|
V8InternalValueType::kScope))
|
|
return v8::MaybeLocal<v8::Value>();
|
|
return copied;
|
|
}
|
|
|
|
v8::MaybeLocal<v8::Value> V8Debugger::functionScopes(
|
|
v8::Local<v8::Context> context, v8::Local<v8::Function> function) {
|
|
return getTargetScopes(context, function, FUNCTION);
|
|
}
|
|
|
|
v8::MaybeLocal<v8::Value> V8Debugger::generatorScopes(
|
|
v8::Local<v8::Context> context, v8::Local<v8::Value> generator) {
|
|
return getTargetScopes(context, generator, GENERATOR);
|
|
}
|
|
|
|
v8::MaybeLocal<v8::Array> V8Debugger::internalProperties(
|
|
v8::Local<v8::Context> context, v8::Local<v8::Value> value) {
|
|
v8::Local<v8::Array> properties;
|
|
if (!v8::debug::GetInternalProperties(m_isolate, value).ToLocal(&properties))
|
|
return v8::MaybeLocal<v8::Array>();
|
|
if (value->IsFunction()) {
|
|
v8::Local<v8::Function> function = value.As<v8::Function>();
|
|
v8::Local<v8::Value> location = functionLocation(context, function);
|
|
if (location->IsObject()) {
|
|
createDataProperty(
|
|
context, properties, properties->Length(),
|
|
toV8StringInternalized(m_isolate, "[[FunctionLocation]]"));
|
|
createDataProperty(context, properties, properties->Length(), location);
|
|
}
|
|
if (function->IsGeneratorFunction()) {
|
|
createDataProperty(context, properties, properties->Length(),
|
|
toV8StringInternalized(m_isolate, "[[IsGenerator]]"));
|
|
createDataProperty(context, properties, properties->Length(),
|
|
v8::True(m_isolate));
|
|
}
|
|
}
|
|
if (!enabled()) return properties;
|
|
if (value->IsMap() || value->IsWeakMap() || value->IsSet() ||
|
|
value->IsWeakSet() || value->IsSetIterator() || value->IsMapIterator()) {
|
|
v8::Local<v8::Value> entries =
|
|
collectionEntries(context, v8::Local<v8::Object>::Cast(value));
|
|
if (entries->IsArray()) {
|
|
createDataProperty(context, properties, properties->Length(),
|
|
toV8StringInternalized(m_isolate, "[[Entries]]"));
|
|
createDataProperty(context, properties, properties->Length(), entries);
|
|
}
|
|
}
|
|
if (value->IsGeneratorObject()) {
|
|
v8::Local<v8::Value> location =
|
|
generatorObjectLocation(context, v8::Local<v8::Object>::Cast(value));
|
|
if (location->IsObject()) {
|
|
createDataProperty(
|
|
context, properties, properties->Length(),
|
|
toV8StringInternalized(m_isolate, "[[GeneratorLocation]]"));
|
|
createDataProperty(context, properties, properties->Length(), location);
|
|
}
|
|
v8::Local<v8::Value> scopes;
|
|
if (generatorScopes(context, value).ToLocal(&scopes)) {
|
|
createDataProperty(context, properties, properties->Length(),
|
|
toV8StringInternalized(m_isolate, "[[Scopes]]"));
|
|
createDataProperty(context, properties, properties->Length(), scopes);
|
|
}
|
|
}
|
|
if (value->IsFunction()) {
|
|
v8::Local<v8::Function> function = value.As<v8::Function>();
|
|
v8::Local<v8::Value> boundFunction = function->GetBoundFunction();
|
|
v8::Local<v8::Value> scopes;
|
|
if (boundFunction->IsUndefined() &&
|
|
functionScopes(context, function).ToLocal(&scopes)) {
|
|
createDataProperty(context, properties, properties->Length(),
|
|
toV8StringInternalized(m_isolate, "[[Scopes]]"));
|
|
createDataProperty(context, properties, properties->Length(), scopes);
|
|
}
|
|
}
|
|
return properties;
|
|
}
|
|
|
|
v8::Local<v8::Value> V8Debugger::collectionEntries(
|
|
v8::Local<v8::Context> context, v8::Local<v8::Object> object) {
|
|
if (!enabled()) {
|
|
UNREACHABLE();
|
|
return v8::Undefined(m_isolate);
|
|
}
|
|
v8::Local<v8::Value> argv[] = {object};
|
|
v8::Local<v8::Value> entriesValue =
|
|
callDebuggerMethod("getCollectionEntries", 1, argv).ToLocalChecked();
|
|
if (!entriesValue->IsArray()) return v8::Undefined(m_isolate);
|
|
|
|
v8::Local<v8::Array> entries = entriesValue.As<v8::Array>();
|
|
v8::Local<v8::Array> copiedArray =
|
|
v8::Array::New(m_isolate, entries->Length());
|
|
if (!copiedArray->SetPrototype(context, v8::Null(m_isolate)).FromMaybe(false))
|
|
return v8::Undefined(m_isolate);
|
|
for (uint32_t i = 0; i < entries->Length(); ++i) {
|
|
v8::Local<v8::Value> item;
|
|
if (!entries->Get(debuggerContext(), i).ToLocal(&item))
|
|
return v8::Undefined(m_isolate);
|
|
v8::Local<v8::Value> copied;
|
|
if (!copyValueFromDebuggerContext(m_isolate, debuggerContext(), context,
|
|
item)
|
|
.ToLocal(&copied))
|
|
return v8::Undefined(m_isolate);
|
|
if (!createDataProperty(context, copiedArray, i, copied).FromMaybe(false))
|
|
return v8::Undefined(m_isolate);
|
|
}
|
|
if (!markArrayEntriesAsInternal(context,
|
|
v8::Local<v8::Array>::Cast(copiedArray),
|
|
V8InternalValueType::kEntry))
|
|
return v8::Undefined(m_isolate);
|
|
return copiedArray;
|
|
}
|
|
|
|
v8::Local<v8::Value> V8Debugger::generatorObjectLocation(
|
|
v8::Local<v8::Context> context, v8::Local<v8::Object> object) {
|
|
if (!enabled()) {
|
|
UNREACHABLE();
|
|
return v8::Null(m_isolate);
|
|
}
|
|
v8::Local<v8::Value> argv[] = {object};
|
|
v8::Local<v8::Value> location =
|
|
callDebuggerMethod("getGeneratorObjectLocation", 1, argv)
|
|
.ToLocalChecked();
|
|
v8::Local<v8::Value> copied;
|
|
if (!copyValueFromDebuggerContext(m_isolate, debuggerContext(), context,
|
|
location)
|
|
.ToLocal(&copied) ||
|
|
!copied->IsObject())
|
|
return v8::Null(m_isolate);
|
|
if (!markAsInternal(context, v8::Local<v8::Object>::Cast(copied),
|
|
V8InternalValueType::kLocation))
|
|
return v8::Null(m_isolate);
|
|
return copied;
|
|
}
|
|
|
|
v8::Local<v8::Value> V8Debugger::functionLocation(
|
|
v8::Local<v8::Context> context, v8::Local<v8::Function> function) {
|
|
int scriptId = function->ScriptId();
|
|
if (scriptId == v8::UnboundScript::kNoScriptId) return v8::Null(m_isolate);
|
|
int lineNumber = function->GetScriptLineNumber();
|
|
int columnNumber = function->GetScriptColumnNumber();
|
|
if (lineNumber == v8::Function::kLineOffsetNotFound ||
|
|
columnNumber == v8::Function::kLineOffsetNotFound)
|
|
return v8::Null(m_isolate);
|
|
v8::Local<v8::Object> location = v8::Object::New(m_isolate);
|
|
if (!location->SetPrototype(context, v8::Null(m_isolate)).FromMaybe(false))
|
|
return v8::Null(m_isolate);
|
|
if (!createDataProperty(
|
|
context, location, toV8StringInternalized(m_isolate, "scriptId"),
|
|
toV8String(m_isolate, String16::fromInteger(scriptId)))
|
|
.FromMaybe(false))
|
|
return v8::Null(m_isolate);
|
|
if (!createDataProperty(context, location,
|
|
toV8StringInternalized(m_isolate, "lineNumber"),
|
|
v8::Integer::New(m_isolate, lineNumber))
|
|
.FromMaybe(false))
|
|
return v8::Null(m_isolate);
|
|
if (!createDataProperty(context, location,
|
|
toV8StringInternalized(m_isolate, "columnNumber"),
|
|
v8::Integer::New(m_isolate, columnNumber))
|
|
.FromMaybe(false))
|
|
return v8::Null(m_isolate);
|
|
if (!markAsInternal(context, location, V8InternalValueType::kLocation))
|
|
return v8::Null(m_isolate);
|
|
return location;
|
|
}
|
|
|
|
bool V8Debugger::isPaused() { return !m_pausedContext.IsEmpty(); }
|
|
|
|
std::unique_ptr<V8StackTraceImpl> V8Debugger::createStackTrace(
|
|
v8::Local<v8::StackTrace> stackTrace) {
|
|
int contextGroupId =
|
|
m_isolate->InContext() ? getGroupId(m_isolate->GetCurrentContext()) : 0;
|
|
return V8StackTraceImpl::create(this, contextGroupId, stackTrace,
|
|
V8StackTraceImpl::maxCallStackSizeToCapture);
|
|
}
|
|
|
|
int V8Debugger::markContext(const V8ContextInfo& info) {
|
|
DCHECK(info.context->GetIsolate() == m_isolate);
|
|
int contextId = ++m_lastContextId;
|
|
String16 debugData = String16::fromInteger(info.contextGroupId) + "," +
|
|
String16::fromInteger(contextId) + "," +
|
|
toString16(info.auxData);
|
|
v8::Context::Scope contextScope(info.context);
|
|
info.context->SetEmbedderData(static_cast<int>(v8::Context::kDebugIdIndex),
|
|
toV8String(m_isolate, debugData));
|
|
return contextId;
|
|
}
|
|
|
|
void V8Debugger::setAsyncCallStackDepth(V8DebuggerAgentImpl* agent, int depth) {
|
|
if (depth <= 0)
|
|
m_maxAsyncCallStackDepthMap.erase(agent);
|
|
else
|
|
m_maxAsyncCallStackDepthMap[agent] = depth;
|
|
|
|
int maxAsyncCallStackDepth = 0;
|
|
for (const auto& pair : m_maxAsyncCallStackDepthMap) {
|
|
if (pair.second > maxAsyncCallStackDepth)
|
|
maxAsyncCallStackDepth = pair.second;
|
|
}
|
|
|
|
if (m_maxAsyncCallStackDepth == maxAsyncCallStackDepth) return;
|
|
m_maxAsyncCallStackDepth = maxAsyncCallStackDepth;
|
|
if (!maxAsyncCallStackDepth) allAsyncTasksCanceled();
|
|
}
|
|
|
|
void V8Debugger::asyncTaskScheduled(const StringView& taskName, void* task,
|
|
bool recurring) {
|
|
if (!m_maxAsyncCallStackDepth) return;
|
|
asyncTaskScheduled(toString16(taskName), task, recurring);
|
|
}
|
|
|
|
void V8Debugger::asyncTaskScheduled(const String16& taskName, void* task,
|
|
bool recurring) {
|
|
if (!m_maxAsyncCallStackDepth) return;
|
|
v8::HandleScope scope(m_isolate);
|
|
int contextGroupId =
|
|
m_isolate->InContext() ? getGroupId(m_isolate->GetCurrentContext()) : 0;
|
|
std::unique_ptr<V8StackTraceImpl> chain = V8StackTraceImpl::capture(
|
|
this, contextGroupId, V8StackTraceImpl::maxCallStackSizeToCapture,
|
|
taskName);
|
|
if (chain) {
|
|
m_asyncTaskStacks[task] = std::move(chain);
|
|
if (recurring) m_recurringTasks.insert(task);
|
|
}
|
|
}
|
|
|
|
void V8Debugger::asyncTaskCanceled(void* task) {
|
|
if (!m_maxAsyncCallStackDepth) return;
|
|
m_asyncTaskStacks.erase(task);
|
|
m_recurringTasks.erase(task);
|
|
}
|
|
|
|
void V8Debugger::asyncTaskStarted(void* task) {
|
|
if (!m_maxAsyncCallStackDepth) return;
|
|
m_currentTasks.push_back(task);
|
|
AsyncTaskToStackTrace::iterator stackIt = m_asyncTaskStacks.find(task);
|
|
// Needs to support following order of events:
|
|
// - asyncTaskScheduled
|
|
// <-- attached here -->
|
|
// - asyncTaskStarted
|
|
// - asyncTaskCanceled <-- canceled before finished
|
|
// <-- async stack requested here -->
|
|
// - asyncTaskFinished
|
|
std::unique_ptr<V8StackTraceImpl> stack;
|
|
if (stackIt != m_asyncTaskStacks.end() && stackIt->second)
|
|
stack = stackIt->second->cloneImpl();
|
|
m_currentStacks.push_back(std::move(stack));
|
|
}
|
|
|
|
void V8Debugger::asyncTaskFinished(void* task) {
|
|
if (!m_maxAsyncCallStackDepth) return;
|
|
// We could start instrumenting half way and the stack is empty.
|
|
if (!m_currentStacks.size()) return;
|
|
|
|
DCHECK(m_currentTasks.back() == task);
|
|
m_currentTasks.pop_back();
|
|
|
|
m_currentStacks.pop_back();
|
|
if (m_recurringTasks.find(task) == m_recurringTasks.end())
|
|
m_asyncTaskStacks.erase(task);
|
|
}
|
|
|
|
void V8Debugger::allAsyncTasksCanceled() {
|
|
m_asyncTaskStacks.clear();
|
|
m_recurringTasks.clear();
|
|
m_currentStacks.clear();
|
|
m_currentTasks.clear();
|
|
}
|
|
|
|
void V8Debugger::muteScriptParsedEvents() {
|
|
++m_ignoreScriptParsedEventsCounter;
|
|
}
|
|
|
|
void V8Debugger::unmuteScriptParsedEvents() {
|
|
--m_ignoreScriptParsedEventsCounter;
|
|
DCHECK_GE(m_ignoreScriptParsedEventsCounter, 0);
|
|
}
|
|
|
|
std::unique_ptr<V8StackTraceImpl> V8Debugger::captureStackTrace(
|
|
bool fullStack) {
|
|
if (!m_isolate->InContext()) return nullptr;
|
|
|
|
v8::HandleScope handles(m_isolate);
|
|
int contextGroupId = getGroupId(m_isolate->GetCurrentContext());
|
|
if (!contextGroupId) return nullptr;
|
|
|
|
size_t stackSize =
|
|
fullStack ? V8StackTraceImpl::maxCallStackSizeToCapture : 1;
|
|
if (m_inspector->enabledRuntimeAgentForGroup(contextGroupId))
|
|
stackSize = V8StackTraceImpl::maxCallStackSizeToCapture;
|
|
|
|
return V8StackTraceImpl::capture(this, contextGroupId, stackSize);
|
|
}
|
|
|
|
} // namespace v8_inspector
|