| // 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. |
| |
| #include "services/js/js_app.h" |
| |
| #include "base/bind.h" |
| #include "base/message_loop/message_loop.h" |
| #include "gin/converter.h" |
| #include "gin/modules/module_registry.h" |
| #include "gin/try_catch.h" |
| #include "mojo/common/data_pipe_utils.h" |
| #include "mojo/edk/js/core.h" |
| #include "mojo/edk/js/handle.h" |
| #include "mojo/edk/js/support.h" |
| #include "mojo/public/cpp/bindings/interface_request.h" |
| #include "services/js/js_app_bridge_module.h" |
| #include "services/js/js_app_message_loop_observers.h" |
| #include "services/js/js_app_shell.h" |
| |
| namespace js { |
| |
| namespace { |
| |
| // If true then result is the value of value.property_name. |
| bool GetValueProperty(v8::Isolate* isolate, |
| v8::Handle<v8::Value> value, |
| const std::string& property_name, |
| v8::Handle<v8::Value>* result) { |
| if (!value->IsObject()) |
| return false; |
| v8::Handle<v8::Object> obj = v8::Handle<v8::Object>::Cast(value); |
| v8::Handle<v8::Value> key = gin::StringToV8(isolate, property_name); |
| *result = obj->Get(key); |
| return true; |
| } |
| |
| // If true then result is the function at value.method_name. |
| bool GetValueMethod(v8::Isolate* isolate, |
| v8::Handle<v8::Value> value, |
| const std::string& method_name, |
| v8::Handle<v8::Function>* result) { |
| v8::Handle<v8::Value> method_value; |
| if (!GetValueProperty(isolate, value, method_name, &method_value)) |
| return false; |
| if (!method_value->IsFunction()) |
| return false; |
| CHECK(gin::ConvertFromV8(isolate, method_value, result)); |
| return true; |
| } |
| |
| // If true then result is the mojo::Handle at value.property_name. |
| bool GetValueMojoHandle(v8::Isolate* isolate, |
| v8::Handle<v8::Value> value, |
| const std::string& property_name, |
| mojo::Handle* result) { |
| v8::Handle<v8::Value> mojo_handle_value; |
| if (!GetValueProperty(isolate, value, property_name, &mojo_handle_value)) |
| return false; |
| return gin::ConvertFromV8(isolate, mojo_handle_value, result); |
| } |
| |
| // If true then result is the mojo::Handle value of |
| // service_provider.getConnection().messagePipeHandle |
| bool GetConnectionMessagePipeHandle(v8::Isolate* isolate, |
| v8::Handle<v8::Value> service_provider, |
| mojo::Handle* result) { |
| v8::Handle<v8::Function> get_connection_method; |
| if (!GetValueMethod( |
| isolate, service_provider, "getConnection$", &get_connection_method)) |
| return false; |
| v8::Local<v8::Value> connection_value = |
| get_connection_method->Call(service_provider, 0, nullptr); |
| return GetValueMojoHandle( |
| isolate, connection_value, "messagePipeHandle", result); |
| } |
| |
| } // namespace |
| |
| const char JSApp::kMainModuleName[] = "main"; |
| |
| JSApp::JSApp(mojo::ShellPtr shell, mojo::URLResponsePtr response) |
| : shell_(shell.Pass()) { |
| v8::Isolate* isolate = isolate_holder_.isolate(); |
| message_loop_observers_.reset(new JSAppMessageLoopObservers(isolate)); |
| |
| DCHECK(!response.is_null()); |
| std::string url(response->url); |
| std::string source; |
| CHECK(mojo::common::BlockingCopyToString(response->body.Pass(), &source)); |
| |
| runner_delegate_.AddBuiltinModule(AppBridge::kModuleName, |
| base::Bind(&AppBridge::GetModule, base::Unretained(this))); |
| |
| shell_runner_.reset(new gin::ShellRunner(&runner_delegate_, isolate)); |
| gin::Runner::Scope scope(shell_runner_.get()); |
| shell_runner_->Run(source.c_str(), kMainModuleName); |
| |
| gin::ModuleRegistry* registry = |
| gin::ModuleRegistry::From(shell_runner_->GetContextHolder()->context()); |
| registry->LoadModule( |
| isolate, |
| kMainModuleName, |
| base::Bind(&JSApp::OnAppLoaded, base::Unretained(this), url)); |
| } |
| |
| JSApp::~JSApp() { |
| app_instance_.Reset(); |
| } |
| |
| void JSApp::OnAppLoaded(std::string url, v8::Handle<v8::Value> main_module) { |
| gin::Runner::Scope scope(shell_runner_.get()); |
| gin::TryCatch try_catch; |
| v8::Isolate* isolate = isolate_holder_.isolate(); |
| |
| v8::Handle<v8::Value> argv[] = { |
| gin::ConvertToV8(isolate, JSAppShell::Create(isolate, this)), |
| gin::ConvertToV8(isolate, url) |
| }; |
| |
| v8::Handle<v8::Function> app_class; |
| CHECK(gin::ConvertFromV8(isolate, main_module, &app_class)); |
| app_instance_.Reset(isolate, app_class->NewInstance(arraysize(argv), argv)); |
| if (try_catch.HasCaught()) |
| runner_delegate_.UnhandledException(shell_runner_.get(), try_catch); |
| |
| shell_.set_client(this); |
| } |
| |
| void JSApp::ConnectToApplication(const std::string& application_url, |
| v8::Handle<v8::Value> service_provider) { |
| gin::Runner::Scope scope(shell_runner_.get()); |
| v8::Isolate* isolate = isolate_holder_.isolate(); |
| |
| mojo::Handle handle; |
| if (GetConnectionMessagePipeHandle(isolate, service_provider, &handle) || |
| gin::ConvertFromV8(isolate, service_provider, &handle)) { |
| mojo::MessagePipeHandle message_pipe_handle(handle.value()); |
| mojo::ScopedMessagePipeHandle scoped_handle(message_pipe_handle); |
| shell_->ConnectToApplication( |
| application_url, |
| mojo::MakeRequest<mojo::ServiceProvider>(scoped_handle.Pass())); |
| } |
| } |
| |
| void JSApp::CallAppInstanceMethod( |
| const std::string& name, int argc, v8::Handle<v8::Value> argv[]) { |
| v8::Isolate* isolate = isolate_holder_.isolate(); |
| v8::Local<v8::Object> app = |
| v8::Local<v8::Object>::New(isolate, app_instance_); |
| |
| v8::Handle<v8::Function> app_method; |
| GetValueMethod(isolate, app, name, &app_method); |
| shell_runner_->Call(app_method, app, argc, argv); |
| } |
| |
| void JSApp::Initialize(mojo::Array<mojo::String> app_args) { |
| gin::Runner::Scope scope(shell_runner_.get()); |
| v8::Isolate* isolate = isolate_holder_.isolate(); |
| v8::Handle<v8::Value> argv[] = { |
| gin::ConvertToV8(isolate, app_args.To<std::vector<std::string>>()), |
| }; |
| CallAppInstanceMethod("initialize", 1, argv); |
| } |
| |
| void JSApp::AcceptConnection(const mojo::String& requestor_url, |
| mojo::ServiceProviderPtr provider) { |
| gin::Runner::Scope scope(shell_runner_.get()); |
| v8::Isolate* isolate = isolate_holder_.isolate(); |
| v8::Handle<v8::Value> argv[] = { |
| gin::ConvertToV8(isolate, requestor_url.To<std::string>()), |
| gin::ConvertToV8(isolate, provider.PassMessagePipe().release()), |
| }; |
| CallAppInstanceMethod("acceptConnection_", arraysize(argv), argv); |
| } |
| |
| void JSApp::Quit() { |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, base::Bind(&JSApp::QuitInternal, base::Unretained(this))); |
| } |
| |
| void JSApp::QuitInternal() { |
| shell_runner_.reset(); |
| base::MessageLoop::current()->QuitWhenIdle(); |
| } |
| |
| } // namespace js |
| |