Commit 76a748ae authored by hansmuller's avatar hansmuller Committed by Commit bot

Mojo JS bindings: simplify mojo.connectToService() usage - Part 2

This is the final step towards the goals listed in crbug.com/419160. It builds on https://codereview.chromium.org/628763002. The trivial NativeViewport application listed there can now be written relatively simply:

  define("test", [
    "mojo/services/public/interfaces/geometry/geometry.mojom",
    "mojo/services/public/interfaces/native_viewport/native_viewport.mojom",
    "mojo/apps/js/mojo",
    "console",
  ], function(geometry, viewport, mojo, console) {

    var client = {
      onDestroyed: mojo.quit,
      onEvent: function(event) {
        console.log("event.type=" + event.action);
        return Promise.resolve(); // This just gates the next event delivery
      },
    };
    var viewport = mojo.connectToService(
      "mojo:mojo_native_viewport_service", viewport.NativeViewport, client);
    viewport.create(new geometry.Size({width: 256, height: 256}));
    viewport.show();
  });

The mojo connectToService() function now just returns a proxy to the remote interface. The 3rd (optional) connectToService() parameter is an object that implements the client interface for the remote interface.

In this version the NativeViewportClient interface implementation is just an object with function properties whose names match the interface. Subclassing isn't needed and it's not necessary to implement all of the client functions.

The Mojo JS bindings now generate a subclass of each FooStub class called DelegatingFooStub. The Delegating version just forwards the Stub methods to the value of |this.delegate$|. The ugly "$" suffix avoids collisions with mojom function names. The JS bindings' interface Foo object now includes a delegatingStubClass property whose value is FooDelegatingStub.

|mojo.connectToService(url, Foo, client)| only uses Foo.client.delegatingStubClass if a client is specified.

The mojo_module has been split into two: a native part, called MojoInternalsModule, which integrates with JSApp, and a JavaScript part that depends on the internals. Apps must now import "mojo/apps/js/mojo", rather than just "mojo". For now, quit and connectToService() are the only functions in the JS mojo module.

BUG=419160

Review URL: https://codereview.chromium.org/637373002

Cr-Commit-Position: refs/heads/master@{#299197}
parent 5834fe5e
...@@ -26,7 +26,7 @@ source_set("js_apps") { ...@@ -26,7 +26,7 @@ source_set("js_apps") {
sources = [ sources = [
"application_delegate_impl.cc", "application_delegate_impl.cc",
"js_app.cc", "js_app.cc",
"mojo_module.cc", "mojo_bridge_module.cc",
] ]
public_deps = [ public_deps = [
......
...@@ -38,7 +38,7 @@ void ApplicationDelegateImpl::QuitJSApp(JSApp* app) { ...@@ -38,7 +38,7 @@ void ApplicationDelegateImpl::QuitJSApp(JSApp* app) {
if (itr != app_vector_.end()) if (itr != app_vector_.end())
app_vector_.erase(itr); app_vector_.erase(itr);
if (app_vector_.empty()) if (app_vector_.empty())
base::MessageLoop::current()->QuitWhenIdle(); base::MessageLoop::current()->QuitNow();
} }
void ApplicationDelegateImpl::ConnectToService( void ApplicationDelegateImpl::ConnectToService(
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
#include "gin/array_buffer.h" #include "gin/array_buffer.h"
#include "gin/converter.h" #include "gin/converter.h"
#include "mojo/apps/js/application_delegate_impl.h" #include "mojo/apps/js/application_delegate_impl.h"
#include "mojo/apps/js/mojo_module.h" #include "mojo/apps/js/mojo_bridge_module.h"
namespace mojo { namespace mojo {
namespace apps { namespace apps {
...@@ -19,8 +19,8 @@ JSApp::JSApp(ApplicationDelegateImpl* app_delegate_impl) ...@@ -19,8 +19,8 @@ JSApp::JSApp(ApplicationDelegateImpl* app_delegate_impl)
app_delegate_impl_task_runner_( app_delegate_impl_task_runner_(
base::MessageLoop::current()->task_runner()) { base::MessageLoop::current()->task_runner()) {
CHECK(on_app_delegate_impl_thread()); CHECK(on_app_delegate_impl_thread());
runner_delegate_.AddBuiltinModule(Mojo::kModuleName, runner_delegate_.AddBuiltinModule(MojoInternals::kModuleName,
base::Bind(Mojo::GetModule, this)); base::Bind(MojoInternals::GetModule, this));
} }
JSApp::~JSApp() { JSApp::~JSApp() {
......
...@@ -35,7 +35,7 @@ class JSApp { ...@@ -35,7 +35,7 @@ class JSApp {
// be called from this app's thread. // be called from this app's thread.
virtual bool Load(std::string* source, std::string* file_name) = 0; virtual bool Load(std::string* source, std::string* file_name) = 0;
// Called by the JS mojo module to quit this JS app. See mojo_module.cc. // Called by the JS mojo module to quit this JS app. See mojo.js.
void Quit(); void Quit();
// Called by the JS mojo module to connect to a Mojo service. // Called by the JS mojo module to connect to a Mojo service.
......
...@@ -3,36 +3,36 @@ ...@@ -3,36 +3,36 @@
// found in the LICENSE file. // found in the LICENSE file.
// This trivial app just loads "cnn.com" using the Mojo Network and URLLoader // This trivial app just loads "cnn.com" using the Mojo Network and URLLoader
// services and then prints a brief summary of the response. It's intended to // services and then prints a brief summary of the response.
// be run using the mojo_js content handler and assumes that this source file
// is specified as a URL. For example:
// //
// To run it using mojo_js_standalone (don't forget the quotes):
// mojo_shell 'mojo://mojo_js_standalone THIS_DIR/main.js'
//
// To run it using mojo_js_content handler this file must be specified as
// a URL. For example:
// (cd YOUR_DIR/mojo/apps/js; python -m SimpleHTTPServer ) & // (cd YOUR_DIR/mojo/apps/js; python -m SimpleHTTPServer ) &
// mojo_shell --content-handlers=application/javascript,mojo://mojo_js \ // mojo_shell \
// http://localhost:8000/test.js // --content-handlers=application/javascript,mojo://mojo_js_content_handler \
// http://localhost:8000/test.js
define("test", [ define("test", [
"mojo/apps/js/mojo",
"mojo/public/js/bindings/core", "mojo/public/js/bindings/core",
"mojo/public/js/bindings/connection", "mojo/public/js/bindings/connection",
"mojo/public/js/bindings/support", "mojo/public/js/bindings/support",
"mojo/services/public/interfaces/network/network_service.mojom", "mojo/services/public/interfaces/network/network_service.mojom",
"mojo/services/public/interfaces/network/url_loader.mojom", "mojo/services/public/interfaces/network/url_loader.mojom",
"mojo",
"console" "console"
], function(core, connection, support, net, loader, mojo, console) { ], function(mojo, core, connection, support, net, loader, console) {
var netServiceHandle = mojo.connectToService( var networkService = mojo.connectToService(
"mojo:mojo_network_service", net.NetworkService.name); "mojo:mojo_network_service", net.NetworkService);
var netConnection = new connection.Connection(
netServiceHandle,
net.NetworkService.stubClass,
net.NetworkService.proxyClass);
var urlLoaderPipe = core.createMessagePipe(); var urlLoaderPipe = core.createMessagePipe();
netConnection.remote.createURLLoader(urlLoaderPipe.handle1); networkService.createURLLoader(urlLoaderPipe.handle1);
var urlLoaderConnection = new connection.Connection( var urlLoaderConnection = new connection.Connection(
urlLoaderPipe.handle0, urlLoaderPipe.handle0,
loader.URLLoader.stubClass, function(){},
loader.URLLoader.proxyClass); loader.URLLoader.proxyClass);
var urlRequest = new loader.URLRequest({ var urlRequest = new loader.URLRequest({
......
// 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/apps/js/mojo", [
"mojo/public/js/bindings/connection",
"mojo/apps/js/bridge",
], function(connection, mojo) {
function connectToService(url, service, client) {
var serviceHandle = mojo.connectToService(url, service.name);
var clientClass = client && service.client.delegatingStubClass;
var serviceConnection =
new connection.Connection(serviceHandle, clientClass, service.proxyClass);
if (serviceConnection.local)
serviceConnection.local.delegate$ = client;
serviceConnection.remote.connection$ = serviceConnection;
return serviceConnection.remote;
}
var exports = {};
exports.connectToService = connectToService;
exports.quit = mojo.quit;
return exports;
});
...@@ -2,16 +2,14 @@ ...@@ -2,16 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "mojo/apps/js/mojo_module.h" #include "mojo/apps/js/mojo_bridge_module.h"
#include "gin/arguments.h" #include "gin/arguments.h"
#include "gin/converter.h" #include "gin/converter.h"
#include "gin/object_template_builder.h" #include "gin/object_template_builder.h"
#include "gin/per_isolate_data.h" #include "gin/per_isolate_data.h"
#include "mojo/apps/js/js_app.h" #include "mojo/apps/js/js_app.h"
#include "mojo/apps/js/mojo_module.h"
#include "mojo/bindings/js/handle.h" #include "mojo/bindings/js/handle.h"
#include "mojo/common/data_pipe_utils.h"
namespace mojo { namespace mojo {
namespace apps { namespace apps {
...@@ -22,9 +20,10 @@ gin::WrapperInfo g_wrapper_info = {gin::kEmbedderNativeGin}; ...@@ -22,9 +20,10 @@ gin::WrapperInfo g_wrapper_info = {gin::kEmbedderNativeGin};
} // namespace } // namespace
const char Mojo::kModuleName[] = "mojo"; const char MojoInternals::kModuleName[] = "mojo/apps/js/bridge";
v8::Local<v8::Value> Mojo::GetModule(JSApp* js_app, v8::Isolate* isolate) { v8::Local<v8::Value> MojoInternals::GetModule(JSApp* js_app,
v8::Isolate* isolate) {
gin::PerIsolateData* data = gin::PerIsolateData::From(isolate); gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
v8::Local<v8::ObjectTemplate> templ = v8::Local<v8::ObjectTemplate> templ =
data->GetObjectTemplate(&g_wrapper_info); data->GetObjectTemplate(&g_wrapper_info);
......
...@@ -13,7 +13,10 @@ namespace apps { ...@@ -13,7 +13,10 @@ namespace apps {
class JSApp; class JSApp;
class Mojo { // The JavaScript "mojo/apps/js/mojo" module depends on this built-in module.
// It provides the bridge between the JSApp class and JavaScript.
class MojoInternals {
public: public:
static const char kModuleName[]; static const char kModuleName[];
static v8::Local<v8::Value> GetModule(JSApp* js_app, v8::Isolate* isolate); static v8::Local<v8::Value> GetModule(JSApp* js_app, v8::Isolate* isolate);
......
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
'sources': [ 'sources': [
'apps/js/application_delegate_impl.cc', 'apps/js/application_delegate_impl.cc',
'apps/js/js_app.cc', 'apps/js/js_app.cc',
'apps/js/mojo_module.cc', 'apps/js/mojo_bridge_module.cc',
], ],
}, },
{ {
......
...@@ -13,11 +13,11 @@ define("mojo/public/js/bindings/connection", [ ...@@ -13,11 +13,11 @@ define("mojo/public/js/bindings/connection", [
routerFactory = router.Router; routerFactory = router.Router;
this.router_ = new routerFactory(handle, connectorFactory); this.router_ = new routerFactory(handle, connectorFactory);
this.remote = new remoteFactory(this.router_); this.remote = new remoteFactory(this.router_);
this.local = new localFactory(this.remote); this.local = localFactory && new localFactory(this.remote);
this.router_.setIncomingReceiver(this.local); this.router_.setIncomingReceiver(this.local);
// Validate incoming messages: remote responses and local requests. // Validate incoming messages: remote responses and local requests.
var validateRequest = localFactory.prototype.validator; var validateRequest = localFactory && localFactory.prototype.validator;
var validateResponse = remoteFactory.prototype.validator; var validateResponse = remoteFactory.prototype.validator;
var payloadValidators = []; var payloadValidators = [];
if (validateRequest) if (validateRequest)
......
...@@ -103,6 +103,24 @@ params.{{parameter.name}}{% if not loop.last %}, {% endif -%} ...@@ -103,6 +103,24 @@ params.{{parameter.name}}{% if not loop.last %}, {% endif -%}
} }
}; };
function {{interface.name}}DelegatingStub() {
}
{{interface.name}}DelegatingStub.prototype =
Object.create({{interface.name}}Stub.prototype);
{{interface.name}}DelegatingStub.prototype.callDelegateMethod$ = function(methodName, args) {
var method = this.delegate$ && this.delegate$[methodName];
return method && method.apply(this.delegate$, args);
}
{%- for method in interface.methods %}
{%- set method_name = method.name|stylize_method %}
{{interface.name}}DelegatingStub.prototype.{{method_name}} = function() {
return this.callDelegateMethod$("{{method_name}}", arguments);
}
{%- endfor %}
{#--- Validation #} {#--- Validation #}
function validate{{interface.name}}Request(messageValidator) { function validate{{interface.name}}Request(messageValidator) {
...@@ -156,6 +174,7 @@ params.{{parameter.name}}{% if not loop.last %}, {% endif -%} ...@@ -156,6 +174,7 @@ params.{{parameter.name}}{% if not loop.last %}, {% endif -%}
name: '{{namespace|replace(".","::")}}::{{interface.name}}', name: '{{namespace|replace(".","::")}}::{{interface.name}}',
proxyClass: {{interface.name}}Proxy, proxyClass: {{interface.name}}Proxy,
stubClass: {{interface.name}}Stub, stubClass: {{interface.name}}Stub,
delegatingStubClass: {{interface.name}}DelegatingStub,
validateRequest: validate{{interface.name}}Request, validateRequest: validate{{interface.name}}Request,
{%- if interface|has_callbacks %} {%- if interface|has_callbacks %}
validateResponse: validate{{interface.name}}Response, validateResponse: validate{{interface.name}}Response,
......
...@@ -50,6 +50,8 @@ define("{{module.path}}", [ ...@@ -50,6 +50,8 @@ define("{{module.path}}", [
{#--- 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}};
{%- elif interface.client in imported_interfaces %}
exports.{{interface.name}}.client = {{imported_interfaces[interface.client]}};
{%- endif %} {%- endif %}
{%- endfor %} {%- endfor %}
......
...@@ -260,6 +260,7 @@ class Generator(generator.Generator): ...@@ -260,6 +260,7 @@ class Generator(generator.Generator):
"module": self.module, "module": self.module,
"structs": self.GetStructs() + self.GetStructsFromMethods(), "structs": self.GetStructs() + self.GetStructsFromMethods(),
"interfaces": self.module.interfaces, "interfaces": self.module.interfaces,
"imported_interfaces": self.GetImportedInterfaces(),
} }
def GenerateFiles(self, args): def GenerateFiles(self, args):
...@@ -273,3 +274,12 @@ class Generator(generator.Generator): ...@@ -273,3 +274,12 @@ class Generator(generator.Generator):
each["unique_name"] = "import" + str(counter) each["unique_name"] = "import" + str(counter)
counter += 1 counter += 1
return self.module.imports return self.module.imports
def GetImportedInterfaces(self):
interface_to_import = {};
for each_import in self.module.imports:
for each_interface in each_import["module"].interfaces:
name = each_interface.name
interface_to_import[name] = each_import["unique_name"] + "." + name
return interface_to_import;
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