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:
parent
74a663be67
commit
14824520fc
@ -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,
|
||||
|
@ -11,7 +11,7 @@
|
||||
},
|
||||
{
|
||||
"domain": "Runtime",
|
||||
"async": ["evaluate", "awaitPromise", "callFunctionOn", "runScript"],
|
||||
"async": ["evaluate", "awaitPromise", "callFunctionOn", "runScript", "terminateExecution"],
|
||||
"exported": ["StackTrace", "StackTraceId", "RemoteObject", "ExecutionContextId"]
|
||||
},
|
||||
{
|
||||
|
@ -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": [
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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*);
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
66
test/inspector/runtime/terminate-execution-expected.txt
Normal file
66
test/inspector/runtime/terminate-execution-expected.txt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
60
test/inspector/runtime/terminate-execution.js
Normal file
60
test/inspector/runtime/terminate-execution.js
Normal 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();
|
||||
})();
|
Loading…
Reference in New Issue
Block a user