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: Peter Beverloo <peter@chromium.org> Reviewed-by: Oksana Zhuravlova <oksamyt@chromium.org> Auto-Submit: Ken Rockot <rockot@google.com> Cr-Commit-Position: refs/heads/master@{#841192}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
b8a846a4a0
commit
54b704a56c
content
mojo/public/js
BUILD.gnbindings_lite.jsbindings_module_export.js.partbindings_module_preamble.js.partbindings_uncompiled_module_preamble.js.partinterface_support.jsinterface_support_preamble.js.part
third_party/blink/web_tests/http/tests/mojo
@ -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"
|
||||
|
@ -20,6 +20,9 @@ mojo.internal.kMessageV0HeaderSize = 24;
|
||||
/** @const {number} */
|
||||
mojo.internal.kMessageV1HeaderSize = 32;
|
||||
|
||||
/** @const {number} */
|
||||
mojo.internal.kMessageV2HeaderSize = 48;
|
||||
|
||||
/** @const {number} */
|
||||
mojo.internal.kMapDataSize = 24;
|
||||
|
||||
@ -32,6 +35,9 @@ mojo.internal.kMessageFlagExpectsResponse = 1 << 0;
|
||||
/** @const {number} */
|
||||
mojo.internal.kMessageFlagIsResponse = 1 << 1;
|
||||
|
||||
/** @const {number} */
|
||||
mojo.internal.kInterfaceNamespaceBit = 0x80000000;
|
||||
|
||||
/** @const {boolean} */
|
||||
mojo.internal.kHostLittleEndian = (function() {
|
||||
const wordBytes = new Uint8Array(new Uint16Array([1]).buffer);
|
||||
@ -137,6 +143,14 @@ mojo.internal.getUint64 = function(dataView, byteOffset) {
|
||||
return (BigInt(high) << BigInt(32)) | BigInt(low);
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* size: number,
|
||||
* numInterfaceIds: (number|undefined),
|
||||
* }}
|
||||
*/
|
||||
mojo.internal.MessageDimensions;
|
||||
|
||||
/**
|
||||
* This computes the total amount of buffer space required to hold a struct
|
||||
* value and all its fields, including indirect objects like arrays, structs,
|
||||
@ -144,32 +158,41 @@ mojo.internal.getUint64 = function(dataView, byteOffset) {
|
||||
*
|
||||
* @param {!mojo.internal.StructSpec} structSpec
|
||||
* @param {!Object} value
|
||||
* @return {number}
|
||||
* @return {!mojo.internal.MessageDimensions}
|
||||
*/
|
||||
mojo.internal.computeTotalStructSize = function(structSpec, value) {
|
||||
mojo.internal.computeStructDimensions = function(structSpec, value) {
|
||||
let size = structSpec.packedSize;
|
||||
let numInterfaceIds = 0;
|
||||
for (const field of structSpec.fields) {
|
||||
const fieldValue = value[field.name];
|
||||
if (field.type.$.computePayloadSize &&
|
||||
!mojo.internal.isNullOrUndefined(fieldValue)) {
|
||||
size += mojo.internal.align(
|
||||
field.type.$.computePayloadSize(fieldValue, field.nullable), 8);
|
||||
if (mojo.internal.isNullOrUndefined(fieldValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.type.$.computeDimensions) {
|
||||
const fieldDimensions =
|
||||
field.type.$.computeDimensions(fieldValue, field.nullable);
|
||||
size += mojo.internal.align(fieldDimensions.size, 8);
|
||||
numInterfaceIds += fieldDimensions.numInterfaceIds;
|
||||
} else if (field.type.$.hasInterfaceId) {
|
||||
numInterfaceIds++;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
return {size, numInterfaceIds};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!mojo.internal.UnionSpec} unionSpec
|
||||
* @param {!Object} value
|
||||
* @return {number}
|
||||
* @return {!mojo.internal.MessageDimensions}
|
||||
*/
|
||||
mojo.internal.computeTotalUnionSize = function(unionSpec, nullable, value) {
|
||||
mojo.internal.computeUnionDimensions = function(unionSpec, nullable, value) {
|
||||
// Unions are normally inlined since they're always a fixed width of 16
|
||||
// bytes, but nullable union-typed fields require indirection. Hence this
|
||||
// unique special case where a union field requires additional storage
|
||||
// beyond the struct's own packed field data only when it's nullable.
|
||||
let size = nullable ? mojo.internal.kUnionDataSize : 0;
|
||||
let numInterfaceIds = 0;
|
||||
|
||||
const keys = Object.keys(value);
|
||||
if (keys.length !== 1) {
|
||||
@ -183,19 +206,20 @@ mojo.internal.computeTotalUnionSize = function(unionSpec, nullable, value) {
|
||||
const field = unionSpec.fields[tag];
|
||||
const fieldValue = value[tag];
|
||||
if (!mojo.internal.isNullOrUndefined(fieldValue)) {
|
||||
if (field['type'].$.unionSpec) {
|
||||
// Nested unions are always encoded with indirection, which we induce by
|
||||
// claiming the field is nullable even if it's not.
|
||||
size += mojo.internal.align(
|
||||
field['type'].$.computePayloadSize(fieldValue, true /* nullable */),
|
||||
8);
|
||||
} else if (field['type'].$.computePayloadSize) {
|
||||
size += mojo.internal.align(
|
||||
field['type'].$.computePayloadSize(fieldValue, field['nullable']), 8);
|
||||
// Nested unions are always encoded with indirection, which we induce by
|
||||
// claiming the field is nullable even if it's not.
|
||||
if (field['type'].$.computeDimensions) {
|
||||
const nullable = !!field['type'].$.unionSpec || field['nullable'];
|
||||
const fieldDimensions =
|
||||
field['type'].$.computeDimensions(fieldValue, nullable);
|
||||
size += mojo.internal.align(fieldDimensions.size, 8);
|
||||
numInterfaceIds += fieldDimensions.numInterfaceIds;
|
||||
} else if (field['type'].$.hasInterfaceId) {
|
||||
numInterfaceIds++;
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
return {size, numInterfaceIds};
|
||||
};
|
||||
|
||||
/**
|
||||
@ -220,15 +244,16 @@ mojo.internal.computeInlineArraySize = function(arraySpec, value) {
|
||||
*/
|
||||
mojo.internal.computeTotalArraySize = function(arraySpec, value) {
|
||||
const inlineSize = mojo.internal.computeInlineArraySize(arraySpec, value);
|
||||
if (!arraySpec.elementType.$.computePayloadSize)
|
||||
if (!arraySpec.elementType.$.computeDimensions)
|
||||
return inlineSize;
|
||||
|
||||
let totalSize = inlineSize;
|
||||
for (let elementValue of value) {
|
||||
if (!mojo.internal.isNullOrUndefined(elementValue)) {
|
||||
totalSize += mojo.internal.align(
|
||||
arraySpec.elementType.$.computePayloadSize(
|
||||
elementValue, !!arraySpec.elementNullable),
|
||||
arraySpec.elementType.$
|
||||
.computeDimensions(elementValue, !!arraySpec.elementNullable)
|
||||
.size,
|
||||
8);
|
||||
}
|
||||
}
|
||||
@ -239,6 +264,7 @@ mojo.internal.computeTotalArraySize = function(arraySpec, value) {
|
||||
/** Owns an outgoing message buffer and facilitates serialization. */
|
||||
mojo.internal.Message = class {
|
||||
/**
|
||||
* @param {?mojo.internal.interfaceSupport.Endpoint} sender
|
||||
* @param {number} interfaceId
|
||||
* @param {number} flags
|
||||
* @param {number} ordinal
|
||||
@ -247,9 +273,17 @@ mojo.internal.Message = class {
|
||||
* @param {!Object} value
|
||||
* @public
|
||||
*/
|
||||
constructor(interfaceId, flags, ordinal, requestId, paramStructSpec, value) {
|
||||
constructor(
|
||||
sender, interfaceId, flags, ordinal, requestId, paramStructSpec, value) {
|
||||
const dimensions =
|
||||
mojo.internal.computeStructDimensions(paramStructSpec, value);
|
||||
|
||||
let headerSize, version;
|
||||
if ((flags &
|
||||
if (dimensions.numInterfaceIds > 0) {
|
||||
headerSize = mojo.internal.kMessageV2HeaderSize;
|
||||
version = 2;
|
||||
} else if (
|
||||
(flags &
|
||||
(mojo.internal.kMessageFlagExpectsResponse |
|
||||
mojo.internal.kMessageFlagIsResponse)) == 0) {
|
||||
headerSize = mojo.internal.kMessageV0HeaderSize;
|
||||
@ -259,8 +293,12 @@ mojo.internal.Message = class {
|
||||
version = 1;
|
||||
}
|
||||
|
||||
const totalMessageSize = headerSize +
|
||||
mojo.internal.computeTotalStructSize(paramStructSpec, value);
|
||||
const headerWithPayloadSize = headerSize + dimensions.size;
|
||||
const interfaceIdsSize = dimensions.numInterfaceIds > 0 ?
|
||||
mojo.internal.kArrayHeaderSize + dimensions.numInterfaceIds * 4 :
|
||||
0;
|
||||
const paddedInterfaceIdsSize = mojo.internal.align(interfaceIdsSize, 8);
|
||||
const totalMessageSize = headerWithPayloadSize + paddedInterfaceIdsSize;
|
||||
|
||||
/** @public {!ArrayBuffer} */
|
||||
this.buffer = new ArrayBuffer(totalMessageSize);
|
||||
@ -275,14 +313,38 @@ mojo.internal.Message = class {
|
||||
header.setUint32(12, ordinal, mojo.internal.kHostLittleEndian);
|
||||
header.setUint32(16, flags, mojo.internal.kHostLittleEndian);
|
||||
header.setUint32(20, 0); // Padding
|
||||
if (version > 0)
|
||||
if (version >= 1) {
|
||||
mojo.internal.setUint64(header, 24, requestId);
|
||||
if (version >= 2) {
|
||||
mojo.internal.setUint64(header, 32, BigInt(16));
|
||||
mojo.internal.setUint64(header, 40, BigInt(headerWithPayloadSize - 40));
|
||||
header.setUint32(
|
||||
headerWithPayloadSize, interfaceIdsSize,
|
||||
mojo.internal.kHostLittleEndian);
|
||||
header.setUint32(
|
||||
headerWithPayloadSize + 4, dimensions.numInterfaceIds || 0,
|
||||
mojo.internal.kHostLittleEndian);
|
||||
}
|
||||
}
|
||||
|
||||
/** @private {number} */
|
||||
this.nextInterfaceIdIndex_ = 0;
|
||||
|
||||
/** @private {?Uint32Array} */
|
||||
this.interfaceIds_ = null;
|
||||
|
||||
if (dimensions.numInterfaceIds) {
|
||||
this.interfaceIds_ = new Uint32Array(
|
||||
this.buffer, headerWithPayloadSize + mojo.internal.kArrayHeaderSize,
|
||||
dimensions.numInterfaceIds);
|
||||
}
|
||||
|
||||
/** @private {number} */
|
||||
this.nextAllocationOffset_ = headerSize;
|
||||
|
||||
const paramStructData = this.allocate(paramStructSpec.packedSize);
|
||||
const encoder = new mojo.internal.Encoder(this, paramStructData);
|
||||
const encoder =
|
||||
new mojo.internal.Encoder(this, paramStructData, {endpoint: sender});
|
||||
encoder.encodeStructInline(paramStructSpec, value);
|
||||
}
|
||||
|
||||
@ -299,6 +361,15 @@ mojo.internal.Message = class {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Additional context to aid in encoding and decoding of message data.
|
||||
*
|
||||
* @typedef {{
|
||||
* endpoint: ?mojo.internal.interfaceSupport.Endpoint,
|
||||
* }}
|
||||
*/
|
||||
mojo.internal.MessageContext;
|
||||
|
||||
/**
|
||||
* Helps encode outgoing messages. Encoders may be created recursively to encode
|
||||
* parial message fragments indexed by indirect message offsets, as with encoded
|
||||
@ -308,9 +379,13 @@ mojo.internal.Encoder = class {
|
||||
/**
|
||||
* @param {!mojo.internal.Message} message
|
||||
* @param {!DataView} data
|
||||
* @param {?mojo.internal.MessageContext=} context
|
||||
* @public
|
||||
*/
|
||||
constructor(message, data) {
|
||||
constructor(message, data, context = null) {
|
||||
/** @const {?mojo.internal.MessageContext} */
|
||||
this.context_ = context;
|
||||
|
||||
/** @private {!mojo.internal.Message} */
|
||||
this.message_ = message;
|
||||
|
||||
@ -371,6 +446,16 @@ mojo.internal.Encoder = class {
|
||||
this.message_.handles.push(value);
|
||||
}
|
||||
|
||||
encodeAssociatedEndpoint(offset, endpoint) {
|
||||
console.assert(
|
||||
endpoint.isPendingAssociation, 'expected unbound associated endpoint');
|
||||
const sender = this.context_.endpoint;
|
||||
const id = sender.associatePeerOfOutgoingEndpoint(endpoint);
|
||||
const index = this.message_.nextInterfaceIdIndex_++;
|
||||
this.encodeUint32(offset, index);
|
||||
this.message_.interfaceIds_[index] = id;
|
||||
}
|
||||
|
||||
encodeString(offset, value) {
|
||||
if (typeof value !== 'string')
|
||||
throw new Error('Unxpected non-string value for string field.');
|
||||
@ -391,7 +476,8 @@ mojo.internal.Encoder = class {
|
||||
encodeArray(arraySpec, offset, value) {
|
||||
const arraySize = mojo.internal.computeInlineArraySize(arraySpec, value);
|
||||
const arrayData = this.message_.allocate(arraySize);
|
||||
const arrayEncoder = new mojo.internal.Encoder(this.message_, arrayData);
|
||||
const arrayEncoder =
|
||||
new mojo.internal.Encoder(this.message_, arrayData, this.context_);
|
||||
this.encodeOffset(offset, arrayData.byteOffset);
|
||||
|
||||
arrayEncoder.encodeUint32(0, arraySize);
|
||||
@ -442,7 +528,8 @@ mojo.internal.Encoder = class {
|
||||
}
|
||||
|
||||
const mapData = this.message_.allocate(mojo.internal.kMapDataSize);
|
||||
const mapEncoder = new mojo.internal.Encoder(this.message_, mapData);
|
||||
const mapEncoder =
|
||||
new mojo.internal.Encoder(this.message_, mapData, this.context_);
|
||||
this.encodeOffset(offset, mapData.byteOffset);
|
||||
|
||||
mapEncoder.encodeUint32(0, mojo.internal.kMapDataSize);
|
||||
@ -463,7 +550,8 @@ mojo.internal.Encoder = class {
|
||||
*/
|
||||
encodeStruct(structSpec, offset, value) {
|
||||
const structData = this.message_.allocate(structSpec.packedSize);
|
||||
const structEncoder = new mojo.internal.Encoder(this.message_, structData);
|
||||
const structEncoder =
|
||||
new mojo.internal.Encoder(this.message_, structData, this.context_);
|
||||
this.encodeOffset(offset, structData.byteOffset);
|
||||
structEncoder.encodeStructInline(structSpec, value);
|
||||
}
|
||||
@ -513,7 +601,8 @@ mojo.internal.Encoder = class {
|
||||
*/
|
||||
encodeUnionAsPointer(unionSpec, offset, value) {
|
||||
const unionData = this.message_.allocate(mojo.internal.kUnionDataSize);
|
||||
const unionEncoder = new mojo.internal.Encoder(this.message_, unionData);
|
||||
const unionEncoder =
|
||||
new mojo.internal.Encoder(this.message_, unionData, this.context_);
|
||||
this.encodeOffset(offset, unionData.byteOffset);
|
||||
unionEncoder.encodeUnion(unionSpec, /*offset=*/0, value);
|
||||
}
|
||||
@ -571,8 +660,12 @@ mojo.internal.Decoder = class {
|
||||
/**
|
||||
* @param {!DataView} data
|
||||
* @param {!Array<MojoHandle>} handles
|
||||
* @param {?mojo.internal.MessageContext=} context
|
||||
*/
|
||||
constructor(data, handles) {
|
||||
constructor(data, handles, context = null) {
|
||||
/** @private {?mojo.internal.MessageContext} */
|
||||
this.context_ = context;
|
||||
|
||||
/** @private {!DataView} */
|
||||
this.data_ = data;
|
||||
|
||||
@ -664,7 +757,8 @@ mojo.internal.Decoder = class {
|
||||
return null;
|
||||
|
||||
const arrayDecoder = new mojo.internal.Decoder(
|
||||
new DataView(this.data_.buffer, arrayOffset), this.handles_);
|
||||
new DataView(this.data_.buffer, arrayOffset), this.handles_,
|
||||
this.context_);
|
||||
|
||||
const size = arrayDecoder.decodeUint32(0);
|
||||
const numElements = arrayDecoder.decodeUint32(4);
|
||||
@ -700,7 +794,8 @@ mojo.internal.Decoder = class {
|
||||
return null;
|
||||
|
||||
const mapDecoder = new mojo.internal.Decoder(
|
||||
new DataView(this.data_.buffer, mapOffset), this.handles_);
|
||||
new DataView(this.data_.buffer, mapOffset), this.handles_,
|
||||
this.context_);
|
||||
const mapStructSize = mapDecoder.decodeUint32(0);
|
||||
const mapStructVersion = mapDecoder.decodeUint32(4);
|
||||
if (mapStructSize != mojo.internal.kMapDataSize || mapStructVersion != 0)
|
||||
@ -739,7 +834,8 @@ mojo.internal.Decoder = class {
|
||||
return null;
|
||||
|
||||
const decoder = new mojo.internal.Decoder(
|
||||
new DataView(this.data_.buffer, structOffset), this.handles_);
|
||||
new DataView(this.data_.buffer, structOffset), this.handles_,
|
||||
this.context_);
|
||||
return decoder.decodeStructInline(structSpec);
|
||||
}
|
||||
|
||||
@ -815,7 +911,8 @@ mojo.internal.Decoder = class {
|
||||
return null;
|
||||
|
||||
const decoder = new mojo.internal.Decoder(
|
||||
new DataView(this.data_.buffer, unionOffset), this.handles_);
|
||||
new DataView(this.data_.buffer, unionOffset), this.handles_,
|
||||
this.context_);
|
||||
return decoder.decodeUnion(unionSpec, 0);
|
||||
}
|
||||
|
||||
@ -870,6 +967,25 @@ mojo.internal.Decoder = class {
|
||||
return null;
|
||||
return new type(mojo.internal.interfaceSupport.createEndpoint(handle));
|
||||
}
|
||||
|
||||
decodeAssociatedEndpoint(offset) {
|
||||
if (!this.context_ || !this.context_.endpoint) {
|
||||
throw new Error('cannot deserialize associated endpoint without context');
|
||||
}
|
||||
const receivingEndpoint = this.context_.endpoint;
|
||||
const message = new DataView(this.data_.buffer);
|
||||
const interfaceIdsOffset = Number(mojo.internal.getUint64(message, 40));
|
||||
const numInterfaceIds = message.getUint32(
|
||||
interfaceIdsOffset + 44, mojo.internal.kHostLittleEndian);
|
||||
const interfaceIds = new Uint32Array(
|
||||
message.buffer,
|
||||
interfaceIdsOffset + mojo.internal.kArrayHeaderSize + 40,
|
||||
numInterfaceIds);
|
||||
const index = this.decodeUint32(offset);
|
||||
const interfaceId = interfaceIds[index];
|
||||
return new mojo.internal.interfaceSupport.Endpoint(
|
||||
receivingEndpoint.router, interfaceId);
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {TextDecoder} */
|
||||
@ -918,8 +1034,10 @@ mojo.internal.deserializeMessageHeader = function(data) {
|
||||
* encode: function(*, !mojo.internal.Encoder, number, number, boolean),
|
||||
* encodeNull: ((function(!mojo.internal.Encoder, number))|undefined),
|
||||
* decode: function(!mojo.internal.Decoder, number, number, boolean):*,
|
||||
* computePayloadSize: ((function(*, boolean):number)|undefined),
|
||||
* computeDimensions:
|
||||
* ((function(*, boolean):!mojo.internal.MessageDimensions)|undefined),
|
||||
* isValidObjectKeyType: boolean,
|
||||
* hasInterfaceId: (boolean|undefined),
|
||||
* arrayElementSize: ((function(boolean):number)|undefined),
|
||||
* arraySpec: (!mojo.internal.ArraySpec|undefined),
|
||||
* mapSpec: (!mojo.internal.MapSpec|undefined),
|
||||
@ -1217,10 +1335,11 @@ mojo.internal.String = {
|
||||
decode: function(decoder, byteOffset, bitOffset, nullable) {
|
||||
return decoder.decodeString(byteOffset);
|
||||
},
|
||||
computePayloadSize: function(value, nullable) {
|
||||
return mojo.internal.computeTotalArraySize(
|
||||
computeDimensions: function(value, nullable) {
|
||||
const size = mojo.internal.computeTotalArraySize(
|
||||
{elementType: mojo.internal.Uint8},
|
||||
mojo.internal.Encoder.stringToUtf8Bytes(value));
|
||||
return {size};
|
||||
},
|
||||
arrayElementSize: nullable => 8,
|
||||
isValidObjectKeyType: true,
|
||||
@ -1249,8 +1368,8 @@ mojo.internal.Array = function(elementType, elementNullable) {
|
||||
decode: function(decoder, byteOffset, bitOffset, nullable) {
|
||||
return decoder.decodeArray(arraySpec, byteOffset);
|
||||
},
|
||||
computePayloadSize: function(value, nullable) {
|
||||
return mojo.internal.computeTotalArraySize(arraySpec, value);
|
||||
computeDimensions: function(value, nullable) {
|
||||
return {size: mojo.internal.computeTotalArraySize(arraySpec, value)};
|
||||
},
|
||||
arrayElementSize: nullable => 8,
|
||||
isValidObjectKeyType: false,
|
||||
@ -1282,13 +1401,13 @@ mojo.internal.Map = function(keyType, valueType, valueNullable) {
|
||||
decode: function(decoder, byteOffset, bitOffset, nullable) {
|
||||
return decoder.decodeMap(mapSpec, byteOffset);
|
||||
},
|
||||
computePayloadSize: function(value, nullable) {
|
||||
computeDimensions: function(value, nullable) {
|
||||
const keys = (value instanceof Map) ? Array.from(value.keys()) :
|
||||
Object.keys(value);
|
||||
const values = (value instanceof Map) ? Array.from(value.values()) :
|
||||
keys.map(k => value[k]);
|
||||
|
||||
return mojo.internal.kMapDataSize +
|
||||
const size = mojo.internal.kMapDataSize +
|
||||
mojo.internal.computeTotalArraySize({elementType: keyType}, keys) +
|
||||
mojo.internal.computeTotalArraySize(
|
||||
{
|
||||
@ -1296,6 +1415,7 @@ mojo.internal.Map = function(keyType, valueType, valueNullable) {
|
||||
elementNullable: valueNullable,
|
||||
},
|
||||
values);
|
||||
return {size};
|
||||
},
|
||||
arrayElementSize: nullable => 8,
|
||||
isValidObjectKeyType: false,
|
||||
@ -1371,8 +1491,8 @@ mojo.internal.Struct = function(
|
||||
decode: function(decoder, byteOffset, bitOffset, nullable) {
|
||||
return decoder.decodeStruct(structSpec, byteOffset);
|
||||
},
|
||||
computePayloadSize: function(value, nullable) {
|
||||
return mojo.internal.computeTotalStructSize(structSpec, value);
|
||||
computeDimensions: function(value, nullable) {
|
||||
return mojo.internal.computeStructDimensions(structSpec, value);
|
||||
},
|
||||
arrayElementSize: nullable => 8,
|
||||
isValidObjectKeyType: false,
|
||||
@ -1416,8 +1536,8 @@ mojo.internal.Union = function(objectToBlessAsUnion, name, fields) {
|
||||
decode: function(decoder, byteOffset, bitOffset, nullable) {
|
||||
return decoder.decodeUnion(unionSpec, byteOffset);
|
||||
},
|
||||
computePayloadSize: function(value, nullable) {
|
||||
return mojo.internal.computeTotalUnionSize(unionSpec, nullable, value);
|
||||
computeDimensions: function(value, nullable) {
|
||||
return mojo.internal.computeUnionDimensions(unionSpec, nullable, value);
|
||||
},
|
||||
arrayElementSize: nullable => (nullable ? 8 : 16),
|
||||
isValidObjectKeyType: false,
|
||||
@ -1490,12 +1610,27 @@ mojo.internal.AssociatedInterfaceProxy = function(type) {
|
||||
$: {
|
||||
type: type,
|
||||
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
|
||||
throw new Error('Associated interfaces not supported yet.');
|
||||
console.assert(
|
||||
value instanceof type,
|
||||
`unexpected object in place of ${type.name}: `, value);
|
||||
console.assert(
|
||||
value.proxy.endpoint && value.proxy.endpoint.isPendingAssociation,
|
||||
`expected ${type.name} to be associated and unbound`);
|
||||
encoder.encodeAssociatedEndpoint(byteOffset, value.proxy.endpoint);
|
||||
encoder.encodeUint32(byteOffset + 4, 0);
|
||||
},
|
||||
encodeNull: function(encoder, byteOffset) {
|
||||
encoder.encodeUint32(byteOffset, 0xffffffff);
|
||||
encoder.encodeUint32(byteOffset + 4, 0);
|
||||
},
|
||||
decode: function(decoder, byteOffset, bitOffset, nullable) {
|
||||
throw new Error('Associated interfaces not supported yet.');
|
||||
return new type(decoder.decodeAssociatedEndpoint(byteOffset));
|
||||
},
|
||||
arrayElementSize: _ => {
|
||||
throw new Error('Arrays of associated endpoints are not yet supported');
|
||||
},
|
||||
isValidObjectKeyType: false,
|
||||
hasInterfaceId: true,
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -1509,12 +1644,26 @@ mojo.internal.AssociatedInterfaceRequest = function(type) {
|
||||
$: {
|
||||
type: type,
|
||||
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
|
||||
throw new Error('Associated interfaces not supported yet.');
|
||||
console.assert(
|
||||
value instanceof type,
|
||||
`unexpected object in place of ${type.name}: `, value);
|
||||
console.assert(
|
||||
value.handle && value.handle.isPendingAssociation,
|
||||
`expected ${type.name} to be associated and unbound`);
|
||||
|
||||
encoder.encodeAssociatedEndpoint(byteOffset, value.handle);
|
||||
},
|
||||
encodeNull: function(encoder, byteOffset) {
|
||||
encoder.encodeUint32(byteOffset, 0xffffffff);
|
||||
},
|
||||
decode: function(decoder, byteOffset, bitOffset, nullable) {
|
||||
throw new Error('Associated interfaces not supported yet.');
|
||||
return new type(decoder.decodeAssociatedEndpoint(byteOffset));
|
||||
},
|
||||
arrayElementSize: _ => {
|
||||
throw new Error('Arrays of associated endpoints are not yet supported');
|
||||
},
|
||||
isValidObjectKeyType: false,
|
||||
hasInterfaceId: true,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
const mojoTmp = self['mojo'];
|
||||
export {mojoTmp as mojo};
|
||||
if (preservedGlobalMojo) {
|
||||
window.mojo = preservedGlobalMojo;
|
||||
self.mojo = preservedGlobalMojo;
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
const preservedGlobalMojo = window && window.mojo;
|
||||
const preservedGlobalMojo = self && self.mojo;
|
||||
|
@ -1,4 +1,5 @@
|
||||
const mojo = {
|
||||
internal: { interfaceSupport: {} },
|
||||
interfaceControl: {}
|
||||
interfaceControl: {},
|
||||
pipeControl: {},
|
||||
};
|
||||
|
@ -9,9 +9,10 @@
|
||||
mojo.internal.interfaceSupport.Router = class {
|
||||
/**
|
||||
* @param {!MojoHandle} pipe
|
||||
* @param {boolean} setNamespaceBit
|
||||
* @public
|
||||
*/
|
||||
constructor(pipe) {
|
||||
constructor(pipe, setNamespaceBit) {
|
||||
/** @const {!MojoHandle} */
|
||||
this.pipe_ = pipe;
|
||||
|
||||
@ -22,6 +23,18 @@ mojo.internal.interfaceSupport.Router = class {
|
||||
|
||||
/** @const {!Map<number, !mojo.internal.interfaceSupport.Endpoint>} */
|
||||
this.endpoints_ = new Map();
|
||||
|
||||
/** @private {number} */
|
||||
this.nextInterfaceId_ = 1;
|
||||
|
||||
/** @const {number} */
|
||||
this.interfaceIdNamespace_ =
|
||||
setNamespaceBit ? mojo.internal.kInterfaceNamespaceBit : 0;
|
||||
|
||||
/** @const {!mojo.internal.interfaceSupport.PipeControlMessageHandler} */
|
||||
this.pipeControlHandler_ =
|
||||
new mojo.internal.interfaceSupport.PipeControlMessageHandler(
|
||||
this, this.onPeerEndpointClosed_.bind(this));
|
||||
}
|
||||
|
||||
/** @return {!MojoHandle} */
|
||||
@ -29,6 +42,11 @@ mojo.internal.interfaceSupport.Router = class {
|
||||
return this.pipe_;
|
||||
}
|
||||
|
||||
/** @return {number} */
|
||||
generateInterfaceId() {
|
||||
return (this.nextInterfaceId_++ | this.interfaceIdNamespace_) >>> 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!mojo.internal.interfaceSupport.Endpoint} endpoint
|
||||
* @param {number} interfaceId
|
||||
@ -57,6 +75,16 @@ mojo.internal.interfaceSupport.Router = class {
|
||||
this.reader_.stopAndCloseHandle();
|
||||
}
|
||||
|
||||
/** @param {number} interfaceId */
|
||||
closeEndpoint(interfaceId) {
|
||||
this.removeEndpoint(interfaceId);
|
||||
if (interfaceId === 0) {
|
||||
this.close();
|
||||
} else {
|
||||
this.pipeControlHandler_.notifyEndpointClosed(interfaceId);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return {boolean} */
|
||||
isReading() {
|
||||
return !this.reader_.isStopped();
|
||||
@ -79,6 +107,10 @@ mojo.internal.interfaceSupport.Router = class {
|
||||
}
|
||||
|
||||
const header = mojo.internal.deserializeMessageHeader(new DataView(buffer));
|
||||
if (this.pipeControlHandler_.maybeHandleMessage(header, buffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const endpoint = this.endpoints_.get(header.interfaceId);
|
||||
if (!endpoint) {
|
||||
console.error(
|
||||
@ -95,6 +127,14 @@ mojo.internal.interfaceSupport.Router = class {
|
||||
}
|
||||
this.endpoints_.clear();
|
||||
}
|
||||
|
||||
/** @param {number} id */
|
||||
onPeerEndpointClosed_(id) {
|
||||
const endpoint = this.endpoints_.get(id);
|
||||
if (endpoint) {
|
||||
endpoint.onError();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -144,6 +184,28 @@ mojo.internal.interfaceSupport.Endpoint = class {
|
||||
|
||||
/** @private {number} */
|
||||
this.nextRequestId_ = 0;
|
||||
|
||||
/** @private {mojo.internal.interfaceSupport.Endpoint} */
|
||||
this.localPeer_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {{
|
||||
* endpoint0: !mojo.internal.interfaceSupport.Endpoint,
|
||||
* endpoint1: !mojo.internal.interfaceSupport.Endpoint,
|
||||
* }}
|
||||
*/
|
||||
static createAssociatedPair() {
|
||||
const endpoint0 = new mojo.internal.interfaceSupport.Endpoint();
|
||||
const endpoint1 = new mojo.internal.interfaceSupport.Endpoint();
|
||||
endpoint1.localPeer_ = endpoint0;
|
||||
endpoint0.localPeer_ = endpoint1;
|
||||
return {endpoint0, endpoint1};
|
||||
}
|
||||
|
||||
/** @return {mojo.internal.interfaceSupport.Router} */
|
||||
get router() {
|
||||
return this.router_;
|
||||
}
|
||||
|
||||
/** @return {boolean} */
|
||||
@ -157,6 +219,11 @@ mojo.internal.interfaceSupport.Endpoint = class {
|
||||
return this.router_.pipe;
|
||||
}
|
||||
|
||||
/** @return {boolean} */
|
||||
get isPendingAssociation() {
|
||||
return this.localPeer_ !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} interfaceName
|
||||
* @param {string} scope
|
||||
@ -168,6 +235,24 @@ mojo.internal.interfaceSupport.Endpoint = class {
|
||||
Mojo.bindInterface(interfaceName, this.router_.pipe, scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!mojo.internal.interfaceSupport.Endpoint} endpoint
|
||||
* @return {number}
|
||||
*/
|
||||
associatePeerOfOutgoingEndpoint(endpoint) {
|
||||
console.assert(this.router_, 'cannot associate with unbound endpoint');
|
||||
const peer = endpoint.localPeer_;
|
||||
endpoint.localPeer_ = peer.localPeer_ = null;
|
||||
|
||||
const id = this.router_.generateInterfaceId();
|
||||
peer.router_ = this.router_;
|
||||
peer.interfaceId_ = id;
|
||||
if (peer.client_) {
|
||||
this.router_.addEndpoint(peer, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/** @return {number} */
|
||||
generateRequestId() {
|
||||
const id = this.nextRequestId_++;
|
||||
@ -186,7 +271,7 @@ mojo.internal.interfaceSupport.Endpoint = class {
|
||||
*/
|
||||
send(ordinal, requestId, flags, paramStruct, value) {
|
||||
const message = new mojo.internal.Message(
|
||||
this.interfaceId_, flags, ordinal, requestId,
|
||||
this, this.interfaceId_, flags, ordinal, requestId,
|
||||
/** @type {!mojo.internal.StructSpec} */ (paramStruct.$.structSpec),
|
||||
value);
|
||||
console.assert(
|
||||
@ -196,10 +281,11 @@ mojo.internal.interfaceSupport.Endpoint = class {
|
||||
|
||||
/** @param {mojo.internal.interfaceSupport.EndpointClient} client */
|
||||
start(client) {
|
||||
console.assert(this.router_, 'starting unassociated secondary endpoint');
|
||||
console.assert(!this.client_, 'endpoint already started');
|
||||
this.client_ = client;
|
||||
this.router_.addEndpoint(this, this.interfaceId_);
|
||||
if (this.router_) {
|
||||
this.router_.addEndpoint(this, this.interfaceId_);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return {boolean} */
|
||||
@ -216,10 +302,11 @@ mojo.internal.interfaceSupport.Endpoint = class {
|
||||
}
|
||||
|
||||
close() {
|
||||
this.stop();
|
||||
if (this.isPrimary()) {
|
||||
this.router_.close();
|
||||
if (this.router_) {
|
||||
this.router.closeEndpoint(this.interfaceId_);
|
||||
}
|
||||
this.client_ = null;
|
||||
this.controlMessageHandler_ = null;
|
||||
}
|
||||
|
||||
async flushForTesting() {
|
||||
@ -253,11 +340,13 @@ mojo.internal.interfaceSupport.Endpoint = class {
|
||||
* Creates a new Endpoint wrapping a given pipe handle.
|
||||
*
|
||||
* @param {!MojoHandle} pipe
|
||||
* @param {boolean=} setNamespaceBit
|
||||
* @return {!mojo.internal.interfaceSupport.Endpoint}
|
||||
*/
|
||||
mojo.internal.interfaceSupport.createEndpoint = function(pipe) {
|
||||
mojo.internal.interfaceSupport.createEndpoint = function(
|
||||
pipe, setNamespaceBit = false) {
|
||||
return new mojo.internal.interfaceSupport.Endpoint(
|
||||
new mojo.internal.interfaceSupport.Router(pipe), 0);
|
||||
new mojo.internal.interfaceSupport.Router(pipe, setNamespaceBit), 0);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -288,6 +377,62 @@ mojo.internal.interfaceSupport.bind = function(endpoint, interfaceName, scope) {
|
||||
endpoint.bindInBrowser(interfaceName, scope);
|
||||
};
|
||||
|
||||
mojo.internal.interfaceSupport.PipeControlMessageHandler = class {
|
||||
/**
|
||||
* @param {!mojo.internal.interfaceSupport.Router} router
|
||||
* @param {function(number)} onDisconnect
|
||||
*/
|
||||
constructor(router, onDisconnect) {
|
||||
/** @const {!mojo.internal.interfaceSupport.Router} */
|
||||
this.router_ = router;
|
||||
|
||||
/** @const {function(number)} */
|
||||
this.onDisconnect_ = onDisconnect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!mojo.pipeControl.RunOrClosePipeInput} input
|
||||
*/
|
||||
send(input) {
|
||||
const spec = /** @type {!mojo.internal.StructSpec} */ (
|
||||
mojo.pipeControl.RunOrClosePipeMessageParamsSpec.$.$.structSpec);
|
||||
const message = new mojo.internal.Message(
|
||||
null, 0xffffffff, 0, mojo.pipeControl.RUN_OR_CLOSE_PIPE_MESSAGE_ID, 0,
|
||||
/** @type {!mojo.internal.StructSpec} */
|
||||
(mojo.pipeControl.RunOrClosePipeMessageParamsSpec.$.$.structSpec),
|
||||
{'input': input});
|
||||
this.router_.send(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!mojo.internal.MessageHeader} header
|
||||
* @param {!ArrayBuffer} buffer
|
||||
* @return {boolean}
|
||||
*/
|
||||
maybeHandleMessage(header, buffer) {
|
||||
if (header.ordinal !== mojo.pipeControl.RUN_OR_CLOSE_PIPE_MESSAGE_ID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = new DataView(buffer, header.headerSize);
|
||||
const decoder = new mojo.internal.Decoder(data, []);
|
||||
const spec = /** @type {!mojo.internal.StructSpec} */ (
|
||||
mojo.pipeControl.RunOrClosePipeMessageParamsSpec.$.$.structSpec);
|
||||
const input = decoder.decodeStructInline(spec)['input'];
|
||||
if (input.hasOwnProperty('peerAssociatedEndpointClosedEvent')) {
|
||||
this.onDisconnect_(input['peerAssociatedEndpointClosedEvent']['id']);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**@param {number} interfaceId */
|
||||
notifyEndpointClosed(interfaceId) {
|
||||
this.send({'peerAssociatedEndpointClosedEvent': {'id': interfaceId}});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles incoming interface control messages on an interface endpoint.
|
||||
*/
|
||||
@ -467,6 +612,11 @@ mojo.internal.interfaceSupport.InterfaceRemoteBase = class {
|
||||
}
|
||||
}
|
||||
|
||||
/** @return {mojo.internal.interfaceSupport.Endpoint} */
|
||||
get endpoint() {
|
||||
return this.endpoint_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!mojo.internal.interfaceSupport.PendingReceiver}
|
||||
*/
|
||||
@ -484,13 +634,23 @@ mojo.internal.interfaceSupport.InterfaceRemoteBase = class {
|
||||
bindHandle(handle) {
|
||||
console.assert(!this.endpoint_, 'already bound');
|
||||
if (handle instanceof MojoHandle) {
|
||||
handle = mojo.internal.interfaceSupport.createEndpoint(handle);
|
||||
handle = mojo.internal.interfaceSupport.createEndpoint(
|
||||
handle, /* setNamespaceBit */ true);
|
||||
}
|
||||
this.endpoint_ = handle;
|
||||
this.endpoint_.start(this);
|
||||
this.pendingResponses_ = new Map;
|
||||
}
|
||||
|
||||
/** @export */
|
||||
associateAndPassReceiver() {
|
||||
console.assert(!this.endpoint_, 'cannot associate when already bound');
|
||||
const {endpoint0, endpoint1} =
|
||||
mojo.internal.interfaceSupport.Endpoint.createAssociatedPair();
|
||||
this.bindHandle(endpoint0);
|
||||
return new this.requestType_(endpoint1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {?mojo.internal.interfaceSupport.Endpoint}
|
||||
* @export
|
||||
@ -575,7 +735,7 @@ mojo.internal.interfaceSupport.InterfaceRemoteBase = class {
|
||||
if (!pendingResponse)
|
||||
return this.onError(endpoint, 'Received unexpected response message');
|
||||
const decoder = new mojo.internal.Decoder(
|
||||
new DataView(buffer, header.headerSize), handles);
|
||||
new DataView(buffer, header.headerSize), handles, {endpoint});
|
||||
const responseValue = decoder.decodeStructInline(
|
||||
/** @type {!mojo.internal.StructSpec} */ (
|
||||
pendingResponse.responseStruct.$.structSpec));
|
||||
@ -633,6 +793,14 @@ mojo.internal.interfaceSupport.InterfaceRemoteBaseWrapper = class {
|
||||
return this.remote_.bindNewPipeAndPassReceiver();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!T}
|
||||
* @export
|
||||
*/
|
||||
associateAndPassReceiver() {
|
||||
return this.remote_.associateAndPassReceiver();
|
||||
}
|
||||
|
||||
/** @export */
|
||||
close() {
|
||||
this.remote_.close();
|
||||
@ -831,6 +999,17 @@ mojo.internal.interfaceSupport.InterfaceReceiverHelperInternal = class {
|
||||
return remote;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!T}
|
||||
* @export
|
||||
*/
|
||||
associateAndPassRemote() {
|
||||
const {endpoint0, endpoint1} =
|
||||
mojo.internal.interfaceSupport.Endpoint.createAssociatedPair();
|
||||
this.bindHandle(endpoint0);
|
||||
return new this.remoteType_(endpoint1);
|
||||
}
|
||||
|
||||
/** @export */
|
||||
closeBindings() {
|
||||
for (const endpoint of this.endpoints_) {
|
||||
@ -855,7 +1034,7 @@ mojo.internal.interfaceSupport.InterfaceReceiverHelperInternal = class {
|
||||
if (!handler)
|
||||
throw new Error('Received unknown message');
|
||||
const decoder = new mojo.internal.Decoder(
|
||||
new DataView(buffer, header.headerSize), handles);
|
||||
new DataView(buffer, header.headerSize), handles, {endpoint});
|
||||
const request = decoder.decodeStructInline(
|
||||
/** @type {!mojo.internal.StructSpec} */ (
|
||||
handler.paramStruct.$.structSpec));
|
||||
@ -945,6 +1124,14 @@ mojo.internal.interfaceSupport.InterfaceReceiverHelper = class {
|
||||
return this.helper_internal_.bindNewPipeAndPassRemote();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!T}
|
||||
* @export
|
||||
*/
|
||||
associateAndPassRemote() {
|
||||
return this.helper_internal_.associateAndPassRemote();
|
||||
}
|
||||
|
||||
/** @export */
|
||||
close() {
|
||||
this.helper_internal_.closeBindings();
|
||||
|
@ -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');
|
||||
|
||||
|
4
third_party/blink/web_tests/http/tests/mojo/associated-interfaces.html
vendored
Normal file
4
third_party/blink/web_tests/http/tests/mojo/associated-interfaces.html
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="./associated-interfaces.js" type="module"></script>
|
163
third_party/blink/web_tests/http/tests/mojo/associated-interfaces.js
vendored
Normal file
163
third_party/blink/web_tests/http/tests/mojo/associated-interfaces.js
vendored
Normal file
@ -0,0 +1,163 @@
|
||||
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');
|
Reference in New Issue
Block a user