summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordarin@chromium.org <darin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-28 06:56:21 +0000
committerdarin@chromium.org <darin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-28 06:56:21 +0000
commitc522c4529efde9ec8682bc2213706a3f45d879d9 (patch)
treef6c634b9025627ca09051f98b7d435c2226f98d5
parent595e54cda57968128e81489e1249126393f114bf (diff)
downloadchromium_src-c522c4529efde9ec8682bc2213706a3f45d879d9.zip
chromium_src-c522c4529efde9ec8682bc2213706a3f45d879d9.tar.gz
chromium_src-c522c4529efde9ec8682bc2213706a3f45d879d9.tar.bz2
Reland: Mojo: add javascript bindings for request/response
If a mojom interface method specifies response parameters, then we'll generate javascript bindings that provide an additional callback parameter used to pass the response parameters. This mirrors the C++ implementation. A future improvement will likely be to use promises instead, especially now that they are enabled on trunk. As part of this CL, connector.js was also made consistent with connector.cc. It starts reading the message pipe immediately, handles write failures in a similar fashion, etc. The Connection type from connector.js is factored out into a separate file as it now holds a Router object. router.js mirrors router.cc. The Connection type is really the mirror of the C++ RemotePtr<S> type. Some basic request/response tests were added to connection_unittests.js. We'll probably want more testing. R=abarth@chromium.org TBR=mpcomplete@chromium.org Originally reviewed at https://codereview.chromium.org/207503004/ Review URL: https://codereview.chromium.org/216443002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@260081 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--content/browser/webui/web_ui_mojo_browsertest.cc7
-rw-r--r--mojo/apps/js/bindings/connection_unittests.js237
-rw-r--r--mojo/apps/js/bindings/connector_unittests.js132
-rw-r--r--mojo/apps/js/main.js10
-rw-r--r--mojo/apps/js/test/run_apps_js_tests.cc8
-rw-r--r--mojo/public/bindings/generators/js_templates/interface_definition.tmpl63
-rw-r--r--mojo/public/bindings/js/codec.js49
-rw-r--r--mojo/public/bindings/js/connection.js30
-rw-r--r--mojo/public/bindings/js/connector.js66
-rw-r--r--mojo/public/bindings/js/router.js75
-rw-r--r--mojo/public/bindings/pylib/generate/mojom_generator.py2
-rw-r--r--mojo/public/bindings/tests/sample_interfaces.mojom9
12 files changed, 497 insertions, 191 deletions
diff --git a/content/browser/webui/web_ui_mojo_browsertest.cc b/content/browser/webui/web_ui_mojo_browsertest.cc
index d0d98d8..36ab3b7 100644
--- a/content/browser/webui/web_ui_mojo_browsertest.cc
+++ b/content/browser/webui/web_ui_mojo_browsertest.cc
@@ -167,12 +167,7 @@ class WebUIMojoTest : public ContentBrowserTest {
// Loads a webui page that contains mojo bindings and verifies a message makes
// it from the browser to the page and back.
// http://crbug.com/357308
-#if defined(OS_WIN)
-#define MAYBE_EndToEnd DISABLED_EndToEnd
-#else
-#define MAYBE_EndToEnd EndToEnd
-#endif
-IN_PROC_BROWSER_TEST_F(WebUIMojoTest, MAYBE_EndToEnd) {
+IN_PROC_BROWSER_TEST_F(WebUIMojoTest, DISABLED_EndToEnd) {
// Currently there is no way to have a generated file included in the isolate
// files. If the bindings file doesn't exist assume we're on such a bot and
// pass.
diff --git a/mojo/apps/js/bindings/connection_unittests.js b/mojo/apps/js/bindings/connection_unittests.js
new file mode 100644
index 0000000..14bb92f
--- /dev/null
+++ b/mojo/apps/js/bindings/connection_unittests.js
@@ -0,0 +1,237 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Mock out the support module to avoid depending on the message loop.
+define("mojo/bindings/js/support", function() {
+ var waitingCallbacks = [];
+
+ function WaitCookie(id) {
+ this.id = id;
+ }
+
+ function asyncWait(handle, flags, callback) {
+ var id = waitingCallbacks.length;
+ waitingCallbacks.push(callback);
+ return new WaitCookie(id);
+ }
+
+ function cancelWait(cookie) {
+ waitingCallbacks[cookie.id] = null;
+ }
+
+ function numberOfWaitingCallbacks() {
+ var count = 0;
+ for (var i = 0; i < waitingCallbacks.length; ++i) {
+ if (waitingCallbacks[i])
+ ++count;
+ }
+ return count;
+ }
+
+ function pumpOnce(result) {
+ var callbacks = waitingCallbacks;
+ waitingCallbacks = [];
+ for (var i = 0; i < callbacks.length; ++i) {
+ if (callbacks[i])
+ callbacks[i](result);
+ }
+ }
+
+ var exports = {};
+ exports.asyncWait = asyncWait;
+ exports.cancelWait = cancelWait;
+ exports.numberOfWaitingCallbacks = numberOfWaitingCallbacks;
+ exports.pumpOnce = pumpOnce;
+ return exports;
+});
+
+define([
+ "gin/test/expect",
+ "mojo/bindings/js/support",
+ "mojo/bindings/js/core",
+ "mojo/public/bindings/js/connection",
+ "mojo/public/bindings/tests/sample_interfaces.mojom",
+ "mojo/public/bindings/tests/sample_service.mojom",
+], function(expect,
+ mockSupport,
+ core,
+ connection,
+ sample_interfaces,
+ sample_service) {
+ testClientServer();
+ testWriteToClosedPipe();
+ testRequestResponse();
+ this.result = "PASS";
+
+ function testClientServer() {
+ var receivedFrobinate = false;
+ var receivedDidFrobinate = false;
+
+ // ServiceImpl -------------------------------------------------------------
+
+ function ServiceImpl(peer) {
+ this.peer = peer;
+ }
+
+ ServiceImpl.prototype = Object.create(sample_service.ServiceStub.prototype);
+
+ ServiceImpl.prototype.frobinate = function(foo, baz, port) {
+ receivedFrobinate = true;
+
+ expect(foo.name).toBe("Example name");
+ expect(baz).toBeTruthy();
+ expect(core.close(port)).toBe(core.RESULT_OK);
+
+ this.peer.didFrobinate(42);
+ };
+
+ // ServiceImpl -------------------------------------------------------------
+
+ function ServiceClientImpl(peer) {
+ this.peer = peer;
+ }
+
+ ServiceClientImpl.prototype =
+ Object.create(sample_service.ServiceClientStub.prototype);
+
+ ServiceClientImpl.prototype.didFrobinate = function(result) {
+ receivedDidFrobinate = true;
+
+ expect(result).toBe(42);
+ };
+
+ var pipe = core.createMessagePipe();
+ var anotherPipe = core.createMessagePipe();
+ var sourcePipe = core.createMessagePipe();
+
+ var connection0 = new connection.Connection(
+ pipe.handle0, ServiceImpl, sample_service.ServiceClientProxy);
+
+ var connection1 = new connection.Connection(
+ pipe.handle1, ServiceClientImpl, sample_service.ServiceProxy);
+
+ var foo = new sample_service.Foo();
+ foo.bar = new sample_service.Bar();
+ foo.name = "Example name";
+ foo.source = sourcePipe.handle0;
+ connection1.remote.frobinate(foo, true, anotherPipe.handle0);
+
+ mockSupport.pumpOnce(core.RESULT_OK);
+
+ expect(receivedFrobinate).toBeTruthy();
+ expect(receivedDidFrobinate).toBeTruthy();
+
+ connection0.close();
+ connection1.close();
+
+ expect(mockSupport.numberOfWaitingCallbacks()).toBe(0);
+
+ // sourcePipe.handle0 was closed automatically when sent over IPC.
+ expect(core.close(sourcePipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
+ // sourcePipe.handle1 hasn't been closed yet.
+ expect(core.close(sourcePipe.handle1)).toBe(core.RESULT_OK);
+
+ // anotherPipe.handle0 was closed automatically when sent over IPC.
+ expect(core.close(anotherPipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
+ // anotherPipe.handle1 hasn't been closed yet.
+ expect(core.close(anotherPipe.handle1)).toBe(core.RESULT_OK);
+
+ // The Connection object is responsible for closing these handles.
+ expect(core.close(pipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
+ expect(core.close(pipe.handle1)).toBe(core.RESULT_INVALID_ARGUMENT);
+ }
+
+ function testWriteToClosedPipe() {
+ var pipe = core.createMessagePipe();
+
+ var connection1 = new connection.Connection(
+ pipe.handle1, function() {}, sample_service.ServiceProxy);
+
+ // Close the other end of the pipe.
+ core.close(pipe.handle0);
+
+ // Not observed yet because we haven't pumped events yet.
+ expect(connection1.encounteredError()).toBeFalsy();
+
+ var foo = new sample_service.Foo();
+ foo.bar = new sample_service.Bar();
+ // TODO(darin): crbug.com/357043: pass null in place of |foo| here.
+ connection1.remote.frobinate(foo, true, core.kInvalidHandle);
+
+ // Write failures are not reported.
+ expect(connection1.encounteredError()).toBeFalsy();
+
+ // Pump events, and then we should start observing the closed pipe.
+ mockSupport.pumpOnce(core.RESULT_OK);
+
+ expect(connection1.encounteredError()).toBeTruthy();
+
+ connection1.close();
+ }
+
+ function testRequestResponse() {
+
+ // ProviderImpl ------------------------------------------------------------
+
+ function ProviderImpl(peer) {
+ this.peer = peer;
+ }
+
+ ProviderImpl.prototype =
+ Object.create(sample_interfaces.ProviderStub.prototype);
+
+ ProviderImpl.prototype.echoString = function(a, callback) {
+ callback(a);
+ };
+
+ ProviderImpl.prototype.echoStrings = function(a, b, callback) {
+ callback(a, b);
+ };
+
+ // ProviderClientImpl ------------------------------------------------------
+
+ function ProviderClientImpl(peer) {
+ this.peer = peer;
+ }
+
+ ProviderClientImpl.prototype =
+ Object.create(sample_interfaces.ProviderClientStub.prototype);
+
+ ProviderClientImpl.prototype.didFrobinate = function(result) {
+ receivedDidFrobinate = true;
+
+ expect(result).toBe(42);
+ };
+
+ var pipe = core.createMessagePipe();
+
+ var connection0 = new connection.Connection(
+ pipe.handle0, ProviderImpl, sample_interfaces.ProviderClientProxy);
+
+ var connection1 = new connection.Connection(
+ pipe.handle1, ProviderClientImpl, sample_interfaces.ProviderProxy);
+
+ var echoedString;
+
+ // echoString
+
+ connection1.remote.echoString("hello", function(a) {
+ echoedString = a;
+ });
+
+ mockSupport.pumpOnce(core.RESULT_OK);
+
+ expect(echoedString).toBe("hello");
+
+ // echoStrings
+
+ connection1.remote.echoStrings("hello", "world", function(a, b) {
+ echoedString = a + " " + b;
+ });
+
+ mockSupport.pumpOnce(core.RESULT_OK);
+
+ expect(echoedString).toBe("hello world");
+ }
+});
diff --git a/mojo/apps/js/bindings/connector_unittests.js b/mojo/apps/js/bindings/connector_unittests.js
deleted file mode 100644
index d50f740..0000000
--- a/mojo/apps/js/bindings/connector_unittests.js
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Mock out the support module to avoid depending on the message loop.
-define("mojo/bindings/js/support", function() {
- var waitingCallbacks = [];
-
- function WaitCookie(id) {
- this.id = id;
- }
-
- function asyncWait(handle, flags, callback) {
- var id = waitingCallbacks.length;
- waitingCallbacks.push(callback);
- return new WaitCookie(id);
- }
-
- function cancelWait(cookie) {
- waitingCallbacks[cookie.id] = null;
- }
-
- function numberOfWaitingCallbacks() {
- var count = 0;
- for (var i = 0; i < waitingCallbacks.length; ++i) {
- if (waitingCallbacks[i])
- ++count;
- }
- return count;
- }
-
- function pumpOnce(result) {
- var callbacks = waitingCallbacks;
- waitingCallbacks = [];
- for (var i = 0; i < callbacks.length; ++i)
- callbacks[i](result);
- }
-
- var exports = {};
- exports.asyncWait = asyncWait;
- exports.cancelWait = cancelWait;
- exports.numberOfWaitingCallbacks = numberOfWaitingCallbacks;
- exports.pumpOnce = pumpOnce;
- return exports;
-});
-
-define([
- "gin/test/expect",
- "mojo/bindings/js/support",
- "mojo/bindings/js/core",
- "mojo/public/bindings/js/connector",
- "mojo/public/bindings/tests/sample_service.mojom",
-], function(expect, mockSupport, core, connector, sample) {
-
- var receivedFrobinate = false;
- var receivedDidFrobinate = false;
-
- // ServiceImpl --------------------------------------------------------------
-
- function ServiceImpl(peer) {
- this.peer = peer;
- }
-
- ServiceImpl.prototype = Object.create(sample.ServiceStub.prototype);
-
- ServiceImpl.prototype.frobinate = function(foo, baz, port) {
- receivedFrobinate = true;
-
- expect(foo.name).toBe("Example name");
- expect(baz).toBeTruthy();
- expect(core.close(port)).toBe(core.RESULT_OK);
-
- this.peer.didFrobinate(42);
- };
-
- // ServiceImpl --------------------------------------------------------------
-
- function ServiceClientImpl(peer) {
- this.peer = peer;
- }
-
- ServiceClientImpl.prototype =
- Object.create(sample.ServiceClientStub.prototype);
-
- ServiceClientImpl.prototype.didFrobinate = function(result) {
- receivedDidFrobinate = true;
-
- expect(result).toBe(42);
- };
-
- var pipe = core.createMessagePipe();
- var anotherPipe = core.createMessagePipe();
- var sourcePipe = core.createMessagePipe();
-
- var connection0 = new connector.Connection(
- pipe.handle0, ServiceImpl, sample.ServiceClientProxy);
-
- var connection1 = new connector.Connection(
- pipe.handle1, ServiceClientImpl, sample.ServiceProxy);
-
- var foo = new sample.Foo();
- foo.bar = new sample.Bar();
- foo.name = "Example name";
- foo.source = sourcePipe.handle0;
- connection1.remote.frobinate(foo, true, anotherPipe.handle0);
-
- mockSupport.pumpOnce(core.RESULT_OK);
-
- expect(receivedFrobinate).toBeTruthy();
- expect(receivedDidFrobinate).toBeTruthy();
-
- connection0.close();
- connection1.close();
-
- expect(mockSupport.numberOfWaitingCallbacks()).toBe(0);
-
- // sourcePipe.handle0 was closed automatically when sent over IPC.
- expect(core.close(sourcePipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
- // sourcePipe.handle1 hasn't been closed yet.
- expect(core.close(sourcePipe.handle1)).toBe(core.RESULT_OK);
-
- // anotherPipe.handle0 was closed automatically when sent over IPC.
- expect(core.close(anotherPipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
- // anotherPipe.handle1 hasn't been closed yet.
- expect(core.close(anotherPipe.handle1)).toBe(core.RESULT_OK);
-
- // The Connection object is responsible for closing these handles.
- expect(core.close(pipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
- expect(core.close(pipe.handle1)).toBe(core.RESULT_INVALID_ARGUMENT);
-
- this.result = "PASS";
-});
diff --git a/mojo/apps/js/main.js b/mojo/apps/js/main.js
index 355fb97..6f6fb56 100644
--- a/mojo/apps/js/main.js
+++ b/mojo/apps/js/main.js
@@ -6,7 +6,7 @@ define([
'console',
'monotonic_clock',
'timer',
- 'mojo/public/bindings/js/connector',
+ 'mojo/public/bindings/js/connection',
'mojo/bindings/js/core',
'mojo/apps/js/bindings/gl',
'mojo/apps/js/bindings/threading',
@@ -15,7 +15,7 @@ define([
], function(console,
monotonicClock,
timer,
- connector,
+ connection,
core,
gljs,
threading,
@@ -281,8 +281,8 @@ define([
var pipe = new core.createMessagePipe();
this.shell_.connect('mojo:mojo_native_viewport_service', pipe.handle1);
- new connector.Connection(pipe.handle0, NativeViewportClientImpl,
- nativeViewport.NativeViewportProxy);
+ new connection.Connection(pipe.handle0, NativeViewportClientImpl,
+ nativeViewport.NativeViewportProxy);
}
// TODO(aa): It is a bummer to need this stub object in JavaScript. We should
// have a 'client' object that contains both the sending and receiving bits of
@@ -390,6 +390,6 @@ define([
return function(handle) {
- new connector.Connection(handle, SampleApp, shell.ShellProxy);
+ new connection.Connection(handle, SampleApp, shell.ShellProxy);
};
});
diff --git a/mojo/apps/js/test/run_apps_js_tests.cc b/mojo/apps/js/test/run_apps_js_tests.cc
index 3427ca4..ade8c26 100644
--- a/mojo/apps/js/test/run_apps_js_tests.cc
+++ b/mojo/apps/js/test/run_apps_js_tests.cc
@@ -60,12 +60,12 @@ TEST(JSTest, MAYBE_sample_test) {
// http://crbug.com/351214
#if defined(OS_POSIX)
-#define MAYBE_connector DISABLED_connector
+#define MAYBE_connection DISABLED_connection
#else
-#define MAYBE_connector connector
+#define MAYBE_connection connection
#endif
-TEST(JSTest, MAYBE_connector) {
- RunTest("connector_unittests.js", true);
+TEST(JSTest, MAYBE_connection) {
+ RunTest("connection_unittests.js", true);
}
TEST(JSTest, monotonic_clock) {
diff --git a/mojo/public/bindings/generators/js_templates/interface_definition.tmpl b/mojo/public/bindings/generators/js_templates/interface_definition.tmpl
index df1483d..fc77059 100644
--- a/mojo/public/bindings/generators/js_templates/interface_definition.tmpl
+++ b/mojo/public/bindings/generators/js_templates/interface_definition.tmpl
@@ -10,18 +10,40 @@
{{interface.name}}Proxy.prototype.{{method.name|stylize_method}} = function(
{%- for parameter in method.parameters -%}
{{parameter.name}}{% if not loop.last %}, {% endif %}
-{%- endfor %}) {
+{%- endfor -%}
+{%- if method.response_parameters != None -%}
+, closure
+{%- endif -%}
+) {
var params = new {{interface.name}}_{{method.name}}_Params();
{%- for parameter in method.parameters %}
params.{{parameter.name}} = {{parameter.name}};
{%- endfor %}
+{%- if method.response_parameters == None %}
var builder = new codec.MessageBuilder(
k{{interface.name}}_{{method.name}}_Name,
codec.align({{interface.name}}_{{method.name}}_Params.encodedSize));
builder.encodeStruct({{interface.name}}_{{method.name}}_Params, params);
var message = builder.finish();
this.receiver_.accept(message);
+{%- else %}
+ var builder = new codec.MessageWithRequestIDBuilder(
+ k{{interface.name}}_{{method.name}}_Name,
+ codec.align({{interface.name}}_{{method.name}}_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct({{interface.name}}_{{method.name}}_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptWithResponder(message, { accept: function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct({{interface.name}}_{{method.name}}_ResponseParams);
+ closure(
+{%- for parameter in method.response_parameters -%}
+responseParams.{{parameter.name}}{% if not loop.last %}, {% endif %}
+{%- endfor -%});
+ }});
+{%- endif %}
};
{%- endfor %}
@@ -32,6 +54,7 @@
var reader = new codec.MessageReader(message);
switch (reader.messageName) {
{%- for method in interface.methods %}
+{%- if method.response_parameters == None %}
case k{{interface.name}}_{{method.name}}_Name:
var params = reader.decodeStruct({{interface.name}}_{{method.name}}_Params);
this.{{method.name|stylize_method}}(
@@ -39,6 +62,44 @@
params.{{parameter.name}}{% if not loop.last %}, {% endif %}
{%- endfor %});
return true;
+{%- endif %}
+{%- endfor %}
+ default:
+ return false;
+ }
+ };
+
+ {{interface.name}}Stub.prototype.acceptWithResponder =
+ function(message, responder) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+{%- for method in interface.methods %}
+{%- if method.response_parameters != None %}
+ case k{{interface.name}}_{{method.name}}_Name:
+ var params = reader.decodeStruct({{interface.name}}_{{method.name}}_Params);
+ this.{{method.name|stylize_method}}(
+{%- for parameter in method.parameters -%}
+params.{{parameter.name}},
+{%- endfor %} function (
+{%- for parameter in method.response_parameters -%}
+{{parameter.name}}{% if not loop.last %}, {% endif -%}
+{%- endfor -%}) {
+ var responseParams =
+ new {{interface.name}}_{{method.name}}_ResponseParams();
+{%- for parameter in method.response_parameters %}
+ responseParams.{{parameter.name}} = {{parameter.name}};
+{%- endfor %}
+ var builder = new codec.MessageWithRequestIDBuilder(
+ k{{interface.name}}_{{method.name}}_Name,
+ codec.align({{interface.name}}_{{method.name}}_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct({{interface.name}}_{{method.name}}_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+{%- endif %}
{%- endfor %}
default:
return false;
diff --git a/mojo/public/bindings/js/codec.js b/mojo/public/bindings/js/codec.js
index 52357f4..9827b36 100644
--- a/mojo/public/bindings/js/codec.js
+++ b/mojo/public/bindings/js/codec.js
@@ -84,6 +84,7 @@ define("mojo/public/bindings/js/codec", function() {
var kArrayHeaderSize = 8;
var kStructHeaderSize = 8;
var kMessageHeaderSize = 16;
+ var kMessageWithRequestIDHeaderSize = 24;
// Decoder ------------------------------------------------------------------
@@ -287,11 +288,24 @@ define("mojo/public/bindings/js/codec", function() {
// Message ------------------------------------------------------------------
+ var kMessageExpectsResponse = 1 << 0;
+ var kMessageIsResponse = 1 << 1;
+
function Message(memory, handles) {
this.memory = memory;
this.handles = handles;
}
+ Message.prototype.setRequestID = function(requestID) {
+ // TODO(darin): Verify that space was reserved for this field!
+ store64(this.memory, 4 + 4 + 4 + 4, requestID);
+ };
+
+ Message.prototype.getFlags = function() {
+ // Skip over num_bytes, num_fields, and message_name.
+ return load32(this.memory, 4 + 4 + 4);
+ };
+
// MessageBuilder -----------------------------------------------------------
function MessageBuilder(messageName, payloadSize) {
@@ -310,7 +324,7 @@ define("mojo/public/bindings/js/codec", function() {
MessageBuilder.prototype.createEncoder = function(size) {
var pointer = this.buffer.alloc(size);
return new Encoder(this.buffer, this.handles, pointer);
- }
+ };
MessageBuilder.prototype.encodeStruct = function(cls, val) {
cls.encode(this.createEncoder(cls.encodedSize), val);
@@ -327,16 +341,40 @@ define("mojo/public/bindings/js/codec", function() {
return message;
};
+ // MessageWithRequestIDBuilder -----------------------------------------------
+
+ function MessageWithRequestIDBuilder(messageName, payloadSize, flags,
+ requestID) {
+ // Currently, we don't compute the payload size correctly ahead of time.
+ // Instead, we resize the buffer at the end.
+ var numberOfBytes = kMessageWithRequestIDHeaderSize + payloadSize;
+ this.buffer = new Buffer(numberOfBytes);
+ this.handles = [];
+ var encoder = this.createEncoder(kMessageWithRequestIDHeaderSize);
+ encoder.write32(kMessageWithRequestIDHeaderSize);
+ encoder.write32(3); // num_fields.
+ encoder.write32(messageName);
+ encoder.write32(flags);
+ encoder.write64(requestID);
+ }
+
+ MessageWithRequestIDBuilder.prototype =
+ Object.create(MessageBuilder.prototype);
+ MessageWithRequestIDBuilder.prototype.constructor =
+ MessageWithRequestIDBuilder;
+
// MessageReader ------------------------------------------------------------
function MessageReader(message) {
this.decoder = new Decoder(message.memory, message.handles, 0);
var messageHeaderSize = this.decoder.read32();
this.payloadSize = message.memory.length - messageHeaderSize;
- var numberOfFields = this.decoder.read32();
- // TODO: better handling of messages of different size.
+ var numFields = this.decoder.read32();
this.messageName = this.decoder.read32();
- var flags = this.decoder.read32();
+ this.flags = this.decoder.read32();
+ if (numFields >= 3)
+ this.requestID = this.decoder.read64();
+ this.decoder.skip(messageHeaderSize - this.decoder.next);
}
MessageReader.prototype.decodeStruct = function(cls) {
@@ -449,10 +487,13 @@ define("mojo/public/bindings/js/codec", function() {
exports.align = align;
exports.Message = Message;
exports.MessageBuilder = MessageBuilder;
+ exports.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder;
exports.MessageReader = MessageReader;
exports.kArrayHeaderSize = kArrayHeaderSize;
exports.kStructHeaderSize = kStructHeaderSize;
exports.kMessageHeaderSize = kMessageHeaderSize;
+ exports.kMessageExpectsResponse = kMessageExpectsResponse;
+ exports.kMessageIsResponse = kMessageIsResponse;
exports.Uint8 = Uint8;
exports.Uint16 = Uint16;
exports.Uint32 = Uint32;
diff --git a/mojo/public/bindings/js/connection.js b/mojo/public/bindings/js/connection.js
new file mode 100644
index 0000000..96c7419
--- /dev/null
+++ b/mojo/public/bindings/js/connection.js
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define("mojo/public/bindings/js/connection", [
+ "mojo/public/bindings/js/router",
+], function(router) {
+
+ function Connection(handle, localFactory, remoteFactory) {
+ this.router_ = new router.Router(handle);
+ this.remote = new remoteFactory(this.router_);
+ this.local = new localFactory(this.remote);
+ this.router_.setIncomingReceiver(this.local);
+ }
+
+ Connection.prototype.close = function() {
+ this.router_.close();
+ this.router_ = null;
+ this.local = null;
+ this.remote = null;
+ };
+
+ Connection.prototype.encounteredError = function() {
+ return this.router_.encounteredError();
+ };
+
+ var exports = {};
+ exports.Connection = Connection;
+ return exports;
+});
diff --git a/mojo/public/bindings/js/connector.js b/mojo/public/bindings/js/connector.js
index 9a1bfc7..2aa9a9f 100644
--- a/mojo/public/bindings/js/connector.js
+++ b/mojo/public/bindings/js/connector.js
@@ -10,9 +10,12 @@ define("mojo/public/bindings/js/connector", [
function Connector(handle) {
this.handle_ = handle;
+ this.dropWrites_ = false;
this.error_ = false;
this.incomingReceiver_ = null;
this.readWaitCookie_ = null;
+
+ this.waitToReadMore_();
}
Connector.prototype.close = function() {
@@ -29,29 +32,45 @@ define("mojo/public/bindings/js/connector", [
Connector.prototype.accept = function(message) {
if (this.error_)
return false;
- this.write_(message);
- return !this.error_;
- };
- Connector.prototype.setIncomingReceiver = function(receiver) {
- this.incomingReceiver_ = receiver;
- if (this.incomingReceiver_)
- this.waitToReadMore_();
- };
+ if (this.dropWrites_)
+ return true;
- Connector.prototype.write_ = function(message) {
var result = core.writeMessage(this.handle_,
message.memory,
message.handles,
core.WRITE_MESSAGE_FLAG_NONE);
- if (result != core.RESULT_OK) {
- this.error_ = true
- return;
+
+ switch (result) {
+ case core.RESULT_OK:
+ // The handles were successfully transferred, so we don't own them
+ // anymore.
+ message.handles = [];
+ break;
+ case core.RESULT_FAILED_PRECONDITION:
+ // There's no point in continuing to write to this pipe since the other
+ // end is gone. Avoid writing any future messages. Hide write failures
+ // from the caller since we'd like them to continue consuming any
+ // backlog of incoming messages before regarding the message pipe as
+ // closed.
+ this.dropWrites_ = true;
+ break;
+ default:
+ // This particular write was rejected, presumably because of bad input.
+ // The pipe is not necessarily in a bad state.
+ return false;
}
- // The handles were successfully transferred, so we don't own them anymore.
- message.handles = [];
+ return true;
+ };
+
+ Connector.prototype.setIncomingReceiver = function(receiver) {
+ this.incomingReceiver_ = receiver;
};
+ Connector.prototype.encounteredError = function() {
+ return this.error_;
+ }
+
Connector.prototype.waitToReadMore_ = function() {
this.readWaitCookie_ = support.asyncWait(this.handle_,
core.WAIT_FLAG_READABLE,
@@ -73,25 +92,12 @@ define("mojo/public/bindings/js/connector", [
// TODO(abarth): Should core.readMessage return a Uint8Array?
var memory = new Uint8Array(read.buffer);
var message = new codec.Message(memory, read.handles);
- this.incomingReceiver_.accept(message);
+ if (this.incomingReceiver_)
+ this.incomingReceiver_.accept(message);
}
};
- function Connection(handle, localFactory, remoteFactory) {
- this.connector_ = new Connector(handle);
- this.remote = new remoteFactory(this.connector_);
- this.local = new localFactory(this.remote);
- this.connector_.setIncomingReceiver(this.local);
- }
-
- Connection.prototype.close = function() {
- this.connector_.close();
- this.connector_ = null;
- this.local = null;
- this.remote = null;
- };
-
var exports = {};
- exports.Connection = Connection;
+ exports.Connector = Connector;
return exports;
});
diff --git a/mojo/public/bindings/js/router.js b/mojo/public/bindings/js/router.js
new file mode 100644
index 0000000..4a51674
--- /dev/null
+++ b/mojo/public/bindings/js/router.js
@@ -0,0 +1,75 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define("mojo/public/bindings/js/router", [
+ "mojo/public/bindings/js/codec",
+ "mojo/public/bindings/js/connector",
+], function(codec, connector) {
+
+ function Router(handle) {
+ this.connector_ = new connector.Connector(handle);
+ this.incomingReceiver_ = null;
+ this.nextRequestID_ = 0;
+ this.responders_ = {};
+
+ this.connector_.setIncomingReceiver({
+ accept: this.handleIncomingMessage_.bind(this),
+ });
+ }
+
+ Router.prototype.close = function() {
+ this.responders_ = {}; // Drop any responders.
+ this.connector_.close();
+ };
+
+ Router.prototype.accept = function(message) {
+ this.connector_.accept(message);
+ };
+
+ Router.prototype.acceptWithResponder = function(message, responder) {
+ // Reserve 0 in case we want it to convey special meaning in the future.
+ var requestID = this.nextRequestID_++;
+ if (requestID == 0)
+ requestID = this.nextRequestID_++;
+
+ message.setRequestID(requestID);
+ this.connector_.accept(message);
+
+ this.responders_[requestID] = responder;
+ };
+
+ Router.prototype.setIncomingReceiver = function(receiver) {
+ this.incomingReceiver_ = receiver;
+ };
+
+ Router.prototype.encounteredError = function() {
+ return this.connector_.encounteredError();
+ };
+
+ Router.prototype.handleIncomingMessage_ = function(message) {
+ var flags = message.getFlags();
+ if (flags & codec.kMessageExpectsResponse) {
+ if (this.incomingReceiver_) {
+ this.incomingReceiver_.acceptWithResponder(message, this);
+ } else {
+ // If we receive a request expecting a response when the client is not
+ // listening, then we have no choice but to tear down the pipe.
+ this.close();
+ }
+ } else if (flags & codec.kMessageIsResponse) {
+ var reader = new codec.MessageReader(message);
+ var requestID = reader.requestID;
+ var responder = this.responders_[requestID];
+ delete this.responders_[requestID];
+ responder.accept(message);
+ } else {
+ if (this.incomingReceiver_)
+ this.incomingReceiver_.accept(message);
+ }
+ };
+
+ var exports = {};
+ exports.Router = Router;
+ return exports;
+});
diff --git a/mojo/public/bindings/pylib/generate/mojom_generator.py b/mojo/public/bindings/pylib/generate/mojom_generator.py
index ebe2ea90..131c248 100644
--- a/mojo/public/bindings/pylib/generate/mojom_generator.py
+++ b/mojo/public/bindings/pylib/generate/mojom_generator.py
@@ -81,6 +81,8 @@ class Generator(object):
for interface in self.module.interfaces:
for method in interface.methods:
result.append(GetStructFromMethod(interface, method))
+ if method.response_parameters != None:
+ result.append(GetResponseStructFromMethod(interface, method))
return map(partial(GetStructInfo, False), result)
def GetStructs(self):
diff --git a/mojo/public/bindings/tests/sample_interfaces.mojom b/mojo/public/bindings/tests/sample_interfaces.mojom
index 93c334d..50e2c60 100644
--- a/mojo/public/bindings/tests/sample_interfaces.mojom
+++ b/mojo/public/bindings/tests/sample_interfaces.mojom
@@ -4,20 +4,11 @@
module sample {
-struct Coord {
- int32 x;
- int32 y;
-};
-
[Peer=ProviderClient]
interface Provider {
EchoString(string a) => (string a);
EchoStrings(string a, string b) => (string a, string b);
EchoMessagePipeHandle(handle<message_pipe> a) => (handle<message_pipe> a);
- //EchoVoid() => ();
- //MakeCoord(int32 x, int32 y) => (Coord coord);
- //Make2Coords(int32 x1, int32 y1, int32 x2, int32 y2) =>
- // (Coord coord1, Coord coord2);
};
[Peer=Provider]