Skip to content

Commit abacd4c

Browse files
caseqCommit Bot
authored andcommitted
DevTools: add support for injecting bindings by context name
This adds support for injecting binding into contexts other than main based on the context name (AKA isolated world name in Blink terms). This would simplify a common use case for addBinding in Puppeteer and other automation tools that use addBinding to expose a back-channel for extension code running in an isolated world by making bindings available to such code at an early stage and in a race-free manner (currently, we can only inject a binding into specific context after the creation of the context has been reported to the client, which typically introduces a race with other evals the client may be running in the context). Change-Id: I66454954491a47a0c9aa4864f0aace4da2e67d3a Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2440984 Reviewed-by: Simon Zünd <szuend@chromium.org> Reviewed-by: Pavel Feldman <pfeldman@chromium.org> Commit-Queue: Andrey Kosyakov <caseq@chromium.org> Cr-Commit-Position: refs/heads/master@{#70266}
1 parent 179f7f4 commit abacd4c

5 files changed

Lines changed: 158 additions & 15 deletions

File tree

include/js_protocol.pdl

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,15 +1542,23 @@ domain Runtime
15421542
# If executionContextId is empty, adds binding with the given name on the
15431543
# global objects of all inspected contexts, including those created later,
15441544
# bindings survive reloads.
1545-
# If executionContextId is specified, adds binding only on global object of
1546-
# given execution context.
15471545
# Binding function takes exactly one argument, this argument should be string,
15481546
# in case of any other input, function throws an exception.
15491547
# Each binding function call produces Runtime.bindingCalled notification.
15501548
experimental command addBinding
15511549
parameters
15521550
string name
1551+
# If specified, the binding would only be exposed to the specified
1552+
# execution context. If omitted and `executionContextName` is not set,
1553+
# the binding is exposed to all execution contexts of the target.
1554+
# This parameter is mutually exclusive with `executionContextName`.
15531555
optional ExecutionContextId executionContextId
1556+
# If specified, the binding is exposed to the executionContext with
1557+
# matching name, even for contexts created after the binding is added.
1558+
# See also `ExecutionContext.name` and `worldName` parameter to
1559+
# `Page.addScriptToEvaluateOnNewDocument`.
1560+
# This parameter is mutually exclusive with `executionContextId`.
1561+
experimental optional string executionContextName
15541562

15551563
# This method does not remove binding function from global object but
15561564
# unsubscribes current runtime agent from Runtime.bindingCalled notifications.

src/inspector/v8-runtime-agent-impl.cc

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ static const char customObjectFormatterEnabled[] =
5656
"customObjectFormatterEnabled";
5757
static const char runtimeEnabled[] = "runtimeEnabled";
5858
static const char bindings[] = "bindings";
59+
static const char globalBindingsKey[] = "";
5960
} // namespace V8RuntimeAgentImplState
6061

6162
using protocol::Runtime::RemoteObject;
@@ -663,32 +664,61 @@ void V8RuntimeAgentImpl::terminateExecution(
663664
m_inspector->debugger()->terminateExecution(std::move(callback));
664665
}
665666

667+
namespace {
668+
protocol::DictionaryValue* getOrCreateDictionary(
669+
protocol::DictionaryValue* dict, const String16& key) {
670+
if (protocol::DictionaryValue* bindings = dict->getObject(key))
671+
return bindings;
672+
dict->setObject(key, protocol::DictionaryValue::create());
673+
return dict->getObject(key);
674+
}
675+
} // namespace
676+
666677
Response V8RuntimeAgentImpl::addBinding(const String16& name,
667-
Maybe<int> executionContextId) {
678+
Maybe<int> executionContextId,
679+
Maybe<String16> executionContextName) {
668680
if (m_activeBindings.count(name)) return Response::Success();
669681
if (executionContextId.isJust()) {
682+
if (executionContextName.isJust()) {
683+
return Response::InvalidParams(
684+
"executionContextName is mutually exclusive with executionContextId");
685+
}
670686
int contextId = executionContextId.fromJust();
671687
InspectedContext* context =
672688
m_inspector->getContext(m_session->contextGroupId(), contextId);
673689
if (!context) {
674-
return Response::ServerError(
690+
return Response::InvalidParams(
675691
"Cannot find execution context with given executionContextId");
676692
}
677693
addBinding(context, name);
678694
return Response::Success();
679695
}
696+
697+
// If it's a globally exposed binding, i.e. no context name specified, use
698+
// a special value for the context name.
699+
String16 contextKey = V8RuntimeAgentImplState::globalBindingsKey;
700+
if (executionContextName.isJust()) {
701+
contextKey = executionContextName.fromJust();
702+
if (contextKey == V8RuntimeAgentImplState::globalBindingsKey) {
703+
return Response::InvalidParams("Invalid executionContextName");
704+
}
705+
}
680706
// Only persist non context-specific bindings, as contextIds don't make
681707
// any sense when state is restored in a different process.
682-
if (!m_state->getObject(V8RuntimeAgentImplState::bindings)) {
683-
m_state->setObject(V8RuntimeAgentImplState::bindings,
684-
protocol::DictionaryValue::create());
685-
}
686708
protocol::DictionaryValue* bindings =
687-
m_state->getObject(V8RuntimeAgentImplState::bindings);
688-
bindings->setBoolean(name, true);
709+
getOrCreateDictionary(m_state, V8RuntimeAgentImplState::bindings);
710+
protocol::DictionaryValue* contextBindings =
711+
getOrCreateDictionary(bindings, contextKey);
712+
contextBindings->setBoolean(name, true);
713+
689714
m_inspector->forEachContext(
690715
m_session->contextGroupId(),
691-
[&name, this](InspectedContext* context) { addBinding(context, name); });
716+
[&name, &executionContextName, this](InspectedContext* context) {
717+
if (executionContextName.isJust() &&
718+
executionContextName.fromJust() != context->humanReadableName())
719+
return;
720+
addBinding(context, name);
721+
});
692722
return Response::Success();
693723
}
694724

@@ -750,12 +780,23 @@ void V8RuntimeAgentImpl::bindingCalled(const String16& name,
750780
}
751781

752782
void V8RuntimeAgentImpl::addBindings(InspectedContext* context) {
783+
const String16 contextName = context->humanReadableName();
753784
if (!m_enabled) return;
754785
protocol::DictionaryValue* bindings =
755786
m_state->getObject(V8RuntimeAgentImplState::bindings);
756787
if (!bindings) return;
757-
for (size_t i = 0; i < bindings->size(); ++i)
758-
addBinding(context, bindings->at(i).first);
788+
protocol::DictionaryValue* globalBindings =
789+
bindings->getObject(V8RuntimeAgentImplState::globalBindingsKey);
790+
if (globalBindings) {
791+
for (size_t i = 0; i < globalBindings->size(); ++i)
792+
addBinding(context, globalBindings->at(i).first);
793+
}
794+
protocol::DictionaryValue* contextBindings =
795+
contextName.isEmpty() ? nullptr : bindings->getObject(contextName);
796+
if (contextBindings) {
797+
for (size_t i = 0; i < contextBindings->size(); ++i)
798+
addBinding(context, contextBindings->at(i).first);
799+
}
759800
}
760801

761802
void V8RuntimeAgentImpl::restore() {

src/inspector/v8-runtime-agent-impl.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ class V8RuntimeAgentImpl : public protocol::Runtime::Backend {
117117
void terminateExecution(
118118
std::unique_ptr<TerminateExecutionCallback> callback) override;
119119

120-
Response addBinding(const String16& name,
121-
Maybe<int> executionContextId) override;
120+
Response addBinding(const String16& name, Maybe<int> executionContextId,
121+
Maybe<String16> executionContextName) override;
122122
Response removeBinding(const String16& name) override;
123123
void addBindings(InspectedContext* context);
124124

test/inspector/runtime/add-binding-expected.txt

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,51 @@ binding called in session1
156156
}
157157
}
158158
Call binding in newly created context (binding should NOT be exposed)
159+
160+
Running test: testAddBindingToContextByName
161+
Call binding in default context (binding should NOT be exposed)
162+
Call binding in Foo (binding should be exposed)
163+
binding called in session1
164+
{
165+
method : Runtime.bindingCalled
166+
params : {
167+
executionContextId : <executionContextId>
168+
name : frobnicate
169+
payload : message
170+
}
171+
}
172+
Call binding in Bar (binding should NOT be exposed)
173+
Call binding in newly-created Foo (binding should be exposed)
174+
binding called in session1
175+
{
176+
method : Runtime.bindingCalled
177+
params : {
178+
executionContextId : <executionContextId>
179+
name : frobnicate
180+
payload : message
181+
}
182+
}
183+
Call binding in newly-created Bazz (binding should NOT be exposed)
184+
185+
Running test: testErrors
186+
{
187+
error : {
188+
code : -32602
189+
message : Invalid executionContextName
190+
}
191+
id : <messageId>
192+
}
193+
{
194+
error : {
195+
code : -32602
196+
message : executionContextName is mutually exclusive with executionContextId
197+
}
198+
id : <messageId>
199+
}
200+
{
201+
error : {
202+
code : -32602
203+
message : Cannot find execution context with given executionContextId
204+
}
205+
id : <messageId>
206+
}

test/inspector/runtime/add-binding.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,53 @@ InspectorTest.runAsyncTestSuite([
8585
contextGroup.createContext();
8686
const contextId3 = (await session.Protocol.Runtime.onceExecutionContextCreated()).params.context.id;
8787
await session.Protocol.Runtime.evaluate({expression, contextId: contextId3});
88+
},
89+
90+
async function testAddBindingToContextByName() {
91+
const {contextGroup, sessions: [session]} = setupSessions(1);
92+
const defaultContext = (await session.Protocol.Runtime.onceExecutionContextCreated()).params.context.id;
93+
94+
contextGroup.createContext("foo");
95+
const contextFoo = (await session.Protocol.Runtime.onceExecutionContextCreated()).params.context.id;
96+
97+
contextGroup.createContext("bar");
98+
const contextBar = (await session.Protocol.Runtime.onceExecutionContextCreated()).params.context.id;
99+
100+
await session.Protocol.Runtime.addBinding({name: 'frobnicate', executionContextName: 'foo'});
101+
const expression = `frobnicate('message')`;
102+
103+
InspectorTest.log('Call binding in default context (binding should NOT be exposed)');
104+
await session.Protocol.Runtime.evaluate({expression});
105+
106+
InspectorTest.log('Call binding in Foo (binding should be exposed)');
107+
await session.Protocol.Runtime.evaluate({expression, contextId: contextFoo});
108+
109+
InspectorTest.log('Call binding in Bar (binding should NOT be exposed)');
110+
await session.Protocol.Runtime.evaluate({expression, contextId: contextBar});
111+
112+
contextGroup.createContext("foo");
113+
const contextFoo2 = (await session.Protocol.Runtime.onceExecutionContextCreated()).params.context.id;
114+
115+
InspectorTest.log('Call binding in newly-created Foo (binding should be exposed)');
116+
await session.Protocol.Runtime.evaluate({expression, contextId: contextFoo2});
117+
118+
contextGroup.createContext("bazz");
119+
const contextBazz = (await session.Protocol.Runtime.onceExecutionContextCreated()).params.context.id;
120+
121+
InspectorTest.log('Call binding in newly-created Bazz (binding should NOT be exposed)');
122+
await session.Protocol.Runtime.evaluate({expression, contextId: contextBazz});
123+
},
124+
125+
async function testErrors() {
126+
const {contextGroup, sessions: [session]} = setupSessions(1);
127+
let err = await session.Protocol.Runtime.addBinding({name: 'frobnicate', executionContextName: ''});
128+
InspectorTest.logMessage(err);
129+
err = await session.Protocol.Runtime.addBinding({name: 'frobnicate', executionContextName: 'foo', executionContextId: 1});
130+
InspectorTest.logMessage(err);
131+
err = await session.Protocol.Runtime.addBinding({name: 'frobnicate', executionContextId: 2128506});
132+
InspectorTest.logMessage(err);
88133
}
134+
89135
]);
90136

91137
function setupSessions(num) {

0 commit comments

Comments
 (0)