Commit 54b704a5 authored by Ken Rockot's avatar Ken Rockot Committed by Chromium LUCI CQ

Support associated interfaces in Mojo JS modules

Adds associated interface support to modern Mojo JS bindings and
generated JS modules.

Fixed: 914165
Bug: 1004256
Change-Id: I5421ace585ad129374526bc7237980333b141b1e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2611647
Commit-Queue: Ken Rockot <rockot@google.com>
Reviewed-by: default avatarPeter Beverloo <peter@chromium.org>
Reviewed-by: default avatarOksana Zhuravlova <oksamyt@chromium.org>
Auto-Submit: Ken Rockot <rockot@google.com>
Cr-Commit-Position: refs/heads/master@{#841192}
parent b8a846a4
......@@ -61,3 +61,26 @@ struct StructVersionTest {
interface InterfaceVersionTest {
Foo(int32 x, [MinVersion=1] int32 y) => (int32 z, [MinVersion=1] int32 w);
};
interface Counter {
// Two different varieties of observer addition to exercise sending remotes
// and receiving receivers.
AddObserver(pending_associated_remote<CounterObserver> observer);
AddNewObserver() => (pending_associated_receiver<CounterObserver> receiver);
RemoveAllObservers();
// Two different varieties of cloning to exercise sending receivers and
// receiving remotes.
Clone(pending_associated_receiver<Counter> receiver);
CloneToNewRemote() => (pending_associated_remote<Counter> remote);
// Increments the counter, notifies all observers, then replies. Because
// observers are associated with this interface, they are therefore guaranteed
// to observe an increment before the caller observes its corresponding reply.
Increment() => (int32 count);
};
interface CounterObserver {
OnCountChanged(int32 count);
OnCloneDisconnected();
};
......@@ -164,6 +164,7 @@ static_library("web_test_browser") {
"//content/public/browser", # For component builds.
"//content/shell:content_shell_lib",
"//content/test:blink_test_browser_support",
"//content/test:mojo_bindings_web_test_mojom",
"//content/test:mojo_web_test_bindings",
"//content/test:test_support",
"//device/bluetooth:fake_bluetooth",
......
......@@ -30,6 +30,7 @@
#include "content/public/common/content_switches.h"
#include "content/shell/browser/shell_browser_context.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "content/test/data/mojo_bindings_web_test.test-mojom.h"
#include "content/test/data/mojo_web_test_helper_test.mojom.h"
#include "content/test/mock_badge_service.h"
#include "content/test/mock_clipboard_host.h"
......@@ -52,8 +53,11 @@
#include "device/bluetooth/public/mojom/test/fake_bluetooth.mojom.h"
#include "device/bluetooth/test/fake_bluetooth.h"
#include "gpu/config/gpu_switches.h"
#include "mojo/public/cpp/bindings/associated_receiver_set.h"
#include "mojo/public/cpp/bindings/binder_map.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/net_buildflags.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "services/service_manager/public/cpp/manifest.h"
......@@ -142,6 +146,65 @@ void CreateChildProcessCrashWatcher() {
static base::NoDestructor<ChildProcessCrashWatcher> watcher;
}
class MojoWebTestCounterImpl : public mojo_bindings_test::mojom::Counter {
public:
using CounterObserver = mojo_bindings_test::mojom::CounterObserver;
MojoWebTestCounterImpl() {
additional_receivers_.set_disconnect_handler(base::BindRepeating(
&MojoWebTestCounterImpl::OnCloneDisconnected, base::Unretained(this)));
}
~MojoWebTestCounterImpl() override = default;
static void Bind(mojo::PendingReceiver<Counter> receiver) {
mojo::MakeSelfOwnedReceiver(std::make_unique<MojoWebTestCounterImpl>(),
std::move(receiver));
}
// mojo_bindings_test::mojom::Counter:
void AddObserver(
mojo::PendingAssociatedRemote<CounterObserver> observer) override {
observers_.Add(std::move(observer));
}
void AddNewObserver(AddNewObserverCallback callback) override {
mojo::PendingAssociatedRemote<CounterObserver> observer;
std::move(callback).Run(observer.InitWithNewEndpointAndPassReceiver());
observers_.Add(std::move(observer));
}
void RemoveAllObservers() override { observers_.Clear(); }
void Clone(mojo::PendingAssociatedReceiver<Counter> receiver) override {
additional_receivers_.Add(this, std::move(receiver));
}
void CloneToNewRemote(CloneToNewRemoteCallback callback) override {
mojo::PendingAssociatedRemote<Counter> new_remote;
additional_receivers_.Add(this,
new_remote.InitWithNewEndpointAndPassReceiver());
std::move(callback).Run(std::move(new_remote));
}
void Increment(IncrementCallback callback) override {
++count_;
for (const auto& observer : observers_)
observer->OnCountChanged(count_);
std::move(callback).Run(count_);
}
private:
void OnCloneDisconnected() {
for (const auto& observer : observers_)
observer->OnCloneDisconnected();
}
int count_ = 0;
mojo::AssociatedReceiverSet<Counter> additional_receivers_;
mojo::AssociatedRemoteSet<CounterObserver> observers_;
};
} // namespace
WebTestContentBrowserClient::WebTestContentBrowserClient() {
......@@ -205,6 +268,8 @@ void WebTestContentBrowserClient::ExposeInterfacesToRenderer(
RenderProcessHost* render_process_host) {
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner =
content::GetUIThreadTaskRunner({});
registry->AddInterface(base::BindRepeating(&MojoWebTestCounterImpl::Bind),
ui_task_runner);
registry->AddInterface(base::BindRepeating(&MojoEcho::Bind), ui_task_runner);
registry->AddInterface(
base::BindRepeating(&WebTestBluetoothFakeAdapterSetterImpl::Create),
......
......@@ -103,6 +103,7 @@ concatenate_files("bindings_uncompiled_module") {
"bindings_uncompiled_module_preamble.js.part",
"bindings_lite.js",
"$root_gen_dir/mojo/public/interfaces/bindings/interface_control_messages.mojom-lite.js",
"$root_gen_dir/mojo/public/interfaces/bindings/pipe_control_messages.mojom-lite.js",
"interface_support.js",
"bindings_uncompiled_module_export.js.part",
]
......@@ -146,6 +147,7 @@ if (enable_mojom_closure_compile || enable_js_type_check) {
inputs = [
"$target_gen_dir/mojo_internal.js",
"$root_gen_dir/mojo/public/interfaces/bindings/interface_control_messages.mojom-lite-for-compile.js",
"$root_gen_dir/mojo/public/interfaces/bindings/pipe_control_messages.mojom-lite-for-compile.js",
"$target_gen_dir/interface_support.js",
]
script = "//mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py"
......
This diff is collapsed.
const mojoTmp = self['mojo'];
export {mojoTmp as mojo};
if (preservedGlobalMojo) {
window.mojo = preservedGlobalMojo;
self.mojo = preservedGlobalMojo;
}
const preservedGlobalMojo = window && window.mojo;
const preservedGlobalMojo = self && self.mojo;
const mojo = {
internal: { interfaceSupport: {} },
interfaceControl: {}
interfaceControl: {},
pipeControl: {},
};
This diff is collapsed.
......@@ -4,5 +4,9 @@ goog.require('mojo.interfaceControl.RUN_MESSAGE_ID');
goog.require('mojo.interfaceControl.RunResponseMessageParamsSpec');
goog.require('mojo.internal');
goog.require('mojo.pipeControl.RUN_OR_CLOSE_PIPE_MESSAGE_ID');
goog.require('mojo.pipeControl.RunOrClosePipeMessageParamsSpec');
goog.require('mojo.pipeControl.RunOrClosePipeInput');
goog.provide('mojo.internal.interfaceSupport');
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./associated-interfaces.js" type="module"></script>
import {Counter, CounterObserverReceiver, CounterRemote} from '/gen/content/test/data/mojo_bindings_web_test.test-mojom.m.js';
class ObserverImpl {
constructor() {
this.count_ = null;
this.disconnectResolvers_ = [];
}
get count() {
return this.count_;
}
async nextCloneDisconnect() {
return new Promise(r => this.disconnectResolvers_.push(r));
}
onCountChanged(count) {
this.count_ = count;
}
onCloneDisconnected() {
let resolvers = [];
[resolvers, this.disconnectResolvers_] =
[this.disconnectResolvers_, resolvers];
resolvers.forEach(r => r());
}
}
function getCounterRemote() {
const {handle0, handle1} = Mojo.createMessagePipe();
const remote = new CounterRemote(handle1);
Mojo.bindInterface(Counter.$interfaceName, handle0, 'process');
return remote;
}
async function waitForDisconnect(receiver) {
return new Promise(r => receiver.onConnectionError.addListener(r));
}
promise_test(async () => {
const counter = getCounterRemote();
counter.increment();
counter.increment();
const {count} = await counter.increment();
assert_equals(count, 3);
}, 'basic validity check for browser-side support of these tests');
promise_test(async () => {
const counter = getCounterRemote();
const observerA = new ObserverImpl;
const receiverA = new CounterObserverReceiver(observerA);
const observerB = new ObserverImpl;
const receiverB = new CounterObserverReceiver(observerB);
counter.addObserver(receiverA.$.associateAndPassRemote());
counter.addObserver(receiverB.$.associateAndPassRemote());
counter.increment();
const {count} = await counter.increment();
assert_equals(count, 2);
// The observers should always observe changes before the caller of increment
// gets a reply, so the above await should guarantee that the observers' count
// values are up-to-date.
assert_equals(observerA.count, 2);
assert_equals(observerB.count, 2);
}, 'associated remotes can be created and passed');
promise_test(async () => {
const counter = getCounterRemote();
const observerA = new ObserverImpl;
const receiverA = new CounterObserverReceiver(observerA);
const observerB = new ObserverImpl;
const receiverB = new CounterObserverReceiver(observerB);
receiverA.$.bindHandle((await counter.addNewObserver()).receiver.handle);
receiverB.$.bindHandle((await counter.addNewObserver()).receiver.handle);
counter.increment();
const {count} = await counter.increment();
assert_equals(count, 2);
assert_equals(observerA.count, 2);
assert_equals(observerB.count, 2);
}, 'associated receivers can be deserialized and receive messages');
promise_test(async () => {
const counterA = getCounterRemote();
const counterB = new CounterRemote;
const counterC = new CounterRemote;
const counterD = new CounterRemote;
counterA.clone(counterB.$.associateAndPassReceiver());
counterA.clone(counterC.$.associateAndPassReceiver());
counterB.clone(counterD.$.associateAndPassReceiver());
// Increment operations among the three interfaces should be strictly ordered.
const increments = [
counterA.increment(),
counterB.increment(),
counterC.increment(),
counterD.increment(),
counterA.increment(),
counterB.increment(),
counterC.increment(),
counterD.increment(),
];
const replies = await Promise.all(increments);
const results = replies.map(reply => reply.count);
assert_array_equals([1, 2, 3, 4, 5, 6, 7, 8], results);
}, 'associated receivers can be created and passed, and message ordering is preserved among endpoints');
promise_test(async () => {
const counterA = getCounterRemote();
const {remote: counterB} = await counterA.cloneToNewRemote();
const {remote: counterC} = await counterA.cloneToNewRemote();
const {remote: counterD} = await counterC.cloneToNewRemote();
const increments = [
counterA.increment(),
counterB.increment(),
counterC.increment(),
counterD.increment(),
counterA.increment(),
counterB.increment(),
counterC.increment(),
counterD.increment(),
];
const replies = await Promise.all(increments);
const results = replies.map(reply => reply.count);
assert_array_equals([1, 2, 3, 4, 5, 6, 7, 8], results);
}, 'associated remotes can be deserialized and used to send messages, and message ordering is preserved among endpoints');
promise_test(async () => {
const counter = getCounterRemote();
const observer = new ObserverImpl;
const receiver = new CounterObserverReceiver(observer);
counter.addObserver(receiver.$.associateAndPassRemote());
counter.increment();
counter.increment();
counter.increment();
await counter.$.flushForTesting();
assert_equals(observer.count, 3);
}, 'associated endpoints can use flushForTesting');
promise_test(async () => {
const counter = getCounterRemote();
const {remote: clone} = await counter.cloneToNewRemote();
const observer = new ObserverImpl;
const receiver = new CounterObserverReceiver(observer);
counter.addObserver(receiver.$.associateAndPassRemote());
clone.$.close();
observer.nextCloneDisconnect();
}, 'closing an associated endpoint from JavaScript will signal its peer');
promise_test(async () => {
const counter = getCounterRemote();
const observer = new ObserverImpl;
const receiver = new CounterObserverReceiver(observer);
counter.addObserver(receiver.$.associateAndPassRemote());
counter.removeAllObservers();
await waitForDisconnect(receiver);
}, 'JavaScript associated endpoints are notified when their peers close');
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment