Reland "[inspector] added Runtime.terminateExecution"

This is a reland of 98dec8f240

Original change's description:
> [inspector] added Runtime.terminateExecution
> 
> Runtime.terminateExecution terminates current or next JavaScript
> call. Termination flag is automatically reset as soon as v8 call
> or microtasks are completed.
> 
> R=pfeldman@chromium.org
> 
> Bug: chromium:820640
> Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel
> Change-Id: Ie21c123be3a61fe25cf6e04c38a8b6c664622ed7
> Reviewed-on: https://chromium-review.googlesource.com/957386
> Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
> Reviewed-by: Dmitry Gozman <dgozman@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#51912}

Bug: chromium:820640
Change-Id: I8f270c2fdbe732f0c40bfb149d26a6e73d988253
Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel
Reviewed-on: https://chromium-review.googlesource.com/966681
Reviewed-by: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#52002}
This commit is contained in:
Alexey Kozyatinskiy 2018-03-16 18:01:37 -07:00 committed by Commit Bot
parent 74a663be67
commit 14824520fc
13 changed files with 207 additions and 6 deletions

View File

@ -618,6 +618,9 @@ Response InjectedScript::wrapEvaluateResult(
m_lastEvaluationResult.AnnotateStrongRetainer(kGlobalHandleLabel);
}
} else {
if (tryCatch.HasTerminated() || !tryCatch.CanContinue()) {
return Response::Error("Execution was terminated");
}
v8::Local<v8::Value> exception = tryCatch.Exception();
Response response =
wrapObject(exception, objectGroup, false,

View File

@ -11,7 +11,7 @@
},
{
"domain": "Runtime",
"async": ["evaluate", "awaitPromise", "callFunctionOn", "runScript"],
"async": ["evaluate", "awaitPromise", "callFunctionOn", "runScript", "terminateExecution"],
"exported": ["StackTrace", "StackTraceId", "RemoteObject", "ExecutionContextId"]
},
{

View File

@ -2794,6 +2794,11 @@
"type": "boolean"
}
]
},
{
"name": "terminateExecution",
"description": "Terminate current or next JavaScript execution.\nWill cancel the termination when the outer-most script execution ends.",
"experimental": true
}
],
"events": [

View File

@ -1282,6 +1282,10 @@ domain Runtime
parameters
boolean enabled
# Terminate current or next JavaScript execution.
# Will cancel the termination when the outer-most script execution ends.
experimental command terminateExecution
# Issued when console API was called.
event consoleAPICalled
parameters

View File

@ -155,6 +155,12 @@ class MatchPrototypePredicate : public v8::debug::QueryObjectPredicate {
v8::Local<v8::Value> m_prototype;
};
// TODO(kozyatinskiy): add V8Inspector* field on i::Isolate instead.
std::unordered_map<v8::Isolate*, V8Debugger*>& IsolateToDebuggerMap() {
static std::unordered_map<v8::Isolate*, V8Debugger*> map;
return map;
}
} // namespace
V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
@ -166,9 +172,19 @@ V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
m_maxAsyncCallStacks(kMaxAsyncTaskStacks),
m_maxAsyncCallStackDepth(0),
m_pauseOnExceptionsState(v8::debug::NoBreakOnException),
m_wasmTranslation(isolate) {}
m_wasmTranslation(isolate) {
IsolateToDebuggerMap()[isolate] = this;
}
V8Debugger::~V8Debugger() {}
V8Debugger::~V8Debugger() {
IsolateToDebuggerMap().erase(m_isolate);
if (m_terminateExecutionCallback) {
m_isolate->RemoveCallCompletedCallback(
&V8Debugger::terminateExecutionCompletedCallback);
m_isolate->RemoveMicrotasksCompletedCallback(
&V8Debugger::terminateExecutionCompletedCallback);
}
}
void V8Debugger::enable() {
if (m_enableCount++) return;
@ -334,6 +350,33 @@ void V8Debugger::pauseOnAsyncCall(int targetContextGroupId, uintptr_t task,
m_taskWithScheduledBreakDebuggerId = debuggerId;
}
void V8Debugger::terminateExecution(
std::unique_ptr<TerminateExecutionCallback> callback) {
if (m_terminateExecutionCallback) {
callback->sendFailure(
Response::Error("There is current termination request in progress"));
return;
}
m_terminateExecutionCallback = std::move(callback);
m_isolate->AddCallCompletedCallback(
&V8Debugger::terminateExecutionCompletedCallback);
m_isolate->AddMicrotasksCompletedCallback(
&V8Debugger::terminateExecutionCompletedCallback);
m_isolate->TerminateExecution();
}
void V8Debugger::terminateExecutionCompletedCallback(v8::Isolate* isolate) {
isolate->RemoveCallCompletedCallback(
&V8Debugger::terminateExecutionCompletedCallback);
isolate->RemoveMicrotasksCompletedCallback(
&V8Debugger::terminateExecutionCompletedCallback);
V8Debugger* debugger = IsolateToDebuggerMap()[isolate];
DCHECK(debugger);
debugger->m_isolate->CancelTerminateExecution();
debugger->m_terminateExecutionCallback->sendSuccess();
debugger->m_terminateExecutionCallback.reset();
}
Response V8Debugger::continueToLocation(
int targetContextGroupId, V8DebuggerScript* script,
std::unique_ptr<protocol::Debugger::Location> location,

View File

@ -32,6 +32,8 @@ struct V8StackTraceId;
using protocol::Response;
using ScheduleStepIntoAsyncCallback =
protocol::Debugger::Backend::ScheduleStepIntoAsyncCallback;
using TerminateExecutionCallback =
protocol::Runtime::Backend::TerminateExecutionCallback;
class V8Debugger : public v8::debug::DebugDelegate {
public:
@ -60,6 +62,8 @@ class V8Debugger : public v8::debug::DebugDelegate {
void pauseOnAsyncCall(int targetContextGroupId, uintptr_t task,
const String16& debuggerId);
void terminateExecution(std::unique_ptr<TerminateExecutionCallback> callback);
Response continueToLocation(int targetContextGroupId,
V8DebuggerScript* script,
std::unique_ptr<protocol::Debugger::Location>,
@ -132,6 +136,7 @@ class V8Debugger : public v8::debug::DebugDelegate {
bool shouldContinueToCurrentLocation();
static void v8OOMCallback(void* data);
static void terminateExecutionCompletedCallback(v8::Isolate* isolate);
void handleProgramBreak(
v8::Local<v8::Context> pausedContext, v8::Local<v8::Value> exception,
@ -232,6 +237,8 @@ class V8Debugger : public v8::debug::DebugDelegate {
protocol::HashMap<String16, std::pair<int64_t, int64_t>>
m_serializedDebuggerIdToDebuggerId;
std::unique_ptr<TerminateExecutionCallback> m_terminateExecutionCallback;
WasmTranslation m_wasmTranslation;
DISALLOW_COPY_AND_ASSIGN(V8Debugger);

View File

@ -197,8 +197,11 @@ Response V8InspectorSessionImpl::findInjectedScript(
if (!context) return Response::Error("Cannot find context with specified id");
injectedScript = context->getInjectedScript(m_sessionId);
if (!injectedScript) {
if (!context->createInjectedScript(m_sessionId))
if (!context->createInjectedScript(m_sessionId)) {
if (m_inspector->isolate()->IsExecutionTerminating())
return Response::Error("Execution was terminated");
return Response::Error("Cannot access specified execution context");
}
injectedScript = context->getInjectedScript(m_sessionId);
if (m_customObjectFormatterEnabled)
injectedScript->setCustomObjectFormatterEnabled(true);

View File

@ -609,6 +609,11 @@ Response V8RuntimeAgentImpl::globalLexicalScopeNames(
return Response::OK();
}
void V8RuntimeAgentImpl::terminateExecution(
std::unique_ptr<TerminateExecutionCallback> callback) {
m_inspector->debugger()->terminateExecution(std::move(callback));
}
void V8RuntimeAgentImpl::restore() {
if (!m_state->booleanProperty(V8RuntimeAgentImplState::runtimeEnabled, false))
return;

View File

@ -104,6 +104,8 @@ class V8RuntimeAgentImpl : public protocol::Runtime::Backend {
Response globalLexicalScopeNames(
Maybe<int> executionContextId,
std::unique_ptr<protocol::Array<String16>>* outNames) override;
void terminateExecution(
std::unique_ptr<TerminateExecutionCallback> callback) override;
void reset();
void reportExecutionContextCreated(InspectedContext*);

View File

@ -93,7 +93,9 @@ void Isolate::FireBeforeCallEnteredCallback() {
}
void Isolate::FireMicrotasksCompletedCallback() {
for (auto& callback : microtasks_completed_callbacks_) {
std::vector<MicrotasksCompletedCallback> callbacks(
microtasks_completed_callbacks_);
for (auto& callback : callbacks) {
callback(reinterpret_cast<v8::Isolate*>(this));
}
}

View File

@ -3727,7 +3727,8 @@ void Isolate::FireCallCompletedCallback() {
// Fire callbacks. Increase call depth to prevent recursive callbacks.
v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(this);
v8::Isolate::SuppressMicrotaskExecutionScope suppress(isolate);
for (auto& callback : call_completed_callbacks_) {
std::vector<CallCompletedCallback> callbacks(call_completed_callbacks_);
for (auto& callback : callbacks) {
callback(reinterpret_cast<v8::Isolate*>(this));
}
}

View File

@ -0,0 +1,66 @@
Tests Runtime.terminateExecution
Terminate first evaluation (it forces injected-script-source compilation)
{
id : <messageId>
result : {
}
}
{
error : {
code : -32000
message : Cannot access specified execution context
}
id : <messageId>
}
Checks that we reset termination after evaluation
{
description : 42
type : number
value : 42
}
{
id : <messageId>
result : {
result : {
type : undefined
}
}
}
Terminate evaluation when injected-script-source already compiled
{
id : <messageId>
result : {
}
}
{
error : {
code : -32000
message : Execution was terminated
}
id : <messageId>
}
Terminate script evaluated with v8 API
{
id : <messageId>
result : {
}
}
Terminate chained callback
Pause inside microtask and terminate execution
{
id : <messageId>
result : {
}
}
{
type : string
value : separate eval after while(true)
}
{
id : <messageId>
result : {
result : {
type : undefined
}
}
}

View File

@ -0,0 +1,60 @@
// Copyright 2018 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.
let {session, contextGroup, Protocol} =
InspectorTest.start('Tests Runtime.terminateExecution');
(async function test() {
Protocol.Runtime.enable();
Protocol.Runtime.onConsoleAPICalled(
message => InspectorTest.logMessage(message.params.args[0]));
InspectorTest.log(
'Terminate first evaluation (it forces injected-script-source compilation)');
await Promise.all([
Protocol.Runtime.terminateExecution().then(InspectorTest.logMessage),
Protocol.Runtime.evaluate({expression: 'console.log(42)'})
.then(InspectorTest.logMessage)
]);
InspectorTest.log('Checks that we reset termination after evaluation');
InspectorTest.logMessage(
await Protocol.Runtime.evaluate({expression: 'console.log(42)'}));
InspectorTest.log(
'Terminate evaluation when injected-script-source already compiled');
await Promise.all([
Protocol.Runtime.terminateExecution().then(InspectorTest.logMessage),
Protocol.Runtime.evaluate({expression: 'console.log(42)'})
.then(InspectorTest.logMessage)
]);
InspectorTest.log('Terminate script evaluated with v8 API');
const terminated =
Protocol.Runtime.terminateExecution().then(InspectorTest.logMessage);
contextGroup.addScript('console.log(42)');
await terminated;
InspectorTest.log('Terminate chained callback');
Protocol.Debugger.enable();
const paused = Protocol.Debugger.oncePaused();
await Protocol.Runtime.evaluate({
expression: `let p = new Promise(resolve => setTimeout(resolve, 0));
p.then(() => {
while(true){ debugger; }
}).then(() => console.log('chained after chained callback'));
p.then(() => { console.log('another chained callback'); });
undefined`
});
await paused;
InspectorTest.log('Pause inside microtask and terminate execution');
Protocol.Runtime.terminateExecution().then(InspectorTest.logMessage);
await Protocol.Debugger.resume();
await Protocol.Runtime
.evaluate({expression: `console.log('separate eval after while(true)')`})
.then(InspectorTest.logMessage);
await Protocol.Debugger.disable();
await Protocol.Runtime.disable();
InspectorTest.completeTest();
})();