Commit 5f3032e7 authored by yzshen's avatar yzshen Committed by Commit bot

Mojo JS bindings: introduce concepts that are more similar to C++ bindings:

- InterfacePtrInfo
- InterfaceRequest
- InterfacePtr
- Binding

BUG=579646

Review-Url: https://codereview.chromium.org/2549683002
Cr-Commit-Position: refs/heads/master@{#436201}
parent 08e5b8e4
...@@ -45,14 +45,22 @@ void RunTest(std::string test) { ...@@ -45,14 +45,22 @@ void RunTest(std::string test) {
gin::RunTestFromFile(path, &delegate, false); gin::RunTestFromFile(path, &delegate, false);
} }
TEST(JSTest, connection) { TEST(JSTest, Connection) {
RunTest("connection_tests.js"); RunTest("connection_tests.js");
} }
TEST(JSTest, sample_service) { TEST(JSTest, SampleService) {
RunTest("sample_service_tests.js"); RunTest("sample_service_tests.js");
} }
TEST(JSTest, InterfacePtr) {
RunTest("interface_ptr_tests.js");
}
TEST(JSTest, Binding) {
RunTest("binding_tests.js");
}
} // namespace } // namespace
} // namespace js } // namespace js
} // namespace edk } // namespace edk
......
...@@ -24,7 +24,9 @@ source_set("js_to_cpp_tests") { ...@@ -24,7 +24,9 @@ source_set("js_to_cpp_tests") {
] ]
data = [ data = [
"binding_tests.js",
"connection_tests.js", "connection_tests.js",
"interface_ptr_tests.js",
"js_to_cpp_tests.js", "js_to_cpp_tests.js",
"sample_service_tests.js", "sample_service_tests.js",
] ]
......
// Copyright 2016 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([
"gin/test/expect",
"mojo/public/js/bindings",
"mojo/public/interfaces/bindings/tests/math_calculator.mojom",
"mojo/public/js/threading",
"gc",
], function(expect,
bindings,
math,
threading,
gc) {
testIsBound()
.then(testReusable)
.then(function() {
this.result = "PASS";
gc.collectGarbage(); // should not crash
threading.quit();
}.bind(this)).catch(function(e) {
this.result = "FAIL: " + (e.stack || e);
threading.quit();
}.bind(this));
function CalculatorImpl() {
this.total = 0;
}
CalculatorImpl.prototype.clear = function() {
this.total = 0;
return Promise.resolve({value: this.total});
};
CalculatorImpl.prototype.add = function(value) {
this.total += value;
return Promise.resolve({value: this.total});
};
CalculatorImpl.prototype.multiply = function(value) {
this.total *= value;
return Promise.resolve({value: this.total});
};
function testIsBound() {
var binding = new bindings.Binding(math.Calculator, new CalculatorImpl());
expect(binding.isBound()).toBeFalsy();
var calc = new math.CalculatorPtr();
var request = bindings.makeRequest(calc);
binding.bind(request);
expect(binding.isBound()).toBeTruthy();
binding.close();
expect(binding.isBound()).toBeFalsy();
return Promise.resolve();
}
function testReusable() {
var calc1 = new math.CalculatorPtr();
var calc2 = new math.CalculatorPtr();
var calcBinding = new bindings.Binding(math.Calculator,
new CalculatorImpl(),
bindings.makeRequest(calc1));
var promise = calc1.add(2).then(function(response) {
expect(response.value).toBe(2);
calcBinding.bind(bindings.makeRequest(calc2));
return calc2.add(2);
}).then(function(response) {
expect(response.value).toBe(4);
return Promise.resolve();
});
return promise;
}
});
// Copyright 2016 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([
"gin/test/expect",
"mojo/public/js/bindings",
"mojo/public/interfaces/bindings/tests/math_calculator.mojom",
"mojo/public/js/threading",
"gc",
], function(expect,
bindings,
math,
threading,
gc) {
testIsBound()
.then(testEndToEnd)
.then(testReusable)
.then(function() {
this.result = "PASS";
gc.collectGarbage(); // should not crash
threading.quit();
}.bind(this)).catch(function(e) {
this.result = "FAIL: " + (e.stack || e);
threading.quit();
}.bind(this));
function CalculatorImpl() {
this.total = 0;
}
CalculatorImpl.prototype.clear = function() {
this.total = 0;
return Promise.resolve({value: this.total});
};
CalculatorImpl.prototype.add = function(value) {
this.total += value;
return Promise.resolve({value: this.total});
};
CalculatorImpl.prototype.multiply = function(value) {
this.total *= value;
return Promise.resolve({value: this.total});
};
function testIsBound() {
var calc = new math.CalculatorPtr();
expect(calc.ptr.isBound()).toBeFalsy();
var request = bindings.makeRequest(calc);
expect(calc.ptr.isBound()).toBeTruthy();
calc.ptr.reset();
expect(calc.ptr.isBound()).toBeFalsy();
return Promise.resolve();
}
function testEndToEnd() {
var calc = new math.CalculatorPtr();
var calcBinding = new bindings.Binding(math.Calculator,
new CalculatorImpl(),
bindings.makeRequest(calc));
var promise = calc.add(2).then(function(response) {
expect(response.value).toBe(2);
return calc.multiply(5);
}).then(function(response) {
expect(response.value).toBe(10);
return calc.clear();
}).then(function(response) {
expect(response.value).toBe(0);
return Promise.resolve();
});
return promise;
}
function testReusable() {
var calc = new math.CalculatorPtr();
var calcImpl1 = new CalculatorImpl();
var calcBinding1 = new bindings.Binding(math.Calculator,
calcImpl1,
bindings.makeRequest(calc));
var calcImpl2 = new CalculatorImpl();
var calcBinding2 = new bindings.Binding(math.Calculator, calcImpl2);
var promise = calc.add(2).then(function(response) {
expect(response.value).toBe(2);
calcBinding2.bind(bindings.makeRequest(calc));
return calc.add(2);
}).then(function(response) {
expect(response.value).toBe(2);
expect(calcImpl1.total).toBe(2);
expect(calcImpl2.total).toBe(2);
return Promise.resolve();
});
return promise;
}
});
...@@ -7,11 +7,10 @@ define([ ...@@ -7,11 +7,10 @@ define([
"mojo/public/interfaces/bindings/tests/sample_service.mojom", "mojo/public/interfaces/bindings/tests/sample_service.mojom",
"mojo/public/interfaces/bindings/tests/sample_import.mojom", "mojo/public/interfaces/bindings/tests/sample_import.mojom",
"mojo/public/interfaces/bindings/tests/sample_import2.mojom", "mojo/public/interfaces/bindings/tests/sample_import2.mojom",
"mojo/public/js/connection", "mojo/public/js/bindings",
"mojo/public/js/core", "mojo/public/js/core",
"mojo/public/js/threading", "mojo/public/js/threading",
], function(expect, sample, imported, imported2, connection, core, ], function(expect, sample, imported, imported2, bindings, core, threading) {
threading) {
testDefaultValues() testDefaultValues()
.then(testSampleService) .then(testSampleService)
.then(function() { .then(function() {
...@@ -71,26 +70,23 @@ define([ ...@@ -71,26 +70,23 @@ define([
function ServiceImpl() { function ServiceImpl() {
} }
ServiceImpl.prototype = Object.create(sample.Service.stubClass.prototype);
ServiceImpl.prototype.frobinate = function(foo, baz, port) { ServiceImpl.prototype.frobinate = function(foo, baz, port) {
checkFoo(foo); checkFoo(foo);
expect(baz).toBe(sample.Service.BazOptions.EXTRA); expect(baz).toBe(sample.Service.BazOptions.EXTRA);
expect(core.isHandle(port)).toBeTruthy(); bindings.ProxyBindings(port).close();
return Promise.resolve({result: 1234}); return Promise.resolve({result: 1234});
}; };
var foo = makeFoo(); var foo = makeFoo();
checkFoo(foo); checkFoo(foo);
var sampleServicePipe = core.createMessagePipe(); var service = new sample.ServicePtr();
var connection0 = new connection.Connection(sampleServicePipe.handle0, var request = bindings.makeRequest(service);
ServiceImpl); var serviceBinding = new bindings.Binding(
var connection1 = new connection.Connection( sample.Service, new ServiceImpl(), request);
sampleServicePipe.handle1, undefined, sample.Service.proxyClass);
var pipe = core.createMessagePipe(); var pipe = core.createMessagePipe();
var promise = connection1.remote.frobinate( var promise = service.frobinate(
foo, sample.Service.BazOptions.EXTRA, pipe.handle0) foo, sample.Service.BazOptions.EXTRA, pipe.handle0)
.then(function(response) { .then(function(response) {
expect(response.result).toBe(1234); expect(response.result).toBe(1234);
......
...@@ -3,115 +3,140 @@ ...@@ -3,115 +3,140 @@
// found in the LICENSE file. // found in the LICENSE file.
define("mojo/public/js/bindings", [ define("mojo/public/js/bindings", [
"mojo/public/js/router", "mojo/public/js/connection",
"mojo/public/js/core", "mojo/public/js/core",
], function(router, core) { ], function(connection, core) {
var Router = router.Router; // ---------------------------------------------------------------------------
var kProxyProperties = Symbol("proxyProperties"); function InterfacePtrInfo(handle, version) {
var kStubProperties = Symbol("stubProperties"); this.handle = handle;
this.version = version;
// Public proxy class properties that are managed at runtime by the JS
// bindings. See ProxyBindings below.
function ProxyProperties(receiver) {
this.receiver = receiver;
} }
// TODO(hansmuller): remove then after 'Client=' has been removed from Mojom. InterfacePtrInfo.prototype.isValid = function() {
ProxyProperties.prototype.getLocalDelegate = function() { return core.isHandle(this.handle);
return this.local && StubBindings(this.local).delegate;
} }
// TODO(hansmuller): remove then after 'Client=' has been removed from Mojom. // ---------------------------------------------------------------------------
ProxyProperties.prototype.setLocalDelegate = function(impl) {
if (this.local)
StubBindings(this.local).delegate = impl;
else
throw new Error("no stub object");
}
ProxyProperties.prototype.close = function() { function InterfaceRequest(handle) {
this.connection.close(); this.handle = handle;
} }
// Public stub class properties that are managed at runtime by the JS InterfaceRequest.prototype.isValid = function() {
// bindings. See StubBindings below. return core.isHandle(this.handle);
function StubProperties(delegate) {
this.delegate = delegate;
} }
StubProperties.prototype.close = function() { // ---------------------------------------------------------------------------
this.connection.close();
function makeRequest(interfacePtr) {
var pipe = core.createMessagePipe();
interfacePtr.ptr.bind(new InterfacePtrInfo(pipe.handle0, 0));
return new InterfaceRequest(pipe.handle1);
} }
// The base class for generated proxy classes. // ---------------------------------------------------------------------------
function ProxyBase(receiver) {
this[kProxyProperties] = new ProxyProperties(receiver);
// TODO(hansmuller): Temporary, for Chrome backwards compatibility. // Operations used to setup/configure an interface pointer. Exposed as the
if (receiver instanceof Router) // |ptr| field of generated interface pointer classes.
this.receiver_ = receiver; function InterfacePtrController(interfaceType) {
this.version = 0;
this.connection = null;
this.interfaceType_ = interfaceType;
} }
// The base class for generated stub classes. InterfacePtrController.prototype.bind = function(interfacePtrInfo) {
function StubBase(delegate) { this.reset();
this[kStubProperties] = new StubProperties(delegate); this.version = interfacePtrInfo.version;
this.connection = new connection.Connection(
interfacePtrInfo.handle, undefined, this.interfaceType_.proxyClass);
} }
// TODO(hansmuller): remove everything except the connection property doc InterfacePtrController.prototype.isBound = function() {
// after 'Client=' has been removed from Mojom. return this.connection !== null;
}
// Provides access to properties added to a proxy object without risking // Although users could just discard the object, reset() closes the pipe
// Mojo interface name collisions. Unless otherwise specified, the initial // immediately.
// value of all properties is undefined. InterfacePtrController.prototype.reset = function() {
// if (!this.isBound())
// ProxyBindings(proxy).connection - The Connection object that links the return;
// proxy for a remote Mojo service to an optional local stub for a local
// service. The value of ProxyBindings(proxy).connection.remote == proxy.
//
// ProxyBindings(proxy).local - The "local" stub object whose delegate
// implements the proxy's Mojo client interface.
//
// ProxyBindings(proxy).setLocalDelegate(impl) - Sets the implementation
// delegate of the proxy's client stub object. This is just shorthand
// for |StubBindings(ProxyBindings(proxy).local).delegate = impl|.
//
// ProxyBindings(proxy).getLocalDelegate() - Returns the implementation
// delegate of the proxy's client stub object. This is just shorthand
// for |StubBindings(ProxyBindings(proxy).local).delegate|.
function ProxyBindings(proxy) { this.version = 0;
return (proxy instanceof ProxyBase) ? proxy[kProxyProperties] : proxy; this.connection.close();
this.connection = null;
} }
// TODO(hansmuller): remove the remote doc after 'Client=' has been // TODO(yzshen): Implement the following methods.
// removed from Mojom. // InterfacePtrController.prototype.setConnectionErrorHandler
// InterfacePtrController.prototype.passInterface
// InterfacePtrController.prototype.queryVersion
// InterfacePtrController.prototype.requireVersion
// ---------------------------------------------------------------------------
// Provides access to properties added to a stub object without risking // |request| could be omitted and passed into bind() later.
// Mojo interface name collisions. Unless otherwise specified, the initial // NOTE: |impl| shouldn't hold a reference to this object, because that
// value of all properties is undefined. // results in circular references.
// //
// StubBindings(stub).delegate - The optional implementation delegate for // Example:
// the Mojo interface stub.
// //
// StubBindings(stub).connection - The Connection object that links an // // FooImpl implements mojom.Foo.
// optional proxy for a remote service to this stub. The value of // function FooImpl() { ... }
// StubBindings(stub).connection.local == stub. // FooImpl.prototype.fooMethod1 = function() { ... }
// FooImpl.prototype.fooMethod2 = function() { ... }
// //
// StubBindings(stub).remote - A proxy for the the stub's Mojo client // var fooPtr = new mojom.FooPtr();
// service. // var request = makeRequest(fooPtr);
// var binding = new Binding(mojom.Foo, new FooImpl(), request);
// fooPtr.fooMethod1();
function Binding(interfaceType, impl, request) {
this.interfaceType_ = interfaceType;
this.impl_ = impl;
this.stub_ = null;
if (request)
this.bind(request);
}
Binding.prototype.isBound = function() {
return this.stub_ !== null;
}
Binding.prototype.bind = function(request) {
this.close();
this.stub_ = connection.bindHandleToStub(request.handle,
this.interfaceType_);
connection.StubBindings(this.stub_).delegate = this.impl_;
}
function StubBindings(stub) { Binding.prototype.close = function() {
return stub instanceof StubBase ? stub[kStubProperties] : stub; if (!this.isBound())
return;
connection.StubBindings(this.stub_).close();
this.stub_ = null;
} }
// TODO(yzshen): Implement the following methods.
// Binding.prototype.setConnectionErrorHandler
// Binding.prototype.unbind
var exports = {}; var exports = {};
exports.EmptyProxy = ProxyBase; exports.InterfacePtrInfo = InterfacePtrInfo;
exports.EmptyStub = StubBase; exports.InterfaceRequest = InterfaceRequest;
exports.ProxyBase = ProxyBase; exports.makeRequest = makeRequest;
exports.ProxyBindings = ProxyBindings; exports.InterfacePtrController = InterfacePtrController;
exports.StubBase = StubBase; exports.Binding = Binding;
exports.StubBindings = StubBindings;
// TODO(yzshen): Remove the following exports.
exports.EmptyProxy = connection.EmptyProxy;
exports.EmptyStub = connection.EmptyStub;
exports.ProxyBase = connection.ProxyBase;
exports.ProxyBindings = connection.ProxyBindings;
exports.StubBase = connection.StubBase;
exports.StubBindings = connection.StubBindings;
return exports; return exports;
}); });
...@@ -3,20 +3,112 @@ ...@@ -3,20 +3,112 @@
// found in the LICENSE file. // found in the LICENSE file.
define("mojo/public/js/connection", [ define("mojo/public/js/connection", [
"mojo/public/js/bindings",
"mojo/public/js/connector", "mojo/public/js/connector",
"mojo/public/js/core", "mojo/public/js/core",
"mojo/public/js/router", "mojo/public/js/router",
], function(bindings, connector, core, router) { ], function(connector, core, router) {
var Router = router.Router; var Router = router.Router;
var EmptyProxy = bindings.EmptyProxy;
var EmptyStub = bindings.EmptyStub;
var ProxyBindings = bindings.ProxyBindings;
var StubBindings = bindings.StubBindings;
var TestConnector = connector.TestConnector; var TestConnector = connector.TestConnector;
var TestRouter = router.TestRouter; var TestRouter = router.TestRouter;
var kProxyProperties = Symbol("proxyProperties");
var kStubProperties = Symbol("stubProperties");
// Public proxy class properties that are managed at runtime by the JS
// bindings. See ProxyBindings below.
function ProxyProperties(receiver) {
this.receiver = receiver;
}
// TODO(hansmuller): remove then after 'Client=' has been removed from Mojom.
ProxyProperties.prototype.getLocalDelegate = function() {
return this.local && StubBindings(this.local).delegate;
}
// TODO(hansmuller): remove then after 'Client=' has been removed from Mojom.
ProxyProperties.prototype.setLocalDelegate = function(impl) {
if (this.local)
StubBindings(this.local).delegate = impl;
else
throw new Error("no stub object");
}
ProxyProperties.prototype.close = function() {
this.connection.close();
}
// Public stub class properties that are managed at runtime by the JS
// bindings. See StubBindings below.
function StubProperties(delegate) {
this.delegate = delegate;
}
StubProperties.prototype.close = function() {
this.connection.close();
}
// The base class for generated proxy classes.
function ProxyBase(receiver) {
this[kProxyProperties] = new ProxyProperties(receiver);
// TODO(hansmuller): Temporary, for Chrome backwards compatibility.
if (receiver instanceof Router)
this.receiver_ = receiver;
}
// The base class for generated stub classes.
function StubBase(delegate) {
this[kStubProperties] = new StubProperties(delegate);
}
// TODO(hansmuller): remove everything except the connection property doc
// after 'Client=' has been removed from Mojom.
// Provides access to properties added to a proxy object without risking
// Mojo interface name collisions. Unless otherwise specified, the initial
// value of all properties is undefined.
//
// ProxyBindings(proxy).connection - The Connection object that links the
// proxy for a remote Mojo service to an optional local stub for a local
// service. The value of ProxyBindings(proxy).connection.remote == proxy.
//
// ProxyBindings(proxy).local - The "local" stub object whose delegate
// implements the proxy's Mojo client interface.
//
// ProxyBindings(proxy).setLocalDelegate(impl) - Sets the implementation
// delegate of the proxy's client stub object. This is just shorthand
// for |StubBindings(ProxyBindings(proxy).local).delegate = impl|.
//
// ProxyBindings(proxy).getLocalDelegate() - Returns the implementation
// delegate of the proxy's client stub object. This is just shorthand
// for |StubBindings(ProxyBindings(proxy).local).delegate|.
function ProxyBindings(proxy) {
return (proxy instanceof ProxyBase) ? proxy[kProxyProperties] : proxy;
}
// TODO(hansmuller): remove the remote doc after 'Client=' has been
// removed from Mojom.
// Provides access to properties added to a stub object without risking
// Mojo interface name collisions. Unless otherwise specified, the initial
// value of all properties is undefined.
//
// StubBindings(stub).delegate - The optional implementation delegate for
// the Mojo interface stub.
//
// StubBindings(stub).connection - The Connection object that links an
// optional proxy for a remote service to this stub. The value of
// StubBindings(stub).connection.local == stub.
//
// StubBindings(stub).remote - A proxy for the the stub's Mojo client
// service.
function StubBindings(stub) {
return stub instanceof StubBase ? stub[kStubProperties] : stub;
}
// TODO(hansmuller): the proxy receiver_ property should be receiver$ // TODO(hansmuller): the proxy receiver_ property should be receiver$
function BaseConnection(localStub, remoteProxy, router) { function BaseConnection(localStub, remoteProxy, router) {
...@@ -184,6 +276,12 @@ define("mojo/public/js/connection", [ ...@@ -184,6 +276,12 @@ define("mojo/public/js/connection", [
var exports = {}; var exports = {};
exports.Connection = Connection; exports.Connection = Connection;
exports.EmptyProxy = ProxyBase;
exports.EmptyStub = StubBase;
exports.ProxyBase = ProxyBase;
exports.ProxyBindings = ProxyBindings;
exports.StubBase = StubBase;
exports.StubBindings = StubBindings;
exports.TestConnection = TestConnection; exports.TestConnection = TestConnection;
exports.bindProxy = bindProxy; exports.bindProxy = bindProxy;
......
...@@ -2,12 +2,21 @@ ...@@ -2,12 +2,21 @@
var k{{interface.name}}_{{method.name}}_Name = {{method.ordinal}}; var k{{interface.name}}_{{method.name}}_Name = {{method.ordinal}};
{%- endfor %} {%- endfor %}
function {{interface.name}}Ptr() {
this.ptr = new bindings.InterfacePtrController({{interface.name}});
}
function {{interface.name}}Proxy(receiver) { function {{interface.name}}Proxy(receiver) {
bindings.ProxyBase.call(this, receiver); bindings.ProxyBase.call(this, receiver);
} }
{{interface.name}}Proxy.prototype = Object.create(bindings.ProxyBase.prototype); {{interface.name}}Proxy.prototype = Object.create(bindings.ProxyBase.prototype);
{%- for method in interface.methods %} {%- for method in interface.methods %}
{{interface.name}}Ptr.prototype.{{method.name|stylize_method}} = function() {
return {{interface.name}}Proxy.prototype.{{method.name|stylize_method}}
.apply(this.ptr.connection.remote, arguments);
};
{{interface.name}}Proxy.prototype.{{method.name|stylize_method}} = function( {{interface.name}}Proxy.prototype.{{method.name|stylize_method}} = function(
{%- for parameter in method.parameters -%} {%- for parameter in method.parameters -%}
{{parameter.name}}{% if not loop.last %}, {% endif %} {{parameter.name}}{% if not loop.last %}, {% endif %}
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
{%- endfor %} {%- endfor %}
{%- for interface in interfaces %} {%- for interface in interfaces %}
exports.{{interface.name}} = {{interface.name}}; exports.{{interface.name}} = {{interface.name}};
exports.{{interface.name}}Ptr = {{interface.name}}Ptr;
{#--- Interface Client #} {#--- Interface Client #}
{%- if interface.client in interfaces|map(attribute='name') %} {%- if interface.client in interfaces|map(attribute='name') %}
exports.{{interface.name}}.client = {{interface.client}}; exports.{{interface.name}}.client = {{interface.client}};
......
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